wip(L25, side branch): FI helix enclosed in a 13 m rock root; support never created in open air
Fixes the L24 ribbon (owner request). NOT merged: the open-air support gate leaves ~4,000 unsupported samples on every SurfaceWorld-recipe passage style (TN -> SW), a walkability regression. L26 fixes that on this branch. See Linux25/REPORT.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX
This commit is contained in:
1 parent
cb9236c265
commit
ef789aaa00
14 files changed
+662
-110
No files matched your search
@@ -453,6 +453,9 @@ namespace StandaloneStreamBenchmark
|
||||
std::array<std::array<double,4>,8> firstUnsupported{};
|
||||
std::array<std::array<bool,5>,8> firstSupportMasks{};
|
||||
std::size_t firstBlockedCount=0,firstUnsupportedCount=0;
|
||||
std::array<double,4> firstDeepUnsupported{};
|
||||
std::array<bool,5> firstDeepSupportMask{};
|
||||
bool hasDeepUnsupported=false;
|
||||
bool passed=false;
|
||||
};
|
||||
const double coreMetres=static_cast<double>(World.CoreVoxelSizeMicrons)/1000000.0;
|
||||
@@ -547,6 +550,13 @@ namespace StandaloneStreamBenchmark
|
||||
if(supportedProbes<minimumSupportProbes)
|
||||
{
|
||||
++audit.unsupported;
|
||||
if(!audit.hasDeepUnsupported&&footZ<-80.0)
|
||||
{
|
||||
audit.hasDeepUnsupported=true;
|
||||
audit.firstDeepUnsupported={firstUnsupportedX,firstUnsupportedY,
|
||||
footZ,lastUnsupportedDensity};
|
||||
audit.firstDeepSupportMask=supportMask;
|
||||
}
|
||||
if(audit.firstUnsupportedCount<audit.firstUnsupported.size())
|
||||
{
|
||||
const std::size_t sample=audit.firstUnsupportedCount++;
|
||||
@@ -658,6 +668,267 @@ namespace StandaloneStreamBenchmark
|
||||
[index](const auto& s){return s.strateIndex==index;});
|
||||
return it==ResolvedLayout->strates.end()?nullptr:&*it;
|
||||
};
|
||||
const auto controlSnapshot=CoreSnapshot.withoutResolvedLayoutPassages();
|
||||
VoxelForgeCore::World blockedWorld(controlSnapshot);
|
||||
std::string blockedCompileError;
|
||||
const bool blockedCompiled=controlSnapshot
|
||||
&&blockedWorld.compile(ShapeRequest,Scheduler,blockedCompileError);
|
||||
if(!blockedCompiled)
|
||||
{
|
||||
Streamer.Stop();
|
||||
std::fprintf(stderr,"[ResolvedPassageBlockedControl] compile_failed=%s\n",
|
||||
blockedCompileError.c_str());
|
||||
return 10;
|
||||
}
|
||||
struct FPassageOpenAirAudit
|
||||
{
|
||||
std::uint64_t supportSamples=0,violations=0,legacyViolations=0;
|
||||
std::uint64_t routeSupportSamples=0,routeSupportViolations=0;
|
||||
std::array<std::array<double,4>,8> firstViolations{};
|
||||
std::size_t firstViolationCount=0;
|
||||
};
|
||||
constexpr double PassageSupportAuditSpacingMetres=0.25;
|
||||
constexpr std::array<double,2> PassageSupportAuditDepthsMetres{{0.125,0.375}};
|
||||
constexpr std::array<std::array<double,2>,16> PassageSupportAuditDirections{{
|
||||
{{1.0000000000000000,0.0000000000000000}},
|
||||
{{0.9238795325112867,0.3826834323650898}},
|
||||
{{0.7071067811865476,0.7071067811865476}},
|
||||
{{0.3826834323650898,0.9238795325112867}},
|
||||
{{0.0000000000000000,1.0000000000000000}},
|
||||
{{-0.3826834323650898,0.9238795325112867}},
|
||||
{{-0.7071067811865476,0.7071067811865476}},
|
||||
{{-0.9238795325112867,0.3826834323650898}},
|
||||
{{-1.0000000000000000,0.0000000000000000}},
|
||||
{{-0.9238795325112867,-0.3826834323650898}},
|
||||
{{-0.7071067811865476,-0.7071067811865476}},
|
||||
{{-0.3826834323650898,-0.9238795325112867}},
|
||||
{{0.0000000000000000,-1.0000000000000000}},
|
||||
{{0.3826834323650898,-0.9238795325112867}},
|
||||
{{0.7071067811865476,-0.7071067811865476}},
|
||||
{{0.9238795325112867,-0.3826834323650898}}}};
|
||||
const auto auditPassageOpenAir=[&](const VoxelForgeCore::World& Field,
|
||||
const VoxelForgeCore::World& Native,
|
||||
const VoxelForgeCore::ResolvedLayoutPassageV1& passage)
|
||||
{
|
||||
FPassageOpenAirAudit audit;
|
||||
const auto inspect=[&](double xMetres,double yMetres,double floorMetres,
|
||||
double depthMetres,double supportThicknessMetres,bool routeSupport=false)
|
||||
{
|
||||
if(depthMetres<=0.0||depthMetres>supportThicknessMetres+1.0e-6)return;
|
||||
++audit.supportSamples;
|
||||
audit.routeSupportSamples+=routeSupport;
|
||||
const VoxelForgeCore::Vec3d sample{xMetres/coreMetres,yMetres/coreMetres,
|
||||
(floorMetres-depthMetres)/coreMetres};
|
||||
const float nativeDensity=Native.densityAt(sample);
|
||||
const float passageDensity=Field.densityAt(sample);
|
||||
const float legacySupportDensity=std::min(nativeDensity,
|
||||
static_cast<float>(-depthMetres/coreMetres));
|
||||
if(nativeDensity>=0.0f&&legacySupportDensity<0.0f)
|
||||
++audit.legacyViolations;
|
||||
if(nativeDensity>=0.0f&&passageDensity<0.0f)
|
||||
{
|
||||
audit.routeSupportViolations+=routeSupport;
|
||||
if(audit.firstViolationCount<audit.firstViolations.size())
|
||||
audit.firstViolations[audit.firstViolationCount++]={xMetres,yMetres,
|
||||
floorMetres-depthMetres,passageDensity};
|
||||
++audit.violations;
|
||||
}
|
||||
};
|
||||
const auto inspectFloorDisc=[&](double xMetres,double yMetres,double floorMetres,
|
||||
double supportRadiusMetres,double depthMetres,double supportThicknessMetres,
|
||||
bool routeSupport=false)
|
||||
{
|
||||
const std::size_t radialSteps=static_cast<std::size_t>(std::ceil(
|
||||
supportRadiusMetres/PassageSupportAuditSpacingMetres));
|
||||
for(std::size_t radialStep=0;radialStep<=radialSteps;++radialStep)
|
||||
{
|
||||
const double radial=std::min(supportRadiusMetres,
|
||||
radialStep*PassageSupportAuditSpacingMetres);
|
||||
for(const auto& direction:PassageSupportAuditDirections)
|
||||
inspect(xMetres+direction[0]*radial,yMetres+direction[1]*radial,
|
||||
floorMetres,depthMetres,supportThicknessMetres,routeSupport);
|
||||
}
|
||||
};
|
||||
for(std::size_t segment=1;segment<passage.controlPoints.size();++segment)
|
||||
{
|
||||
const auto& a=passage.controlPoints[segment-1];
|
||||
const auto& b=passage.controlPoints[segment];
|
||||
const double ax=qToMetres(a.positionSubvoxels[0]);
|
||||
const double ay=qToMetres(a.positionSubvoxels[1]);
|
||||
const double az=qToMetres(a.positionSubvoxels[2]);
|
||||
const double bx=qToMetres(b.positionSubvoxels[0]);
|
||||
const double by=qToMetres(b.positionSubvoxels[1]);
|
||||
const double bz=qToMetres(b.positionSubvoxels[2]);
|
||||
const double length=std::sqrt((bx-ax)*(bx-ax)+(by-ay)*(by-ay)+(bz-az)*(bz-az));
|
||||
const std::size_t intervals=std::max<std::size_t>(1,static_cast<std::size_t>(
|
||||
std::ceil(length/PassageSupportAuditSpacingMetres)));
|
||||
for(std::size_t sample=0;sample<=intervals;++sample)
|
||||
{
|
||||
if(segment>1&&sample==0)continue;
|
||||
const double t=static_cast<double>(sample)/intervals;
|
||||
const double centerX=ax+(bx-ax)*t,centerY=ay+(by-ay)*t;
|
||||
const double centerZ=az+(bz-az)*t;
|
||||
const double radius=qToMetres(a.radiusSubvoxels)
|
||||
+(qToMetres(b.radiusSubvoxels)-qToMetres(a.radiusSubvoxels))*t;
|
||||
const double floorZ=centerZ-radius+0.20;
|
||||
for(const double depth:PassageSupportAuditDepthsMetres)
|
||||
inspectFloorDisc(centerX,centerY,floorZ,radius,depth,1.0);
|
||||
}
|
||||
}
|
||||
const auto upperStratum=std::find_if(ResolvedLayout->strates.begin(),
|
||||
ResolvedLayout->strates.end(),[&](const auto& value)
|
||||
{return value.strateIndex==passage.upperStrateIndex;});
|
||||
if(upperStratum!=ResolvedLayout->strates.end())
|
||||
{
|
||||
std::array<const VoxelForgeCore::ResolvedLayoutAnchorV2*,5> route{};
|
||||
std::size_t routeCount=0;
|
||||
for(const auto& anchor:upperStratum->anchors)
|
||||
if(anchor.kind=="route_point"&&anchor.values.size()>=3)
|
||||
{
|
||||
if(routeCount<route.size())route[routeCount++]=&anchor;
|
||||
else
|
||||
{
|
||||
std::move(route.begin()+1,route.end(),route.begin());
|
||||
route.back()=&anchor;
|
||||
}
|
||||
}
|
||||
const double mouthX=qToMetres(passage.upperMouthSubvoxels[0]);
|
||||
const double mouthY=qToMetres(passage.upperMouthSubvoxels[1]);
|
||||
constexpr double RouteSupportReachMetres=13.0;
|
||||
constexpr double RouteFloorOffsetMetres=1.20;
|
||||
constexpr double RouteSupportThicknessMetres=1.0;
|
||||
constexpr double RouteSupportDepthStepMetres=0.125;
|
||||
for(std::size_t segment=1;segment<routeCount;++segment)
|
||||
{
|
||||
const auto& a=*route[segment-1];
|
||||
const auto& b=*route[segment];
|
||||
const double ax=qToMetres(a.values[0]),ay=qToMetres(a.values[1]);
|
||||
const double az=qToMetres(a.values[2]);
|
||||
const double bx=qToMetres(b.values[0]),by=qToMetres(b.values[1]);
|
||||
const double bz=qToMetres(b.values[2]);
|
||||
const double dx=bx-ax,dy=by-ay,dz=bz-az;
|
||||
const double length=std::sqrt(dx*dx+dy*dy+dz*dz);
|
||||
const std::size_t intervals=std::max<std::size_t>(1,
|
||||
static_cast<std::size_t>(std::ceil(length
|
||||
/PassageSupportAuditSpacingMetres)));
|
||||
const double routeRadius=a.values.size()>=4
|
||||
?std::max(0.75,qToMetres(a.values[3])):0.75;
|
||||
for(std::size_t sample=0;sample<=intervals;++sample)
|
||||
{
|
||||
if(segment>1&&sample==0)continue;
|
||||
const double t=static_cast<double>(sample)/intervals;
|
||||
const double centerX=ax+dx*t,centerY=ay+dy*t;
|
||||
const double centerZ=az+dz*t;
|
||||
for(std::size_t radialStep=0;radialStep<=static_cast<std::size_t>(
|
||||
std::ceil(routeRadius/PassageSupportAuditSpacingMetres));++radialStep)
|
||||
{
|
||||
const double radial=std::min(routeRadius,
|
||||
radialStep*PassageSupportAuditSpacingMetres);
|
||||
for(const auto& direction:PassageSupportAuditDirections)
|
||||
{
|
||||
const double x=centerX+direction[0]*radial;
|
||||
const double y=centerY+direction[1]*radial;
|
||||
if(std::hypot(x-mouthX,y-mouthY)>RouteSupportReachMetres)continue;
|
||||
const double floorZ=centerZ-RouteFloorOffsetMetres;
|
||||
for(double depth=RouteSupportDepthStepMetres;
|
||||
depth<=RouteSupportThicknessMetres+1.0e-6;
|
||||
depth+=RouteSupportDepthStepMetres)
|
||||
inspect(x,y,floorZ,depth,RouteSupportThicknessMetres,true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return audit;
|
||||
};
|
||||
if(Assembly)
|
||||
for(const auto& stratum:ResolvedLayout->strates)
|
||||
{
|
||||
if(stratum.generatorKind!="FloatingIslands")continue;
|
||||
const auto anchor=std::find_if(stratum.anchors.begin(),stratum.anchors.end(),
|
||||
[](const auto& value){return value.kind=="passage_anchor"&&value.values.size()>=7;});
|
||||
const auto islandPlan=std::find_if(Assembly->floatingIslands.begin(),
|
||||
Assembly->floatingIslands.end(),[&](const auto& value)
|
||||
{return value.strateIndex==stratum.strateIndex;});
|
||||
if(anchor==stratum.anchors.end()||islandPlan==Assembly->floatingIslands.end())continue;
|
||||
const auto ordinal=islandPlan->manifest.recipe.anchorIslandOrdinal;
|
||||
if(ordinal>=islandPlan->manifest.plan.islands.size())continue;
|
||||
const auto& anchorIsland=islandPlan->manifest.plan.islands[ordinal];
|
||||
const double axisX=qToMetres(anchor->values[0]);
|
||||
const double axisY=qToMetres(anchor->values[1]);
|
||||
const double rootBottom=qToMetres(anchor->values[5]);
|
||||
const auto& islandRecipe=islandPlan->manifest.recipe;
|
||||
const std::int64_t crustRatioQ16=islandRecipe.thicknessRatioQ16
|
||||
+islandRecipe.crustDepthBonusQ16;
|
||||
const std::int64_t crustDepthQ=(anchorIsland.radiusSubvoxels*crustRatioQ16
|
||||
+32768)/65536;
|
||||
const double rootTop=qToMetres(anchorIsland.topZSubvoxels-crustDepthQ);
|
||||
const double nominalRootRadius=qToMetres(anchor->values[2]);
|
||||
double helixMinimumRadius=std::numeric_limits<double>::infinity();
|
||||
double helixMaximumRadius=0.0;
|
||||
for(const auto& passage:ResolvedLayout->passages)
|
||||
if(passage.upperStrateIndex==stratum.strateIndex)
|
||||
for(const auto& point:passage.controlPoints)
|
||||
{
|
||||
const double z=qToMetres(point.positionSubvoxels[2]);
|
||||
if(z<rootBottom||z>rootTop)continue;
|
||||
const double radial=std::hypot(
|
||||
qToMetres(point.positionSubvoxels[0]-anchor->values[0]),
|
||||
qToMetres(point.positionSubvoxels[1]-anchor->values[1]));
|
||||
helixMinimumRadius=std::min(helixMinimumRadius,radial);
|
||||
helixMaximumRadius=std::max(helixMaximumRadius,radial);
|
||||
}
|
||||
if(!std::isfinite(helixMinimumRadius))continue;
|
||||
double minimumMeasuredRootRadius=std::numeric_limits<double>::infinity();
|
||||
std::uint64_t rootRaySamples=0;
|
||||
constexpr std::array<double,5> RootHeightFractions{{0.01,0.25,0.5,0.75,0.99}};
|
||||
for(const double fraction:RootHeightFractions)
|
||||
{
|
||||
const double z=rootTop+(rootBottom-rootTop)*fraction;
|
||||
double minimumRadius=std::numeric_limits<double>::infinity();
|
||||
double maximumRadius=0.0,meanRadius=0.0;
|
||||
constexpr std::size_t DirectionCount=32;
|
||||
constexpr double RayStepMetres=0.125;
|
||||
const std::size_t raySteps=static_cast<std::size_t>(std::ceil(
|
||||
(nominalRootRadius+2.0)/RayStepMetres));
|
||||
for(std::size_t direction=0;direction<DirectionCount;++direction)
|
||||
{
|
||||
const double angle=2.0*3.14159265358979323846*direction/DirectionCount;
|
||||
double radius=0.0;
|
||||
for(std::size_t step=0;step<=raySteps;++step)
|
||||
{
|
||||
const double probeRadius=step*RayStepMetres;
|
||||
const VoxelForgeCore::Vec3d probe{
|
||||
(axisX+probeRadius*std::cos(angle))/coreMetres,
|
||||
(axisY+probeRadius*std::sin(angle))/coreMetres,z/coreMetres};
|
||||
if(blockedWorld.densityAt(probe)>=0.0f)break;
|
||||
radius=probeRadius;
|
||||
}
|
||||
minimumRadius=std::min(minimumRadius,radius);
|
||||
maximumRadius=std::max(maximumRadius,radius);
|
||||
meanRadius+=radius;
|
||||
++rootRaySamples;
|
||||
}
|
||||
meanRadius/=DirectionCount;
|
||||
minimumMeasuredRootRadius=std::min(minimumMeasuredRootRadius,minimumRadius);
|
||||
std::printf("[PassageRootRadiusMeasure] strate=%d z_m=%.3f solid_radius_min_m=%.3f mean_m=%.3f max_m=%.3f nominal_root_m=%.3f rays=%zu\n",
|
||||
stratum.strateIndex,z,minimumRadius,meanRadius,maximumRadius,
|
||||
nominalRootRadius,DirectionCount);
|
||||
}
|
||||
const auto firstRootPassage=std::find_if(ResolvedLayout->passages.begin(),
|
||||
ResolvedLayout->passages.end(),[&](const auto& passage)
|
||||
{return passage.upperStrateIndex==stratum.strateIndex;});
|
||||
const double mouthBoreRadius=firstRootPassage==ResolvedLayout->passages.end()?0.0
|
||||
:qToMetres(firstRootPassage->mouthRadiusSubvoxels);
|
||||
const double midBoreRadius=firstRootPassage==ResolvedLayout->passages.end()?0.0
|
||||
:qToMetres(firstRootPassage->midRadiusSubvoxels);
|
||||
const double maximumBoreRadius=std::max(mouthBoreRadius,midBoreRadius);
|
||||
std::printf("[PassageRootMeasure] strate=%d axis_m=(%.3f,%.3f) helix_centerline_radius_min_m=%.3f max_m=%.3f mouth_bore_radius_m=%.3f mid_bore_radius_m=%.3f nominal_root_m=%.3f measured_min_root_m=%.3f minimum_wall_m=%.3f samples=%llu\n",
|
||||
stratum.strateIndex,axisX,axisY,helixMinimumRadius,helixMaximumRadius,
|
||||
mouthBoreRadius,midBoreRadius,nominalRootRadius,minimumMeasuredRootRadius,
|
||||
minimumMeasuredRootRadius-helixMaximumRadius-maximumBoreRadius,
|
||||
static_cast<unsigned long long>(rootRaySamples));
|
||||
}
|
||||
const auto mouthInContent=[&](std::int32_t index,const std::array<std::int64_t,3>& mouth)
|
||||
{
|
||||
const auto* stratum=findStratum(index);if(!stratum)return false;
|
||||
@@ -667,6 +938,8 @@ namespace StandaloneStreamBenchmark
|
||||
return zUm>=stratum->content.bottomMicrometres&&zUm<stratum->content.topMicrometres;
|
||||
};
|
||||
std::uint64_t passageCount=0,passagePasses=0,blockedControls=0,naturalPasses=0;
|
||||
std::uint64_t openAirViolations=0,openAirLegacyViolations=0,openAirSamples=0;
|
||||
std::uint64_t openAirAudits=0;
|
||||
for(const auto& passage:ResolvedLayout->passages)
|
||||
{
|
||||
std::vector<std::array<double,3>> path;path.reserve(passage.controlPoints.size());
|
||||
@@ -680,6 +953,11 @@ namespace StandaloneStreamBenchmark
|
||||
}
|
||||
const FWalkAudit passageAudit=auditPlayerPath(Shape,path,0.125,0.125,0.25,3,0.45);
|
||||
const FPassageNaturalnessAudit naturalAudit=auditPassageNaturalness(Shape,passage);
|
||||
const FPassageOpenAirAudit openAirAudit=auditPassageOpenAir(Shape,blockedWorld,passage);
|
||||
openAirSamples+=openAirAudit.supportSamples;
|
||||
openAirViolations+=openAirAudit.violations;
|
||||
openAirLegacyViolations+=openAirAudit.legacyViolations;
|
||||
++openAirAudits;
|
||||
const auto radialSquared=[](const std::array<std::int64_t,3>& p)
|
||||
{return p[0]*p[0]+p[1]*p[1];};
|
||||
const bool mouthsInRadius=radialSquared(passage.upperMouthSubvoxels)
|
||||
@@ -706,15 +984,32 @@ namespace StandaloneStreamBenchmark
|
||||
static_cast<unsigned long long>(naturalAudit.wallSamples),
|
||||
static_cast<unsigned long long>(naturalAudit.missingWallSamples),
|
||||
naturalAudit.passed?"PASS":"FAIL");
|
||||
std::printf("[PassageOpenAirAudit] id=%s support_samples=%llu violations=%llu l24_ungated_violations=%llu route_support_samples=%llu route_support_violations=%llu spacing_m=%.3f status=%s\n",
|
||||
passage.stableId.c_str(),static_cast<unsigned long long>(openAirAudit.supportSamples),
|
||||
static_cast<unsigned long long>(openAirAudit.violations),
|
||||
static_cast<unsigned long long>(openAirAudit.legacyViolations),
|
||||
static_cast<unsigned long long>(openAirAudit.routeSupportSamples),
|
||||
static_cast<unsigned long long>(openAirAudit.routeSupportViolations),
|
||||
PassageSupportAuditSpacingMetres,
|
||||
openAirAudit.violations==0?"PASS":"FAIL");
|
||||
for(std::size_t i=0;i<openAirAudit.firstViolationCount;++i)
|
||||
std::printf("[PassageOpenAirAuditSample] id=%s kind=unsupported_support xyz_m=(%.3f,%.3f,%.3f) density=%.6f\n",
|
||||
passage.stableId.c_str(),openAirAudit.firstViolations[i][0],
|
||||
openAirAudit.firstViolations[i][1],openAirAudit.firstViolations[i][2],
|
||||
openAirAudit.firstViolations[i][3]);
|
||||
for(std::size_t i=0;i<passageAudit.firstBlockedCount;++i)
|
||||
{
|
||||
const auto& sample=passageAudit.firstBlocked[i];
|
||||
std::printf("[ResolvedPassageAuditSample] id=%s kind=blocked xyz_m=(%.3f,%.3f,%.3f) density=%.6f\n",
|
||||
passage.stableId.c_str(),passageAudit.firstBlocked[i][0],passageAudit.firstBlocked[i][1],
|
||||
passageAudit.firstBlocked[i][2],passageAudit.firstBlocked[i][3]);
|
||||
passage.stableId.c_str(),sample[0],sample[1],sample[2],sample[3]);
|
||||
}
|
||||
for(std::size_t i=0;i<passageAudit.firstUnsupportedCount;++i)
|
||||
{
|
||||
std::printf("[ResolvedPassageAuditSample] id=%s kind=unsupported xyz_m=(%.3f,%.3f,%.3f) density=%.6f\n",
|
||||
passage.stableId.c_str(),passageAudit.firstUnsupported[i][0],
|
||||
passageAudit.firstUnsupported[i][1],passageAudit.firstUnsupported[i][2],
|
||||
passageAudit.firstUnsupported[i][3]);
|
||||
}
|
||||
}
|
||||
std::uint64_t routePoints=0,routePasses=0,routeWalkPasses=0,routeBindings=0,routeBindingFailures=0;
|
||||
struct FRouteAuditInput
|
||||
@@ -798,15 +1093,28 @@ namespace StandaloneStreamBenchmark
|
||||
{return a[0]==b[0]&&a[1]==b[1]&&a[2]==b[2];};
|
||||
if(passage.upperStrateIndex==stratum.strateIndex)
|
||||
{
|
||||
const std::array<std::int64_t,3> departure{{anchors.back()->values[0],
|
||||
anchors.back()->values[1],anchors.back()->values[2]}};
|
||||
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 std::array<std::int64_t,3> arrival{{anchors.front()->values[0],
|
||||
anchors.front()->values[1],anchors.front()->values[2]}};
|
||||
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);
|
||||
}
|
||||
@@ -831,43 +1139,45 @@ namespace StandaloneStreamBenchmark
|
||||
routeAudit.firstUnsupported[i][1],routeAudit.firstUnsupported[i][2],
|
||||
routeAudit.firstUnsupported[i][3]);
|
||||
}
|
||||
const auto controlSnapshot=CoreSnapshot.withoutResolvedLayoutPassages();
|
||||
VoxelForgeCore::World blockedWorld(controlSnapshot);
|
||||
std::string blockedCompileError;
|
||||
const bool blockedCompiled=controlSnapshot
|
||||
&&blockedWorld.compile(ShapeRequest,Scheduler,blockedCompileError);
|
||||
if(blockedCompiled)
|
||||
for(const auto& passage:ResolvedLayout->passages)
|
||||
for(const auto& passage:ResolvedLayout->passages)
|
||||
{
|
||||
std::vector<std::array<double,3>> path;path.reserve(passage.controlPoints.size());
|
||||
for(const auto& point:passage.controlPoints)
|
||||
{
|
||||
std::vector<std::array<double,3>> path;path.reserve(passage.controlPoints.size());
|
||||
for(const auto& point:passage.controlPoints)
|
||||
{
|
||||
const double playerZ=static_cast<double>(point.positionSubvoxels[2]
|
||||
-point.radiusSubvoxels)+1.13*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel
|
||||
*1000000.0/World.CoreVoxelSizeMicrons;
|
||||
path.push_back({qToMetres(point.positionSubvoxels[0])/coreMetres,
|
||||
qToMetres(point.positionSubvoxels[1])/coreMetres,playerZ/256.0});
|
||||
}
|
||||
const FWalkAudit blockedAudit=auditPlayerPath(blockedWorld,path,
|
||||
0.125,0.125,0.25,3,0.45);
|
||||
blockedControls+=blockedAudit.blocked>0&&!blockedAudit.passed;
|
||||
std::printf("[ResolvedPassageBlockedControl] id=%s blocked_capsule_samples=%llu detected=%s\n",
|
||||
passage.stableId.c_str(),static_cast<unsigned long long>(blockedAudit.blocked),
|
||||
blockedAudit.blocked>0&&!blockedAudit.passed?"true":"false");
|
||||
}
|
||||
else
|
||||
std::fprintf(stderr,"[ResolvedPassageBlockedControl] compile_failed=%s\n",blockedCompileError.c_str());
|
||||
if(blockedCompiled)
|
||||
for(const auto& input:routeAuditInputs)
|
||||
{
|
||||
const FWalkAudit controlRoute=auditPlayerPath(blockedWorld,input.path,
|
||||
input.supportStart,input.supportStep,input.supportMaximum,
|
||||
input.minimumSupportProbes,input.supportProbeRadius);
|
||||
std::printf("[ResolvedStrateRouteBlockedControl] strate=%d blocked=%llu unsupported=%llu status=%s\n",
|
||||
input.strateIndex,static_cast<unsigned long long>(controlRoute.blocked),
|
||||
static_cast<unsigned long long>(controlRoute.unsupported),
|
||||
controlRoute.passed?"PASS":"FAIL");
|
||||
const double playerZ=static_cast<double>(point.positionSubvoxels[2]
|
||||
-point.radiusSubvoxels)+1.13*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel
|
||||
*1000000.0/World.CoreVoxelSizeMicrons;
|
||||
path.push_back({qToMetres(point.positionSubvoxels[0])/coreMetres,
|
||||
qToMetres(point.positionSubvoxels[1])/coreMetres,playerZ/256.0});
|
||||
}
|
||||
const FWalkAudit blockedAudit=auditPlayerPath(blockedWorld,path,
|
||||
0.125,0.125,0.25,3,0.45);
|
||||
blockedControls+=blockedAudit.blocked>0&&!blockedAudit.passed;
|
||||
std::printf("[ResolvedPassageBlockedControl] id=%s blocked_capsule_samples=%llu detected=%s\n",
|
||||
passage.stableId.c_str(),static_cast<unsigned long long>(blockedAudit.blocked),
|
||||
blockedAudit.blocked>0&&!blockedAudit.passed?"true":"false");
|
||||
}
|
||||
for(const auto& input:routeAuditInputs)
|
||||
{
|
||||
const FWalkAudit controlRoute=auditPlayerPath(blockedWorld,input.path,
|
||||
input.supportStart,input.supportStep,input.supportMaximum,
|
||||
input.minimumSupportProbes,input.supportProbeRadius);
|
||||
std::printf("[ResolvedStrateRouteBlockedControl] strate=%d blocked=%llu unsupported=%llu status=%s\n",
|
||||
input.strateIndex,static_cast<unsigned long long>(controlRoute.blocked),
|
||||
static_cast<unsigned long long>(controlRoute.unsupported),
|
||||
controlRoute.passed?"PASS":"FAIL");
|
||||
}
|
||||
const bool l24OpenAirControlDetected=openAirLegacyViolations>0;
|
||||
std::printf("[PassageOpenAirNegativeControl] source=L24_ungated_support unsupported_samples=%llu detected=%s\n",
|
||||
static_cast<unsigned long long>(openAirLegacyViolations),
|
||||
l24OpenAirControlDetected?"true":"false");
|
||||
std::printf("[PassageOpenAirAuditSummary] interfaces=%llu samples=%llu violations=%llu l24_negative_control=%s status=%s\n",
|
||||
static_cast<unsigned long long>(openAirAudits),
|
||||
static_cast<unsigned long long>(openAirSamples),
|
||||
static_cast<unsigned long long>(openAirViolations),
|
||||
l24OpenAirControlDetected?"DETECTED":"MISSED",
|
||||
openAirAudits==passageCount&&openAirViolations==0&&l24OpenAirControlDetected
|
||||
?"PASS":"FAIL");
|
||||
const bool l23SmoothWallDetected=!looksNatural(0.0,0.0);
|
||||
std::printf("[PassageNaturalnessNegativeControl] source=L23_smooth_unrelieved_wall relief_p2p_m=0.000 threshold_m=%.3f detected=%s\n",
|
||||
MinimumWallReliefMetres,l23SmoothWallDetected?"true":"false");
|
||||
@@ -877,6 +1187,7 @@ namespace StandaloneStreamBenchmark
|
||||
MinimumWallReliefMetres,naturalPasses==passageCount&&l23SmoothWallDetected?"PASS":"FAIL");
|
||||
const bool auditPassed=passageCount>0&&passagePasses==passageCount
|
||||
&&naturalPasses==passageCount&&l23SmoothWallDetected
|
||||
&&openAirAudits==passageCount&&openAirViolations==0&&l24OpenAirControlDetected
|
||||
&&routePasses>0&&routePasses==routeWalkPasses&&routePasses==routeBindings
|
||||
&&routeBindingFailures==0
|
||||
&&blockedCompiled&&blockedControls==passageCount;
|
||||
|
||||
@@ -540,7 +540,7 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
FIntVector(0,0,islandSlot->TopChunkZ));
|
||||
const FStratePassageConfig& passage=islandSlot->Definition->PassageConfig;
|
||||
recipe.seed=static_cast<std::uint32_t>(owner.Seed);
|
||||
recipe.geometryVersion=5;
|
||||
recipe.geometryVersion=7;
|
||||
recipe.ownerVoxelSizeMicrons=owner.OwnerVoxelSizeMicrons;
|
||||
recipe.coreVoxelSizeMicrons=owner.CoreVoxelSizeMicrons;
|
||||
recipe.ownerToCoreScaleNumerator=owner.OwnerVoxelSizeMicrons/static_cast<std::uint32_t>(divisor);
|
||||
@@ -573,17 +573,22 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
recipe.anchorPlacementRadiusSubvoxels=ownerVoxelsToCoreQ(passage.DistanceMax);
|
||||
const std::int64_t spiralRadius=ownerVoxelsToCoreQ(passage.SpiralRadius);
|
||||
const std::int64_t mouthBore=std::max(ownerVoxelsToCoreQ(passage.MouthRadius),
|
||||
ownerVoxelsToCoreQ(12.0));
|
||||
ownerVoxelsToCoreQ(36.0));
|
||||
const std::int64_t bore=std::max(ownerVoxelsToCoreQ(passage.MidRadius),
|
||||
ownerVoxelsToCoreQ(12.0));
|
||||
const std::int64_t wallReserve=ownerVoxelsToCoreQ(12.0);
|
||||
// Put the mouth near the island rim, with enough island and root around the
|
||||
// first coil to read as a cave entrance instead of a hole in open air.
|
||||
recipe.anchorMouthOffsetSubvoxels=spiralRadius+mouthBore+ownerVoxelsToCoreQ(4.0);
|
||||
const std::int64_t excursion=std::max(ownerVoxelsToCoreQ(passage.Wander),spiralRadius);
|
||||
recipe.anchorRootRadiusSubvoxels=excursion+bore+wallReserve;
|
||||
ownerVoxelsToCoreQ(24.0));
|
||||
const std::int64_t wallReserve=ownerVoxelsToCoreQ(20.0);
|
||||
// Retain a 4.5-metre mouth for capsule clearance while keeping the long
|
||||
// ramp at three metres to leave more rock around the first coil.
|
||||
recipe.anchorMouthOffsetSubvoxels=ownerVoxelsToCoreQ(40.0);
|
||||
const std::int64_t supportOverhang=ownerVoxelsToCoreQ(16.0);
|
||||
const std::int64_t rootTaperReserve=ownerVoxelsToCoreQ(10.0);
|
||||
const std::int64_t mouthEnvelope=recipe.anchorMouthOffsetSubvoxels+mouthBore;
|
||||
const std::int64_t treadEnvelope=spiralRadius+bore+supportOverhang;
|
||||
recipe.anchorRootRadiusSubvoxels=std::max(mouthEnvelope,treadEnvelope)
|
||||
+wallReserve+rootTaperReserve;
|
||||
recipe.anchorIslandMinimumRadiusSubvoxels=std::max(recipe.minimumRadiusSubvoxels,
|
||||
recipe.anchorMouthOffsetSubvoxels+mouthBore+ownerVoxelsToCoreQ(4.0));
|
||||
std::max(recipe.anchorMouthOffsetSubvoxels+mouthBore+ownerVoxelsToCoreQ(4.0),
|
||||
recipe.anchorRootRadiusSubvoxels+wallReserve));
|
||||
recipe.anchorRootBottomZSubvoxels=recipe.layoutMinimumZSubvoxels
|
||||
+recipe.boundarySealSubvoxels;
|
||||
|
||||
@@ -713,6 +718,39 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
[&](const auto& p){return p.strateIndex==slot.StrateIndex;});
|
||||
if(it!=assembly.floatingIslands.end())
|
||||
{
|
||||
std::size_t passageMouthRouteIndex=it->manifest.plan.route.empty()?0
|
||||
:it->manifest.plan.route.size()-1;
|
||||
std::size_t resolvedRoutePointCount=it->manifest.plan.route.size();
|
||||
if(it->manifest.recipe.geometryVersion>=5
|
||||
&&!it->manifest.plan.route.empty()
|
||||
&&it->manifest.recipe.anchorIslandOrdinal<it->manifest.plan.islands.size())
|
||||
{
|
||||
const auto& rootedIsland=it->manifest.plan.islands[
|
||||
it->manifest.recipe.anchorIslandOrdinal];
|
||||
const double qPerMetre=static_cast<double>(
|
||||
VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel)*1000000.0
|
||||
/owner.CoreVoxelSizeMicrons;
|
||||
const double thresholdQ=static_cast<double>(
|
||||
it->manifest.recipe.anchorMouthOffsetSubvoxels)+qPerMetre;
|
||||
for(std::size_t reverse=it->manifest.plan.route.size();reverse>1;--reverse)
|
||||
{
|
||||
const std::size_t index=reverse-1;
|
||||
const auto& point=it->manifest.plan.route[index].positionSubvoxels;
|
||||
const double dx=static_cast<double>(point[0]-rootedIsland.centerSubvoxels[0]);
|
||||
const double dy=static_cast<double>(point[1]-rootedIsland.centerSubvoxels[1]);
|
||||
if(std::hypot(dx,dy)>thresholdQ)
|
||||
{
|
||||
passageMouthRouteIndex=std::min(index+1,
|
||||
it->manifest.plan.route.size()-1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Stop the island surface route immediately before it enters
|
||||
// the outer coil. The helical passage owns the walkable path
|
||||
// from its mouth inward.
|
||||
resolvedRoutePointCount=passageMouthRouteIndex>1
|
||||
?passageMouthRouteIndex-1:passageMouthRouteIndex;
|
||||
}
|
||||
const auto recipeBottom=fixedToMicrons(it->manifest.recipe.layoutMinimumZSubvoxels);
|
||||
const auto recipeTop=fixedToMicrons(it->manifest.recipe.layoutMaximumZSubvoxels);
|
||||
if(recipeBottom!=bottom||recipeTop!=top)
|
||||
@@ -737,10 +775,17 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
it->manifest.recipe.anchorPlacementRadiusSubvoxels,
|
||||
it->manifest.recipe.anchorRootBottomZSubvoxels,
|
||||
a.radiusSubvoxels};
|
||||
if(!it->manifest.plan.route.empty())
|
||||
{
|
||||
const auto& routeMouth=it->manifest.plan.route[
|
||||
passageMouthRouteIndex].positionSubvoxels;
|
||||
passageAnchor.values.insert(passageAnchor.values.end(),
|
||||
{routeMouth[0],routeMouth[1],routeMouth[2]});
|
||||
}
|
||||
s.anchors.push_back(std::move(passageAnchor));
|
||||
}
|
||||
}
|
||||
for(std::size_t n=0;n<it->manifest.plan.route.size();++n)
|
||||
for(std::size_t n=0;n<resolvedRoutePointCount;++n)
|
||||
{
|
||||
const auto& p=it->manifest.plan.route[n];ResolvedLayoutAnchorV2 anchor;
|
||||
anchor.kind="route_point";anchor.stableId="route/"+std::to_string(n);
|
||||
@@ -788,6 +833,32 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
{const auto& p=plan->route[n];ResolvedLayoutAnchorV2 a;a.kind="route_point";a.stableId="route/"+std::to_string(n);
|
||||
a.units="1/256 Core voxel";a.values={p.positionSubvoxels[0],p.positionSubvoxels[1],p.positionSubvoxels[2],p.radiusSubvoxels};
|
||||
s.anchors.push_back(std::move(a));}
|
||||
const auto upperAnchorStratum=std::find_if(out.strates.begin(),out.strates.end(),
|
||||
[](const auto& candidate)
|
||||
{return candidate.generatorKind=="FloatingIslands"
|
||||
&&std::any_of(candidate.anchors.begin(),candidate.anchors.end(),
|
||||
[](const auto& anchor){return anchor.kind=="passage_anchor"
|
||||
&&anchor.values.size()>=7;});});
|
||||
if(upperAnchorStratum!=out.strates.end())
|
||||
{
|
||||
const auto rootAnchor=std::find_if(upperAnchorStratum->anchors.begin(),
|
||||
upperAnchorStratum->anchors.end(),[](const auto& anchor)
|
||||
{return anchor.kind=="passage_anchor"&&anchor.values.size()>=7;});
|
||||
const std::int64_t arrivalZMicrometres=top-seal-1250000;
|
||||
if(rootAnchor!=upperAnchorStratum->anchors.end()
|
||||
&&arrivalZMicrometres>=bottom+seal&&arrivalZMicrometres<top-seal)
|
||||
{
|
||||
ResolvedLayoutAnchorV2 arrival;arrival.kind="passage_arrival";
|
||||
arrival.stableId="passage-arrival/from-strate/"
|
||||
+std::to_string(upperAnchorStratum->strateIndex);
|
||||
arrival.units="1/256 Core voxel";
|
||||
arrival.values={rootAnchor->values[0],rootAnchor->values[1],
|
||||
static_cast<std::int64_t>(std::llround(
|
||||
static_cast<double>(arrivalZMicrometres)
|
||||
*WorldAssemblySubvoxelsPerVoxel/owner.CoreVoxelSizeMicrons))};
|
||||
s.anchors.push_back(std::move(arrival));
|
||||
}
|
||||
}
|
||||
}
|
||||
out.strates.push_back(std::move(s));
|
||||
}
|
||||
@@ -803,9 +874,9 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
config.connections=authored.Connections;
|
||||
config.style=static_cast<std::uint32_t>(authored.Style);
|
||||
config.mouthRadiusSubvoxels=std::max(ownerVoxelsToCoreQ(authored.MouthRadius),
|
||||
ownerVoxelsToCoreQ(12.0));
|
||||
ownerVoxelsToCoreQ(36.0));
|
||||
config.midRadiusSubvoxels=std::max(ownerVoxelsToCoreQ(authored.MidRadius),
|
||||
ownerVoxelsToCoreQ(12.0));
|
||||
ownerVoxelsToCoreQ(24.0));
|
||||
config.reachMinimumSubvoxels=ownerVoxelsToCoreQ(authored.ReachMin);
|
||||
config.reachMaximumSubvoxels=ownerVoxelsToCoreQ(authored.ReachMax);
|
||||
config.distanceMinimumSubvoxels=ownerVoxelsToCoreQ(authored.DistanceMin);
|
||||
|
||||
Reference in new issue
Block a user