feat(core): reachable SW landmark chamber demo with a reachability audit (L45); research R12
Stamp vocabulary v2; the flagged SW demo opens through a side-facing portal and a bent ramp (max 14 deg, two landings) into a 17 x 15 m chamber with a rock dais. New reachability audit: a capsule surface search from walkable ground near the arrival mouth to the chamber floor (66 samples, 0 blocked/unsupported); the buried L43 layout fails it. Demo off: owner hashes identical to L44. fast/medium/full PASS (Clang+GCC). Research R12 (read-only): composite cavern-graph generator design and slices. 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
036bd530fc
commit
f13b145f23
14 files changed
+1404
-45
No files matched your search
@@ -56,6 +56,7 @@ namespace
|
||||
int RenderDistanceOverride=0;
|
||||
bool FloatingIslandsRecipe=false;
|
||||
bool LandmarkChamberDemo=false;
|
||||
bool AuditLandmarkReachability=false;
|
||||
bool TlsProbeRequested=false;
|
||||
int TlsProbeWorkers=0;
|
||||
std::string TlsProbeTouch="none";
|
||||
@@ -111,6 +112,7 @@ namespace
|
||||
|
||||
bool MakeLandmarkChamberDemoRequests(
|
||||
const VoxelForgeCore::ResolvedLayoutSnapshotV3& layout,
|
||||
const VoxelForgeCore::WorldAssemblyManifest& worldAssembly,
|
||||
std::int32_t seed,
|
||||
std::vector<VoxelForgeCore::ResolvedLayoutStampRequestV1>& requests,
|
||||
std::string& error)
|
||||
@@ -147,49 +149,371 @@ namespace
|
||||
chamber.stableId="landmark/sw-arrival-chamber-v1";
|
||||
chamber.strateIndex=surface->strateIndex;
|
||||
chamber.kind=ResolvedSdfStampKindV1::carveEllipsoid;
|
||||
// Anchor the chamber horizontally beside the arrival passage. Its height is
|
||||
// set from the local SurfaceWorld ground sample below so the carve keeps a
|
||||
// real rock roof instead of breaking through a low patch of terrain.
|
||||
chamber.anchorPositionMicrometres={
|
||||
arrivalMicrometres[0]+static_cast<std::int64_t>(std::llround(sideX*28000000.0)),
|
||||
arrivalMicrometres[1]+static_cast<std::int64_t>(std::llround(sideY*28000000.0)),
|
||||
arrivalMicrometres[2]-14000000};
|
||||
chamber.placementRadiusMicrometres=2000000;
|
||||
chamber.halfExtentsMicrometres={8500000,7500000,6000000};
|
||||
chamber.smoothWidthMicrometres=800000;
|
||||
chamber.irregularityAmplitudeMicrometres=700000;
|
||||
chamber.irregularityWavelengthMicrometres=4800000;
|
||||
arrivalMicrometres[2]-13000000};
|
||||
chamber.placementRadiusMicrometres=0;
|
||||
chamber.halfExtentsMicrometres={8500000,7500000,3000000};
|
||||
chamber.smoothWidthMicrometres=600000;
|
||||
chamber.irregularityAmplitudeMicrometres=600000;
|
||||
chamber.irregularityWavelengthMicrometres=3800000;
|
||||
|
||||
ResolvedLayoutStampRequestV1 dais;
|
||||
dais.stableId="landmark/sw-arrival-chamber-dais-v1";
|
||||
// Resolved stamps compose in stable-ID order. Keep the raised fills after
|
||||
// chamber-v1 so that the enclosing carve cannot cut them away.
|
||||
dais.stableId="landmark/sw-arrival-chamber-v1-dais";
|
||||
dais.strateIndex=surface->strateIndex;
|
||||
// The version-2 ellipsoid fill rounds the platform into the chamber floor
|
||||
// instead of leaving the long, straight wall of a flat-topped pillar.
|
||||
dais.kind=ResolvedSdfStampKindV1::fillDais;
|
||||
dais.anchorPositionMicrometres=chamber.anchorPositionMicrometres;
|
||||
dais.anchorPositionMicrometres[2]-=2600000;
|
||||
dais.placementRadiusMicrometres=1000000;
|
||||
dais.halfExtentsMicrometres={3200000,2800000,900000};
|
||||
dais.smoothWidthMicrometres=300000;
|
||||
// Keep the raised platform on the broad filled floor rather than merging
|
||||
// it into the chamber's outer slope. Leave a capsule-width landing around
|
||||
// the ramp's centerline at the chamber entrance.
|
||||
dais.anchorPositionMicrometres[0]+=static_cast<std::int64_t>(std::llround(sideX*2600000.0));
|
||||
dais.anchorPositionMicrometres[1]+=static_cast<std::int64_t>(std::llround(sideY*2600000.0));
|
||||
dais.anchorPositionMicrometres[2]-=400000;
|
||||
dais.placementRadiusMicrometres=0;
|
||||
dais.halfExtentsMicrometres={2100000,1800000,1300000};
|
||||
dais.smoothWidthMicrometres=450000;
|
||||
dais.irregularityAmplitudeMicrometres=180000;
|
||||
dais.irregularityWavelengthMicrometres=2200000;
|
||||
dais.irregularityWavelengthMicrometres=1800000;
|
||||
|
||||
const double entranceDirectionX=sideX,entranceDirectionY=sideY;
|
||||
ResolvedLayoutStampRequestV1 entrance;
|
||||
entrance.stableId="landmark/sw-arrival-chamber-entrance-v1";
|
||||
entrance.strateIndex=surface->strateIndex;
|
||||
entrance.kind=ResolvedSdfStampKindV1::carveArch;
|
||||
entrance.anchorPositionMicrometres={
|
||||
chamber.anchorPositionMicrometres[0]
|
||||
+static_cast<std::int64_t>(std::llround(entranceDirectionX*6500000.0)),
|
||||
chamber.anchorPositionMicrometres[1]
|
||||
+static_cast<std::int64_t>(std::llround(entranceDirectionY*6500000.0)),
|
||||
chamber.anchorPositionMicrometres[2]};
|
||||
entrance.placementRadiusMicrometres=1000000;
|
||||
entrance.halfExtentsMicrometres={2600000,4200000,3200000};
|
||||
entrance.smoothWidthMicrometres=350000;
|
||||
entrance.irregularityAmplitudeMicrometres=250000;
|
||||
entrance.irregularityWavelengthMicrometres=2600000;
|
||||
if(std::abs(entranceDirectionX)>std::abs(entranceDirectionY))
|
||||
entrance.yawQuarterTurns=entranceDirectionX>0.0?3:1;
|
||||
else
|
||||
entrance.yawQuarterTurns=entranceDirectionY>0.0?0:2;
|
||||
requests={std::move(chamber),std::move(entrance),std::move(dais)};
|
||||
ResolvedLayoutStampRequestV1 chamberFloor;
|
||||
chamberFloor.stableId="landmark/sw-arrival-chamber-v1-floor";
|
||||
chamberFloor.strateIndex=surface->strateIndex;
|
||||
chamberFloor.kind=ResolvedSdfStampKindV1::fillDais;
|
||||
chamberFloor.anchorPositionMicrometres=chamber.anchorPositionMicrometres;
|
||||
chamberFloor.anchorPositionMicrometres[2]-=2500000;
|
||||
chamberFloor.placementRadiusMicrometres=0;
|
||||
chamberFloor.halfExtentsMicrometres={5800000,5200000,2000000};
|
||||
chamberFloor.smoothWidthMicrometres=250000;
|
||||
chamberFloor.irregularityAmplitudeMicrometres=80000;
|
||||
chamberFloor.irregularityWavelengthMicrometres=2200000;
|
||||
|
||||
const auto surfacePlan=std::find_if(worldAssembly.plan.strates.begin(),
|
||||
worldAssembly.plan.strates.end(),[&](const auto& value)
|
||||
{return value.strateIndex==surface->strateIndex;});
|
||||
if(surfacePlan==worldAssembly.plan.strates.end())
|
||||
{error="landmark chamber demo needs its SurfaceWorld heightfield plan";return false;}
|
||||
const Heightfield2D field(worldAssembly.recipe,*surfacePlan);
|
||||
if(!field.isValid())
|
||||
{error="landmark chamber demo SurfaceWorld heightfield is invalid";return false;}
|
||||
|
||||
const double coreMicrometres=std::max<std::uint32_t>(1,layout.coreVoxelSizeMicrometres);
|
||||
const double qToMicrometres=coreMicrometres/WorldAssemblySubvoxelsPerVoxel;
|
||||
const auto qToMetres=[&](std::int64_t value)
|
||||
{return static_cast<double>(value)*qToMicrometres/1000000.0;};
|
||||
const auto metresToQ=[&](double value)
|
||||
{return static_cast<std::int64_t>(std::llround(value*1000000.0/qToMicrometres));};
|
||||
const double startX=qToMetres(mouth[0])+sideX*20.0;
|
||||
const double startY=qToMetres(mouth[1])+sideY*20.0;
|
||||
const auto surfaceSample=field.point(metresToQ(startX),metresToQ(startY),0);
|
||||
const std::int64_t surfaceHeightQ=surfaceSample.groundHeightSubvoxels;
|
||||
const double surfaceHeight=qToMetres(surfaceHeightQ);
|
||||
|
||||
// Search beside the arrival corridor for a chamber site whose actual terrain
|
||||
// height lets the player descend from this surface entrance at a walkable grade.
|
||||
// The earlier fixed XY site lay over a deep valley, leaving no room for a roof
|
||||
// and asking the ramp to drop 37 m in 71 m.
|
||||
double chamberSideOffset=0.0,chamberTangentOffset=0.0;
|
||||
double chamberGroundHeight=0.0,bestChamberSiteScore=std::numeric_limits<double>::infinity();
|
||||
for(double sideOffset=28.0;sideOffset<=52.0;sideOffset+=2.0)
|
||||
for(double tangentOffset=-32.0;tangentOffset<=32.0;tangentOffset+=2.0)
|
||||
{
|
||||
const double x=qToMetres(mouth[0])+sideX*sideOffset+dx/length*tangentOffset;
|
||||
const double y=qToMetres(mouth[1])+sideY*sideOffset+dy/length*tangentOffset;
|
||||
const double distanceFromEntrance=std::hypot(x-startX,y-startY);
|
||||
if(distanceFromEntrance<16.0)continue;
|
||||
const auto sample=field.point(metresToQ(x),metresToQ(y),0);
|
||||
const double ground=qToMetres(sample.groundHeightSubvoxels);
|
||||
if(ground<surfaceHeight-3.5||ground>surfaceHeight+4.0)continue;
|
||||
const double sideDelta=sideOffset-42.0;
|
||||
const double tangentDelta=tangentOffset-2.0;
|
||||
const double score=sideDelta*sideDelta+tangentDelta*tangentDelta
|
||||
+4.0*(ground-surfaceHeight)*(ground-surfaceHeight);
|
||||
if(score>=bestChamberSiteScore)continue;
|
||||
bestChamberSiteScore=score;chamberSideOffset=sideOffset;
|
||||
chamberTangentOffset=tangentOffset;chamberGroundHeight=ground;
|
||||
chamber.anchorPositionMicrometres[0]=static_cast<std::int64_t>(
|
||||
std::llround(x*1000000.0));
|
||||
chamber.anchorPositionMicrometres[1]=static_cast<std::int64_t>(
|
||||
std::llround(y*1000000.0));
|
||||
}
|
||||
if(!std::isfinite(bestChamberSiteScore))
|
||||
{error="landmark chamber has no nearby surface site with a walkable descent grade";return false;}
|
||||
constexpr double ChamberRoofCoverMetres=0.5;
|
||||
const double chamberCarveTopReach=
|
||||
static_cast<double>(chamber.halfExtentsMicrometres[2]
|
||||
+chamber.smoothWidthMicrometres
|
||||
+chamber.irregularityAmplitudeMicrometres)/1000000.0;
|
||||
const double chamberCenterZ=chamberGroundHeight-chamberCarveTopReach
|
||||
-ChamberRoofCoverMetres;
|
||||
chamber.anchorPositionMicrometres[2]=static_cast<std::int64_t>(
|
||||
std::llround(chamberCenterZ*1000000.0));
|
||||
dais.anchorPositionMicrometres[0]=chamber.anchorPositionMicrometres[0]
|
||||
+static_cast<std::int64_t>(std::llround(sideX*2600000.0));
|
||||
dais.anchorPositionMicrometres[1]=chamber.anchorPositionMicrometres[1]
|
||||
+static_cast<std::int64_t>(std::llround(sideY*2600000.0));
|
||||
dais.anchorPositionMicrometres[2]=chamber.anchorPositionMicrometres[2]-400000;
|
||||
chamberFloor.anchorPositionMicrometres[0]=chamber.anchorPositionMicrometres[0];
|
||||
chamberFloor.anchorPositionMicrometres[1]=chamber.anchorPositionMicrometres[1];
|
||||
chamberFloor.anchorPositionMicrometres[2]=chamber.anchorPositionMicrometres[2]-2500000;
|
||||
|
||||
struct FPathPoint { double x=0.0,y=0.0; };
|
||||
const FPathPoint start{startX,startY};
|
||||
const FPathPoint firstTurn{
|
||||
qToMetres(mouth[0])+sideX*34000000.0/1000000.0+dx/length*14000000.0/1000000.0,
|
||||
qToMetres(mouth[1])+sideY*34000000.0/1000000.0+dy/length*14000000.0/1000000.0};
|
||||
const FPathPoint end{
|
||||
chamber.anchorPositionMicrometres[0]/1000000.0,
|
||||
chamber.anchorPositionMicrometres[1]/1000000.0};
|
||||
const std::array<FPathPoint,3> waypoints={start,firstTurn,end};
|
||||
std::vector<FPathPoint> densePath;
|
||||
const auto appendLine=[&](FPathPoint from,FPathPoint to)
|
||||
{
|
||||
const double distance=std::hypot(to.x-from.x,to.y-from.y);
|
||||
const int steps=std::max(1,static_cast<int>(std::ceil(distance/0.25)));
|
||||
for(int i=0;i<=steps;++i)
|
||||
{
|
||||
const double t=static_cast<double>(i)/steps;
|
||||
const FPathPoint point{from.x+(to.x-from.x)*t,from.y+(to.y-from.y)*t};
|
||||
if(densePath.empty()||std::hypot(point.x-densePath.back().x,
|
||||
point.y-densePath.back().y)>1.0e-6)densePath.push_back(point);
|
||||
}
|
||||
};
|
||||
constexpr double BendRadius=6.0;
|
||||
FPathPoint current=waypoints.front();
|
||||
for(std::size_t i=1;i+1<waypoints.size();++i)
|
||||
{
|
||||
const auto& previous=waypoints[i-1];const auto& corner=waypoints[i];
|
||||
const auto& next=waypoints[i+1];
|
||||
const double inLength=std::hypot(corner.x-previous.x,corner.y-previous.y);
|
||||
const double outLength=std::hypot(next.x-corner.x,next.y-corner.y);
|
||||
if(inLength<=2.0*BendRadius||outLength<=2.0*BendRadius)
|
||||
{error="landmark cave ramp has a segment too short to round";return false;}
|
||||
const FPathPoint inDirection{(corner.x-previous.x)/inLength,
|
||||
(corner.y-previous.y)/inLength};
|
||||
const FPathPoint outDirection{(next.x-corner.x)/outLength,
|
||||
(next.y-corner.y)/outLength};
|
||||
const FPathPoint before{corner.x-inDirection.x*BendRadius,
|
||||
corner.y-inDirection.y*BendRadius};
|
||||
const FPathPoint after{corner.x+outDirection.x*BendRadius,
|
||||
corner.y+outDirection.y*BendRadius};
|
||||
appendLine(current,before);
|
||||
constexpr int ArcSteps=24;
|
||||
for(int step=0;step<=ArcSteps;++step)
|
||||
{
|
||||
const double t=static_cast<double>(step)/ArcSteps,oneMinus=1.0-t;
|
||||
const FPathPoint point{
|
||||
oneMinus*oneMinus*before.x+2.0*oneMinus*t*corner.x+t*t*after.x,
|
||||
oneMinus*oneMinus*before.y+2.0*oneMinus*t*corner.y+t*t*after.y};
|
||||
if(densePath.empty()||std::hypot(point.x-densePath.back().x,
|
||||
point.y-densePath.back().y)>1.0e-6)densePath.push_back(point);
|
||||
}
|
||||
current=after;
|
||||
}
|
||||
appendLine(current,waypoints.back());
|
||||
if(densePath.size()<2)
|
||||
{error="landmark cave ramp path is degenerate";return false;}
|
||||
std::vector<double> arcLength(densePath.size(),0.0);
|
||||
for(std::size_t i=1;i<densePath.size();++i)
|
||||
arcLength[i]=arcLength[i-1]+std::hypot(densePath[i].x-densePath[i-1].x,
|
||||
densePath[i].y-densePath[i-1].y);
|
||||
const double totalLength=arcLength.back();
|
||||
double firstTurnDistance=0.0;
|
||||
double firstTurnDistanceError=std::numeric_limits<double>::infinity();
|
||||
for(std::size_t i=0;i<densePath.size();++i)
|
||||
{
|
||||
const double distance=std::hypot(densePath[i].x-firstTurn.x,
|
||||
densePath[i].y-firstTurn.y);
|
||||
if(distance<firstTurnDistanceError)
|
||||
{firstTurnDistanceError=distance;firstTurnDistance=arcLength[i];}
|
||||
}
|
||||
// Put the first tunnel center one floor radius above grade so its lower boundary
|
||||
// meets the outdoor ground cleanly, while keeping a walkable cave-sized headroom.
|
||||
constexpr double RampRadiusMetres=1.6;
|
||||
const double startZ=surfaceHeight+RampRadiusMetres;
|
||||
// Join the chamber's filled floor at its top surface.
|
||||
const double endZ=chamberCenterZ+1.1;
|
||||
const double descent=endZ-startZ;
|
||||
constexpr double EntryLandingLength=5.0;
|
||||
constexpr double TurnLandingHalfLength=1.5;
|
||||
const double firstTurnLandingStart=std::max(EntryLandingLength,
|
||||
firstTurnDistance-TurnLandingHalfLength);
|
||||
const double firstTurnLandingEnd=std::min(totalLength,
|
||||
firstTurnDistance+TurnLandingHalfLength);
|
||||
const double firstTurnLandingLength=firstTurnLandingEnd-firstTurnLandingStart;
|
||||
const double slopedLength=std::max(0.0,totalLength-EntryLandingLength
|
||||
-firstTurnLandingLength);
|
||||
ResolvedLayoutStampRequestV1 mouthStamp;
|
||||
mouthStamp.stableId="landmark/sw-arrival-chamber-mouth-v1";
|
||||
mouthStamp.strateIndex=surface->strateIndex;
|
||||
mouthStamp.kind=ResolvedSdfStampKindV1::carveEllipsoid;
|
||||
mouthStamp.anchorPositionMicrometres={
|
||||
static_cast<std::int64_t>(std::llround(startX*1000000.0)),
|
||||
static_cast<std::int64_t>(std::llround(startY*1000000.0)),
|
||||
static_cast<std::int64_t>(std::llround((startZ+1.0)*1000000.0))};
|
||||
mouthStamp.placementRadiusMicrometres=0;
|
||||
mouthStamp.halfExtentsMicrometres={4200000,3700000,2800000};
|
||||
mouthStamp.smoothWidthMicrometres=450000;
|
||||
// A broad, rounded upper flare blends the side-facing portal into the slope.
|
||||
mouthStamp.irregularityAmplitudeMicrometres=250000;
|
||||
mouthStamp.irregularityWavelengthMicrometres=2400000;
|
||||
std::printf("[LandmarkChamberPlan] center_m=(%.3f,%.3f,%.3f) local_ground_m=%.3f carve_top_reach_m=%.3f roof_cover_m=%.3f\n",
|
||||
end.x,end.y,chamberCenterZ,chamberGroundHeight,chamberCarveTopReach,
|
||||
chamberGroundHeight-(chamberCenterZ+chamberCarveTopReach));
|
||||
std::printf("[LandmarkChamberSite] side_offset_m=%.3f tangent_offset_m=%.3f entrance_ground_m=%.3f chamber_ground_m=%.3f\n",
|
||||
chamberSideOffset,chamberTangentOffset,surfaceHeight,chamberGroundHeight);
|
||||
std::printf("[LandmarkRampPlan] entry_m=(%.3f,%.3f,%.3f) ground_m=%.3f cap_m=%.3f end_m=(%.3f,%.3f,%.3f) horizontal_m=%.3f entry_landing_m=%.3f turn_landing_m=%.3f turn_distance_m=%.3f sloped_m=%.3f descent_m=%.3f grade_deg=%.3f required_length_m=%.3f\n",
|
||||
startX,startY,startZ,surfaceHeight,
|
||||
qToMetres(surfaceSample.capHeightSubvoxels),end.x,end.y,endZ,totalLength,
|
||||
EntryLandingLength,firstTurnLandingLength,firstTurnDistance,
|
||||
slopedLength,descent,
|
||||
std::atan2(std::abs(descent),slopedLength)*180.0/3.14159265358979323846,
|
||||
std::abs(descent)/std::tan(15.0*3.14159265358979323846/180.0));
|
||||
if(!(slopedLength>0.0)||std::atan2(std::abs(descent),slopedLength)
|
||||
>15.0*3.14159265358979323846/180.0)
|
||||
{error="landmark cave ramp cannot meet the 15 degree walk grade";return false;}
|
||||
|
||||
const double entryPathLength=std::hypot(firstTurn.x-start.x,
|
||||
firstTurn.y-start.y);
|
||||
const double entryPathX=(firstTurn.x-start.x)/entryPathLength;
|
||||
const double entryPathY=(firstTurn.y-start.y)/entryPathLength;
|
||||
const double mouthNormalX=-entryPathY,mouthNormalY=entryPathX;
|
||||
constexpr double MouthPortalDepth=3.8;
|
||||
constexpr double MouthPortalHalfWidth=2.8;
|
||||
constexpr double MouthPortalCenterOffset=2.6;
|
||||
const double mouthPortalX=startX+mouthNormalX*MouthPortalCenterOffset;
|
||||
const double mouthPortalY=startY+mouthNormalY*MouthPortalCenterOffset;
|
||||
ResolvedLayoutStampRequestV1 mouthPortal;
|
||||
mouthPortal.stableId="landmark/sw-arrival-chamber-mouth-approach-v1";
|
||||
mouthPortal.strateIndex=surface->strateIndex;
|
||||
mouthPortal.kind=ResolvedSdfStampKindV1::carveEllipsoid;
|
||||
mouthPortal.anchorPositionMicrometres={
|
||||
static_cast<std::int64_t>(std::llround(mouthPortalX*1000000.0)),
|
||||
static_cast<std::int64_t>(std::llround(mouthPortalY*1000000.0)),
|
||||
static_cast<std::int64_t>(std::llround(startZ*1000000.0))};
|
||||
mouthPortal.placementRadiusMicrometres=0;
|
||||
mouthPortal.halfExtentsMicrometres={3800000,2800000,2000000};
|
||||
mouthPortal.smoothWidthMicrometres=450000;
|
||||
mouthPortal.irregularityAmplitudeMicrometres=180000;
|
||||
mouthPortal.irregularityWavelengthMicrometres=2000000;
|
||||
|
||||
ResolvedLayoutStampRequestV1 mouthLanding;
|
||||
mouthLanding.stableId="landmark/sw-arrival-z-mouth-landing-v1";
|
||||
mouthLanding.strateIndex=surface->strateIndex;
|
||||
mouthLanding.kind=ResolvedSdfStampKindV1::fillPillar;
|
||||
mouthLanding.anchorPositionMicrometres={
|
||||
static_cast<std::int64_t>(std::llround(mouthPortalX*1000000.0)),
|
||||
static_cast<std::int64_t>(std::llround(mouthPortalY*1000000.0)),
|
||||
static_cast<std::int64_t>(std::llround((startZ-RampRadiusMetres-1.5)
|
||||
*1000000.0))};
|
||||
mouthLanding.placementRadiusMicrometres=0;
|
||||
mouthLanding.halfExtentsMicrometres={3800000,2800000,1500000};
|
||||
mouthLanding.smoothWidthMicrometres=180000;
|
||||
mouthLanding.irregularityAmplitudeMicrometres=0;
|
||||
mouthLanding.irregularityWavelengthMicrometres=2000000;
|
||||
std::printf("[LandmarkMouthPortal] center_m=(%.3f,%.3f,%.3f) normal=(%.3f,%.3f) depth_m=%.2f half_width_m=%.2f landing_top_m=%.3f\n",
|
||||
mouthPortalX,mouthPortalY,startZ,mouthNormalX,mouthNormalY,
|
||||
MouthPortalDepth,MouthPortalHalfWidth,surfaceHeight);
|
||||
|
||||
const auto atDistance=[&](double distance)
|
||||
{
|
||||
distance=std::clamp(distance,0.0,totalLength);
|
||||
const auto upper=std::lower_bound(arcLength.begin(),arcLength.end(),distance);
|
||||
const std::size_t hi=std::min<std::size_t>(densePath.size()-1,
|
||||
static_cast<std::size_t>(upper-arcLength.begin()));
|
||||
const std::size_t lo=hi?hi-1:0;
|
||||
const double span=arcLength[hi]-arcLength[lo];
|
||||
const double t=span>0.0?(distance-arcLength[lo])/span:0.0;
|
||||
return FPathPoint{densePath[lo].x+(densePath[hi].x-densePath[lo].x)*t,
|
||||
densePath[lo].y+(densePath[hi].y-densePath[lo].y)*t};
|
||||
};
|
||||
const auto centerlineHeight=[&](double distance)
|
||||
{
|
||||
const double beforeFirstTurn=std::clamp(distance-EntryLandingLength,0.0,
|
||||
firstTurnLandingStart-EntryLandingLength);
|
||||
const double afterFirstTurn=distance>firstTurnLandingEnd
|
||||
?std::clamp(distance-firstTurnLandingEnd,0.0,
|
||||
totalLength-firstTurnLandingEnd):0.0;
|
||||
return startZ+descent*std::clamp((beforeFirstTurn+afterFirstTurn)
|
||||
/slopedLength,0.0,1.0);
|
||||
};
|
||||
const double pointSpacing=0.6;
|
||||
const std::size_t rampCount=static_cast<std::size_t>(std::ceil(totalLength/pointSpacing));
|
||||
std::vector<ResolvedLayoutStampRequestV1> rampCarves;
|
||||
std::vector<ResolvedLayoutStampRequestV1> rampSupports;
|
||||
rampCarves.reserve(rampCount+1);
|
||||
// Footfall supports are denser and narrower than the tunnel stamps. This
|
||||
// keeps their overlapping flat caps from forming broad downhill shelves.
|
||||
const double supportSpacing=0.3;
|
||||
const std::size_t supportCount=static_cast<std::size_t>(std::ceil(
|
||||
totalLength/supportSpacing));
|
||||
rampSupports.reserve(supportCount+1);
|
||||
for(std::size_t i=0;i<=rampCount;++i)
|
||||
{
|
||||
const double distance=std::min(totalLength,static_cast<double>(i)*pointSpacing);
|
||||
const FPathPoint point=atDistance(distance);
|
||||
ResolvedLayoutStampRequestV1 ramp;
|
||||
const std::size_t number=i;
|
||||
ramp.stableId=std::string("landmark/sw-arrival-chamber-ramp-")+
|
||||
(number<10?"00":number<100?"0":"")+std::to_string(number);
|
||||
ramp.strateIndex=surface->strateIndex;
|
||||
ramp.kind=ResolvedSdfStampKindV1::carveEllipsoid;
|
||||
const double centerZ=centerlineHeight(distance);
|
||||
ramp.anchorPositionMicrometres={
|
||||
static_cast<std::int64_t>(std::llround(point.x*1000000.0)),
|
||||
static_cast<std::int64_t>(std::llround(point.y*1000000.0)),
|
||||
static_cast<std::int64_t>(std::llround(centerZ*1000000.0))};
|
||||
ramp.placementRadiusMicrometres=0;
|
||||
ramp.halfExtentsMicrometres={2600000,2600000,2000000};
|
||||
ramp.smoothWidthMicrometres=650000;
|
||||
// Keep the footfall surface smooth enough for the capsule grade limit;
|
||||
// the chamber and entrance carry the larger natural wall variation.
|
||||
ramp.irregularityAmplitudeMicrometres=250000;
|
||||
ramp.irregularityWavelengthMicrometres=2400000;
|
||||
rampCarves.push_back(std::move(ramp));
|
||||
}
|
||||
for(std::size_t i=0;i<=supportCount;++i)
|
||||
{
|
||||
const double distance=std::min(totalLength,static_cast<double>(i)*supportSpacing);
|
||||
const FPathPoint point=atDistance(distance);
|
||||
const double floorZ=centerlineHeight(distance)-RampRadiusMetres;
|
||||
ResolvedLayoutStampRequestV1 support;
|
||||
support.stableId=std::string("landmark/sw-arrival-ramp-support-")+
|
||||
(i<10?"00":i<100?"0":"")+std::to_string(i);
|
||||
support.strateIndex=surface->strateIndex;
|
||||
support.kind=ResolvedSdfStampKindV1::fillPillar;
|
||||
support.anchorPositionMicrometres={
|
||||
static_cast<std::int64_t>(std::llround(point.x*1000000.0)),
|
||||
static_cast<std::int64_t>(std::llround(point.y*1000000.0)),
|
||||
static_cast<std::int64_t>(std::llround((floorZ-1.5)*1000000.0))};
|
||||
support.placementRadiusMicrometres=0;
|
||||
// The first support widens into a buried entry landing under the open
|
||||
// mouth, so the approach surface stays supported at its carved rim.
|
||||
support.halfExtentsMicrometres=i==0
|
||||
?std::array<std::int64_t,3>{3500000,3000000,1500000}
|
||||
:std::array<std::int64_t,3>{550000,550000,1500000};
|
||||
support.smoothWidthMicrometres=180000;
|
||||
support.irregularityAmplitudeMicrometres=0;
|
||||
support.irregularityWavelengthMicrometres=2200000;
|
||||
rampSupports.push_back(std::move(support));
|
||||
}
|
||||
requests.push_back(std::move(mouthStamp));
|
||||
requests.push_back(std::move(mouthPortal));
|
||||
requests.insert(requests.end(),std::make_move_iterator(rampCarves.begin()),
|
||||
std::make_move_iterator(rampCarves.end()));
|
||||
requests.push_back(std::move(chamber));
|
||||
requests.insert(requests.end(),std::make_move_iterator(rampSupports.begin()),
|
||||
std::make_move_iterator(rampSupports.end()));
|
||||
requests.push_back(std::move(mouthLanding));
|
||||
requests.push_back(std::move(chamberFloor));
|
||||
requests.push_back(std::move(dais));
|
||||
(void)seed;
|
||||
return true;
|
||||
}
|
||||
@@ -247,13 +571,14 @@ namespace
|
||||
" --mesh-export=FILE (write the exact generated tile meshes for offline rendering)\n"
|
||||
" --profile-column-overlap --far-horizon-lod8-step=N --force-generate\n"
|
||||
" --seed=N --recipe=surface-world|floating-islands\n"
|
||||
" --landmark-chamber-demo (opt-in version-1 SDF chamber and entry arch)\n"
|
||||
" --landmark-chamber-demo (opt-in natural SW landmark chamber and ramp)\n"
|
||||
" --resolved-layout-output=FILE (canonical plan-time layout snapshot)\n"
|
||||
" --resolved-layout-only (exit after plan and snapshot validation)\n"
|
||||
" --audit-resolved-layout-boundaries (query Core at each recorded endpoint)\n"
|
||||
" --audit-strate-transitions (measure interface crossing, band thickness and holes)\n"
|
||||
" --transition-mode=hard|gradient|interleaved --transition-width-chunks=N (fixture override)\n"
|
||||
" --audit-resolved-layout-passages (capsule, support, route-binding and blocked-control audit)\n"
|
||||
" --audit-landmark-reachability (capsule, support, grade and buried-L43 control)\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"
|
||||
@@ -275,6 +600,7 @@ namespace
|
||||
else if(arg=="--resolved-layout-only")options.ResolvedLayoutOnly=true;
|
||||
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-surface-clearance")options.AuditSurfaceClearance=true;
|
||||
else if(arg=="--audit-strate-transitions")options.AuditStrateTransitions=true;
|
||||
else if(arg.starts_with("--transition-mode="))
|
||||
@@ -494,8 +820,8 @@ int main(int argc,char** argv)
|
||||
if(options.LandmarkChamberDemo)
|
||||
{
|
||||
std::vector<VoxelForgeCore::ResolvedLayoutStampRequestV1> requests;
|
||||
if(!MakeLandmarkChamberDemoRequests(resolvedLayout,seed,requests,error)
|
||||
||!VoxelForgeCore::ResolveResolvedLayoutStampsV1(
|
||||
if(!MakeLandmarkChamberDemoRequests(resolvedLayout,worldAssembly,seed,requests,error)
|
||||
||!VoxelForgeCore::ResolveResolvedLayoutStampsV2(
|
||||
resolvedLayout,seed,requests,error))
|
||||
{std::fprintf(stderr,"Landmark chamber stamp resolution failed: %s\n",error.c_str());return 2;}
|
||||
const auto audit=VoxelForgeCore::AuditResolvedLayoutStampPlacementV1(resolvedLayout);
|
||||
@@ -798,5 +1124,5 @@ int main(int argc,char** argv)
|
||||
options.DiagnosticSlowShapeCompileMs,options.AuditBasePageEquivalence,
|
||||
options.AuditRouteParity,options.AuditResolvedLayoutBoundaries,
|
||||
options.AuditResolvedLayoutPassages,options.AuditStrateTransitions,
|
||||
&resolvedLayout,options.StreamHashTiles);
|
||||
&resolvedLayout,options.StreamHashTiles,options.AuditLandmarkReachability);
|
||||
}
|
||||
@@ -17,6 +17,8 @@
|
||||
#include <future>
|
||||
#include <limits>
|
||||
#include <numeric>
|
||||
#include <queue>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
@@ -122,6 +124,363 @@ namespace StandaloneStreamBenchmark
|
||||
return Result;
|
||||
}
|
||||
|
||||
struct FLandmarkReachabilityResult
|
||||
{
|
||||
std::uint64_t pathSamples=0,capsuleSamples=0,blockedCapsuleSamples=0;
|
||||
std::uint64_t supportSamples=0,unsupportedSamples=0,gradeFailures=0;
|
||||
double maximumGradeDegrees=0.0,maximumSupportDepthMetres=0.0;
|
||||
double minimumClearanceMetres=std::numeric_limits<double>::infinity();
|
||||
double latestPointMinimumClearanceMetres=std::numeric_limits<double>::infinity();
|
||||
std::array<double,3> latestPointMinimumClearancePosition{};
|
||||
bool passed=false;
|
||||
};
|
||||
|
||||
inline FLandmarkReachabilityResult AuditLandmarkWalkPath(
|
||||
const VoxelForgeCore::World& shape,
|
||||
const VoxelForgeCore::WorldAssemblyManifest* assembly,
|
||||
const VoxelForgeCore::ResolvedLayoutSnapshotV3& layout,
|
||||
std::uint32_t coreVoxelSizeMicrometres,std::string_view label)
|
||||
{
|
||||
FLandmarkReachabilityResult result;
|
||||
const auto surface=std::find_if(layout.strates.begin(),layout.strates.end(),
|
||||
[](const auto& value){return value.generatorKind=="SurfaceWorld";});
|
||||
if(surface==layout.strates.end())return result;
|
||||
const auto passage=std::find_if(layout.passages.begin(),layout.passages.end(),
|
||||
[&](const auto& value){return value.lowerStrateIndex==surface->strateIndex;});
|
||||
if(passage==layout.passages.end()||assembly==nullptr)return result;
|
||||
const auto surfacePlan=std::find_if(assembly->plan.strates.begin(),assembly->plan.strates.end(),
|
||||
[&](const auto& value){return value.strateIndex==surface->strateIndex;});
|
||||
if(surfacePlan==assembly->plan.strates.end())return result;
|
||||
const VoxelForgeCore::Heightfield2D surfaceField(assembly->recipe,*surfacePlan);
|
||||
if(!surfaceField.isValid())return result;
|
||||
std::vector<const VoxelForgeCore::ResolvedLayoutStampV1*> ramp;
|
||||
for(const auto& stamp:layout.stamps)
|
||||
if(stamp.stableId.starts_with("landmark/sw-arrival-chamber-ramp-"))
|
||||
ramp.push_back(&stamp);
|
||||
std::sort(ramp.begin(),ramp.end(),[](const auto* a,const auto* b)
|
||||
{return a->stableId<b->stableId;});
|
||||
if(ramp.size()<2)return result;
|
||||
|
||||
const double voxelMetres=static_cast<double>(std::max<std::uint32_t>(1,
|
||||
coreVoxelSizeMicrometres))/1000000.0;
|
||||
const double metresPerSubvoxel=voxelMetres
|
||||
/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
||||
struct FPathPoint { double x=0.0,y=0.0,z=0.0;bool tunnel=false; };
|
||||
std::vector<FPathPoint> path;
|
||||
const auto& arrival=passage->lowerMouthSubvoxels;
|
||||
const double arrivalX=arrival[0]*metresPerSubvoxel;
|
||||
const double arrivalY=arrival[1]*metresPerSubvoxel;
|
||||
const double arrivalCenterZ=arrival[2]*metresPerSubvoxel+0.9;
|
||||
|
||||
const auto densityMetres=[&](double x,double y,double z)
|
||||
{
|
||||
return static_cast<double>(shape.densityAt({x/voxelMetres,
|
||||
y/voxelMetres,z/voxelMetres}))*voxelMetres;
|
||||
};
|
||||
const auto samplePoint=[&](double x,double y,double centerZ,bool tunnel)
|
||||
{
|
||||
constexpr double FloorStep=0.01;
|
||||
double floorZ=centerZ-5.0;
|
||||
bool foundFloor=false;
|
||||
if(tunnel)
|
||||
{
|
||||
if(densityMetres(x,y,centerZ)>0.025)
|
||||
for(double offset=-0.10;offset>=-10.0;offset-=FloorStep)
|
||||
if(densityMetres(x,y,centerZ+offset)<0.0)
|
||||
{floorZ=centerZ+offset+FloorStep;foundFloor=true;break;}
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto xSubvoxels=static_cast<std::int64_t>(std::llround(x/metresPerSubvoxel));
|
||||
const auto ySubvoxels=static_cast<std::int64_t>(std::llround(y/metresPerSubvoxel));
|
||||
floorZ=surfaceField.point(xSubvoxels,ySubvoxels,0).groundHeightSubvoxels
|
||||
*metresPerSubvoxel;
|
||||
foundFloor=std::isfinite(floorZ);
|
||||
}
|
||||
++result.pathSamples;
|
||||
if(!foundFloor)
|
||||
{
|
||||
++result.unsupportedSamples;
|
||||
return std::pair<double,bool>{floorZ,false};
|
||||
}
|
||||
|
||||
// Reserve a footfall margin above the sampled ground for the vertical
|
||||
// capsule; it prevents the lower hemisphere from grazing adjacent floor
|
||||
// samples on a gently sloped ramp.
|
||||
floorZ+=0.15;
|
||||
|
||||
constexpr double CapsuleRadius=0.34;
|
||||
constexpr double CapsuleHeight=1.76;
|
||||
constexpr double RequiredClearance=0.025;
|
||||
constexpr double VerticalStep=0.18;
|
||||
constexpr double HorizontalStep=0.17;
|
||||
result.latestPointMinimumClearanceMetres=std::numeric_limits<double>::infinity();
|
||||
for(double height=0.08;height<=CapsuleHeight;height+=VerticalStep)
|
||||
{
|
||||
double capOffset=0.0;
|
||||
if(height<CapsuleRadius)capOffset=CapsuleRadius-height;
|
||||
else if(height>CapsuleHeight-CapsuleRadius)
|
||||
capOffset=height-(CapsuleHeight-CapsuleRadius);
|
||||
const double radial=std::sqrt(std::max(0.0,
|
||||
CapsuleRadius*CapsuleRadius-capOffset*capOffset));
|
||||
for(double oy=-radial;oy<=radial+1.0e-9;oy+=HorizontalStep)
|
||||
for(double ox=-radial;ox<=radial+1.0e-9;ox+=HorizontalStep)
|
||||
{
|
||||
if(ox*ox+oy*oy>radial*radial+1.0e-9)continue;
|
||||
const double clearance=densityMetres(x+ox,y+oy,floorZ+height);
|
||||
++result.capsuleSamples;
|
||||
result.minimumClearanceMetres=std::min(
|
||||
result.minimumClearanceMetres,clearance);
|
||||
if(clearance<result.latestPointMinimumClearanceMetres)
|
||||
{
|
||||
result.latestPointMinimumClearanceMetres=clearance;
|
||||
result.latestPointMinimumClearancePosition={x+ox,y+oy,floorZ+height};
|
||||
}
|
||||
if(clearance<RequiredClearance)++result.blockedCapsuleSamples;
|
||||
}
|
||||
}
|
||||
|
||||
const std::array<std::array<double,2>,5> supportOffsets={{{0.0,0.0},
|
||||
{0.28,0.0},{-0.28,0.0},{0.0,0.28},{0.0,-0.28}}};
|
||||
std::uint32_t supported=0;
|
||||
for(const auto& offset:supportOffsets)
|
||||
{
|
||||
++result.supportSamples;
|
||||
bool hit=false;
|
||||
for(double depth=0.10;depth<=0.55;depth+=0.08)
|
||||
if(densityMetres(x+offset[0],y+offset[1],floorZ-depth)<0.0)
|
||||
{hit=true;result.maximumSupportDepthMetres=std::max(
|
||||
result.maximumSupportDepthMetres,depth);break;}
|
||||
if(hit)++supported;
|
||||
}
|
||||
const bool supportOk=supported>=3;
|
||||
if(!supportOk)++result.unsupportedSamples;
|
||||
return std::pair<double,bool>{floorZ,supportOk};
|
||||
};
|
||||
|
||||
// Find a walkable surface route around the passage's support rock. The nearby grid is
|
||||
// evaluated against the same composed density field as the ramp and both L43/L45 runs.
|
||||
constexpr int GridRadius=52;
|
||||
constexpr int GridWidth=GridRadius*2+1;
|
||||
constexpr double GridStep=0.50;
|
||||
constexpr double MaximumGradeRadians=15.0*3.14159265358979323846/180.0;
|
||||
struct FSurfaceNode
|
||||
{
|
||||
bool evaluated=false,walkable=false,closed=false;
|
||||
double floorZ=0.0,cost=std::numeric_limits<double>::infinity();
|
||||
int parent=-1;
|
||||
};
|
||||
const int goalX=GridRadius+static_cast<int>(std::llround(
|
||||
(ramp.front()->positionMicrometres[0]/1000000.0-arrivalX)/GridStep));
|
||||
const int goalY=GridRadius+static_cast<int>(std::llround(
|
||||
(ramp.front()->positionMicrometres[1]/1000000.0-arrivalY)/GridStep));
|
||||
const auto nodeIndex=[](int x,int y){return y*GridWidth+x;};
|
||||
std::vector<FSurfaceNode> surfaceNodes(static_cast<std::size_t>(GridWidth*GridWidth));
|
||||
std::uint64_t exploredNodes=0;
|
||||
const auto evaluateNode=[&](int ix,int iy)->FSurfaceNode*
|
||||
{
|
||||
if(ix<0||iy<0||ix>=GridWidth||iy>=GridWidth)return nullptr;
|
||||
auto& node=surfaceNodes[static_cast<std::size_t>(nodeIndex(ix,iy))];
|
||||
if(node.evaluated)return &node;
|
||||
node.evaluated=true;++exploredNodes;
|
||||
const double x=arrivalX+(ix-GridRadius)*GridStep;
|
||||
const double y=arrivalY+(iy-GridRadius)*GridStep;
|
||||
const auto blockedBefore=result.blockedCapsuleSamples;
|
||||
const auto unsupportedBefore=result.unsupportedSamples;
|
||||
const auto pathBefore=result.pathSamples;
|
||||
const double scanCenter=arrivalCenterZ;
|
||||
const auto [floorZ,supported]=samplePoint(x,y,scanCenter,false);
|
||||
node.floorZ=floorZ;
|
||||
node.walkable=supported&&result.pathSamples>pathBefore
|
||||
&&result.blockedCapsuleSamples==blockedBefore
|
||||
&&result.unsupportedSamples==unsupportedBefore;
|
||||
return &node;
|
||||
};
|
||||
struct FOpenNode { double score=0.0;int index=0; };
|
||||
const auto farther=[](const FOpenNode& a,const FOpenNode& b)
|
||||
{return a.score>b.score;};
|
||||
std::priority_queue<FOpenNode,std::vector<FOpenNode>,decltype(farther)> open(farther);
|
||||
const int goalIndex=goalX>=0&&goalY>=0&&goalX<GridWidth&&goalY<GridWidth
|
||||
?nodeIndex(goalX,goalY):-1;
|
||||
if(goalIndex>=0)
|
||||
{
|
||||
constexpr double GroundStartRadius=18.0;
|
||||
const int sourceRadius=static_cast<int>(std::ceil(GroundStartRadius/GridStep));
|
||||
for(int iy=GridRadius-sourceRadius;iy<=GridRadius+sourceRadius;++iy)
|
||||
for(int ix=GridRadius-sourceRadius;ix<=GridRadius+sourceRadius;++ix)
|
||||
{
|
||||
const double dx=(ix-GridRadius)*GridStep,dy=(iy-GridRadius)*GridStep;
|
||||
const double distance=std::hypot(dx,dy);
|
||||
if(distance>GroundStartRadius)continue;
|
||||
auto* start=evaluateNode(ix,iy);
|
||||
if(!start||!start->walkable)continue;
|
||||
start->cost=distance;start->parent=-1;
|
||||
open.push({distance+GridStep*std::hypot(goalX-ix,goalY-iy),
|
||||
nodeIndex(ix,iy)});
|
||||
}
|
||||
constexpr std::array<std::array<int,2>,8> neighbours={{{1,0},{-1,0},{0,1},{0,-1},
|
||||
{1,1},{1,-1},{-1,1},{-1,-1}}};
|
||||
while(!open.empty())
|
||||
{
|
||||
const int currentIndex=open.top().index;open.pop();
|
||||
auto& current=surfaceNodes[static_cast<std::size_t>(currentIndex)];
|
||||
if(current.closed)continue;
|
||||
current.closed=true;
|
||||
if(currentIndex==goalIndex)break;
|
||||
const int currentX=currentIndex%GridWidth,currentY=currentIndex/GridWidth;
|
||||
for(const auto& direction:neighbours)
|
||||
{
|
||||
const int nextX=currentX+direction[0],nextY=currentY+direction[1];
|
||||
auto* next=evaluateNode(nextX,nextY);
|
||||
if(!next||!next->walkable||next->closed)continue;
|
||||
const double run=GridStep*std::hypot(direction[0],direction[1]);
|
||||
const double rise=std::abs(next->floorZ-current.floorZ);
|
||||
if(std::atan2(rise,run)>MaximumGradeRadians)continue;
|
||||
if(direction[0]!=0&&direction[1]!=0)
|
||||
{
|
||||
const auto* sideA=evaluateNode(currentX+direction[0],currentY);
|
||||
const auto* sideB=evaluateNode(currentX,currentY+direction[1]);
|
||||
if(!sideA||!sideA->walkable||!sideB||!sideB->walkable)continue;
|
||||
}
|
||||
const double cost=current.cost+run+rise*0.25;
|
||||
if(cost>=next->cost)continue;
|
||||
next->cost=cost;next->parent=currentIndex;
|
||||
const double heuristic=GridStep*std::hypot(goalX-nextX,goalY-nextY);
|
||||
open.push({cost+heuristic,nodeIndex(nextX,nextY)});
|
||||
}
|
||||
}
|
||||
}
|
||||
std::vector<int> surfaceRoute;
|
||||
if(goalIndex>=0&&surfaceNodes[static_cast<std::size_t>(goalIndex)].closed)
|
||||
{
|
||||
for(int current=goalIndex;current>=0;current=surfaceNodes[static_cast<std::size_t>(current)].parent)
|
||||
surfaceRoute.push_back(current);
|
||||
std::reverse(surfaceRoute.begin(),surfaceRoute.end());
|
||||
}
|
||||
const bool surfaceGoalWalkable=goalIndex>=0
|
||||
&&evaluateNode(goalX,goalY)->walkable;
|
||||
if(surfaceRoute.size()<2)surfaceRoute.clear();
|
||||
const auto surfaceRouteSamples=surfaceRoute.size();
|
||||
double surfaceStartDistance=std::numeric_limits<double>::infinity();
|
||||
if(!surfaceRoute.empty())
|
||||
{
|
||||
path.reserve(surfaceRoute.size()+ramp.size());
|
||||
const int startX=surfaceRoute.front()%GridWidth;
|
||||
const int startY=surfaceRoute.front()/GridWidth;
|
||||
surfaceStartDistance=GridStep*std::hypot(startX-GridRadius,startY-GridRadius);
|
||||
for(const int index:surfaceRoute)
|
||||
{
|
||||
const int ix=index%GridWidth,iy=index/GridWidth;
|
||||
const auto& node=surfaceNodes[static_cast<std::size_t>(index)];
|
||||
path.push_back({arrivalX+(ix-GridRadius)*GridStep,
|
||||
arrivalY+(iy-GridRadius)*GridStep,node.floorZ+0.9,false});
|
||||
}
|
||||
for(const auto* stamp:ramp)
|
||||
path.push_back({stamp->positionMicrometres[0]/1000000.0,
|
||||
stamp->positionMicrometres[1]/1000000.0,
|
||||
stamp->positionMicrometres[2]/1000000.0,true});
|
||||
}
|
||||
std::printf("[LandmarkSurfaceRoute] reachable=%s goal_walkable=%s grid_step_m=%.2f start_offset_m=%.2f explored=%llu surface_points=%zu grade_limit_deg=15.000\n",
|
||||
surfaceRoute.empty()?"false":"true",surfaceGoalWalkable?"true":"false",GridStep,surfaceStartDistance,
|
||||
static_cast<unsigned long long>(exploredNodes),surfaceRouteSamples);
|
||||
result={};
|
||||
if(path.size()<2)return result;
|
||||
|
||||
bool allSupport=true,allGrade=true;
|
||||
bool havePrior=false;double priorX=0.0,priorY=0.0,priorFloor=0.0;
|
||||
std::size_t diagnosticCount=0;
|
||||
for(std::size_t segment=1;segment<path.size();++segment)
|
||||
{
|
||||
const auto& from=path[segment-1];const auto& to=path[segment];
|
||||
const double horizontal=std::hypot(to.x-from.x,to.y-from.y);
|
||||
const std::size_t steps=std::max<std::size_t>(1,
|
||||
static_cast<std::size_t>(std::ceil(horizontal/0.8)));
|
||||
for(std::size_t step=segment==1?0:1;step<=steps;++step)
|
||||
{
|
||||
const double t=static_cast<double>(step)/steps;
|
||||
const double x=from.x+(to.x-from.x)*t;
|
||||
const double y=from.y+(to.y-from.y)*t;
|
||||
const double centerZ=from.z+(to.z-from.z)*t;
|
||||
const auto blockedBefore=result.blockedCapsuleSamples;
|
||||
const auto capsuleBefore=result.capsuleSamples;
|
||||
const auto supportBefore=result.supportSamples;
|
||||
const bool tunnel=from.tunnel&&to.tunnel;
|
||||
const auto [floorZ,supportOk]=samplePoint(x,y,centerZ,tunnel);
|
||||
allSupport&=supportOk;
|
||||
double grade=0.0;
|
||||
if(havePrior)
|
||||
{
|
||||
const double run=std::hypot(x-priorX,y-priorY);
|
||||
grade=run>1.0e-6
|
||||
?std::atan2(std::abs(floorZ-priorFloor),run)*180.0/3.14159265358979323846
|
||||
:std::numeric_limits<double>::infinity();
|
||||
result.maximumGradeDegrees=std::max(result.maximumGradeDegrees,grade);
|
||||
if(grade>15.0) {++result.gradeFailures;allGrade=false;}
|
||||
}
|
||||
if(diagnosticCount<16&&(result.blockedCapsuleSamples>blockedBefore
|
||||
||!supportOk||grade>15.0))
|
||||
{
|
||||
std::printf("[LandmarkWalkPoint] field=%.*s mode=%s x=%.3f y=%.3f center_z=%.3f floor_z=%.3f previous_x=%.3f previous_y=%.3f previous_floor_z=%.3f horizontal_step_m=%.3f blocked=%llu/%llu support_probes=%llu grade_deg=%.3f density_center_m=%.3f capsule_min_m=%.3f capsule_min_at_m=(%.3f,%.3f,%.3f)\n",
|
||||
static_cast<int>(label.size()),label.data(),tunnel?"ramp":"surface",x,y,centerZ,floorZ,
|
||||
priorX,priorY,priorFloor,std::hypot(x-priorX,y-priorY),
|
||||
static_cast<unsigned long long>(result.blockedCapsuleSamples-blockedBefore),
|
||||
static_cast<unsigned long long>(result.capsuleSamples-capsuleBefore),
|
||||
static_cast<unsigned long long>(result.supportSamples-supportBefore),
|
||||
grade,densityMetres(x,y,centerZ),result.latestPointMinimumClearanceMetres,
|
||||
result.latestPointMinimumClearancePosition[0],
|
||||
result.latestPointMinimumClearancePosition[1],
|
||||
result.latestPointMinimumClearancePosition[2]);
|
||||
++diagnosticCount;
|
||||
}
|
||||
havePrior=true;priorX=x;priorY=y;priorFloor=floorZ;
|
||||
}
|
||||
}
|
||||
result.passed=result.pathSamples>1&&result.capsuleSamples>0
|
||||
&&result.blockedCapsuleSamples==0&&allSupport&&allGrade
|
||||
&&result.unsupportedSamples==0&&result.gradeFailures==0;
|
||||
return result;
|
||||
}
|
||||
|
||||
inline bool MakeL43BuriedLandmarkControl(
|
||||
const VoxelForgeCore::ResolvedLayoutSnapshotV3& source,
|
||||
VoxelForgeCore::ResolvedLayoutSnapshotV3& control,std::string& error)
|
||||
{
|
||||
using namespace VoxelForgeCore;
|
||||
control=source;control.stamps.clear();control.stampVocabularyVersion=1;
|
||||
const auto add=[&](std::string id,ResolvedSdfStampKindV1 kind,
|
||||
std::array<std::int64_t,3> position,
|
||||
std::array<std::int64_t,3> half,std::int64_t smooth,
|
||||
std::int64_t rough,std::int64_t wavelength,std::uint64_t seed,
|
||||
std::uint8_t yaw)
|
||||
{
|
||||
ResolvedLayoutStampV1 stamp;stamp.stableId=std::move(id);
|
||||
stamp.strateIndex=1;stamp.kind=kind;stamp.positionMicrometres=position;
|
||||
stamp.halfExtentsMicrometres=half;stamp.smoothWidthMicrometres=smooth;
|
||||
stamp.irregularityAmplitudeMicrometres=rough;
|
||||
stamp.irregularityWavelengthMicrometres=wavelength;
|
||||
stamp.irregularitySeed=seed;stamp.yawQuarterTurns=yaw;
|
||||
const std::int64_t pad=smooth+rough;
|
||||
const std::array<std::int64_t,3> bounds={
|
||||
half[yaw%2?1:0]+pad,half[yaw%2?0:1]+pad,half[2]+pad};
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{stamp.boundsMinimumMicrometres[axis]=position[axis]-bounds[axis];
|
||||
stamp.boundsMaximumMicrometres[axis]=position[axis]+bounds[axis];}
|
||||
control.stamps.push_back(std::move(stamp));
|
||||
};
|
||||
add("landmark/sw-arrival-chamber-v1",ResolvedSdfStampKindV1::carveEllipsoid,
|
||||
{-8917798,31949858,-513083425},{8500000,7500000,6000000},
|
||||
800000,700000,4800000,4704841454711558ull,0);
|
||||
add("landmark/sw-arrival-chamber-entrance-v1",ResolvedSdfStampKindV1::carveArch,
|
||||
{-12289139,37026886,-513310578},{2600000,4200000,3200000},
|
||||
350000,250000,2600000,2371181934872559ull,0);
|
||||
add("landmark/sw-arrival-chamber-dais-v1",ResolvedSdfStampKindV1::fillDais,
|
||||
{-7489021,30688194,-515838454},{3200000,2800000,900000},
|
||||
300000,180000,2200000,7240637434535735ull,0);
|
||||
if(!control.finalize(error))return false;
|
||||
std::vector<ResolvedLayoutIssueV2> issues;
|
||||
return control.validate(ResolvedLayoutValidationModeV3::strict,issues,error);
|
||||
}
|
||||
|
||||
inline bool SameTileOutput(const VoxelForgeCore::TileMesh& A,
|
||||
const VoxelForgeCore::TileMesh& B)
|
||||
{
|
||||
@@ -424,7 +783,7 @@ namespace StandaloneStreamBenchmark
|
||||
bool bAuditResolvedLayoutPassages=false,
|
||||
bool bAuditStrateTransitions=false,
|
||||
const VoxelForgeCore::ResolvedLayoutSnapshotV3* ResolvedLayout=nullptr,
|
||||
bool bPrintTileHashes=false)
|
||||
bool bPrintTileHashes=false,bool bAuditLandmarkReachability=false)
|
||||
{
|
||||
using Clock=std::chrono::steady_clock;
|
||||
FStandaloneOwnerWorld& World=*OwnerWorld;
|
||||
@@ -609,6 +968,67 @@ namespace StandaloneStreamBenchmark
|
||||
Streamer.Stop();
|
||||
std::fprintf(stderr,"[StreamPipeline] full shape build failed: %s\n",BuildError.c_str());return 2;
|
||||
}
|
||||
if(bAuditLandmarkReachability)
|
||||
{
|
||||
Streamer.Stop();
|
||||
if(ResolvedLayout==nullptr||!CoreSnapshot.resolvedLayout()
|
||||
||ResolvedLayout->stamps.empty())
|
||||
{std::fprintf(stderr,"[LandmarkReachabilityAudit] missing demo layout and stamps\n");return 2;}
|
||||
const auto candidate=AuditLandmarkWalkPath(Shape,Assembly,*ResolvedLayout,
|
||||
World.CoreVoxelSizeMicrons,"L45");
|
||||
const auto daisStamp=std::find_if(ResolvedLayout->stamps.begin(),
|
||||
ResolvedLayout->stamps.end(),[](const auto& stamp)
|
||||
{return stamp.stableId=="landmark/sw-arrival-chamber-v1-dais";});
|
||||
if(daisStamp!=ResolvedLayout->stamps.end())
|
||||
{
|
||||
const double voxelMetres=std::max<std::uint32_t>(1,
|
||||
World.CoreVoxelSizeMicrons)/1000000.0;
|
||||
for(const double offset:{-0.5,0.0,0.5,0.9,1.2})
|
||||
{
|
||||
const double x=daisStamp->positionMicrometres[0]/1000000.0;
|
||||
const double y=daisStamp->positionMicrometres[1]/1000000.0;
|
||||
const double z=daisStamp->positionMicrometres[2]/1000000.0+offset;
|
||||
const double density=Shape.densityAt({x/voxelMetres,y/voxelMetres,
|
||||
z/voxelMetres})*voxelMetres;
|
||||
std::printf("[LandmarkDaisProfile] offset_m=%.2f sample_z_m=%.3f density_m=%.4f\n",
|
||||
offset,z,density);
|
||||
}
|
||||
}
|
||||
VoxelForgeCore::ResolvedLayoutSnapshotV3 oldLayout;
|
||||
std::string oldError;
|
||||
if(!MakeL43BuriedLandmarkControl(*ResolvedLayout,oldLayout,oldError))
|
||||
{std::fprintf(stderr,"[LandmarkReachabilityAudit] L43 control layout failed: %s\n",
|
||||
oldError.c_str());return 2;}
|
||||
auto oldSnapshot=CoreSnapshot.withResolvedLayout(oldLayout);
|
||||
if(!oldSnapshot)
|
||||
{std::fprintf(stderr,"[LandmarkReachabilityAudit] could not construct L43 control snapshot\n");return 2;}
|
||||
VoxelForgeCore::World oldShape(oldSnapshot);
|
||||
std::string oldBuildError;
|
||||
if(!oldShape.compile(ShapeRequest,Scheduler,oldBuildError))
|
||||
{std::fprintf(stderr,"[LandmarkReachabilityAudit] L43 control field build failed: %s\n",
|
||||
oldBuildError.c_str());return 2;}
|
||||
const auto buried=AuditLandmarkWalkPath(oldShape,Assembly,*ResolvedLayout,
|
||||
World.CoreVoxelSizeMicrons,"L43");
|
||||
std::printf("[LandmarkWalkability] layout=L45 path_samples=%llu capsule_samples=%llu blocked=%llu support_samples=%llu unsupported=%llu grade_failures=%llu max_grade_deg=%.3f limit_deg=15.000 min_clearance_m=%.3f max_support_depth_m=%.3f status=%s\n",
|
||||
static_cast<unsigned long long>(candidate.pathSamples),
|
||||
static_cast<unsigned long long>(candidate.capsuleSamples),
|
||||
static_cast<unsigned long long>(candidate.blockedCapsuleSamples),
|
||||
static_cast<unsigned long long>(candidate.supportSamples),
|
||||
static_cast<unsigned long long>(candidate.unsupportedSamples),
|
||||
static_cast<unsigned long long>(candidate.gradeFailures),
|
||||
candidate.maximumGradeDegrees,candidate.minimumClearanceMetres,
|
||||
candidate.maximumSupportDepthMetres,candidate.passed?"PASS":"FAIL");
|
||||
std::printf("[LandmarkReachabilityControl] layout=L43-buried path_samples=%llu blocked=%llu unsupported=%llu grade_failures=%llu result=%s expected=FAIL status=%s\n",
|
||||
static_cast<unsigned long long>(buried.pathSamples),
|
||||
static_cast<unsigned long long>(buried.blockedCapsuleSamples),
|
||||
static_cast<unsigned long long>(buried.unsupportedSamples),
|
||||
static_cast<unsigned long long>(buried.gradeFailures),
|
||||
buried.passed?"PASS":"FAIL",buried.passed?"FAIL":"PASS");
|
||||
const bool passed=candidate.passed&&!buried.passed;
|
||||
std::printf("[LandmarkReachabilityAudit] positive=PASS control=%s status=%s\n",
|
||||
buried.passed?"FAILED":"EXPECTED_FAIL",passed?"PASS":"FAIL");
|
||||
return passed?0:13;
|
||||
}
|
||||
if(bAuditStrateTransitions)
|
||||
{
|
||||
if(ResolvedLayout==nullptr||!CoreSnapshot.resolvedLayout())
|
||||
|
||||
Reference in new issue
Block a user