#pragma once #include "Json.h" #include "VoxelForgeCore/VoxelForgeCore.h" #include #include #include #include #include #include #include namespace VoxelForgeCore { namespace { constexpr std::int64_t TransitionQ16 = 1ll << 16; void TransitionHashWord(std::uint64_t& hash, std::uint64_t word) { for (int byte = 0; byte < 8; ++byte) { hash ^= static_cast(word >> (byte * 8)); hash *= 1099511628211ull; } } std::uint64_t TransitionHash(std::int32_t seed, std::int32_t interfaceIndex, std::int64_t x, std::int64_t y, std::int64_t z, std::uint32_t salt) { std::uint64_t hash = 14695981039346656037ull; TransitionHashWord(hash, static_cast(seed)); TransitionHashWord(hash, static_cast(interfaceIndex)); TransitionHashWord(hash, static_cast(x)); TransitionHashWord(hash, static_cast(y)); TransitionHashWord(hash, static_cast(z)); TransitionHashWord(hash, salt); hash ^= hash >> 30; hash *= 0xbf58476d1ce4e5b9ull; hash ^= hash >> 27; hash *= 0x94d049bb133111ebull; hash ^= hash >> 31; return hash; } std::int64_t TransitionNoiseValue(std::int32_t seed, std::int32_t interfaceIndex, std::int64_t x, std::int64_t y, std::int64_t z, std::uint32_t salt) { const std::uint32_t bits = static_cast( TransitionHash(seed, interfaceIndex, x, y, z, salt) >> 32); return static_cast(bits >> 15) - TransitionQ16; } std::int64_t TransitionFloorDiv(std::int64_t value, std::int64_t divisor) { const std::int64_t quotient = value / divisor; const std::int64_t remainder = value % divisor; return quotient - (remainder < 0 ? 1 : 0); } std::int64_t TransitionFade(std::int64_t valueQ16) { valueQ16 = std::clamp(valueQ16, std::int64_t{0}, TransitionQ16); const std::int64_t square = (valueQ16 * valueQ16) / TransitionQ16; return (square * (3 * TransitionQ16 - 2 * valueQ16)) / TransitionQ16; } std::int64_t TransitionLerp(std::int64_t a, std::int64_t b, std::int64_t tQ16) { return a + ((b - a) * tQ16) / TransitionQ16; } std::int64_t TransitionValueNoise2D(std::int32_t seed, std::int32_t interfaceIndex, std::int64_t x, std::int64_t y, std::int64_t cell, std::uint32_t salt) { const std::int64_t ix = TransitionFloorDiv(x, cell), iy = TransitionFloorDiv(y, cell); const std::int64_t tx = TransitionFade(((x - ix * cell) * TransitionQ16) / cell); const std::int64_t ty = TransitionFade(((y - iy * cell) * TransitionQ16) / cell); std::uint64_t prefix=14695981039346656037ull; TransitionHashWord(prefix,static_cast(seed)); TransitionHashWord(prefix,static_cast(interfaceIndex)); const auto at = [&](std::int64_t dx, std::int64_t dy) { std::uint64_t hash=prefix; TransitionHashWord(hash,static_cast(ix+dx)); TransitionHashWord(hash,static_cast(iy+dy)); TransitionHashWord(hash,0); TransitionHashWord(hash,salt); hash ^= hash >> 30; hash *= 0xbf58476d1ce4e5b9ull; hash ^= hash >> 27; hash *= 0x94d049bb133111ebull; hash ^= hash >> 31; const std::uint32_t bits=static_cast(hash>>32); return static_cast(bits>>15)-TransitionQ16; }; return TransitionLerp(TransitionLerp(at(0, 0), at(1, 0), tx), TransitionLerp(at(0, 1), at(1, 1), tx), ty); } std::int64_t TransitionValueNoise3D(std::int32_t seed, std::int32_t interfaceIndex, std::int64_t x, std::int64_t y, std::int64_t z, std::int64_t cell, std::uint32_t salt) { const std::int64_t ix = TransitionFloorDiv(x, cell), iy = TransitionFloorDiv(y, cell); const std::int64_t iz = TransitionFloorDiv(z, cell); const std::int64_t tx = TransitionFade(((x - ix * cell) * TransitionQ16) / cell); const std::int64_t ty = TransitionFade(((y - iy * cell) * TransitionQ16) / cell); const std::int64_t tz = TransitionFade(((z - iz * cell) * TransitionQ16) / cell); const auto at = [&](std::int64_t dx, std::int64_t dy, std::int64_t dz) { return TransitionNoiseValue(seed, interfaceIndex, ix + dx, iy + dy, iz + dz, salt); }; const std::int64_t z0 = TransitionLerp( TransitionLerp(at(0, 0, 0), at(1, 0, 0), tx), TransitionLerp(at(0, 1, 0), at(1, 1, 0), tx), ty); const std::int64_t z1 = TransitionLerp( TransitionLerp(at(0, 0, 1), at(1, 0, 1), tx), TransitionLerp(at(0, 1, 1), at(1, 1, 1), tx), ty); return TransitionLerp(z0, z1, tz); } std::int64_t LayoutTransitionLimit(const ResolvedLayoutStratumV2& stratum) { if (stratum.transitionMode == "Hard" || stratum.transitionFallsBackToHard) return 0; return std::max(0, stratum.transitionWidthMicrometres); } std::string LayoutEscape(std::string_view value) { std::string out="\""; for(unsigned char c:value) { switch(c) { case '"':out+="\\\"";break; case '\\':out+="\\\\";break; case '\b':out+="\\b";break; case '\f':out+="\\f";break; case '\n':out+="\\n";break; case '\r':out+="\\r";break; case '\t':out+="\\t";break; default:if(c<0x20){char b[7];std::snprintf(b,sizeof(b),"\\u%04x",c);out+=b;} else out+=static_cast(c); } } out+='"';return out; } std::uint64_t LayoutHash(std::string_view value) { std::uint64_t hash=14695981039346656037ull; for(unsigned char c:value){hash^=c;hash*=1099511628211ull;} return hash; } std::uint64_t LayoutStampMix(std::uint64_t value) { value^=value>>30;value*=0xbf58476d1ce4e5b9ull; value^=value>>27;value*=0x94d049bb133111ebull; return value^(value>>31); } std::uint64_t LayoutStampSeed(std::int32_t seed,std::int32_t strate, std::string_view stableId) { std::uint64_t hash=14695981039346656037ull; TransitionHashWord(hash,static_cast(seed)); TransitionHashWord(hash,static_cast(strate)); for(unsigned char c:stableId){hash^=c;hash*=1099511628211ull;} return LayoutStampMix(hash); } std::uint64_t LayoutLatticeFeatureSeed(std::int32_t seed,std::int32_t strate, std::uint32_t recipeVersion,std::string_view stableId) { std::uint64_t hash=14695981039346656037ull; TransitionHashWord(hash,static_cast(seed)); TransitionHashWord(hash,static_cast(strate)); TransitionHashWord(hash,recipeVersion); for(unsigned char c:stableId){hash^=c;hash*=1099511628211ull;} return LayoutStampMix(hash); } std::uint64_t LayoutStampNext(std::uint64_t& state) {state=LayoutStampMix(state+0x9e3779b97f4a7c15ull);return state;} bool LayoutStampKindValid(ResolvedSdfStampKindV1 kind) { const auto value=static_cast(kind); return value>=static_cast(ResolvedSdfStampKindV1::carveEllipsoid) &&value<=static_cast(ResolvedSdfStampKindV1::fillPlatform); } bool LayoutStampIsMotif(ResolvedSdfStampKindV1 kind) {return kind==ResolvedSdfStampKindV1::fillMonolith ||kind==ResolvedSdfStampKindV1::fillPlatform;} double LayoutStampPointSegmentDistance(const std::array& point, const std::array& a,const std::array& b) { double lengthSquared=0.0,dot=0.0; for(std::size_t axis=0;axis<3;++axis) { const double delta=static_cast(b[axis])-a[axis]; lengthSquared+=delta*delta; dot+=(static_cast(point[axis])-a[axis])*delta; } const double t=lengthSquared>0.0?std::clamp(dot/lengthSquared,0.0,1.0):0.0; double distanceSquared=0.0; for(std::size_t axis=0;axis<3;++axis) { const double nearest=static_cast(a[axis]) +t*(static_cast(b[axis])-a[axis]); const double delta=static_cast(point[axis])-nearest; distanceSquared+=delta*delta; } return std::sqrt(distanceSquared); } double LayoutStampBoundRadius(const ResolvedLayoutStampV1& stamp) { double radiusSquared=0.0; for(std::size_t axis=0;axis<3;++axis) { const double extent=(static_cast(stamp.boundsMaximumMicrometres[axis]) -stamp.boundsMinimumMicrometres[axis])*0.5; radiusSquared+=extent*extent; } return std::sqrt(radiusSquared); } bool LayoutStampSafe(const ResolvedLayoutSnapshotV3& layout, const ResolvedLayoutStampV1& stamp,std::string* reason=nullptr) { const auto strate=std::find_if(layout.strates.begin(),layout.strates.end(), [&](const auto& value){return value.strateIndex==stamp.strateIndex;}); if(strate==layout.strates.end()||!strate->content.presentInCurrentField) {if(reason)*reason="stamp strate has no current content band";return false;} if(stamp.boundsMinimumMicrometres[2]<=strate->content.bottomMicrometres ||stamp.boundsMaximumMicrometres[2]>=strate->content.topMicrometres) {if(reason)*reason="stamp bounds touch or cross the ceiling/bedrock content faces";return false;} for(const auto* band:{&strate->ceiling,&strate->bedrock}) if(band->presentInCurrentField &&stamp.boundsMinimumMicrometres[2]topMicrometres &&stamp.boundsMaximumMicrometres[2]>band->bottomMicrometres) {if(reason)*reason="stamp bounds overlap a ceiling or bedrock band";return false;} const double boundRadius=LayoutStampBoundRadius(stamp); const double coreMicrometres=std::max(1,layout.coreVoxelSizeMicrometres); constexpr double RouteClearanceMicrometres=1000000.0; const auto routeSafe=[&](const std::array& a, const std::array& b,double radiusSubvoxels) { const double radius=radiusSubvoxels*coreMicrometres /WorldAssemblySubvoxelsPerVoxel; return LayoutStampPointSegmentDistance(stamp.positionMicrometres,a,b) >boundRadius+radius+RouteClearanceMicrometres; }; const auto qToMicrometres=[&](const std::array& q) { std::array result{}; for(std::size_t axis=0;axis<3;++axis) result[axis]=static_cast(std::llround( static_cast(q[axis])*coreMicrometres /WorldAssemblySubvoxelsPerVoxel)); return result; }; for(const auto& passage:layout.passages) { if(passage.upperStrateIndex!=stamp.strateIndex &&passage.lowerStrateIndex!=stamp.strateIndex)continue; const auto upper=qToMicrometres(passage.upperMouthSubvoxels); const auto lower=qToMicrometres(passage.lowerMouthSubvoxels); const double mouthRadius=static_cast(passage.mouthRadiusSubvoxels) *coreMicrometres/WorldAssemblySubvoxelsPerVoxel; if(LayoutStampPointSegmentDistance(stamp.positionMicrometres,upper,upper) <=boundRadius+mouthRadius+RouteClearanceMicrometres ||LayoutStampPointSegmentDistance(stamp.positionMicrometres,lower,lower) <=boundRadius+mouthRadius+RouteClearanceMicrometres) {if(reason)*reason="stamp is inside a passage mouth safety volume";return false;} for(std::size_t i=0;i+1 route; for(const auto& anchor:strate->anchors) if(anchor.kind=="route_point"&&anchor.values.size()>=3) route.push_back(&anchor); for(std::size_t i=0;i{ static_cast(std::llround(static_cast(anchor.values[0]) *coreMicrometres/WorldAssemblySubvoxelsPerVoxel)), static_cast(std::llround(static_cast(anchor.values[1]) *coreMicrometres/WorldAssemblySubvoxelsPerVoxel)), static_cast(std::llround(static_cast(anchor.values[2]) *coreMicrometres/WorldAssemblySubvoxelsPerVoxel))};}; const auto a=makePoint(*route[i]); const auto b=i+1values.size()>=4?route[i]->values[3]:0, route[i+1]->values.size()>=4?route[i+1]->values[3]:0) :route[i]->values.size()>=4?route[i]->values[3]:0; if(!routeSafe(a,b,radius)) {if(reason)*reason="stamp overlaps a resolved strate walk route";return false;} } return true; } double LayoutSegmentDistance(const std::array& p0, const std::array& p1,const std::array& q0, const std::array& q1) { std::array u{},v{},w{}; double a=0.0,b=0.0,c=0.0,d=0.0,e=0.0; for(std::size_t axis=0;axis<3;++axis) { u[axis]=p1[axis]-p0[axis];v[axis]=q1[axis]-q0[axis]; w[axis]=p0[axis]-q0[axis]; a+=u[axis]*u[axis];b+=u[axis]*v[axis]; c+=v[axis]*v[axis];d+=u[axis]*w[axis];e+=v[axis]*w[axis]; } const double denominator=a*c-b*b; double sN=0.0,sD=denominator,tN=0.0,tD=denominator; if(denominator<1.0e-18){sN=0.0;sD=1.0;tN=e;tD=c;} else { sN=b*e-c*d;tN=a*e-b*d; if(sN<0.0){sN=0.0;tN=e;tD=c;} else if(sN>sD){sN=sD;tN=e+b;tD=c;} } if(tN<0.0) { tN=0.0; if(-d<0.0)sN=0.0; else if(-d>a)sN=sD; else{sN=-d;sD=a;} } else if(tN>tD) { tN=tD; if(-d+b<0.0)sN=0.0; else if(-d+b>a)sN=sD; else{sN=-d+b;sD=a;} } const double s=std::abs(sN)<1.0e-18?0.0:sN/sD; const double t=std::abs(tN)<1.0e-18?0.0:tN/tD; double distanceSquared=0.0; for(std::size_t axis=0;axis<3;++axis) { const double delta=w[axis]+s*u[axis]-t*v[axis]; distanceSquared+=delta*delta; } return std::sqrt(std::max(0.0,distanceSquared)); } std::array LayoutOrePointUm(const std::array& point) {return {static_cast(point[0]),static_cast(point[1]), static_cast(point[2])};} bool LayoutOreVeinSafe(const ResolvedLayoutSnapshotV3& layout, const ResolvedLayoutOreVeinV1& vein,std::string* reason=nullptr) { const auto fail=[&](const char* text) {if(reason)*reason=text;return false;}; const auto strate=std::find_if(layout.strates.begin(),layout.strates.end(), [&](const auto& value){return value.strateIndex==vein.strateIndex;}); if(strate==layout.strates.end()||!strate->content.presentInCurrentField) return fail("vein strate has no current content band"); if(vein.boundsMinimumMicrometres[2]<=strate->content.bottomMicrometres ||vein.boundsMaximumMicrometres[2]>=strate->content.topMicrometres) return fail("vein bounds touch or cross the ceiling/bedrock content faces"); for(const auto* band:{&strate->ceiling,&strate->bedrock}) if(band->presentInCurrentField &&vein.boundsMinimumMicrometres[2]topMicrometres &&vein.boundsMaximumMicrometres[2]>band->bottomMicrometres) return fail("vein bounds overlap a ceiling or bedrock band"); const auto veinA=LayoutOrePointUm(vein.endpointsMicrometres[0]); const auto veinB=LayoutOrePointUm(vein.endpointsMicrometres[1]); const double coreRadius=static_cast(vein.radiusMicrometres +vein.irregularityAmplitudeMicrometres+vein.blendWidthMicrometres); const double coreMicrometres=std::max(1,layout.coreVoxelSizeMicrometres); constexpr double RouteClearanceMicrometres=100000.0; const auto qToUm=[&](const std::array& q) {return std::array{q[0]*coreMicrometres/WorldAssemblySubvoxelsPerVoxel, q[1]*coreMicrometres/WorldAssemblySubvoxelsPerVoxel, q[2]*coreMicrometres/WorldAssemblySubvoxelsPerVoxel};}; for(const auto& passage:layout.passages) { if(passage.upperStrateIndex!=vein.strateIndex &&passage.lowerStrateIndex!=vein.strateIndex)continue; const auto upper=qToUm(passage.upperMouthSubvoxels); const auto lower=qToUm(passage.lowerMouthSubvoxels); const double mouthRadius=passage.mouthRadiusSubvoxels*coreMicrometres /WorldAssemblySubvoxelsPerVoxel; if(LayoutSegmentDistance(veinA,veinB,upper,upper) <=coreRadius+mouthRadius+RouteClearanceMicrometres ||LayoutSegmentDistance(veinA,veinB,lower,lower) <=coreRadius+mouthRadius+RouteClearanceMicrometres) return fail("vein overlaps a clear passage landing"); for(std::size_t i=0;i+1 route; for(const auto& anchor:strate->anchors) { if(anchor.values.size()<3)continue; if(anchor.kind=="route_point")route.push_back(&anchor); if(anchor.kind.find("landing")!=std::string::npos) { const std::array landing={ anchor.values[0]*coreMicrometres/WorldAssemblySubvoxelsPerVoxel, anchor.values[1]*coreMicrometres/WorldAssemblySubvoxelsPerVoxel, anchor.values[2]*coreMicrometres/WorldAssemblySubvoxelsPerVoxel}; const double radius=anchor.values.size()>=4 ?anchor.values[3]*coreMicrometres/WorldAssemblySubvoxelsPerVoxel:3000000.0; if(LayoutSegmentDistance(veinA,veinB,landing,landing) <=coreRadius+radius+RouteClearanceMicrometres) return fail("vein overlaps a resolved landing footprint"); } } for(std::size_t i=0;i+1{anchor.values[0]*coreMicrometres/WorldAssemblySubvoxelsPerVoxel, anchor.values[1]*coreMicrometres/WorldAssemblySubvoxelsPerVoxel, anchor.values[2]*coreMicrometres/WorldAssemblySubvoxelsPerVoxel};}; const auto a=qPoint(*route[i]),b=qPoint(*route[i+1]); const double routeRadius=std::max(route[i]->values.size()>=4?route[i]->values[3]:0, route[i+1]->values.size()>=4?route[i+1]->values[3]:0) *coreMicrometres/WorldAssemblySubvoxelsPerVoxel; if(LayoutSegmentDistance(veinA,veinB,a,b) <=coreRadius+routeRadius+RouteClearanceMicrometres) return fail("vein enters a clear strate route corridor"); } for(const auto& room:layout.cavernRooms) if(room.strateIndex==vein.strateIndex) for(std::size_t axis=0;axis<3;++axis) if(vein.boundsMinimumMicrometres[axis] <=room.boundsMaximumMicrometres[axis] &&vein.boundsMaximumMicrometres[axis] >=room.boundsMinimumMicrometres[axis]) return fail("vein overlaps a cavern landmark footprint"); for(const auto& edge:layout.cavernEdges) { if(edge.strateIndex!=vein.strateIndex)continue; std::array previous{}; const auto bezier=[&](double t) { const double one=1.0-t; const std::array weights={one*one*one,3.0*one*one*t, 3.0*one*t*t,t*t*t}; std::array point{}; for(std::size_t i=0;i<4;++i) for(std::size_t axis=0;axis<3;++axis) point[axis]+=weights[i]*edge.controlPointsMicrometres[i][axis]; return point; }; previous=bezier(0.0); for(int step=1;step<=256;++step) { const auto next=bezier(static_cast(step)/256.0); if(LayoutSegmentDistance(veinA,veinB,previous,next) <=coreRadius+edge.boreRadiusMicrometres+RouteClearanceMicrometres) return fail("vein enters a landmark passage corridor"); previous=next; } } for(const auto& stamp:layout.stamps) { if(stamp.strateIndex!=vein.strateIndex ||(stamp.kind!=ResolvedSdfStampKindV1::fillDais &&stamp.kind!=ResolvedSdfStampKindV1::fillPillar &&stamp.kind!=ResolvedSdfStampKindV1::fillMonolith &&stamp.kind!=ResolvedSdfStampKindV1::fillPlatform))continue; bool overlaps=true; for(std::size_t axis=0;axis<3;++axis) overlaps=overlaps&&vein.boundsMinimumMicrometres[axis] <=stamp.boundsMaximumMicrometres[axis] &&vein.boundsMaximumMicrometres[axis] >=stamp.boundsMinimumMicrometres[axis]; if(overlaps)return fail("vein overlaps a resolved landmark solid footprint"); } return true; } std::string LayoutHex(std::uint64_t value) {std::ostringstream out;out<(stamp.kind) <<",\"position_um\":["<(stamp.yawQuarterTurns); if(s.stampVocabularyVersion>=ResolvedLayoutStampVocabularyVersionV3) out<<",\"rounding_radius_um\":"<(stamp.attachmentPolicy) <<",\"floor_um\":"<(room.shapeKind) <<",\"ledge_spacing_um\":"<(room.ledgeSide); if(room.unionWidthMicrometres>0) out<<",\"union_width_um\":"<0) out<<",\"union_width_um\":"<>30))*0xbf58476d1ce4e5b9ull; value=(value^(value>>27))*0x94d049bb133111ebull; return value^(value>>31); } std::uint64_t PassageSeed(std::int32_t seed,std::int32_t upper, std::int32_t lower,std::uint32_t connection, std::uint32_t salt) { std::uint64_t h=PassageHash(static_cast(seed)); h=PassageHash(h^static_cast(upper)); h=PassageHash(h^static_cast(lower)); h=PassageHash(h^connection);return PassageHash(h^salt); } std::uint64_t PassageIsqrt(std::uint64_t n) { std::uint64_t x=0,bit=std::uint64_t{1}<<62; while(bit>n)bit>>=2; while(bit) { if(n>=x+bit){n-=x+bit;x=(x>>1)+bit;} else x>>=1; bit>>=2; } return x; } std::int64_t PassageRoundRatio(std::int64_t value,std::int64_t numerator, std::int64_t denominator) { if(denominator<=0)return 0; const bool negative=value<0; const std::uint64_t magnitude=static_cast(negative?-value:value); const std::uint64_t product=magnitude*static_cast(numerator); const std::uint64_t rounded=(product+static_cast(denominator)/2) /static_cast(denominator); return negative?-static_cast(rounded):static_cast(rounded); } std::array PassageRandomXY(std::uint64_t seed, std::int64_t inner,std::int64_t outer) { if(outer<=0)return {}; inner=std::clamp(inner,std::int64_t{0},outer); const std::uint64_t span=static_cast(outer)*2+1; for(std::uint32_t attempt=0;attempt<64;++attempt) { const std::uint64_t hx=PassageHash(seed+2ull*attempt); const std::uint64_t hy=PassageHash(seed+2ull*attempt+1); const std::int64_t x=static_cast(hx%span)-outer; const std::int64_t y=static_cast(hy%span)-outer; const std::uint64_t radiusSquared=static_cast(x*x+y*y); if(radiusSquared<=static_cast(outer*outer) &&radiusSquared>=static_cast(inner*inner))return {x,y}; } return {outer,0}; } } ResolvedLayoutTransitionBandsV1 EvaluateResolvedLayoutTransitionBandsV1( const ResolvedLayoutStratumV2& upper, const ResolvedLayoutStratumV2& lower, std::int32_t worldSeed, std::int64_t layoutChunkSizeMicrometres, std::int64_t xMicrometres, std::int64_t yMicrometres) { ResolvedLayoutTransitionBandsV1 result; const std::int64_t seam = upper.bedrock.bottomMicrometres; const std::int64_t upperThickness = upper.bedrock.topMicrometres - seam; const std::int64_t lowerThickness = lower.ceiling.topMicrometres - lower.ceiling.bottomMicrometres; result.upperBedrockBottomMicrometres = seam; result.upperBedrockTopMicrometres = seam + std::max(0, upperThickness); result.lowerCeilingBottomMicrometres = seam - std::max(0, lowerThickness); result.lowerCeilingTopMicrometres = seam; const std::int64_t width = upper.transitionFallsBackToHard ? 0 : std::max(0, upper.transitionWidthMicrometres); if (upper.transitionMode == "Hard" || width == 0 || (upper.transitionMode != "Gradient" && upper.transitionMode != "Interleaved") || !upper.bedrock.presentInCurrentField || !lower.ceiling.presentInCurrentField || upperThickness <= 0 || lowerThickness <= 0) return result; if (width <= 0) return result; std::int64_t noise = 0; if (upper.transitionMode == "Gradient") { const std::int64_t wavelength = std::max( 1000000, std::max(1, layoutChunkSizeMicrometres) * 4); noise = TransitionValueNoise2D(worldSeed, upper.strateIndex, xMicrometres, yMicrometres, wavelength, 0x47524144u); } else { // A face crossing wider than the sum of the authored bands would separate // their far edges and leave an air slab. Limit the physical crossing to the // smaller of the declared transition width and the combined band thickness. const std::int64_t maximumContinuousCrossing=upperThickness+lowerThickness; const std::int64_t crossingLimit=std::min(width,maximumContinuousCrossing); const std::int64_t cell = std::max(1000000, std::max(1, layoutChunkSizeMicrometres) * 2); const std::int64_t warp = std::min(crossingLimit / 2, cell / 2); const std::int64_t warpX = (TransitionValueNoise3D(worldSeed, upper.strateIndex, xMicrometres, yMicrometres, seam, cell, 0x49574158u) * warp) / TransitionQ16; const std::int64_t warpY = (TransitionValueNoise3D(worldSeed, upper.strateIndex, xMicrometres, yMicrometres, seam, cell, 0x49574159u) * warp) / TransitionQ16; const std::int64_t warpZ = (TransitionValueNoise3D(worldSeed, upper.strateIndex, xMicrometres, yMicrometres, seam, cell, 0x4957415au) * warp) / TransitionQ16; noise = TransitionValueNoise3D(worldSeed, upper.strateIndex, xMicrometres + warpX, yMicrometres + warpY, seam + warpZ, cell, 0x494e544cu); // Interleaved means physical crossing: the two faces enter each other's // bands by a total depth in [0, declared width]. Split that overlap across // both faces so neither stratum owns the entire displacement. Since both // resulting intervals are solid and overlap, the transition cannot open a gap. const std::int64_t overlap = ((noise + TransitionQ16) * crossingLimit) / (2 * TransitionQ16); const std::int64_t upperInset = overlap / 2; const std::int64_t lowerInset = overlap - upperInset; result.crossingDepthMicrometres = overlap; result.upperBedrockBottomMicrometres = seam - upperInset; result.upperBedrockTopMicrometres = result.upperBedrockBottomMicrometres + upperThickness; result.lowerCeilingTopMicrometres = seam + lowerInset; result.lowerCeilingBottomMicrometres = result.lowerCeilingTopMicrometres - lowerThickness; return result; } const std::int64_t displacement = (noise * (width / 8)) / TransitionQ16; const std::int64_t interfaceZ = seam + displacement; result.upperBedrockBottomMicrometres = interfaceZ; result.upperBedrockTopMicrometres = interfaceZ + upperThickness; result.lowerCeilingBottomMicrometres = interfaceZ - lowerThickness; result.lowerCeilingTopMicrometres = interfaceZ; return result; } std::string ResolvedLayoutSnapshotV3::ToJson() const { const std::string body=LayoutBodyJson(*this); return body.substr(0,body.size()-1)+",\"stable_identity\":" +LayoutEscape(stableIdentity.empty()?"resolved-layout-v3-fnv64:"+LayoutHex(LayoutHash(body)):stableIdentity)+"}"; } bool ResolvedLayoutSnapshotV3::finalize(std::string& error) { error.clear(); if(schemaVersion!=ResolvedLayoutSchemaVersionV3||worldIdentity.empty()||strates.empty() ||coreVoxelSizeMicrometres==0||strateLayoutChunkSizeMicrometres==0 ||strateLayoutChunkWidthSubvoxels<=0) {error="resolved layout has an unsupported version, empty world identity, or no strates";return false;} std::stable_sort(strates.begin(),strates.end(),[](const auto& a,const auto& b) {return a.interiorTopMicrometres!=b.interiorTopMicrometres ?a.interiorTopMicrometres>b.interiorTopMicrometres:a.order(i); if(x.stableId.empty())x.stableId=worldIdentity+"/strate/"+std::to_string(x.strateIndex); if(x.content.kind.empty())x.content.kind="content"; if(x.ceiling.kind.empty())x.ceiling.kind="ceiling"; if(x.bedrock.kind.empty())x.bedrock.kind="bedrock"; if(x.interiorTopMicrometres<=x.interiorBottomMicrometres||x.voxelSizeMicrometres==0) {error="resolved layout contains an invalid interior interval or voxel size";return false;} // Materialize generator-specific composition policy at layout resolution. The // sampling/composition path consumes these capabilities without examining names. x.passageCapabilities={}; x.lowerInterfaceCapabilities={}; if(x.generatorKind=="SurfaceWorld") x.passageCapabilities.hasSurfaceLanding=true; else if(IsNativeCaveFieldStratum(x)) x.passageCapabilities.hasNativeCaveField=true; else if(IsNativeLatticeFieldStratum(x)) x.passageCapabilities.hasNativeLatticeField=true; else if(x.generatorKind=="FloatingIslands") x.passageCapabilities.hasFloatingIslandLanding=true; if(i+1(std::llround( 1.13*WorldAssemblySubvoxelsPerVoxel*1000000.0/coreVoxelSizeMicrometres)); for(auto& passage:passages) { passage.lowerMouthControlIndex=passage.controlPoints.empty()?0:passage.controlPoints.size()-1; for(std::size_t i=1;i issues; return validate(ResolvedLayoutValidationModeV3::compatibility,issues,error); } bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode, std::vector& issues,std::string& error) const { issues.clear();error.clear(); if(schemaVersion!=ResolvedLayoutSchemaVersionV3||worldIdentity.empty()||strates.empty() ||coreVoxelSizeMicrometres==0||strateLayoutChunkSizeMicrometres==0 ||strateLayoutChunkWidthSubvoxels<=0) {error="resolved layout schema/world/strata header is invalid";return false;} const auto add=[&](std::string code,std::string detail) {issues.push_back({std::move(code),std::move(detail)});}; const bool strict=mode==ResolvedLayoutValidationModeV3::strict; for(std::size_t i=0;ieffectiveTransitionWidth ||s.transitionInterpenetrationMicrometres<0) add("transition_overlap_exceeds_width",s.stableId+" transition interpenetration exceeds its declared width"); if(s.transitionWidthMicrometres<0||s.transitionBlendChunks<0) add("invalid_transition_width",s.stableId+" has a negative transition width"); if(s.managerChunkWidthMicrometres>0&&s.dispatchChunkWidthMicrometres>0 &&s.managerChunkWidthMicrometres!=s.dispatchChunkWidthMicrometres) add("ambiguous_dispatch_units",s.stableId+" manager chunk width differs from Core dispatch chunk width"); if(i+1(i))add("wrong_order",s.stableId+" order is not top-to-bottom"); if(!s.ceiling.presentInCurrentField||!s.bedrock.presentInCurrentField) add(!s.ceiling.presentInCurrentField?"missing_ceiling":"missing_bedrock",s.stableId+" lacks a required boundary band"); if(!s.content.presentInCurrentField) add("missing_content",s.stableId+" has no content generator band"); const auto bandShape=[&](const ResolvedLayoutBandV2& b,std::string_view expected) { if(b.kind!=expected||b.topMicrometres<=b.bottomMicrometres) add("wrong_order",s.stableId+" has an invalid "+std::string(expected)+" band"); const std::int64_t minimumThickness=2ll*coreVoxelSizeMicrometres; if(b.topMicrometres-b.bottomMicrometres content -> bedrock"); if(i+1strates[i+1].ceiling.topMicrometres?"gap":"overlap", s.stableId+" bedrock and next strate ceiling are not nominally contiguous"); if(s.transitionInterpenetrationMicrometres>effectiveTransitionWidth) add("transition_overlap_exceeds_width",s.stableId+" interpenetration is wider than the declared transition zone"); } } if(!strict) { for(const auto& s:strates) { if(s.content.presentInCurrentField&&s.ceiling.presentInCurrentField) { if(s.ceiling.bottomMicrometress.content.topMicrometres) add("uncovered_interval",s.stableId+" has a "+std::to_string( s.ceiling.bottomMicrometres-s.content.topMicrometres)+" um gap below its ceiling"); } if(s.content.presentInCurrentField&&s.bedrock.presentInCurrentField) { if(s.bedrock.topMicrometres>s.content.bottomMicrometres) add("band_overlap",s.stableId+" bedrock overlaps content by "+std::to_string( s.bedrock.topMicrometres-s.content.bottomMicrometres)+" um"); else if(s.bedrock.topMicrometresb.interiorTopMicrometres) add("uncovered_interval",a.stableId+" to "+b.stableId+" leaves "+std::to_string( a.interiorBottomMicrometres-b.interiorTopMicrometres)+" um uncovered"); else if(a.interiorBottomMicrometreseffectiveTransitionWidth) add("overlap",a.stableId+" and "+b.stableId+" interiors overlap by "+std::to_string(overlap)+" um"); else add("transition_interpenetration",a.stableId+" and "+b.stableId+" overlap inside the declared transition zone"); } if(a.bedrock.presentInCurrentField&&b.ceiling.presentInCurrentField) { if(a.bedrock.bottomMicrometres>b.ceiling.topMicrometres) add("uncovered_interval",a.stableId+" bedrock and "+b.stableId+" ceiling leave "+std::to_string( a.bedrock.bottomMicrometres-b.ceiling.topMicrometres)+" um uncovered"); else if(a.bedrock.bottomMicrometreseffectiveTransitionWidth) add("overlap",a.stableId+" bedrock and "+b.stableId+" ceiling overlap by "+std::to_string(overlap)+" um"); else add("transition_interpenetration",a.stableId+" bedrock and "+b.stableId+" ceiling interpenetrate by "+std::to_string(overlap)+" um inside the declared transition zone"); } } } } const auto findStratum=[this](std::int32_t index)->const ResolvedLayoutStratumV2* { const auto it=std::find_if(strates.begin(),strates.end(),[index](const auto& s) {return s.strateIndex==index;}); return it==strates.end()?nullptr:&*it; }; std::vector passageIds; for(std::size_t i=0;strict&&i+1order!=upper->order+1) add("passage_interface",passage.stableId+" does not join adjacent resolved strates"); if(!upper||!lower||passage.placementRadiusSubvoxels<=0 ||passage.mouthRadiusSubvoxels<=0||passage.midRadiusSubvoxels<=0 ||passage.controlPoints.size()<2) { add("passage_geometry",passage.stableId+" has invalid radii, placement bound, or control points"); continue; } const auto insidePlacement=[&](const std::array& p) { const auto x=p[0],y=p[1],r=passage.placementRadiusSubvoxels; return std::abs(x)<=r&&std::abs(y)<=r&&x*x+y*y<=r*r; }; const auto insideContent=[&](const ResolvedLayoutStratumV2& s, const std::array& p) { const std::int64_t zMicrometres=p[2]*static_cast(coreVoxelSizeMicrometres) /WorldAssemblySubvoxelsPerVoxel; return s.content.presentInCurrentField&&zMicrometres>=s.content.bottomMicrometres &&zMicrometres& mouth,const char* side) { const double playerCenterHeight=(side==std::string_view("upper") &&host.generatorKind=="SurfaceWorld")?0.98:1.13; const std::int64_t playerCenterHeightQ=static_cast( std::llround(playerCenterHeight*WorldAssemblySubvoxelsPerVoxel *1000000.0/std::max(1,coreVoxelSizeMicrometres))); if(chamber.floorCenterSubvoxels[0]!=mouth[0] ||chamber.floorCenterSubvoxels[1]!=mouth[1] ||chamber.floorCenterSubvoxels[2]+playerCenterHeightQ!=mouth[2] ||chamber.radiusSubvoxels<=0 ||chamber.clearHeightSubvoxels<=0 ||chamber.radiusSubvoxels( std::llround(2.2*WorldAssemblySubvoxelsPerVoxel*1000000.0 /std::max(1,coreVoxelSizeMicrometres)))) { add("passage_landing_chamber",passage.stableId+" "+side +" landing chamber has invalid floor, radius, or capsule clearance"); return; } const std::uint64_t gradientSquared=static_cast( chamber.floorGradientQ16[0]*chamber.floorGradientQ16[0] +chamber.floorGradientQ16[1]*chamber.floorGradientQ16[1]); const std::int64_t gradientMagnitude=static_cast( PassageIsqrt(gradientSquared)); if(gradientMagnitude>17560) add("passage_landing_grade",passage.stableId+" "+side +" landing chamber floor exceeds 15 degrees"); const std::int64_t planeReachQ=static_cast( (chamber.radiusSubvoxels*gradientMagnitude+65535)/65536); const std::int64_t shellQ=static_cast(std::llround( 0.75*WorldAssemblySubvoxelsPerVoxel*1000000.0 /std::max(1,coreVoxelSizeMicrometres))); const auto qToUm=[&](std::int64_t q) {return q*static_cast(coreVoxelSizeMicrometres) /WorldAssemblySubvoxelsPerVoxel;}; if(!host.content.presentInCurrentField ||qToUm(chamber.floorCenterSubvoxels[2]-planeReachQ-shellQ) host.content.topMicrometres) add("passage_landing_content",passage.stableId+" "+side +" landing chamber or plain shell leaves its host content band"); }; if(upper&&lower) { validateLandingChamber(passage.upperLandingChamber,*upper, passage.upperMouthSubvoxels,"upper"); validateLandingChamber(passage.lowerLandingChamber,*lower, passage.lowerMouthSubvoxels,"lower"); } if(passage.controlPoints.size()<2)continue; const std::int64_t PlayerCenterAboveFloorQ=coreVoxelSizeMicrometres>0 ?static_cast(std::llround(1.13*WorldAssemblySubvoxelsPerVoxel *1000000.0/coreVoxelSizeMicrometres)):1157; const std::int64_t SurfacePlayerCenterAboveFloorQ=coreVoxelSizeMicrometres>0 ?static_cast(std::llround(0.98*WorldAssemblySubvoxelsPerVoxel *1000000.0/coreVoxelSizeMicrometres)):901; std::vector centerHeightOffsets(passage.controlPoints.size(), PlayerCenterAboveFloorQ); const auto& upperMouthPoint=passage.controlPoints.front(); const bool hasSurfaceRoute=upper->generatorKind=="SurfaceWorld" &&upperMouthPoint.positionSubvoxels[0]==passage.upperMouthSubvoxels[0] &&upperMouthPoint.positionSubvoxels[1]==passage.upperMouthSubvoxels[1] &&upperMouthPoint.positionSubvoxels[2]-upperMouthPoint.radiusSubvoxels +SurfacePlayerCenterAboveFloorQ==passage.upperMouthSubvoxels[2]; if(hasSurfaceRoute) { const std::int64_t qPerMetre=static_cast(std::llround( static_cast(WorldAssemblySubvoxelsPerVoxel)*1000000.0 /std::max(1,coreVoxelSizeMicrometres))); const std::int64_t blendLength=std::max(8*qPerMetre, 2*passage.mouthRadiusSubvoxels); std::int64_t distance=0; for(std::size_t i=0;i0) { const auto& a=passage.controlPoints[i-1].positionSubvoxels; const auto& b=passage.controlPoints[i].positionSubvoxels; const auto dx=static_cast(std::abs(b[0]-a[0])); const auto dy=static_cast(std::abs(b[1]-a[1])); const auto square=dx*dx+dy*dy; auto length=PassageIsqrt(square); if(length*length(length); } const double t=std::clamp(static_cast(distance) /std::max(1,blendLength),0.0,1.0); const double smooth=t*t*(3.0-2.0*t); centerHeightOffsets[i]=SurfacePlayerCenterAboveFloorQ +static_cast(std::llround( (PlayerCenterAboveFloorQ-SurfacePlayerCenterAboveFloorQ)*smooth)); } } const auto floorProfile=[&](std::size_t index, const ResolvedLayoutPassagePointV1& p) {return p.positionSubvoxels[2]-p.radiusSubvoxels +centerHeightOffsets[index];}; std::size_t lowerMouthControlIndex=passage.controlPoints.size(); for(std::size_t i=1;i(dx*dx+dy*dy)); const std::int64_t signedDz=floorProfile(i,point) -floorProfile(i-1,previous); const std::int64_t dz=std::abs(signedDz); const bool landingExtension=i>lowerMouthControlIndex; if((landingExtension?dz*65536>static_cast(horizontal)*17560 :signedDz>=0||dz*65536>static_cast(horizontal)*17560) ||horizontal==0) add("passage_grade",passage.stableId+" is not strictly descending or exceeds the 15 degree grade limit"); } } if(stamps.empty()) { if(stampVocabularyVersion!=0) add("stamp_vocabulary_version","stamp vocabulary version is set without any stamp records"); } else if(stampVocabularyVersion!=ResolvedLayoutStampVocabularyVersionV1 &&stampVocabularyVersion!=ResolvedLayoutStampVocabularyVersionV2 &&stampVocabularyVersion!=ResolvedLayoutStampVocabularyVersionV3) add("stamp_vocabulary_version","resolved stamps require supported vocabulary version 1, 2 or 3"); std::vector stampIds; for(const auto& stamp:stamps) { if(stamp.stableId.empty() ||std::find(stampIds.begin(),stampIds.end(),stamp.stableId)!=stampIds.end()) add("stamp_identity","stamp has an empty or duplicate stable identity"); else stampIds.push_back(stamp.stableId); const bool motif=LayoutStampIsMotif(stamp.kind); if(!LayoutStampKindValid(stamp.kind)||stamp.yawQuarterTurns>3 ||(motif&&stampVocabularyVersion=500000&&extent<=100000000; if(!validExtents||stamp.smoothWidthMicrometres<0 ||stamp.smoothWidthMicrometres>10000000 ||stamp.irregularityAmplitudeMicrometres<0 ||stamp.irregularityAmplitudeMicrometres>10000000 ||stamp.irregularityWavelengthMicrometres<0 ||stamp.irregularityWavelengthMicrometres>100000000 ||(stamp.irregularityAmplitudeMicrometres>0 &&stamp.irregularityWavelengthMicrometres<=0)) add("stamp_parameters",stamp.stableId+" has invalid or unbounded geometry parameters"); if(motif) { const auto minimumExtent=*std::min_element(stamp.halfExtentsMicrometres.begin(), stamp.halfExtentsMicrometres.end()); const bool monolith=stamp.kind==ResolvedSdfStampKindV1::fillMonolith; const bool shapeValid=monolith ?stamp.halfExtentsMicrometres[0]==stamp.halfExtentsMicrometres[1] &&stamp.halfExtentsMicrometres[0]>=800000 &&stamp.halfExtentsMicrometres[0]<=2000000 &&stamp.halfExtentsMicrometres[2]>=6000000 &&stamp.halfExtentsMicrometres[2]<=16000000 :stamp.halfExtentsMicrometres[0]>=1000000 &&stamp.halfExtentsMicrometres[1]>=1000000; if(stampVocabularyVersionminimumExtent ||stamp.minimumSpacingMicrometres<0 ||stamp.minimumSpacingMicrometres>100000000 ||stamp.attachmentPolicy!=1 ||stamp.positionMicrometres[2] !=stamp.floorMicrometres+stamp.halfExtentsMicrometres[2] ||!shapeValid||stamp.irregularityAmplitudeMicrometres!=0) add("stamp_motif_parameters",stamp.stableId+" has invalid kind, size, fillet or floor attachment"); } const std::int64_t pad=motif ?(stamp.smoothWidthMicrometres+5)/6+stamp.irregularityAmplitudeMicrometres :stamp.smoothWidthMicrometres+stamp.irregularityAmplitudeMicrometres; const std::array extents={ stamp.halfExtentsMicrometres[stamp.yawQuarterTurns%2?1:0]+pad, stamp.halfExtentsMicrometres[stamp.yawQuarterTurns%2?0:1]+pad, stamp.halfExtentsMicrometres[2]+pad}; for(std::size_t axis=0;axis<3;++axis) if(stamp.boundsMinimumMicrometres[axis] !=stamp.positionMicrometres[axis]-extents[axis] ||stamp.boundsMaximumMicrometres[axis] !=stamp.positionMicrometres[axis]+extents[axis]) add("stamp_bounds",stamp.stableId+" conservative bounds do not match its parameters"); std::string safetyReason; if(!LayoutStampSafe(*this,stamp,&safetyReason)) add("stamp_placement",stamp.stableId+" is unsafe: "+safetyReason); } if(stampVocabularyVersion>=ResolvedLayoutStampVocabularyVersionV3) { for(std::size_t i=0;istamps[j].boundsMaximumMicrometres[axis] ?stamps[i].boundsMinimumMicrometres[axis] -stamps[j].boundsMaximumMicrometres[axis] :stamps[j].boundsMinimumMicrometres[axis] >stamps[i].boundsMaximumMicrometres[axis] ?stamps[j].boundsMinimumMicrometres[axis] -stamps[i].boundsMaximumMicrometres[axis]:0; gapSquared+=static_cast(gap)*gap; } const auto required=std::max(stamps[i].minimumSpacingMicrometres, stamps[j].minimumSpacingMicrometres); if(gapSquared(required)*required) add("stamp_motif_spacing",stamps[i].stableId+" is too close to "+stamps[j].stableId); } } } if(cavernRooms.empty()&&cavernEdges.empty()) { if(cavernGraphVocabularyVersion!=0) add("cavern_graph_vocabulary_version", "cavern graph vocabulary version is set without graph records"); } else if(cavernGraphVocabularyVersion!=ResolvedLayoutCavernGraphVocabularyVersionV1 &&cavernGraphVocabularyVersion!=ResolvedLayoutCavernGraphVocabularyVersionV2 &&cavernGraphVocabularyVersion!=ResolvedLayoutCavernGraphVocabularyVersionV3) add("cavern_graph_vocabulary_version", "cavern graph records require a supported vocabulary version"); if(cavernGraphVocabularyVersion!=0) { if(cavernRooms.empty()||cavernEdges.empty()) add("cavern_graph_shape","a resolved cavern graph needs at least one room and one edge"); std::vector graphIds; const auto graphIdValid=[&](const std::string& id) { if(id.empty()||std::find(graphIds.begin(),graphIds.end(),id)!=graphIds.end()) return false; graphIds.push_back(id);return true; }; for(const auto& room:cavernRooms) { if(!graphIdValid(room.stableId)) add("cavern_graph_identity","room has an empty or duplicate stable identity"); if(!findStratum(room.strateIndex)) add("cavern_graph_strate",room.stableId+" names an unknown strate"); bool validExtents=true; for(const auto extent:room.halfExtentsMicrometres) validExtents=validExtents&&extent>=500000&&extent<=100000000; if(!validExtents||room.smoothWidthMicrometres<0 ||room.smoothWidthMicrometres>10000000 ||room.unionWidthMicrometres<0||room.unionWidthMicrometres>10000000 ||room.irregularityAmplitudeMicrometres<0 ||room.irregularityAmplitudeMicrometres>10000000 ||room.irregularityWavelengthMicrometres<0 ||room.irregularityWavelengthMicrometres>100000000 ||(room.irregularityAmplitudeMicrometres>0 &&room.irregularityWavelengthMicrometres<=0)) add("cavern_room_parameters",room.stableId+" has invalid or unbounded parameters"); const auto* roomStratum=findStratum(room.strateIndex); if(room.shapeKind>1) add("cavern_room_shape",room.stableId+" has an unknown shape kind"); if(room.shapeKind==1) { const bool verticalShaftStratum=roomStratum &&roomStratum->generatorKind=="VerticalShafts" &&IsNativeCaveFieldStratum(*roomStratum); if(cavernGraphVocabularyVersion100000000 ||room.ledgeDepthMicrometres<0 ||room.ledgeDepthMicrometres>=room.halfExtentsMicrometres[0] ||room.ledgeSide>3 ||room.floorMicrometres !=room.centerMicrometres[2]+room.halfExtentsMicrometres[2] ||!room.floorProfileStableId.empty() ||room.floorGradientQ16[0]!=0||room.floorGradientQ16[1]!=0) add("cavern_shaft_parameters",room.stableId+ " has invalid native shaft bounds, ledges, or recipe binding"); } else if(room.ledgeSpacingMicrometres!=0||room.ledgeDepthMicrometres!=0 ||room.ledgeSide!=0) add("cavern_room_shape",room.stableId+ " carries shaft parameters on an ordinary room"); const std::int64_t gradientSquared=static_cast(room.floorGradientQ16[0]) *room.floorGradientQ16[0]+static_cast(room.floorGradientQ16[1]) *room.floorGradientQ16[1]; if((room.floorProfileStableId.empty() &&room.floorProfileDistanceMicrometres!=-1) ||(!room.floorProfileStableId.empty() &&room.floorProfileDistanceMicrometres<0) ||gradientSquared>static_cast(13108)*13108) add("cavern_room_floor_profile",room.stableId+ " has an invalid shared profile reference or floor grade"); const std::int64_t pad=room.smoothWidthMicrometres +room.unionWidthMicrometres+room.irregularityAmplitudeMicrometres; for(std::size_t axis=0;axis<3;++axis) if(room.boundsMinimumMicrometres[axis] !=room.centerMicrometres[axis]-room.halfExtentsMicrometres[axis]-pad ||room.boundsMaximumMicrometres[axis] !=room.centerMicrometres[axis]+room.halfExtentsMicrometres[axis]+pad) add("cavern_room_bounds",room.stableId+" conservative bounds do not match its parameters"); if(room.floorMicrometresroom.centerMicrometres[2]+room.halfExtentsMicrometres[2]) add("cavern_room_floor",room.stableId+" floor lies outside its room envelope"); } for(const auto& edge:cavernEdges) { if(!graphIdValid(edge.stableId)) add("cavern_graph_identity","edge has an empty or duplicate stable identity"); if(edge.startAnchorStableId.empty()||edge.endRoomStableId.empty()) add("cavern_edge_binding",edge.stableId+" has a missing endpoint identity"); const auto room=std::find_if(cavernRooms.begin(),cavernRooms.end(), [&](const auto& value){return value.stableId==edge.endRoomStableId;}); if(room==cavernRooms.end()||room->strateIndex!=edge.strateIndex) add("cavern_edge_binding",edge.stableId+" does not terminate at a room in its strate"); if(!findStratum(edge.strateIndex)) add("cavern_graph_strate",edge.stableId+" names an unknown strate"); const auto* edgeStratum=findStratum(edge.strateIndex); const bool nativeLattice=edgeStratum &&IsNativeLatticeFieldStratum(*edgeStratum); if(edge.boreRadiusMicrometres<500000||edge.boreRadiusMicrometres>10000000 ||edge.boreHeightMicrometres<1000000||edge.boreHeightMicrometres>20000000 ||edge.floorSupportDepthMicrometres<0 ||edge.floorSupportDepthMicrometres>2000000 ||edge.smoothWidthMicrometres<0||edge.smoothWidthMicrometres>10000000 ||edge.unionWidthMicrometres<0||edge.unionWidthMicrometres>10000000 ||edge.irregularityAmplitudeMicrometres<0 ||edge.irregularityAmplitudeMicrometres>10000000 ||edge.irregularityWavelengthMicrometres<0 ||edge.irregularityWavelengthMicrometres>100000000 ||(edge.irregularityAmplitudeMicrometres>0 &&edge.irregularityWavelengthMicrometres<=0)) add("cavern_edge_parameters",edge.stableId+" has invalid or unbounded parameters"); const bool hasFloorProfile=!edge.floorProfileStableId.empty(); if((hasFloorProfile&&(edge.floorProfileStartDistanceMicrometres<0 ||edge.floorProfileEndDistanceMicrometres<0 ||edge.floorProfileStartDistanceMicrometres ==edge.floorProfileEndDistanceMicrometres)) ||(!hasFloorProfile&&(edge.floorProfileStartDistanceMicrometres!=-1 ||edge.floorProfileEndDistanceMicrometres!=-1))) add("cavern_edge_floor_profile",edge.stableId+ " has an invalid shared profile distance interval"); std::array low=edge.controlPointsMicrometres[0]; std::array high=low; if(nativeLattice&&!edge.polylineMicrometres.empty()) {low=edge.polylineMicrometres.front();high=low;} const auto includeBoundPoint=[&](const auto& point) { for(std::size_t axis=0;axis<3;++axis) {low[axis]=std::min(low[axis],point[axis]);high[axis]=std::max(high[axis],point[axis]);} }; if(nativeLattice&&!edge.polylineMicrometres.empty()) for(const auto& point:edge.polylineMicrometres)includeBoundPoint(point); else for(const auto& point:edge.controlPointsMicrometres)includeBoundPoint(point); const std::int64_t xyPad=edge.boreRadiusMicrometres +edge.smoothWidthMicrometres+edge.unionWidthMicrometres +edge.irregularityAmplitudeMicrometres; const std::int64_t zPad=edge.smoothWidthMicrometres +edge.unionWidthMicrometres+edge.irregularityAmplitudeMicrometres; for(std::size_t axis=0;axis<3;++axis) { const std::int64_t pad=axis==2?zPad:xyPad; const std::int64_t lowSupport=axis==2?edge.floorSupportDepthMicrometres:0; const std::int64_t highBore=axis==2?edge.boreHeightMicrometres:0; if(edge.boundsMinimumMicrometres[axis]!=low[axis]-pad-lowSupport ||edge.boundsMaximumMicrometres[axis]!=high[axis]+pad+highBore) add("cavern_edge_bounds",edge.stableId+" conservative bounds do not match its parameters"); } if(room!=cavernRooms.end()) { const auto& endpoint=edge.controlPointsMicrometres.back(); bool validRoomEndpoint=false; if(room->shapeKind==1) { const std::int64_t shaftBottom=room->centerMicrometres[2] -room->halfExtentsMicrometres[2]; const std::int64_t shaftTop=room->centerMicrometres[2] +room->halfExtentsMicrometres[2]; const bool insideShaft=endpoint[2]>=shaftBottom &&endpoint[2]<=shaftTop; bool onLanding=room->ledgeSpacingMicrometres==0; if(room->ledgeSpacingMicrometres>0) { const std::int64_t ledgeIndex=(shaftTop-endpoint[2] +room->ledgeSpacingMicrometres/2) /room->ledgeSpacingMicrometres; const std::int64_t ledgeZ=shaftTop -ledgeIndex*room->ledgeSpacingMicrometres; const std::int64_t maximumLedge=(shaftTop-shaftBottom-1000000) /room->ledgeSpacingMicrometres; onLanding=ledgeIndex>=1&&ledgeIndex<=maximumLedge &&std::abs(endpoint[2]-ledgeZ)<=500000; } validRoomEndpoint=insideShaft&&onLanding; } else validRoomEndpoint= std::abs(endpoint[2]-room->floorMicrometres)<=500000; if(endpoint[0]centerMicrometres[0]-room->halfExtentsMicrometres[0] ||endpoint[0]>room->centerMicrometres[0]+room->halfExtentsMicrometres[0] ||endpoint[1]centerMicrometres[1]-room->halfExtentsMicrometres[1] ||endpoint[1]>room->centerMicrometres[1]+room->halfExtentsMicrometres[1] ||!validRoomEndpoint) { add("cavern_edge_portal",edge.stableId+" endpoint_um=(" +std::to_string(endpoint[0])+","+std::to_string(endpoint[1])+","+ std::to_string(endpoint[2])+") room="+room->stableId+" center_um=(" +std::to_string(room->centerMicrometres[0])+","+ std::to_string(room->centerMicrometres[1])+","+ std::to_string(room->centerMicrometres[2])+") floor_um="+ std::to_string(room->floorMicrometres)+" half_um=(" +std::to_string(room->halfExtentsMicrometres[0])+","+ std::to_string(room->halfExtentsMicrometres[1])+","+ std::to_string(room->halfExtentsMicrometres[2])+")"); } } if(edgeStratum&&HasNativeGraphField(*edgeStratum) &&(edge.polylineMicrometres.size()<2 ||edge.polylineMicrometres.front()!=edge.controlPointsMicrometres.front() ||edge.polylineMicrometres.back()!=edge.controlPointsMicrometres.back() ||edge.polylineMicrometres.size()>65536)) add("cavern_edge_polyline",edge.stableId+ " has no bounded resolve-time polyline with matching endpoints"); } std::vector floorProfileIds; for(const auto& edge:cavernEdges) if(!edge.floorProfileStableId.empty() &&std::find(floorProfileIds.begin(),floorProfileIds.end(), edge.floorProfileStableId)==floorProfileIds.end()) floorProfileIds.push_back(edge.floorProfileStableId); for(const std::string& profileId:floorProfileIds) { std::vector profileEdges; for(const auto& edge:cavernEdges) if(edge.floorProfileStableId==profileId)profileEdges.push_back(&edge); std::sort(profileEdges.begin(),profileEdges.end(),[](const auto* a,const auto* b) { return std::min(a->floorProfileStartDistanceMicrometres, a->floorProfileEndDistanceMicrometres) floorProfileStartDistanceMicrometres, b->floorProfileEndDistanceMicrometres); }); const auto endpointZ=[](const ResolvedCavernEdgeV1& edge,bool atMinimum) { const bool forward=edge.floorProfileStartDistanceMicrometres (edge.floorProfileEndDistanceMicrometres -edge.floorProfileStartDistanceMicrometres); if(span==0.0)return std::numeric_limits::infinity(); const bool forward=edge.floorProfileStartDistanceMicrometres floorProfileStartDistanceMicrometres, edge->floorProfileEndDistanceMicrometres); const std::int64_t high=std::max(edge->floorProfileStartDistanceMicrometres, edge->floorProfileEndDistanceMicrometres); const double slope0=endpointSlope(*edge,true); const double slope1=endpointSlope(*edge,false); if(std::max(std::abs(slope0),std::abs(slope1))>0.200001) add("cavern_floor_profile_grade",edge->stableId+ " exceeds the shared backbone 1:5 grade limit"); if(previous) { const std::int64_t previousHigh=std::max( previous->floorProfileStartDistanceMicrometres, previous->floorProfileEndDistanceMicrometres); if(low!=previousHigh ||std::abs(endpointZ(*previous,false)-endpointZ(*edge,true))>1000 ||std::abs(endpointSlope(*previous,false)-slope0)>0.00001) add("cavern_floor_profile_joint",profileId+" between "+previous->stableId +" and "+edge->stableId+" has distance gap=" +std::to_string(low-previousHigh)+" um height step=" +std::to_string(endpointZ(*previous,false)-endpointZ(*edge,true)) +" um slope step="+std::to_string( endpointSlope(*previous,false)-slope0) +" ("+std::to_string(endpointSlope(*previous,false))+" vs " +std::to_string(slope0)+") spans_um=(" +std::to_string(previous->floorProfileEndDistanceMicrometres -previous->floorProfileStartDistanceMicrometres)+","+ std::to_string(edge->floorProfileEndDistanceMicrometres -edge->floorProfileStartDistanceMicrometres)+")"); } previous=edge; (void)high; } for(const auto& room:cavernRooms) { if(room.floorProfileStableId!=profileId)continue; const ResolvedCavernEdgeV1* containing=nullptr; for(const auto* edge:profileEdges) { const auto low=std::min(edge->floorProfileStartDistanceMicrometres, edge->floorProfileEndDistanceMicrometres); const auto high=std::max(edge->floorProfileStartDistanceMicrometres, edge->floorProfileEndDistanceMicrometres); if(room.floorProfileDistanceMicrometres>=low &&room.floorProfileDistanceMicrometres<=high) {containing=edge;break;} } if(!containing) { add("cavern_room_floor_profile",room.stableId+ " does not lie on its referenced continuous floor profile"); continue; } const auto& edge=*containing; const double t=static_cast(room.floorProfileDistanceMicrometres -edge.floorProfileStartDistanceMicrometres) /(edge.floorProfileEndDistanceMicrometres -edge.floorProfileStartDistanceMicrometres); const double one=1.0-t; const double expected=one*one*one*edge.controlPointsMicrometres[0][2] +3.0*one*one*t*edge.controlPointsMicrometres[1][2] +3.0*one*t*t*edge.controlPointsMicrometres[2][2] +t*t*t*edge.controlPointsMicrometres[3][2]; std::array routeXY{}; for(std::size_t axis=0;axis<2;++axis) routeXY[axis]=one*one*one*edge.controlPointsMicrometres[0][axis] +3.0*one*one*t*edge.controlPointsMicrometres[1][axis] +3.0*one*t*t*edge.controlPointsMicrometres[2][axis] +t*t*t*edge.controlPointsMicrometres[3][axis]; const double roomFloorAtProfile=room.floorMicrometres +room.floorGradientQ16[0]/65536.0 *(routeXY[0]-room.centerMicrometres[0]) +room.floorGradientQ16[1]/65536.0 *(routeXY[1]-room.centerMicrometres[1]); if(std::abs(expected-roomFloorAtProfile)>1000.0) add("cavern_room_floor_profile",room.stableId+ " floor does not match its referenced h(s)"); } } } if(oreVeins.empty()) { if(oreVeinVocabularyVersion!=0) add("ore_vein_vocabulary_version", "ore-vein vocabulary version is set without records"); } else if(oreVeinVocabularyVersion!=ResolvedLayoutOreVeinVocabularyVersionV1) add("ore_vein_vocabulary_version", "ore-vein records require vocabulary version 1"); std::vector oreVeinIds; for(const auto& vein:oreVeins) { if(vein.stableId.empty() ||std::find(oreVeinIds.begin(),oreVeinIds.end(),vein.stableId)!=oreVeinIds.end()) add("ore_vein_identity","ore vein has an empty or duplicate stable identity"); else oreVeinIds.push_back(vein.stableId); if(vein.resourceMaterialId==0||vein.radiusMicrometres<250000 ||vein.radiusMicrometres>20000000 ||vein.irregularityAmplitudeMicrometres<0 ||vein.irregularityAmplitudeMicrometres>10000000 ||vein.irregularityWavelengthMicrometres<0 ||vein.irregularityWavelengthMicrometres>100000000 ||(vein.irregularityAmplitudeMicrometres>0 &&vein.irregularityWavelengthMicrometres<=0) ||vein.blendWidthMicrometres<50000 ||vein.blendWidthMicrometres>2000000 ||vein.irregularitySeed>0x001fffffffffffffull) add("ore_vein_parameters",vein.stableId+" has invalid or unbounded parameters"); const std::int64_t pad=vein.radiusMicrometres +vein.irregularityAmplitudeMicrometres+vein.blendWidthMicrometres; for(std::size_t axis=0;axis<3;++axis) { if(vein.endpointsMicrometres[0][axis]<-1000000000000ll ||vein.endpointsMicrometres[0][axis]>1000000000000ll ||vein.endpointsMicrometres[1][axis]<-1000000000000ll ||vein.endpointsMicrometres[1][axis]>1000000000000ll) add("ore_vein_bounds",vein.stableId+" endpoint is outside the bounded world envelope"); if(vein.boundsMinimumMicrometres[axis] !=std::min(vein.endpointsMicrometres[0][axis], vein.endpointsMicrometres[1][axis])-pad ||vein.boundsMaximumMicrometres[axis] !=std::max(vein.endpointsMicrometres[0][axis], vein.endpointsMicrometres[1][axis])+pad) add("ore_vein_bounds",vein.stableId+" conservative bounds do not match its parameters"); } std::string safetyReason; if(!LayoutOreVeinSafe(*this,vein,&safetyReason)) add("ore_vein_placement",vein.stableId+" is unsafe: "+safetyReason); } if(strict&&!issues.empty()) { error="strict resolved-layout validation rejected "+issues.front().code+": " +issues.front().detail; return false; } return true; } std::optional ResolvedLayoutSnapshotV3::FromJson( std::string_view json,std::string& error) { VFJson::Value root;if(!VFJson::Parser(std::string(json)).Parse(root,error))return std::nullopt; if(!root.IsObject()){error="resolved layout JSON root must be an object";return std::nullopt;} ResolvedLayoutSnapshotV3 s;s.schemaVersion=static_cast(LayoutInt(root,"schema_version")); s.worldIdentity=LayoutString(root,"world_identity"); s.ownerVoxelSizeMicrometres=static_cast(LayoutInt(root,"owner_voxel_size_um")); s.coreVoxelSizeMicrometres=static_cast(LayoutInt(root,"core_voxel_size_um")); s.strateLayoutChunkSizeMicrometres=static_cast(LayoutInt(root,"strate_layout_chunk_size_um")); s.strateLayoutChunkWidthSubvoxels=LayoutInt(root,"strate_layout_chunk_width_subvoxels"); s.stampVocabularyVersion=static_cast(LayoutInt(root,"stamp_vocabulary_version")); s.cavernGraphVocabularyVersion=static_cast( LayoutInt(root,"cavern_graph_vocabulary_version")); s.oreVeinVocabularyVersion=static_cast( LayoutInt(root,"ore_vein_vocabulary_version")); for(const auto& v:root.Get("strates").AsArray()) { ResolvedLayoutStratumV2 x;x.stableId=LayoutString(v,"stable_id"); x.order=static_cast(LayoutInt(v,"order"));x.strateIndex=static_cast(LayoutInt(v,"strate_index")); x.generatorKind=LayoutString(v,"generator_kind");x.recipeId=LayoutString(v,"recipe_id"); x.recipeVersion=static_cast(LayoutInt(v,"recipe_version")); x.interiorBottomMicrometres=LayoutInt(v,"interior_bottom_um");x.interiorTopMicrometres=LayoutInt(v,"interior_top_um"); x.voxelSizeMicrometres=static_cast(LayoutInt(v,"voxel_size_um")); const auto& origin=v.Get("lattice_origin_um").AsArray();for(std::size_t i=0;i<3&&i(LayoutInt(v,"transition_blend_chunks")); x.transitionWidthMicrometres=LayoutInt(v,"transition_width_um"); x.transitionWarpAmplitudeMicrometres=LayoutInt(v,"transition_warp_amplitude_um"); x.transitionInterpenetrationMicrometres=LayoutInt(v,"transition_interpenetration_um"); x.transitionHonouredInCore=LayoutBool(v,"transition_honoured_in_core"); x.transitionFallsBackToHard=LayoutBool(v,"transition_falls_back_to_hard"); x.transitionDispatchPath=LayoutString(v,"transition_dispatch_path"); x.boundDispatchPath=LayoutString(v,"bound_dispatch_path");x.queryDispatchPath=LayoutString(v,"query_dispatch_path"); x.managerLookup=LayoutString(v,"manager_lookup");x.managerChunkWidthMicrometres=LayoutInt(v,"manager_chunk_width_um"); x.dispatchChunkWidthMicrometres=LayoutInt(v,"dispatch_chunk_width_um"); for(const auto& a:v.Get("anchors").AsArray()) {ResolvedLayoutAnchorV2 p;p.kind=LayoutString(a,"kind");p.stableId=LayoutString(a,"stable_id");p.units=LayoutString(a,"units"); for(const auto& value:a.Get("values").AsArray())p.values.push_back(value.Integer());x.anchors.push_back(std::move(p));} s.strates.push_back(std::move(x)); } for(const auto& v:root.Get("passages").AsArray()) { ResolvedLayoutPassageV1 p;p.stableId=LayoutString(v,"stable_id"); p.upperStrateIndex=static_cast(LayoutInt(v,"upper_strate_index")); p.lowerStrateIndex=static_cast(LayoutInt(v,"lower_strate_index")); if(v.Get("style").IsNumber()) p.style=static_cast(LayoutInt(v,"style")); p.placementRadiusSubvoxels=LayoutInt(v,"placement_radius_q"); p.mouthRadiusSubvoxels=LayoutInt(v,"mouth_radius_q");p.midRadiusSubvoxels=LayoutInt(v,"mid_radius_q"); const auto& upper=v.Get("upper_mouth_q").AsArray(); const auto& lower=v.Get("lower_mouth_q").AsArray(); for(std::size_t axis=0;axis<3&&axis(LayoutInt(v,"strate_index")); stamp.kind=static_cast(LayoutInt(v,"kind")); const auto readTriplet=[&](std::string_view key,std::array& target) { const auto& values=v.Get(key).AsArray(); for(std::size_t axis=0;axis<3&&axis(LayoutInt(v,"attachment_policy")); stamp.floorMicrometres=LayoutInt(v,"floor_um"); stamp.irregularitySeed=static_cast(LayoutInt(v,"irregularity_seed")); stamp.yawQuarterTurns=static_cast(LayoutInt(v,"yaw_quarter_turns")); s.stamps.push_back(std::move(stamp)); } for(const auto& v:root.Get("cavern_rooms").AsArray()) { ResolvedCavernRoomV1 room;room.stableId=LayoutString(v,"stable_id"); room.strateIndex=static_cast(LayoutInt(v,"strate_index")); const auto readTriplet=[&](std::string_view key,std::array& target) { const auto& values=v.Get(key).AsArray(); for(std::size_t axis=0;axis<3&&axis(floorGradient[axis].Integer()); room.smoothWidthMicrometres=LayoutInt(v,"smooth_width_um"); room.unionWidthMicrometres=LayoutInt(v,"union_width_um"); room.irregularityAmplitudeMicrometres=LayoutInt(v,"irregularity_amplitude_um"); room.irregularityWavelengthMicrometres=LayoutInt(v,"irregularity_wavelength_um"); room.irregularitySeed=static_cast(LayoutInt(v,"irregularity_seed")); room.shapeKind=static_cast(LayoutInt(v,"shape_kind")); room.ledgeSpacingMicrometres=LayoutInt(v,"ledge_spacing_um"); room.ledgeDepthMicrometres=LayoutInt(v,"ledge_depth_um"); room.ledgeSide=static_cast(LayoutInt(v,"ledge_side")); s.cavernRooms.push_back(std::move(room)); } for(const auto& v:root.Get("cavern_edges").AsArray()) { ResolvedCavernEdgeV1 edge;edge.stableId=LayoutString(v,"stable_id"); edge.startAnchorStableId=LayoutString(v,"start_anchor_stable_id"); edge.endRoomStableId=LayoutString(v,"end_room_stable_id"); edge.strateIndex=static_cast(LayoutInt(v,"strate_index")); edge.floorProfileStableId=LayoutString(v,"floor_profile_id"); if(v.Get("floor_profile_start_distance_um").IsNumber()) edge.floorProfileStartDistanceMicrometres= LayoutInt(v,"floor_profile_start_distance_um"); if(v.Get("floor_profile_end_distance_um").IsNumber()) edge.floorProfileEndDistanceMicrometres= LayoutInt(v,"floor_profile_end_distance_um"); const auto& points=v.Get("control_points_um").AsArray(); for(std::size_t i=0;i& target) { const auto& values=v.Get(key).AsArray(); for(std::size_t axis=0;axis<3&&axis(LayoutInt(v,"irregularity_seed")); s.cavernEdges.push_back(std::move(edge)); } for(const auto& v:root.Get("ore_veins").AsArray()) { ResolvedLayoutOreVeinV1 vein;vein.stableId=LayoutString(v,"stable_id"); vein.strateIndex=static_cast(LayoutInt(v,"strate_index")); vein.resourceMaterialId=static_cast(LayoutInt(v,"resource_material_id")); const auto& endpoints=v.Get("endpoints_um").AsArray(); for(std::size_t endpoint=0;endpoint<2&&endpoint& target) { const auto& values=v.Get(key).AsArray(); for(std::size_t axis=0;axis<3&&axis(LayoutInt(v,"irregularity_seed")); s.oreVeins.push_back(std::move(vein)); } const std::string serializedIdentity=LayoutString(root,"stable_identity"); if(!s.finalize(error))return std::nullopt; if(s.stableIdentity!=serializedIdentity){error="resolved layout stable identity does not match its canonical payload";return std::nullopt;} return s; } bool ResolveResolvedLayoutStampsV1(ResolvedLayoutSnapshotV3& layout,std::int32_t seed, std::span requests,std::string& error) { error.clear(); if(requests.empty()) { ResolvedLayoutSnapshotV3 candidate=layout; candidate.stampVocabularyVersion=0;candidate.stamps.clear(); if(!candidate.finalize(error))return false; layout=std::move(candidate);return true; } std::vector ordered(requests.begin(),requests.end()); std::sort(ordered.begin(),ordered.end(),[](const auto& a,const auto& b) {return a.stableId0 &&ordered[requestIndex-1].stableId==request.stableId)) {error="stamp requests require non-empty unique stable IDs";return false;} if(!LayoutStampKindValid(request.kind)||request.yawQuarterTurns>3 ||request.placementRadiusMicrometres<0 ||request.placementRadiusMicrometres>100000000 ||request.smoothWidthMicrometres<0||request.smoothWidthMicrometres>10000000 ||request.irregularityAmplitudeMicrometres<0 ||request.irregularityAmplitudeMicrometres>10000000 ||request.irregularityWavelengthMicrometres<0 ||request.irregularityWavelengthMicrometres>100000000 ||(request.irregularityAmplitudeMicrometres>0 &&request.irregularityWavelengthMicrometres<=0)) {error="stamp request "+request.stableId+" has invalid or unbounded parameters";return false;} if(std::any_of(request.halfExtentsMicrometres.begin(),request.halfExtentsMicrometres.end(), [](std::int64_t extent){return extent<500000||extent>100000000;})) {error="stamp request "+request.stableId+" has an extent outside 0.5..100 m";return false;} const auto stratum=std::find_if(candidate.strates.begin(),candidate.strates.end(), [&](const auto& value){return value.strateIndex==request.strateIndex;}); if(stratum==candidate.strates.end()) {error="stamp request "+request.stableId+" names an unknown strate";return false;} for(const auto coordinate:request.anchorPositionMicrometres) if(coordinate<-1000000000000ll||coordinate>1000000000000ll) {error="stamp request "+request.stableId+" has an unbounded anchor";return false;} // The shared JSON value type stores numbers as IEEE-754 doubles; keep the // serialized seed in its exact integer range so the canonical round trip holds. const std::uint64_t baseSeed=LayoutStampSeed(seed,request.strateIndex,request.stableId) &0x001fffffffffffffull; std::uint64_t state=baseSeed; const auto randomOffset=[&](std::uint64_t bits) { const std::int64_t radius=request.placementRadiusMicrometres; if(radius==0)return std::int64_t{0}; const std::uint64_t span=static_cast(radius)*2+1; return static_cast(bits%span)-radius; }; bool placed=false; for(std::uint32_t attempt=0;attempt<(request.placementRadiusMicrometres==0?1u:512u);++attempt) { ResolvedLayoutStampV1 stamp;stamp.stableId=request.stableId; stamp.strateIndex=request.strateIndex;stamp.kind=request.kind; stamp.halfExtentsMicrometres=request.halfExtentsMicrometres; stamp.smoothWidthMicrometres=request.smoothWidthMicrometres; stamp.irregularityAmplitudeMicrometres=request.irregularityAmplitudeMicrometres; stamp.irregularityWavelengthMicrometres=request.irregularityWavelengthMicrometres; stamp.irregularitySeed=baseSeed;stamp.yawQuarterTurns=request.yawQuarterTurns; std::array offset{}; if(request.placementRadiusMicrometres>0) { offset={randomOffset(LayoutStampNext(state)), randomOffset(LayoutStampNext(state)), randomOffset(LayoutStampNext(state))/3}; const long double radius=static_cast(request.placementRadiusMicrometres); const long double distanceSquared=static_cast(offset[0])*offset[0] +static_cast(offset[1])*offset[1] +static_cast(offset[2])*offset[2]; if(distanceSquared>radius*radius)continue; } for(std::size_t axis=0;axis<3;++axis) stamp.positionMicrometres[axis]=request.anchorPositionMicrometres[axis]+offset[axis]; const std::int64_t pad=stamp.smoothWidthMicrometres +stamp.irregularityAmplitudeMicrometres; const std::array extents={ stamp.halfExtentsMicrometres[stamp.yawQuarterTurns%2?1:0]+pad, stamp.halfExtentsMicrometres[stamp.yawQuarterTurns%2?0:1]+pad, stamp.halfExtentsMicrometres[2]+pad}; for(std::size_t axis=0;axis<3;++axis) { stamp.boundsMinimumMicrometres[axis]=stamp.positionMicrometres[axis]-extents[axis]; stamp.boundsMaximumMicrometres[axis]=stamp.positionMicrometres[axis]+extents[axis]; } if(!LayoutStampSafe(candidate,stamp))continue; candidate.stamps.push_back(std::move(stamp));placed=true;break; } if(!placed) {error="stamp request "+request.stableId+" has no deterministic passage-safe content placement";return false;} } if(!candidate.finalize(error))return false; std::vector issues;std::string strictError; if(!candidate.validate(ResolvedLayoutValidationModeV3::strict,issues,strictError)) {error="strict stamp placement validation failed: "+strictError;return false;} layout=std::move(candidate);return true; } bool ResolveResolvedLayoutStampsV2(ResolvedLayoutSnapshotV3& layout,std::int32_t seed, std::span requests,std::string& error) { if(!ResolveResolvedLayoutStampsV1(layout,seed,requests,error))return false; if(requests.empty())return true; ResolvedLayoutSnapshotV3 candidate=layout; candidate.stampVocabularyVersion=ResolvedLayoutStampVocabularyVersionV2; if(!candidate.finalize(error))return false; std::vector issues;std::string strictError; if(!candidate.validate(ResolvedLayoutValidationModeV3::strict,issues,strictError)) {error="strict stamp vocabulary-2 validation failed: "+strictError;return false;} layout=std::move(candidate);return true; } bool ResolveResolvedLayoutStampsV3(ResolvedLayoutSnapshotV3& layout,std::int32_t seed, std::span requests,std::string& error) { error.clear(); if(requests.empty()) { ResolvedLayoutSnapshotV3 candidate=layout; candidate.stampVocabularyVersion=0;candidate.stamps.clear(); if(!candidate.finalize(error))return false; layout=std::move(candidate);return true; } std::vector ordered(requests.begin(),requests.end()); std::sort(ordered.begin(),ordered.end(),[](const auto& a,const auto& b) {return a.stableId0 &&ordered[requestIndex-1].stableId==request.stableId)) {error="motif requests require non-empty unique stable IDs";return false;} if(!LayoutStampIsMotif(request.kind)||request.yawQuarterTurns>3 ||request.placementRadiusMicrometres<0 ||request.placementRadiusMicrometres>100000000 ||request.smoothWidthMicrometres<0||request.smoothWidthMicrometres>10000000 ||request.irregularityAmplitudeMicrometres!=0 ||request.irregularityWavelengthMicrometres<0 ||request.irregularityWavelengthMicrometres>100000000 ||request.roundingRadiusMicrometres<500000 ||request.minimumSpacingMicrometres<0 ||request.minimumSpacingMicrometres>100000000 ||request.attachmentPolicy!=1) {error="motif request "+request.stableId+" has invalid or unbounded parameters";return false;} if(std::any_of(request.halfExtentsMicrometres.begin(),request.halfExtentsMicrometres.end(), [](std::int64_t extent){return extent<500000||extent>100000000;})) {error="motif request "+request.stableId+" has an extent outside 0.5..100 m";return false;} const bool monolith=request.kind==ResolvedSdfStampKindV1::fillMonolith; const auto minimumExtent=*std::min_element(request.halfExtentsMicrometres.begin(), request.halfExtentsMicrometres.end()); const bool dimensionsValid=monolith ?request.halfExtentsMicrometres[0]==request.halfExtentsMicrometres[1] &&request.halfExtentsMicrometres[0]>=800000 &&request.halfExtentsMicrometres[0]<=2000000 &&request.halfExtentsMicrometres[2]>=6000000 &&request.halfExtentsMicrometres[2]<=16000000 :request.halfExtentsMicrometres[0]>=1000000 &&request.halfExtentsMicrometres[1]>=1000000; if(!dimensionsValid||request.roundingRadiusMicrometres>minimumExtent) {error="motif request "+request.stableId+" has a shape size outside its authored family";return false;} const auto stratum=std::find_if(candidate.strates.begin(),candidate.strates.end(), [&](const auto& value){return value.strateIndex==request.strateIndex;}); if(stratum==candidate.strates.end()) {error="motif request "+request.stableId+" names an unknown strate";return false;} for(const auto coordinate:request.anchorPositionMicrometres) if(coordinate<-1000000000000ll||coordinate>1000000000000ll) {error="motif request "+request.stableId+" has an unbounded anchor";return false;} if(request.floorMicrometres<-1000000000000ll ||request.floorMicrometres>1000000000000ll) {error="motif request "+request.stableId+" has an unbounded floor anchor";return false;} const std::uint64_t baseSeed=LayoutStampSeed(seed,request.strateIndex,request.stableId) &0x001fffffffffffffull; std::uint64_t state=baseSeed; const auto randomOffset=[&](std::uint64_t bits) { const std::uint64_t span=static_cast( request.placementRadiusMicrometres)*2+1; return request.placementRadiusMicrometres==0?std::int64_t{0} :static_cast(bits%span)-request.placementRadiusMicrometres; }; bool placed=false; const std::uint32_t attempts=request.placementRadiusMicrometres==0?1u:512u; for(std::uint32_t attempt=0;attempt offset={ randomOffset(LayoutStampNext(state)),randomOffset(LayoutStampNext(state))}; if(request.placementRadiusMicrometres>0) { const long double radius=request.placementRadiusMicrometres; const long double distanceSquared=static_cast(offset[0])*offset[0] +static_cast(offset[1])*offset[1]; if(distanceSquared>radius*radius)continue; } stamp.positionMicrometres={request.anchorPositionMicrometres[0]+offset[0], request.anchorPositionMicrometres[1]+offset[1], request.floorMicrometres+request.halfExtentsMicrometres[2]}; const std::int64_t pad=(stamp.smoothWidthMicrometres+5)/6; const std::array extents={ stamp.halfExtentsMicrometres[stamp.yawQuarterTurns%2?1:0]+pad, stamp.halfExtentsMicrometres[stamp.yawQuarterTurns%2?0:1]+pad, stamp.halfExtentsMicrometres[2]+pad}; for(std::size_t axis=0;axis<3;++axis) { stamp.boundsMinimumMicrometres[axis]=stamp.positionMicrometres[axis]-extents[axis]; stamp.boundsMaximumMicrometres[axis]=stamp.positionMicrometres[axis]+extents[axis]; } if(!LayoutStampSafe(candidate,stamp))continue; bool spaced=true; for(const auto& prior:candidate.stamps) { if(!LayoutStampIsMotif(prior.kind)||prior.strateIndex!=stamp.strateIndex)continue; double gapSquared=0.0; for(std::size_t axis=0;axis<3;++axis) { const auto gap=stamp.boundsMinimumMicrometres[axis] >prior.boundsMaximumMicrometres[axis] ?stamp.boundsMinimumMicrometres[axis]-prior.boundsMaximumMicrometres[axis] :prior.boundsMinimumMicrometres[axis] >stamp.boundsMaximumMicrometres[axis] ?prior.boundsMinimumMicrometres[axis]-stamp.boundsMaximumMicrometres[axis]:0; gapSquared+=static_cast(gap)*gap; } const auto required=std::max(stamp.minimumSpacingMicrometres, prior.minimumSpacingMicrometres); if(gapSquared(required)*required) {spaced=false;break;} } if(!spaced)continue; candidate.stamps.push_back(std::move(stamp));placed=true;break; } if(!placed) {error="motif request "+request.stableId+" has no deterministic route-, band- and spacing-safe placement";return false;} } if(!candidate.finalize(error))return false; std::vector issues;std::string strictError; if(!candidate.validate(ResolvedLayoutValidationModeV3::strict,issues,strictError)) {error="strict stamp vocabulary-3 validation failed: "+strictError;return false;} layout=std::move(candidate);return true; } bool ResolveResolvedCavernGraphV1(ResolvedLayoutSnapshotV3& layout,std::int32_t seed, std::span rooms, std::span edges,std::string& error) { error.clear(); ResolvedLayoutSnapshotV3 candidate=layout; candidate.cavernRooms.clear();candidate.cavernEdges.clear(); if(rooms.empty()&&edges.empty()) { candidate.cavernGraphVocabularyVersion=0; if(!candidate.finalize(error))return false; layout=std::move(candidate);return true; } const bool hasVerticalShaft=std::any_of(rooms.begin(),rooms.end(), [](const auto& room){return room.shapeKind==1;}); candidate.cavernGraphVocabularyVersion=hasVerticalShaft ?ResolvedLayoutCavernGraphVocabularyVersionV3 :ResolvedLayoutCavernGraphVocabularyVersionV2; candidate.cavernRooms.assign(rooms.begin(),rooms.end()); candidate.cavernEdges.assign(edges.begin(),edges.end()); const auto isNativeCavesStrate=[&candidate](std::int32_t strateIndex) { const auto found=std::find_if(candidate.strates.begin(),candidate.strates.end(), [strateIndex](const auto& stratum){return stratum.strateIndex==strateIndex;}); return found!=candidate.strates.end()&&IsNativeCaveFieldStratum(*found); }; const auto isNativeLatticeStrate=[&candidate](std::int32_t strateIndex) { const auto found=std::find_if(candidate.strates.begin(),candidate.strates.end(), [strateIndex](const auto& stratum){return stratum.strateIndex==strateIndex;}); return found!=candidate.strates.end()&&IsNativeLatticeFieldStratum(*found); }; for(auto& room:candidate.cavernRooms) { // The shared JSON value type stores numbers as IEEE-754 doubles. Keep the // serialized seed in the exact integer range, as the stamp vocabulary does. room.irregularitySeed=(isNativeLatticeStrate(room.strateIndex) ?LayoutLatticeFeatureSeed(seed,room.strateIndex,1,room.stableId) :LayoutStampSeed(seed,room.strateIndex,room.stableId)) &0x001fffffffffffffull; const std::int64_t pad=room.smoothWidthMicrometres +room.unionWidthMicrometres+room.irregularityAmplitudeMicrometres; for(std::size_t axis=0;axis<3;++axis) { room.boundsMinimumMicrometres[axis]=room.centerMicrometres[axis] -room.halfExtentsMicrometres[axis]-pad; room.boundsMaximumMicrometres[axis]=room.centerMicrometres[axis] +room.halfExtentsMicrometres[axis]+pad; } } for(auto& edge:candidate.cavernEdges) { const bool nativeLattice=isNativeLatticeStrate(edge.strateIndex); edge.irregularitySeed=(nativeLattice ?LayoutLatticeFeatureSeed(seed,edge.strateIndex,1,edge.stableId) :LayoutStampSeed(seed,edge.strateIndex,edge.stableId)) &0x001fffffffffffffull; using FCurvePoint=std::array; std::array curve{}; for(std::size_t point=0;point(edge.controlPointsMicrometres[point][axis]); const auto pointSegmentDistance=[](const FCurvePoint& point, const FCurvePoint& a,const FCurvePoint& b) { double lengthSquared=0.0,dot=0.0; for(std::size_t axis=0;axis<3;++axis) { const double delta=b[axis]-a[axis]; lengthSquared+=delta*delta;dot+=(point[axis]-a[axis])*delta; } const double t=lengthSquared>0.0 ?std::clamp(dot/lengthSquared,0.0,1.0):0.0; double distanceSquared=0.0; for(std::size_t axis=0;axis<3;++axis) { const double delta=point[axis]-(a[axis]+t*(b[axis]-a[axis])); distanceSquared+=delta*delta; } return std::sqrt(distanceSquared); }; std::vector> authoredPolyline; if(nativeLattice)authoredPolyline=std::move(edge.polylineMicrometres); edge.polylineMicrometres.clear(); if(isNativeCavesStrate(edge.strateIndex)) { edge.polylineMicrometres.push_back(edge.controlPointsMicrometres.front()); constexpr double MaximumChordErrorMicrometres=10000.0; bool polylineValid=true; const auto subdivide=[&](const auto& self,const std::array& points, int depth)->void { if(!polylineValid)return; const double flatness=std::max(pointSegmentDistance(points[1],points[0],points[3]), pointSegmentDistance(points[2],points[0],points[3])); if(flatness<=MaximumChordErrorMicrometres) { std::array endpoint{}; for(std::size_t axis=0;axis<3;++axis) { if(!std::isfinite(points[3][axis]) ||std::abs(points[3][axis])>9.0e15) {polylineValid=false;return;} endpoint[axis]=static_cast(std::llround(points[3][axis])); } edge.polylineMicrometres.push_back(endpoint); if(edge.polylineMicrometres.size()>65536)polylineValid=false; return; } if(depth>=20){polylineValid=false;return;} std::array left{},right{}; FCurvePoint a{},b{},c{},d{},e{},mid{}; for(std::size_t axis=0;axis<3;++axis) { a[axis]=(points[0][axis]+points[1][axis])*0.5; b[axis]=(points[1][axis]+points[2][axis])*0.5; c[axis]=(points[2][axis]+points[3][axis])*0.5; d[axis]=(a[axis]+b[axis])*0.5; e[axis]=(b[axis]+c[axis])*0.5; mid[axis]=(d[axis]+e[axis])*0.5; } left={points[0],a,d,mid};right={mid,e,c,points[3]}; self(self,left,depth+1);self(self,right,depth+1); }; subdivide(subdivide,curve,0); if(!polylineValid) {error="cavern edge "+edge.stableId+" exceeds the bounded polyline subdivision budget";return false;} } else if(nativeLattice) { if(authoredPolyline.size()<2||authoredPolyline.size()>65536 ||authoredPolyline.front()!=edge.controlPointsMicrometres.front() ||authoredPolyline.back()!=edge.controlPointsMicrometres.back()) {error="lattice edge "+edge.stableId+ " needs a bounded explicit path with matching endpoints";return false;} edge.polylineMicrometres=std::move(authoredPolyline); } std::array low=edge.controlPointsMicrometres[0]; std::array high=low; const auto includeBoundPoint=[&](const auto& point) { for(std::size_t axis=0;axis<3;++axis) {low[axis]=std::min(low[axis],point[axis]);high[axis]=std::max(high[axis],point[axis]);} }; if(nativeLattice&&!edge.polylineMicrometres.empty()) {low=edge.polylineMicrometres.front();high=low; for(const auto& point:edge.polylineMicrometres)includeBoundPoint(point);} else for(const auto& point:edge.controlPointsMicrometres)includeBoundPoint(point); const std::int64_t xyPad=edge.boreRadiusMicrometres +edge.smoothWidthMicrometres+edge.unionWidthMicrometres +edge.irregularityAmplitudeMicrometres; const std::int64_t zPad=edge.smoothWidthMicrometres +edge.unionWidthMicrometres+edge.irregularityAmplitudeMicrometres; for(std::size_t axis=0;axis<3;++axis) { const std::int64_t pad=axis==2?zPad:xyPad; edge.boundsMinimumMicrometres[axis]=low[axis]-pad -(axis==2?edge.floorSupportDepthMicrometres:0); edge.boundsMaximumMicrometres[axis]=high[axis]+pad +(axis==2?edge.boreHeightMicrometres:0); } } if(!candidate.finalize(error))return false; std::vector issues;std::string strictError; if(!candidate.validate(ResolvedLayoutValidationModeV3::strict,issues,strictError)) {error="strict cavern graph validation failed: "+strictError;return false;} layout=std::move(candidate);return true; } bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout, std::int32_t seed,std::int32_t strateIndex, const ResolvedCavernPlannerSettingsV1& settings, std::vector& rooms, std::vector& edges,std::string& error) { rooms.clear();edges.clear();error.clear(); const auto plannerStarted=std::chrono::steady_clock::now(); const auto stratum=std::find_if(layout.strates.begin(),layout.strates.end(), [strateIndex](const auto& value){return value.strateIndex==strateIndex;}); if(stratum==layout.strates.end()||!IsNativeCaveFieldStratum(*stratum)) {error="cavern planner target is not a resolved native cave stratum";return false;} if(settings.roomSpacingMicrometres<=0 ||settings.minimumRoomRadiusMicrometres<500000 ||settings.maximumRoomRadiusMicrometres100000000 ||settings.destinationRadiusMicrometres100000000 ||settings.floorSupportDepthMicrometres<250000 ||settings.floorSupportDepthMicrometres>2000000 ||settings.maximumTunnelLengthMicrometres<=0 ||settings.minimumTunnelRadiusMicrometres<0 ||settings.maximumTunnelRadiusMicrometres10000000 ||settings.irregularityAmplitudeMicrometres<0 ||settings.irregularityAmplitudeMicrometres>10000000 ||(settings.irregularityAmplitudeMicrometres>0 &&settings.irregularityWavelengthMicrometres<=0) ||(settings.ledgeSpacingMicrometres<0 ||settings.ledgeSpacingMicrometres>100000000) ||settings.ledgeDepthMicrometres<0 ||settings.ledgeDepthMicrometres>5000000) {error="cavern planner settings are outside the bounded room, tunnel or relief ranges";return false;} const ResolvedLayoutPassageV1* arrivalPassage=nullptr; const ResolvedLayoutPassageV1* departurePassage=nullptr; for(const auto& passage:layout.passages) { if(passage.lowerStrateIndex==strateIndex&&passage.upperStrateIndexstableId)) arrivalPassage=&passage; if(passage.upperStrateIndex==strateIndex&&passage.lowerStrateIndex>strateIndex &&(!departurePassage||passage.stableIdstableId)) departurePassage=&passage; } const bool terminalShaftRoute=settings.verticalShaftMode &&stratum->generatorKind=="VerticalShafts"&&!departurePassage; if(!arrivalPassage||(!departurePassage&&!terminalShaftRoute) ||(settings.verticalShaftMode&&!terminalShaftRoute)) {error="cavern planner needs an incoming mouth and either an outgoing mouth or a Vertical Shafts basal terminal";return false;} if(layout.coreVoxelSizeMicrometres==0) {error="cavern planner requires a nonzero Core voxel size";return false;} using FPoint=std::array; const std::string routePrefix=(terminalShaftRoute?"vertical-shafts/strate-" :"tunnel-network/strate-")+std::to_string(strateIndex)+"/"; constexpr std::int64_t MicrometresPerMetre=1000000; constexpr std::int64_t MinimumBoreRadius=1750000; constexpr std::int64_t MinimumBoreHeight=2200000; constexpr std::int64_t PlannedBoreHeight=4500000; constexpr std::int64_t PortalLeadLength=6000000; constexpr std::int64_t MaximumBoreRadius=10000000; constexpr std::int64_t MaximumWalkGradeDenominator=5; // 1:5 = 11.31 degrees. const auto integerSqrt=[](std::uint64_t value) { std::uint64_t result=0; std::uint64_t bit=std::uint64_t{1}<<62; while(bit>value)bit>>=2; while(bit!=0) { if(value>=result+bit) {value-=result+bit;result=(result>>1)+bit;} else result>>=1; bit>>=2; } return result; }; const auto distance2d=[&](const FPoint& a,const FPoint& b) { const std::int64_t dx=b[0]-a[0],dy=b[1]-a[1]; const auto squared=static_cast(dx*dx+dy*dy); return static_cast(integerSqrt(squared)); }; const auto qToMicrometres=[&](std::int64_t q) {return q*static_cast(layout.coreVoxelSizeMicrometres) /WorldAssemblySubvoxelsPerVoxel;}; constexpr std::int64_t LandingConnectorBoreRadius=2000000; const std::int64_t mouthBoreRadius=LandingConnectorBoreRadius; const auto chooseMouthRouteTangent=[&](const ResolvedLayoutPassageV1& passage, std::size_t mouthIndex,bool lowerSide, const std::array& authoredRouteTangentQ16, std::int64_t authoredFloorSlopeQ16,const FPoint& mouthFloorPoint, std::int64_t chamberRadiusMicrometres) { constexpr std::array,16> DirectionsQ16={{ {{65536,0}},{{60547,25080}},{{46341,46341}},{{25080,60547}}, {{0,65536}},{{-25080,60547}},{{-46341,46341}},{{-60547,25080}}, {{-65536,0}},{{-60547,-25080}},{{-46341,-46341}},{{-25080,-60547}}, {{0,-65536}},{{25080,-60547}},{{46341,-46341}},{{60547,-25080}}}}; const auto scoreDirection=[&](const std::array& outwardQ16) { const std::size_t segmentCount=passage.controlPoints.size()>1 ?passage.controlPoints.size()-1:0; std::int64_t worst=std::numeric_limits::max(); bool sampled=false; const std::int64_t projectedSlopeQ16=(authoredFloorSlopeQ16 *(outwardQ16[0]*authoredRouteTangentQ16[0] +outwardQ16[1]*authoredRouteTangentQ16[1]) /(65536ll*65536ll)); constexpr std::int64_t MouthClearanceProbeExtent=48000000; const std::int64_t firstClearanceProbe=std::max( 2000000,chamberRadiusMicrometres+1000000); for(std::int64_t distance=firstClearanceProbe; distance<=MouthClearanceProbeExtent;distance+=2000000) { const std::int64_t x=mouthFloorPoint[0] +outwardQ16[0]*distance/65536; const std::int64_t y=mouthFloorPoint[1] +outwardQ16[1]*distance/65536; const std::int64_t caveFloor=mouthFloorPoint[2] +projectedSlopeQ16*distance/65536; const std::int64_t caveCeiling=caveFloor+PlannedBoreHeight; for(std::size_t segmentIndex=0;segmentIndex=mouthIndex)continue; if(!lowerSide&&segmentIndex<2)continue; const auto& a=passage.controlPoints[segmentIndex]; const auto& b=passage.controlPoints[segmentIndex+1]; const std::int64_t ax=qToMicrometres(a.positionSubvoxels[0]); const std::int64_t ay=qToMicrometres(a.positionSubvoxels[1]); const std::int64_t az=qToMicrometres(a.positionSubvoxels[2]); const std::int64_t bx=qToMicrometres(b.positionSubvoxels[0]); const std::int64_t by=qToMicrometres(b.positionSubvoxels[1]); const std::int64_t bz=qToMicrometres(b.positionSubvoxels[2]); const std::int64_t dx=bx-ax,dy=by-ay; const double lengthSquared=static_cast(dx)*dx +static_cast(dy)*dy; const double projection=lengthSquared>0.0 ?std::clamp((static_cast(x-ax)*dx +static_cast(y-ay)*dy)/lengthSquared,0.0,1.0):0.0; const std::int64_t tQ16=static_cast(std::llround( projection*65536.0)); const std::int64_t closestX=ax+dx*tQ16/65536; const std::int64_t closestY=ay+dy*tQ16/65536; const std::int64_t horizontalDistance=static_cast( integerSqrt(static_cast((x-closestX)*(x-closestX) +(y-closestY)*(y-closestY)))); const std::int64_t passageCenterZ=qToMicrometres( a.positionSubvoxels[2]) +(qToMicrometres(b.positionSubvoxels[2]) -qToMicrometres(a.positionSubvoxels[2]))*tQ16/65536; const std::int64_t passageRadius=qToMicrometres(a.radiusSubvoxels) +(qToMicrometres(b.radiusSubvoxels) -qToMicrometres(a.radiusSubvoxels))*tQ16/65536; const std::int64_t horizontalGap=horizontalDistance -passageRadius-mouthBoreRadius; const std::int64_t passageFloor=passageCenterZ-passageRadius; const std::int64_t passageCeiling=passageCenterZ+passageRadius; const std::int64_t verticalGap=caveFloor>passageCeiling ?caveFloor-passageCeiling :(passageFloor>caveCeiling?passageFloor-caveCeiling :-std::min(caveCeiling-passageFloor, passageCeiling-caveFloor)); const std::int64_t separation=horizontalGap>=0&&verticalGap>=0 ?static_cast(integerSqrt(static_cast( horizontalGap*horizontalGap+verticalGap*verticalGap))) :std::max(horizontalGap,verticalGap); worst=std::min(worst,separation);sampled=true; } } return sampled?worst:std::numeric_limits::max(); }; // Keep the authored tangent whenever it clears the other passage spans. // If the passage folds back over the mouth, choose the outward direction // with the greatest 3D clearance while preserving deterministic tie order. const std::array authoredOutwardQ16={ lowerSide?authoredRouteTangentQ16[0]:-authoredRouteTangentQ16[0], lowerSide?authoredRouteTangentQ16[1]:-authoredRouteTangentQ16[1]}; const std::int64_t authoredScore=scoreDirection(authoredOutwardQ16); if(authoredScore>=0)return authoredRouteTangentQ16; std::array bestOutwardQ16=authoredOutwardQ16; std::int64_t bestScore=authoredScore; std::int64_t bestAlignment=authoredOutwardQ16[0]*authoredOutwardQ16[0] +authoredOutwardQ16[1]*authoredOutwardQ16[1]; for(const auto& candidate:DirectionsQ16) { const std::int64_t score=scoreDirection(candidate); const std::int64_t alignment=candidate[0]*authoredOutwardQ16[0] +candidate[1]*authoredOutwardQ16[1]; if(score>bestScore||(score==bestScore&&alignment>bestAlignment)) {bestScore=score;bestAlignment=alignment;bestOutwardQ16=candidate;} } std::printf("[CavernMouthClearancePlan] passage=%s side=%s authored_clearance_m=%.3f selected_clearance_m=%.3f outward_q16=(%lld,%lld)\n", passage.stableId.c_str(),lowerSide?"lower":"upper", authoredScore/1.0e6,bestScore/1.0e6, static_cast(bestOutwardQ16[0]), static_cast(bestOutwardQ16[1])); return std::array{ lowerSide?bestOutwardQ16[0]:-bestOutwardQ16[0], lowerSide?bestOutwardQ16[1]:-bestOutwardQ16[1]}; }; const auto mouthInnerPoint=[&](const ResolvedLayoutPassageV1& passage, bool lowerSide,FPoint& point,std::array& routeTangentQ16, std::int64_t& floorSlopeQ16) { const auto& chamber=lowerSide?passage.lowerLandingChamber :passage.upperLandingChamber; for(std::size_t axis=0;axis<3;++axis) point[axis]=qToMicrometres(chamber.floorCenterSubvoxels[axis]); if(passage.controlPoints.size()<2)return false; const std::size_t mouthIndex=lowerSide ?std::min(passage.lowerMouthControlIndex,passage.controlPoints.size()-1):0; const std::size_t tangentIndex=lowerSide ?(mouthIndex>0?mouthIndex-1:1):1; // The lower arrival continues from the previous passage point through its // mouth. The upper departure approaches its mouth from the cave and then // continues toward control point 1, so the two route tangents have opposite // signs even though both portal offsets lie on the cave side. const std::int64_t direction=lowerSide?1:-1; const std::int64_t tx=direction*(passage.controlPoints[mouthIndex].positionSubvoxels[0] -passage.controlPoints[tangentIndex].positionSubvoxels[0]); const std::int64_t ty=direction*(passage.controlPoints[mouthIndex].positionSubvoxels[1] -passage.controlPoints[tangentIndex].positionSubvoxels[1]); const auto tangentLength=static_cast(integerSqrt( static_cast(tx*tx+ty*ty))); if(tangentLength<=0)return false; routeTangentQ16={tx*65536/tangentLength,ty*65536/tangentLength}; const auto& mouthControl=passage.controlPoints[mouthIndex]; const std::int64_t mouthFloor=chamber.floorCenterSubvoxels[2]; point[2]=qToMicrometres(mouthFloor); floorSlopeQ16=(chamber.floorGradientQ16[0]*routeTangentQ16[0] +chamber.floorGradientQ16[1]*routeTangentQ16[1])/65536; const auto authoredRouteTangentQ16=routeTangentQ16; routeTangentQ16=chooseMouthRouteTangent(passage,mouthIndex,lowerSide, authoredRouteTangentQ16,floorSlopeQ16,point, qToMicrometres(chamber.radiusSubvoxels)); floorSlopeQ16=(chamber.floorGradientQ16[0]*routeTangentQ16[0] +chamber.floorGradientQ16[1]*routeTangentQ16[1])/65536; const std::int64_t offsetDirection=lowerSide?1:-1; const std::int64_t portOffset=std::max(0, qToMicrometres(chamber.radiusSubvoxels)-LandingConnectorBoreRadius); point[0]+=offsetDirection*routeTangentQ16[0]*portOffset/65536; point[1]+=offsetDirection*routeTangentQ16[1]*portOffset/65536; point[2]=qToMicrometres(mouthFloor) +(chamber.floorGradientQ16[0]*(point[0] -qToMicrometres(chamber.floorCenterSubvoxels[0])) +chamber.floorGradientQ16[1]*(point[1] -qToMicrometres(chamber.floorCenterSubvoxels[1])))/65536; return true; }; FPoint routeStart{},routeEnd{}; std::array arrivalTangentQ16{},departureTangentQ16{}; std::int64_t arrivalFloorSlopeQ16=0,departureFloorSlopeQ16=0; if(!mouthInnerPoint(*arrivalPassage,true,routeStart,arrivalTangentQ16, arrivalFloorSlopeQ16)) {error="cavern passage mouth is missing a usable horizontal route tangent";return false;} if(!terminalShaftRoute &&!mouthInnerPoint(*departurePassage,false,routeEnd,departureTangentQ16, departureFloorSlopeQ16)) {error="cavern passage mouth is missing a usable horizontal route tangent";return false;} if(terminalShaftRoute) { departureTangentQ16=arrivalTangentQ16; routeEnd[2]=stratum->content.bottomMicrometres+3000000; departureFloorSlopeQ16=0; } std::int64_t dx=routeEnd[0]-routeStart[0]; std::int64_t dy=routeEnd[1]-routeStart[1]; const std::int64_t dz=routeEnd[2]-routeStart[2]; const std::int64_t horizontalBias=std::clamp( settings.tunnelHorizontalBiasQ16,0,65536); const std::int64_t planGradeDenominator=MaximumWalkGradeDenominator +horizontalBias/32768; const std::int64_t requiredLength=(std::abs(dz)*planGradeDenominator*130)/100+1000; if(terminalShaftRoute) { // The basal stratum has no next mouth by design. The walk-only spine starts // at its incoming passage and ends on a supported landing above bedrock. // Optional open shafts are side features with separate climb/fall reporting. // Normalize by the truncated Q16 tangent magnitude: dividing by 65536 // shortens non-cardinal vectors enough to miss the strict grade-run bound. const std::uint64_t tangentSquared=static_cast( departureTangentQ16[0]*departureTangentQ16[0] +departureTangentQ16[1]*departureTangentQ16[1]); const std::int64_t tangentMagnitudeQ16=static_cast( integerSqrt(tangentSquared)); if(tangentMagnitudeQ16<=0) {error="cavern planner terminal tangent has zero horizontal length";return false;} const std::int64_t terminalProjectionLength=requiredLength+1000; for(std::size_t axis=0;axis<2;++axis) routeEnd[axis]=routeStart[axis] +departureTangentQ16[axis]*terminalProjectionLength/tangentMagnitudeQ16; dx=routeEnd[0]-routeStart[0];dy=routeEnd[1]-routeStart[1]; } std::int64_t pathAxisX=dx,pathAxisY=dy; FPoint routeLeadStart=routeStart,routeLeadEnd=routeEnd; for(std::size_t axis=0;axis<2;++axis) { routeLeadStart[axis]+=arrivalTangentQ16[axis]*PortalLeadLength/65536; routeLeadEnd[axis]-=departureTangentQ16[axis]*PortalLeadLength/65536; } routeLeadStart[2]+=arrivalFloorSlopeQ16*PortalLeadLength/65536; routeLeadEnd[2]-=departureFloorSlopeQ16*PortalLeadLength/65536; const std::int64_t mouthTangentDot=arrivalTangentQ16[0]*departureTangentQ16[0] +arrivalTangentQ16[1]*departureTangentQ16[1]; const auto makeRoute=[&](std::int64_t handleLength, std::int64_t lateralOffsetMicrometres=0) { if(mouthTangentDot<0) { // A cubic with opposed portal tangents folds back over itself when its // handles are lengthened to satisfy the walking grade. Make the loop // explicit: leave both mouths on their authored tangents, carry two // parallel legs on separate lanes, and turn through cavern rooms. constexpr std::int64_t LaneSeparation=30000000; const std::array sideQ16={ -arrivalTangentQ16[1],arrivalTangentQ16[0]}; FPoint outTip=routeLeadStart,shiftedTip{},returnTip{},terminal=routeLeadEnd; for(std::size_t axis=0;axis<2;++axis) { outTip[axis]+=arrivalTangentQ16[axis]*handleLength/65536; shiftedTip[axis]=outTip[axis]+sideQ16[axis] *(LaneSeparation+lateralOffsetMicrometres)/65536; } outTip[2]+=arrivalFloorSlopeQ16*handleLength/65536; shiftedTip[2]=outTip[2]; terminal=routeLeadEnd; const std::int64_t departureTangentLengthSquared= departureTangentQ16[0]*departureTangentQ16[0] +departureTangentQ16[1]*departureTangentQ16[1]; const std::int64_t toTerminalX=terminal[0]-shiftedTip[0]; const std::int64_t toTerminalY=terminal[1]-shiftedTip[1]; const std::int64_t returnLength=std::max(1000000, (toTerminalX*departureTangentQ16[0] +toTerminalY*departureTangentQ16[1])*65536 /departureTangentLengthSquared); for(std::size_t axis=0;axis<2;++axis) returnTip[axis]=shiftedTip[axis] +departureTangentQ16[axis]*returnLength/65536; returnTip[2]=routeEnd[2] -departureFloorSlopeQ16*(returnLength+PortalLeadLength)/65536; return std::vector{routeStart,routeLeadStart,outTip, shiftedTip,returnTip,routeLeadEnd,routeEnd}; } FPoint controlStart=routeLeadStart,controlEnd=routeLeadEnd; for(std::size_t axis=0;axis<2;++axis) { controlStart[axis]+=arrivalTangentQ16[axis]*handleLength/65536; controlEnd[axis]-=departureTangentQ16[axis]*handleLength/65536; } controlStart[2]+=arrivalFloorSlopeQ16*handleLength/65536; controlEnd[2]-=departureFloorSlopeQ16*handleLength/65536; std::vector points; points.reserve(35); points.push_back(routeStart); points.push_back(routeLeadStart); constexpr std::int64_t CurveParameterOne=std::int64_t{1}<<20; constexpr std::int64_t CurveSamples=32; const auto interpolate=[=](const FPoint& a,const FPoint& b,std::int64_t t) { FPoint point{}; for(std::size_t axis=0;axis<3;++axis) point[axis]=a[axis]+(b[axis]-a[axis])*t/CurveParameterOne; return point; }; const std::int64_t pathAxisLength=static_cast(integerSqrt( static_cast(pathAxisX*pathAxisX+pathAxisY*pathAxisY))); const std::array pathSideQ16=pathAxisLength>0 ?std::array{{-pathAxisY*65536/pathAxisLength, pathAxisX*65536/pathAxisLength}} :std::array{{-arrivalTangentQ16[1],arrivalTangentQ16[0]}}; for(std::int64_t sample=1;sample<=CurveSamples;++sample) { const std::int64_t t=sample*CurveParameterOne/CurveSamples; const FPoint a=interpolate(routeLeadStart,controlStart,t); const FPoint b=interpolate(controlStart,controlEnd,t); const FPoint c=interpolate(controlEnd,routeLeadEnd,t); const FPoint d=interpolate(a,b,t); const FPoint e=interpolate(b,c,t); FPoint point=interpolate(d,e,t); if(lateralOffsetMicrometres!=0) { const std::int64_t one=CurveParameterOne-t; const std::int64_t t2=t*t/CurveParameterOne; const std::int64_t one2=one*one/CurveParameterOne; const std::int64_t envelope=std::min(CurveParameterOne, 16*t2*one2/CurveParameterOne); const std::int64_t offset=lateralOffsetMicrometres*envelope /CurveParameterOne; for(std::size_t axis=0;axis<2;++axis) point[axis]+=pathSideQ16[axis]*offset/65536; } points.push_back(point); } points.push_back(routeEnd); return points; }; const auto routeLength=[&](const std::vector& points) { std::int64_t length=0; for(std::size_t i=1;i a{},b{}; double radius=0.0; }; std::vector routePassageClearanceSpans; const auto appendPassageClearanceSpans=[&]( const ResolvedLayoutPassageV1& passage,bool lowerMouth) { if(passage.controlPoints.size()<2)return; std::vector controls; controls.reserve(passage.controlPoints.size()); for(const auto& point:passage.controlPoints) controls.push_back({static_cast(qToMicrometres( point.positionSubvoxels[0])), static_cast(qToMicrometres(point.positionSubvoxels[1])), static_cast(qToMicrometres(point.positionSubvoxels[2])), static_cast(qToMicrometres(point.radiusSubvoxels))}); const auto segments=VoxelForgeCore::PassageSpline::BuildSegments(controls, controls.size()-1,2.0,1000000.0); const double totalLength=segments.empty()?0.0: segments.back().alongMetres+segments.back().lengthMetres; constexpr double PortalExclusionMetres=10.0; for(const auto& segment:segments) { const double midpoint=segment.alongMetres+segment.lengthMetres*0.5; if((lowerMouth&&totalLength-midpoint& p0, const std::array& p1,const std::array& q0, const std::array& q1) { constexpr double Epsilon=1.0e-12; std::array u{},v{},w{}; double a=0.0,b=0.0,c=0.0,d=0.0,e=0.0; for(std::size_t axis=0;axis<3;++axis) { u[axis]=p1[axis]-p0[axis];v[axis]=q1[axis]-q0[axis]; w[axis]=p0[axis]-q0[axis]; a+=u[axis]*u[axis];b+=u[axis]*v[axis];c+=v[axis]*v[axis]; d+=u[axis]*w[axis];e+=v[axis]*w[axis]; } double sNumerator=0.0,sDenominator=a*c-b*b; double tNumerator=0.0,tDenominator=sDenominator; if(a<=Epsilon&&c<=Epsilon)return std::sqrt(w[0]*w[0]+w[1]*w[1]+w[2]*w[2]); if(a<=Epsilon) {sNumerator=0.0;sDenominator=1.0;tNumerator=e;tDenominator=c;} else if(c<=Epsilon) {tNumerator=0.0;tDenominator=1.0;sNumerator=-d;sDenominator=a;} else { if(sDenominatorsDenominator) {sNumerator=sDenominator;tNumerator=e+b;tDenominator=c;} } } if(tNumerator<0.0) { tNumerator=0.0; if(-d<0.0)sNumerator=0.0; else if(-d>a)sNumerator=sDenominator; else {sNumerator=-d;sDenominator=a;} } else if(tNumerator>tDenominator) { tNumerator=tDenominator; if(-d+b<0.0)sNumerator=0.0; else if(-d+b>a)sNumerator=sDenominator; else {sNumerator=-d+b;sDenominator=a;} } const double s=std::abs(sNumerator)& points) { if(routePassageClearanceSpans.empty()||points.size()<2) return std::numeric_limits::infinity(); const double totalLength=routeLength(points)/1.0e6; constexpr double PortalExclusionMetres=10.0; double minimumClearance=std::numeric_limits::infinity(); double along=0.0; for(std::size_t index=1;index=PortalExclusionMetres &&totalLength-midpoint>=PortalExclusionMetres) { const std::array a={static_cast(points[index-1][0]), static_cast(points[index-1][1]), static_cast(points[index-1][2])}; const std::array b={static_cast(points[index][0]), static_cast(points[index][1]), static_cast(points[index][2])}; for(const auto& span:routePassageClearanceSpans) minimumClearance=std::min(minimumClearance, segmentDistanceMicrometres(a,b,span.a,span.b)/1.0e6 -span.radius/1.0e6-mouthBoreRadius/1.0e6); } along+=segmentLength; } return minimumClearance; }; std::int64_t handleLength=10000000; std::int64_t handleLow=handleLength; std::int64_t handleHigh=std::max(handleLength,requiredLength/2); auto routePoints=makeRoute(handleHigh); while(routeLength(routePoints)(handleHigh*2,1000000000ll); routePoints=makeRoute(handleHigh); } if(routeLength(makeRoute(handleLow))>=requiredLength) handleHigh=handleLow; else { for(int attempt=0;attempt<24&&handleHigh-handleLow>1;++attempt) { const std::int64_t middle=handleLow+(handleHigh-handleLow)/2; if(routeLength(makeRoute(middle))>=requiredLength)handleHigh=middle; else handleLow=middle; } } handleLength=handleHigh; routePoints=makeRoute(handleLength); double plannedPassageClearanceMetres=routePassageClearance(routePoints); std::int64_t selectedLateralOffsetMicrometres=0; if(plannedPassageClearanceMetres<0.25) { constexpr std::array LateralOffsetsMicrometres={ 20000000,-20000000,40000000,-40000000, 80000000,-80000000,160000000,-160000000}; for(const std::int64_t offset:LateralOffsetsMicrometres) { auto candidate=makeRoute(handleLength,offset); if(routeLength(candidate)plannedPassageClearanceMetres+1.0e-6) { routePoints=std::move(candidate); plannedPassageClearanceMetres=clearance; selectedLateralOffsetMicrometres=offset; } } } const auto routeTangentAlignment=[&](std::size_t from,std::size_t to, const std::array& tangent) { const double tangentLength=std::hypot(static_cast(tangent[0]), static_cast(tangent[1])); const double dx=static_cast(routePoints[to][0]-routePoints[from][0]); const double dy=static_cast(routePoints[to][1]-routePoints[from][1]); const double segmentLength=std::hypot(dx,dy); return tangentLength>0.0&&segmentLength>0.0 ?(dx*tangent[0]+dy*tangent[1])/(tangentLength*segmentLength):0.0; }; const double plannedMouthTangentAlignment=std::min( routeTangentAlignment(1,2,arrivalTangentQ16), routeTangentAlignment(routePoints.size()-2,routePoints.size()-1, departureTangentQ16)); const std::int64_t totalRouteLength=routeLength(routePoints); if(totalRouteLength<=0||totalRouteLength cumulative(routePoints.size(),0); for(std::size_t i=1;i(distance,0,totalRouteLength); const std::int64_t u=static_cast(static_cast(distance) *ProfileOne/totalRouteLength); const std::int64_t u2=u*u/ProfileOne; const std::int64_t u3=u2*u/ProfileOne; return std::array{ 2*u3-3*u2+ProfileOne,u3-2*u2+u,-2*u3+3*u2,u3-u2, 6*u2-6*u,3*u2-4*u+ProfileOne,-6*u2+6*u,3*u2-2*u}; }; const auto profileSlopeAt=[&](std::int64_t distance, std::int64_t startSlope,std::int64_t endSlope) { const auto basis=profileBasis(distance); const std::int64_t endpointDerivative=basis[4]*routeStart[2] +basis[6]*routeEnd[2]; const std::int64_t tangentDerivative=basis[5]*startSlope +basis[7]*endSlope; return roundedDivide(roundedDivide(endpointDerivative,ProfileOne)*65536, totalRouteLength) +roundedDivide(tangentDerivative,ProfileOne); }; const std::int64_t averageFloorSlopeQ16=roundedDivide( (routeEnd[2]-routeStart[2])*65536,totalRouteLength); const std::int64_t authoredArrivalFloorSlopeQ16=arrivalFloorSlopeQ16; const std::int64_t authoredDepartureFloorSlopeQ16=departureFloorSlopeQ16; const std::int64_t maximumProfileSlopeQ16=10813; // 0.165, below the 1:5 walk grade. const auto profileSlopeBlendSafe=[&](std::int64_t blendQ16) { const std::int64_t startSlope=averageFloorSlopeQ16 +(authoredArrivalFloorSlopeQ16-averageFloorSlopeQ16)*blendQ16/65536; const std::int64_t endSlope=averageFloorSlopeQ16 +(authoredDepartureFloorSlopeQ16-averageFloorSlopeQ16)*blendQ16/65536; for(std::int64_t sample=0;sample<=128;++sample) { const std::int64_t distance=totalRouteLength*sample/128; if(std::abs(profileSlopeAt(distance,startSlope,endSlope)) >maximumProfileSlopeQ16)return false; } return true; }; std::int64_t slopeBlendLow=0,slopeBlendHigh=65536; for(int attempt=0;attempt<17;++attempt) { const std::int64_t middle=(slopeBlendLow+slopeBlendHigh+1)/2; if(profileSlopeBlendSafe(middle))slopeBlendLow=middle; else slopeBlendHigh=middle-1; } arrivalFloorSlopeQ16=averageFloorSlopeQ16 +(authoredArrivalFloorSlopeQ16-averageFloorSlopeQ16)*slopeBlendLow/65536; departureFloorSlopeQ16=averageFloorSlopeQ16 +(authoredDepartureFloorSlopeQ16-averageFloorSlopeQ16)*slopeBlendLow/65536; const auto routeFloorAt=[&](std::int64_t distance) { const auto basis=profileBasis(distance); const std::int64_t positionTerms=basis[0]*routeStart[2] +basis[2]*routeEnd[2]; const std::int64_t startTangentDistance=basis[1]*totalRouteLength/ProfileOne; const std::int64_t endTangentDistance=basis[3]*totalRouteLength/ProfileOne; const std::int64_t tangentTerms=(startTangentDistance*arrivalFloorSlopeQ16 +endTangentDistance*departureFloorSlopeQ16)/65536; return roundedDivide(positionTerms,ProfileOne)+tangentTerms; }; const auto routeFloorSlopeQ16At=[&](std::int64_t distance) {return profileSlopeAt(distance,arrivalFloorSlopeQ16,departureFloorSlopeQ16);}; const auto routeFloorGradientAt=[&](std::int64_t distance) { distance=std::clamp(distance,0,totalRouteLength); std::size_t segment=1; while(segmentcumulative[segment])++segment; const auto unitTangent=[&](std::size_t index) { const double tx=static_cast(routePoints[index][0] -routePoints[index-1][0]); const double ty=static_cast(routePoints[index][1] -routePoints[index-1][1]); const double length=std::hypot(tx,ty); return length>0.0?std::array{tx/length,ty/length} :std::array{0.0,0.0}; }; auto tangent=unitTangent(segment); if(segment+11.0e-12){tangent[0]/=length;tangent[1]/=length;} } const std::int64_t profileSlope=routeFloorSlopeQ16At(distance); return std::array{ static_cast(std::llround(tangent[0]*profileSlope)), static_cast(std::llround(tangent[1]*profileSlope))}; }; const auto pointAt=[&](std::int64_t distance) { distance=std::clamp(distance,0,totalRouteLength); std::size_t segment=1; while(segmentcumulative[segment])++segment; const std::int64_t startDistance=cumulative[segment-1]; const std::int64_t segmentLength=std::max(1, cumulative[segment]-startDistance); const std::int64_t local=distance-startDistance; FPoint point{}; for(std::size_t axis=0;axis<2;++axis) { const std::int64_t delta=routePoints[segment][axis] -routePoints[segment-1][axis]; point[axis]=routePoints[segment-1][axis]+delta*local/segmentLength; } point[2]=routeFloorAt(distance); return point; }; std::int64_t spacing=std::min(std::max(settings.roomSpacingMicrometres, 4000000),settings.maximumTunnelLengthMicrometres); spacing=std::max(4000000,spacing); if(spacing>settings.maximumTunnelLengthMicrometres) spacing=settings.maximumTunnelLengthMicrometres; if(spacing<=0) {error="cavern planner has no positive route spacing after applying tunnel length limits";return false;} std::vector nodeDistances; for(std::int64_t distance=spacing;distance(beforeX), static_cast(beforeY)); const double afterLength=std::hypot(static_cast(afterX), static_cast(afterY)); if(beforeLength<=0.0||afterLength<=0.0)continue; const double alignment=(static_cast(beforeX)*afterX +static_cast(beforeY)*afterY)/(beforeLength*afterLength); if(alignment<0.95||std::min(beforeLength,afterLength) profileSpacedNodeDistances; profileSpacedNodeDistances.reserve(nodeDistances.size()); for(const std::int64_t distance:nodeDistances) if(distance>=MinimumProfileSpanMicrometres &&(profileSpacedNodeDistances.empty() ||distance-profileSpacedNodeDistances.back() >=MinimumProfileSpanMicrometres)) profileSpacedNodeDistances.push_back(distance); while(!profileSpacedNodeDistances.empty() &&totalRouteLength-profileSpacedNodeDistances.back() 256) {error="cavern planner would exceed the 256-node bounded backbone";return false;} const auto clampQ16=[](std::int32_t value) {return std::clamp(value,0,65536);}; const std::int64_t smooth=std::clamp( std::max(1500000,settings.smoothWidthMicrometres), 50000,10000000); const std::int64_t irregularity=std::clamp( settings.irregularityAmplitudeMicrometres,0,500000); const std::int64_t routeBoreRadius=std::clamp( std::max(MinimumBoreRadius,settings.minimumTunnelRadiusMicrometres), MinimumBoreRadius,MaximumBoreRadius); const std::int64_t maximumRouteBoreRadius=std::clamp( std::max(routeBoreRadius,settings.maximumTunnelRadiusMicrometres), routeBoreRadius,MaximumBoreRadius); const auto selectedBoreRadius=[&](const std::string& stableId) { const std::uint64_t span=static_cast( maximumRouteBoreRadius-routeBoreRadius); return routeBoreRadius+(span==0?0:static_cast( LayoutStampSeed(seed,strateIndex,stableId)%(span+1))); }; const auto fractional=[](std::int64_t value,std::int64_t q16) {return value*std::clamp(q16,0,65536)/65536;}; struct FRoomNode { std::string id; FPoint point{}; std::string floorProfileStableId; std::int64_t floorProfileDistanceMicrometres=-1; std::array floorGradientQ16{}; std::int64_t radiusMicrometres=0; bool destination=false; }; std::vector nodes;nodes.reserve(nodeDistances.size()+1); const std::int64_t floorCutMinimum=clampQ16(settings.roomFloorCutMinimumQ16); const std::int64_t floorCutMaximum=std::max(floorCutMinimum, clampQ16(settings.roomFloorCutMaximumQ16)); const std::int64_t floorRange=floorCutMaximum-floorCutMinimum; const std::int64_t heightRatio=std::clamp( settings.roomHeightRatioQ16,6554,65536); const std::int64_t baseRoomSpan=settings.maximumRoomRadiusMicrometres -settings.minimumRoomRadiusMicrometres; const std::int64_t mainRouteMidpoint=totalRouteLength/2; for(std::size_t nodeIndex=0;nodeIndex(hash%static_cast(baseRoomSpan+1)); nodes.push_back(std::move(node)); } if(terminalShaftRoute) { FRoomNode terminal;terminal.id=routePrefix+"basal-landing"; terminal.point=routeEnd;terminal.floorProfileStableId=backboneFloorProfileId; terminal.floorProfileDistanceMicrometres=totalRouteLength; terminal.floorGradientQ16=routeFloorGradientAt(totalRouteLength); terminal.radiusMicrometres=settings.destinationRadiusMicrometres; terminal.destination=true;nodes.push_back(std::move(terminal)); } const auto makeCompound=[&](const FRoomNode& node,bool destination, std::vector& outRooms) { const std::uint64_t hash=LayoutStampSeed(seed,strateIndex,node.id+"/compound"); const std::int64_t variety=clampQ16(settings.roomShapeVarietyQ16); const std::size_t additionalPrimitiveCount=static_cast( variety*2/65536); const std::size_t primitiveCount=2+static_cast( hash%(additionalPrimitiveCount+1)); std::uint64_t state=hash; const std::int64_t radius=node.radiusMicrometres; const std::int64_t floorCutBase=clampQ16(settings.roomFloorCutMinimumQ16); const std::int64_t componentCount=static_cast(primitiveCount); for(std::int64_t component=0;component(componentHash%22001); const std::int64_t componentRadius=std::max(500000, fractional(radius,componentScaleQ16)); const std::int64_t axisScaleQ16=component==0?65536 :47000+static_cast((componentHash>>8)%19001); const bool swapAxes=((componentHash>>24)&1u)!=0; const std::int64_t halfX=swapAxes?fractional(componentRadius,axisScaleQ16) :componentRadius; const std::int64_t halfY=swapAxes?componentRadius :fractional(componentRadius,axisScaleQ16); const std::int64_t halfZ=std::max(500000, fractional(componentRadius,heightRatio)); const std::int64_t floorCut=floorCutBase +(floorRange==0?0:static_cast(componentHash%( static_cast(floorRange+1)))); const std::int64_t offsetMagnitude=component==0?0 :fractional(radius,12000+static_cast(componentHash%11001)); const std::int64_t routeLengthXY=std::max(1, static_cast(integerSqrt(static_cast( pathAxisX*pathAxisX+pathAxisY*pathAxisY)))); const std::int64_t centerOffset=(component%2==0?1:-1)*offsetMagnitude; ResolvedCavernRoomV1 room;room.stableId=stableId; room.strateIndex=strateIndex; room.floorProfileStableId=node.floorProfileStableId; room.floorProfileDistanceMicrometres=node.floorProfileDistanceMicrometres; room.floorGradientQ16=node.floorGradientQ16; room.halfExtentsMicrometres={halfX,halfY,halfZ}; room.centerMicrometres={node.point[0]+(-pathAxisY)*centerOffset/routeLengthXY, node.point[1]+pathAxisX*centerOffset/routeLengthXY,0}; // Keep the local floor plane on the backbone profile at the route point, // then place each offset primitive from that plane at its own XY centre. room.floorMicrometres=node.point[2] +(static_cast(room.floorGradientQ16[0]) *(room.centerMicrometres[0]-node.point[0]) +static_cast(room.floorGradientQ16[1]) *(room.centerMicrometres[1]-node.point[1]))/65536; room.centerMicrometres[2]=room.floorMicrometres+fractional(halfZ,floorCut); if(component>0) { const std::int64_t headroom=std::max(0, halfZ-fractional(halfZ,floorCut)); const std::int64_t jitterRange=std::min(headroom,halfZ/5); const std::int64_t ceilingJitter=jitterRange>0 ?static_cast((componentHash>>32) %static_cast(jitterRange+1)):0; room.centerMicrometres[2]+=ceilingJitter; } room.smoothWidthMicrometres=smooth; room.irregularityAmplitudeMicrometres=irregularity; room.irregularityWavelengthMicrometres=settings.irregularityWavelengthMicrometres; outRooms.push_back(std::move(room)); } (void)destination; }; for(const auto& node:nodes)makeCompound(node,node.destination,rooms); const auto componentRoomId=[](const FRoomNode& node) {return node.id+"/primitive-0";}; const auto makeEdge=[&](std::string id,std::string startAnchor, const FPoint& start,const FRoomNode& endNode,std::int64_t boreRadius, std::int64_t profileStartDistance,std::int64_t profileEndDistance) { ResolvedCavernEdgeV1 edge;edge.stableId=std::move(id); edge.startAnchorStableId=std::move(startAnchor); edge.endRoomStableId=componentRoomId(endNode);edge.strateIndex=strateIndex; edge.boreRadiusMicrometres=boreRadius; edge.boreHeightMicrometres=std::max(PlannedBoreHeight,MinimumBoreHeight); edge.floorSupportDepthMicrometres=settings.floorSupportDepthMicrometres; if(profileStartDistance>=0&&profileEndDistance>=0) { edge.floorProfileStableId=backboneFloorProfileId; edge.floorProfileStartDistanceMicrometres=profileStartDistance; edge.floorProfileEndDistanceMicrometres=profileEndDistance; } edge.smoothWidthMicrometres=smooth; edge.irregularityAmplitudeMicrometres=irregularity; edge.irregularityWavelengthMicrometres=settings.irregularityWavelengthMicrometres; const FPoint end={endNode.point[0],endNode.point[1],endNode.point[2]}; for(std::size_t axis=0;axis<3;++axis) { const std::int64_t delta=end[axis]-start[axis]; edge.controlPointsMicrometres[0][axis]=start[axis]; edge.controlPointsMicrometres[1][axis]=start[axis]+delta/3; edge.controlPointsMicrometres[2][axis]=start[axis]+(delta*2)/3; edge.controlPointsMicrometres[3][axis]=end[axis]; } if(profileStartDistance>=0&&profileEndDistance>=0) { const std::int64_t floorStart=routeFloorAt(profileStartDistance); const std::int64_t floorEnd=routeFloorAt(profileEndDistance); const std::int64_t profileSpan=profileEndDistance-profileStartDistance; const std::int64_t startSlope=routeFloorSlopeQ16At(profileStartDistance); const std::int64_t endSlope=routeFloorSlopeQ16At(profileEndDistance); edge.controlPointsMicrometres[0][2]=floorStart; edge.controlPointsMicrometres[1][2]=floorStart +roundedDivide(startSlope*profileSpan,3*65536); edge.controlPointsMicrometres[2][2]=floorEnd -roundedDivide(endSlope*profileSpan,3*65536); edge.controlPointsMicrometres[3][2]=floorEnd; } return edge; }; struct FPlannedShaftLanding { FRoomNode node; FPoint point{}; }; std::vector shaftLandings; std::size_t plannedShaftCount=0; if(terminalShaftRoute&&settings.roomDensityQ16>0) { const std::int64_t ledgeSpacing=std::max(0, settings.ledgeSpacingMicrometres); const std::int64_t ledgeDepth=std::clamp( settings.ledgeDepthMicrometres,0,5000000); bool emittedShaft=false; const std::int64_t routeLengthXY=std::max(1, static_cast(integerSqrt(static_cast( pathAxisX*pathAxisX+pathAxisY*pathAxisY)))); const std::int64_t sideX=-pathAxisY*65536/routeLengthXY; const std::int64_t sideY=pathAxisX*65536/routeLengthXY; const auto selectedRadius=[&](const std::string& stableId) { const std::int64_t span=settings.maximumRoomRadiusMicrometres -settings.minimumRoomRadiusMicrometres; return settings.minimumRoomRadiusMicrometres+(span==0?0: static_cast(LayoutStampSeed(seed,strateIndex,stableId) %static_cast(span+1))); }; for(std::size_t nodeIndex=1;nodeIndex+1(hash&0xffffu) >=settings.roomDensityQ16)continue; const std::int64_t radius=selectedRadius(parent.id+"/shaft-radius"); const std::int64_t top=parent.point[2] +(ledgeSpacing>0?ledgeSpacing:1000000); const std::int64_t minimumTop=stratum->content.bottomMicrometres+4000000; if(top>stratum->content.topMicrometres-1000000 ||topcontent.bottomMicrometres+1000000,top-200000000); if(ledgeSpacing>0&&top-bottom=0?1:-1)*(positiveSide?1:-1); const std::int64_t sideYSign=(sideY>=0?1:-1)*(positiveSide?1:-1); const bool useX=std::abs(sideX)>=std::abs(sideY); const std::uint8_t outwardSide=useX ?static_cast(sideXSign>0?0:2) :static_cast(sideYSign>0?1:3); const std::array,4> cardinal={{{1,0},{0,1}, {-1,0},{0,-1}}}; const auto outward=cardinal[outwardSide]; const std::int64_t offset=std::max( parent.radiusMicrometres+radius+3000000, settings.roomSpacingMicrometres/2); FPlannedShaftLanding landing;landing.node.id=routePrefix+"shaft-" +std::to_string(shaftLandings.size()); landing.node.point={parent.point[0]+outward[0]*offset, parent.point[1]+outward[1]*offset,parent.point[2]}; landing.node.radiusMicrometres=radius; const std::int64_t landingZ=top-(ledgeSpacing>0?ledgeSpacing:1000000); const std::array toParent={-outward[0],-outward[1]}; const std::uint8_t ledgeSide=static_cast( (outwardSide+2)%4); ResolvedCavernRoomV1 shaft;shaft.stableId=landing.node.id+"/primitive-0"; shaft.strateIndex=strateIndex;shaft.shapeKind=1; shaft.centerMicrometres={landing.node.point[0],landing.node.point[1], (top+bottom)/2}; shaft.halfExtentsMicrometres={radius,radius,(top-bottom)/2}; shaft.floorMicrometres=top;shaft.smoothWidthMicrometres=smooth; shaft.ledgeSpacingMicrometres=ledgeSpacing; shaft.ledgeDepthMicrometres=ledgeDepth;shaft.ledgeSide=ledgeSide; shaft.irregularityAmplitudeMicrometres=irregularity; shaft.irregularityWavelengthMicrometres= settings.irregularityWavelengthMicrometres; shaft.irregularitySeed=LayoutStampSeed(seed,strateIndex,shaft.stableId); rooms.push_back(std::move(shaft)); landing.point={landing.node.point[0] +toParent[0]*(radius-ledgeDepth/2), landing.node.point[1]+toParent[1]*(radius-ledgeDepth/2),landingZ}; landing.node.point=landing.point; const std::string accessId=routePrefix+"shaft-access-" +std::to_string(shaftLandings.size()); auto access=makeEdge(accessId,componentRoomId(parent),parent.point, landing.node,routeBoreRadius,-1,-1); // The connector's own floor SDF retains its tread. It must not enter // the descending backbone's finite floor-cap arbitration, where its // nearby but independent floor could suppress backbone support. access.floorSupportDepthMicrometres=0; const std::int64_t run=distance2d(parent.point,landing.point); const std::int64_t rise=std::abs(parent.point[2]-landing.point[2]); if(run<=0||rise*MaximumWalkGradeDenominator>run) { rooms.pop_back(); continue; } edges.push_back(std::move(access)); shaftLandings.push_back(std::move(landing)); emittedShaft=true; } plannedShaftCount=shaftLandings.size(); for(std::size_t index=1;index(hash&0xffffu)>=settings.tunnelDensityQ16) continue; const auto& from=shaftLandings[index-1]; const auto& to=shaftLandings[index]; const std::int64_t run=distance2d(from.point,to.point); const std::int64_t rise=std::abs(from.point[2]-to.point[2]); if(run<=0||run>settings.maximumTunnelLengthMicrometres ||rise*MaximumWalkGradeDenominator>run)continue; auto link=makeEdge(routePrefix+"shaft-cross-"+std::to_string(index-1), componentRoomId(from.node),from.point,to.node,routeBoreRadius,-1,-1); link.floorSupportDepthMicrometres=0; edges.push_back(std::move(link)); } } const auto arrivalPoint=routeStart; FPoint previousPoint=arrivalPoint; std::int64_t previousProfileDistance=0; std::string previousAnchor=arrivalPassage->stableId+"/lower-landing-chamber"; for(std::size_t nodeIndex=0;nodeIndexstableId+"/upper-landing-chamber", routeEnd,nodes.back(), std::min(selectedBoreRadius(departureEdgeId),LandingConnectorBoreRadius), totalRouteLength,nodes.back().floorProfileDistanceMicrometres); edges.push_back(std::move(edge)); } const std::int64_t roomDensity=clampQ16(settings.roomDensityQ16); const std::int64_t tunnelDensity=clampQ16(settings.tunnelDensityQ16); const std::int64_t branchThreshold=roomDensity*tunnelDensity/65536; const std::size_t branchLimit=settings.originRoomMaximumConnections<=0 ?32u:static_cast(std::clamp(settings.originRoomMaximumConnections,0,32)); std::size_t branchCount=0; for(std::size_t nodeIndex=1;!terminalShaftRoute&&nodeIndex+1(branchHash&0xffffu)>=branchThreshold)continue; const std::int64_t branchLength=std::max( routeBoreRadius*3,std::min(spacing/2,settings.maximumTunnelLengthMicrometres/2)); const std::int64_t direction=(branchHash&0x10000u)?1:-1; FRoomNode branch;branch.id=parent.id+"/side-"+std::to_string(branchCount); branch.radiusMicrometres=settings.minimumRoomRadiusMicrometres +static_cast((branchHash>>17) %static_cast(baseRoomSpan+1)); const std::int64_t pathAxisLength=std::max(1, static_cast(integerSqrt(static_cast( pathAxisX*pathAxisX+pathAxisY*pathAxisY)))); branch.point={parent.point[0]+(-pathAxisY)*branchLength/pathAxisLength*direction, parent.point[1]+pathAxisX*branchLength/pathAxisLength*direction, parent.point[2]}; // A side branch leaves the backbone laterally, so it has its own local flat // floor and must not reference the parent's one-dimensional h(s) profile. makeCompound(branch,false,rooms); const std::string branchEdgeId=routePrefix+"side-edge-"+std::to_string(branchCount); ResolvedCavernEdgeV1 branchEdge; if(settings.tunnelsFlowTowardOrigin) branchEdge=makeEdge(branchEdgeId,componentRoomId(branch), branch.point,parent,selectedBoreRadius(branchEdgeId),-1,-1); else branchEdge=makeEdge(branchEdgeId,componentRoomId(parent), parent.point,branch,selectedBoreRadius(branchEdgeId),-1,-1); // Side branches retain their cave floor SDF, but they do not cap the // descending backbone when their tubes overlap it near a junction. branchEdge.floorSupportDepthMicrometres=0; edges.push_back(std::move(branchEdge)); ++branchCount; } if(nodes.empty()||rooms.empty()||edges.empty()) {error="cavern planner emitted an empty mouth-to-terminal graph";return false;} double maximumGradeDegrees=0.0; for(const auto& edge:edges) { const auto& a=edge.controlPointsMicrometres.front(); const auto& b=edge.controlPointsMicrometres.back(); const std::int64_t run=static_cast(integerSqrt( static_cast((b[0]-a[0])*(b[0]-a[0]) +(b[1]-a[1])*(b[1]-a[1])))); const std::int64_t rise=std::abs(b[2]-a[2]); if(run<=0&&rise>0) {error="cavern planner produced a vertical route edge with no walking run";return false;} if(run>0&&rise*MaximumWalkGradeDenominator>run) { std::printf("[CavernPlannerGradeReject] edge=%s run_m=%.3f rise_m=%.3f grade_deg=%.3f start=(%.3f,%.3f,%.3f) end=(%.3f,%.3f,%.3f)\n", edge.stableId.c_str(),run/1.0e6,rise/1.0e6, std::atan2(static_cast(rise),static_cast(run)) *180.0/3.14159265358979323846, a[0]/1.0e6,a[1]/1.0e6,a[2]/1.0e6, b[0]/1.0e6,b[1]/1.0e6,b[2]/1.0e6); error="cavern planner emitted an edge beyond its 1:5 route grade budget";return false; } if(run>0)maximumGradeDegrees=std::max(maximumGradeDegrees, std::atan2(static_cast(rise),static_cast(run)) *180.0/3.14159265358979323846); } const double plannerMilliseconds=std::chrono::duration( std::chrono::steady_clock::now()-plannerStarted).count(); std::printf("[CavernPlannerV1] strate=%d seed=%d route=%s->%s route_nodes=%zu room_primitives=%zu shafts=%zu edges=%zu branches=%zu route_horizontal_m=%.3f floor_delta_m=%.3f max_grade_deg=%.3f mouth_tangent_min_dot=%.3f grade_limit_deg=11.310 clearance_radius_m=%.3f clearance_height_m=%.3f destination_radius_m=%.3f plan_ms=%.3f status=PASS\n", strateIndex,seed,arrivalPassage->stableId.c_str(), departurePassage?departurePassage->stableId.c_str():"basal-terminal", nodes.size(),rooms.size(),plannedShaftCount,edges.size(),branchCount,totalRouteLength/1.0e6, std::abs(dz)/1.0e6,maximumGradeDegrees,plannedMouthTangentAlignment, routeBoreRadius/1.0e6,PlannedBoreHeight/1.0e6, settings.destinationRadiusMicrometres/1.0e6,plannerMilliseconds); return true; } bool ResolveResolvedLayoutOreVeinsV1(ResolvedLayoutSnapshotV3& layout,std::int32_t seed, std::span requests,std::string& error) { error.clear(); ResolvedLayoutSnapshotV3 candidate=layout; candidate.oreVeins.clear();candidate.oreVeinVocabularyVersion=0; if(requests.empty()) { if(!candidate.finalize(error))return false; layout=std::move(candidate);return true; } candidate.oreVeinVocabularyVersion=ResolvedLayoutOreVeinVocabularyVersionV1; std::vector ordered(requests.begin(),requests.end()); std::sort(ordered.begin(),ordered.end(),[](const auto& a,const auto& b) {return a.stableId requestIds; candidate.oreVeins.reserve(ordered.size()); for(const auto& request:ordered) { if(request.stableId.empty() ||std::find(requestIds.begin(),requestIds.end(),request.stableId)!=requestIds.end()) {error="ore-vein requests require non-empty unique stable IDs";return false;} requestIds.push_back(request.stableId); if(request.resourceMaterialId==0||request.placementRadiusMicrometres<0 ||request.placementRadiusMicrometres>10000000 ||request.radiusMicrometres<250000||request.radiusMicrometres>20000000 ||request.irregularityAmplitudeMicrometres<0 ||request.irregularityAmplitudeMicrometres>10000000 ||request.irregularityWavelengthMicrometres<0 ||request.irregularityWavelengthMicrometres>100000000 ||(request.irregularityAmplitudeMicrometres>0 &&request.irregularityWavelengthMicrometres<=0) ||request.blendWidthMicrometres<50000 ||request.blendWidthMicrometres>2000000) {error="ore-vein request "+request.stableId+" has invalid or unbounded parameters";return false;} const auto strate=std::find_if(candidate.strates.begin(),candidate.strates.end(), [&](const auto& value){return value.strateIndex==request.strateIndex;}); if(strate==candidate.strates.end()) {error="ore-vein request "+request.stableId+" names an unknown strate";return false;} const std::uint64_t baseSeed=LayoutStampSeed(seed,request.strateIndex, std::string("ore-vein/")+request.stableId)&0x001fffffffffffffull; std::uint64_t state=baseSeed; const auto randomOffset=[&](std::uint64_t bits) { const std::int64_t radius=request.placementRadiusMicrometres; if(radius==0)return std::int64_t{0}; const std::uint64_t span=static_cast(radius)*2+1; return static_cast(bits%span)-radius; }; bool placed=false; std::string lastUnsafeReason="all candidates were outside the bounded content volume"; const std::uint32_t attempts=request.placementRadiusMicrometres==0?1u:512u; for(std::uint32_t attempt=0;attempt offset{}; if(request.placementRadiusMicrometres>0) { offset={randomOffset(LayoutStampNext(state)), randomOffset(LayoutStampNext(state)), randomOffset(LayoutStampNext(state))/3}; const long double radius=request.placementRadiusMicrometres; const long double distanceSquared=static_cast(offset[0])*offset[0] +static_cast(offset[1])*offset[1] +static_cast(offset[2])*offset[2]; if(distanceSquared>radius*radius)continue; } ResolvedLayoutOreVeinV1 vein;vein.stableId=request.stableId; vein.strateIndex=request.strateIndex; vein.resourceMaterialId=request.resourceMaterialId; vein.radiusMicrometres=request.radiusMicrometres; vein.irregularityAmplitudeMicrometres=request.irregularityAmplitudeMicrometres; vein.irregularityWavelengthMicrometres=request.irregularityWavelengthMicrometres; vein.blendWidthMicrometres=request.blendWidthMicrometres; vein.irregularitySeed=baseSeed; for(std::size_t endpoint=0;endpoint<2;++endpoint) for(std::size_t axis=0;axis<3;++axis) vein.endpointsMicrometres[endpoint][axis] =request.anchorEndpointsMicrometres[endpoint][axis]+offset[axis]; const std::int64_t pad=vein.radiusMicrometres +vein.irregularityAmplitudeMicrometres+vein.blendWidthMicrometres; for(std::size_t axis=0;axis<3;++axis) { vein.boundsMinimumMicrometres[axis]=std::min( vein.endpointsMicrometres[0][axis],vein.endpointsMicrometres[1][axis])-pad; vein.boundsMaximumMicrometres[axis]=std::max( vein.endpointsMicrometres[0][axis],vein.endpointsMicrometres[1][axis])+pad; } if(!LayoutOreVeinSafe(candidate,vein,&lastUnsafeReason))continue; candidate.oreVeins.push_back(std::move(vein));placed=true;break; } if(!placed) {error="ore-vein request "+request.stableId +" has no deterministic passage-safe content placement: "+lastUnsafeReason;return false;} } if(!candidate.finalize(error))return false; std::vector issues;std::string strictError; if(!candidate.validate(ResolvedLayoutValidationModeV3::strict,issues,strictError)) {error="strict ore-vein placement validation failed: "+strictError;return false;} layout=std::move(candidate);return true; } ResolvedLayoutOreVeinPlacementAuditV1 AuditResolvedLayoutOreVeinPlacementV1( const ResolvedLayoutSnapshotV3& layout) { ResolvedLayoutOreVeinPlacementAuditV1 result; if(layout.passages.empty()||layout.passages.front().controlPoints.empty())return result; const auto& passage=layout.passages.front(); const auto& control=passage.controlPoints.front().positionSubvoxels; ResolvedLayoutOreVeinV1 forced;forced.stableId="negative-control/forced-clear-passage"; forced.strateIndex=passage.upperStrateIndex;forced.resourceMaterialId=1; forced.radiusMicrometres=500000;forced.blendWidthMicrometres=500000; forced.irregularitySeed=1; for(std::size_t axis=0;axis<3;++axis) { const std::int64_t point=static_cast(std::llround( static_cast(control[axis])*layout.coreVoxelSizeMicrometres /WorldAssemblySubvoxelsPerVoxel)); forced.endpointsMicrometres[0][axis]=point; forced.endpointsMicrometres[1][axis]=point; forced.boundsMinimumMicrometres[axis]=point-1000000; forced.boundsMaximumMicrometres[axis]=point+1000000; } ResolvedLayoutSnapshotV3 candidate=layout; candidate.oreVeinVocabularyVersion=ResolvedLayoutOreVeinVocabularyVersionV1; candidate.oreVeins.push_back(forced); std::string finalizeError; ++result.negativeControlsRun; if(candidate.finalize(finalizeError)) { std::vector issues;std::string validationError; if(!candidate.validate(ResolvedLayoutValidationModeV3::strict,issues,validationError) &&std::any_of(issues.begin(),issues.end(),[](const auto& issue) {return issue.code=="ore_vein_placement" &&issue.detail.find("clear passage")!=std::string::npos;})) ++result.negativeControlsRejected; } result.passed=result.negativeControlsRun>0 &&result.negativeControlsRejected==result.negativeControlsRun; return result; } ResolvedLayoutStampPlacementAuditV1 AuditResolvedLayoutStampPlacementV1( const ResolvedLayoutSnapshotV3& layout) { ResolvedLayoutStampPlacementAuditV1 result; const auto makeControl=[&](const std::array& position, std::int32_t strateIndex,std::array minimum, std::array maximum,std::string id, std::string_view expectedReason) { ResolvedLayoutSnapshotV3 candidate=layout; ResolvedLayoutStampV1 stamp;stamp.stableId=std::move(id); stamp.strateIndex=strateIndex;stamp.kind=ResolvedSdfStampKindV1::carveEllipsoid; stamp.positionMicrometres=position;stamp.boundsMinimumMicrometres=minimum; stamp.boundsMaximumMicrometres=maximum; stamp.halfExtentsMicrometres={250000,250000,250000}; stamp.irregularitySeed=1; candidate.stampVocabularyVersion=ResolvedLayoutStampVocabularyVersionV1; candidate.stamps={stamp}; std::vector issues;std::string error; ++result.negativeControlsRun; if(!candidate.validate(ResolvedLayoutValidationModeV3::strict,issues,error) &&std::any_of(issues.begin(),issues.end(),[&](const auto& issue) {return issue.code=="stamp_placement" &&issue.detail.find(expectedReason)!=std::string::npos;})) ++result.negativeControlsRejected; }; const auto passage=std::find_if(layout.passages.begin(),layout.passages.end(), [](const auto& value){return value.controlPoints.size()>=2;}); if(passage!=layout.passages.end()) { const double scale=static_cast(layout.coreVoxelSizeMicrometres) /WorldAssemblySubvoxelsPerVoxel; const std::size_t routeControlIndex=passage->lowerMouthControlIndex>4 ?passage->lowerMouthControlIndex-4:0; std::array position{}; for(std::size_t axis=0;axis<3;++axis) position[axis]=static_cast(std::llround( passage->controlPoints[routeControlIndex].positionSubvoxels[axis]*scale)); makeControl(position,passage->lowerStrateIndex, {position[0]-250000,position[1]-250000,position[2]-250000}, {position[0]+250000,position[1]+250000,position[2]+250000}, "negative-control/forced-on-route","resolved passage walk corridor"); } const auto stratum=std::find_if(layout.strates.begin(),layout.strates.end(), [](const auto& value){return value.content.presentInCurrentField;}); if(stratum!=layout.strates.end()) { const std::array position={0,0,stratum->content.topMicrometres}; makeControl(position,stratum->strateIndex, {position[0]-250000,position[1]-250000,position[2]-250000}, {position[0]+250000,position[1]+250000,position[2]+250000}, "negative-control/forced-into-ceiling-band","touch or cross the ceiling/bedrock"); } result.passed=result.negativeControlsRun==2 &&result.negativeControlsRejected==result.negativeControlsRun; return result; } bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t seed, const std::vector& configs,std::string& error) { error.clear();layout.passages.clear(); if(layout.strates.size()<2)return true; std::stable_sort(layout.strates.begin(),layout.strates.end(),[](const auto& a,const auto& b) {return a.interiorTopMicrometres!=b.interiorTopMicrometres ?a.interiorTopMicrometres>b.interiorTopMicrometres:a.order(i); if(layout.coreVoxelSizeMicrometres==0) {error="passage planner requires the resolved Core voxel size";return false;} const std::int64_t qPerMetre=static_cast(std::llround( static_cast(WorldAssemblySubvoxelsPerVoxel)*1000000.0 /layout.coreVoxelSizeMicrometres)); if(qPerMetre<=0){error="passage planner could not resolve fixed-point metres";return false;} const std::int64_t playerCenterAboveFloorQ=static_cast(std::llround(1.13*qPerMetre)); const std::int64_t surfacePlayerCenterAboveFloorQ=static_cast( std::llround(0.98*qPerMetre)); const std::array,16> directions{{ {{65536,0}},{{60547,25080}},{{46341,46341}},{{25080,60547}}, {{0,65536}},{{-25080,60547}},{{-46341,46341}},{{-60547,25080}}, {{-65536,0}},{{-60547,-25080}},{{-46341,-46341}},{{-25080,-60547}}, {{0,-65536}},{{25080,-60547}},{{46341,-46341}},{{60547,-25080}}}}; const auto configFor=[&](std::int32_t strate)->const ResolvedLayoutPassageConfigV1* { const auto it=std::find_if(configs.begin(),configs.end(),[strate](const auto& c) {return c.upperStrateIndex==strate;}); return it==configs.end()?nullptr:&*it; }; const auto routeAnchor=[](const ResolvedLayoutStratumV2& s,bool departure, std::array& out) { if(departure) for(const auto& a:s.anchors) if(a.kind=="passage_anchor"&&a.values.size()>=10) {out={a.values[7],a.values[8],a.values[9]};return true;} if(!departure) for(const auto& a:s.anchors) if(a.kind=="passage_arrival"&&a.values.size()>=3) {out={a.values[0],a.values[1],a.values[2]};return true;} const ResolvedLayoutAnchorV2* selected=nullptr; for(const auto& a:s.anchors) if(a.kind=="route_point"&&a.values.size()>=3) {selected=&a;if(!departure)break;} if(!selected)return false; out={selected->values[0],selected->values[1],selected->values[2]};return true; }; const auto routeTangent=[](const ResolvedLayoutStratumV2& s,bool departure, std::array& out) { const ResolvedLayoutAnchorV2* first=nullptr,*second=nullptr,*last=nullptr,*previous=nullptr; for(const auto& a:s.anchors) if(a.kind=="route_point"&&a.values.size()>=3) {if(!first)first=&a;else if(!second)second=&a;previous=last;last=&a;} if(!first||!second||!last||!previous)return false; const auto* from=departure?previous:first; const auto* to=departure?last:second; out={to->values[0]-from->values[0],to->values[1]-from->values[1]}; return out[0]!=0||out[1]!=0; }; const auto radialWithin=[](const std::array& p,std::int64_t radius) {return radius>0&&p[0]*p[0]+p[1]*p[1]<=radius*radius;}; const auto reachValue=[](std::uint64_t hash,std::int64_t lo,std::int64_t hi) { if(hilo?static_cast(hash%static_cast(hi-lo+1)):0); }; const auto contentZQ=[&](std::int64_t micrometres) {return static_cast(std::llround(static_cast(micrometres) *WorldAssemblySubvoxelsPerVoxel/layout.coreVoxelSizeMicrometres));}; const auto distanceQ=[](std::int64_t dx,std::int64_t dy) { const auto square=static_cast(dx*dx+dy*dy); const auto floor=PassageIsqrt(square);return floor*floor==square?floor:floor+1; }; for(std::size_t interface=0;interface+1distanceMinimumSubvoxels, cfg->distanceMaximumSubvoxels):24*qPerMetre; const std::int64_t inner=cfg?std::min(cfg->distanceMinimumSubvoxels, cfg->distanceMaximumSubvoxels):8*qPerMetre; const std::int64_t placementRadius=std::max(1,outer); // Leave room for the capsule and bounded wall relief at the floor/wall seam. const bool upperIsSurfaceWorld=upper.generatorKind=="SurfaceWorld"; const std::int64_t minimumMouthRadius=static_cast(std::llround( (upperIsSurfaceWorld?2.0:1.5)*qPerMetre)); const std::int64_t mouthRadius=std::max(minimumMouthRadius, cfg?cfg->mouthRadiusSubvoxels:0); const std::int64_t midRadius=std::max( static_cast(std::llround((upperIsSurfaceWorld?3.5:1.5)*qPerMetre)), cfg?cfg->midRadiusSubvoxels:mouthRadius); const std::int64_t requestedReachMinimum=cfg ?std::max(0,cfg->reachMinimumSubvoxels):8*qPerMetre; const std::int64_t requestedReachMaximum=cfg ?std::max(requestedReachMinimum,cfg->reachMaximumSubvoxels):24*qPerMetre; // Keep the landing several metres inside the stratum's authored content. // Near a band edge, the sampled native terrain can exist at the centerline // while the full capsule footprint has no supporting ground. const std::int64_t mouthClearance=std::max(mouthRadius,midRadius)+2*qPerMetre; const auto usableReach=[&](const ResolvedLayoutStratumV2& stratum) { const std::int64_t depth=contentZQ(stratum.content.topMicrometres) -contentZQ(stratum.content.bottomMicrometres); return std::max(0,depth-mouthClearance); }; const std::int64_t upperReachLimit=usableReach(upper); const std::int64_t lowerReachLimit=usableReach(lower); const std::uint32_t style=cfg?std::min(cfg->style,3u):1u; const std::uint32_t segments=cfg?std::clamp(cfg->segments,16u,48u):20u; const std::int64_t wander=cfg?std::max(0,cfg->wanderSubvoxels):3*qPerMetre; const std::int64_t spiralRadius=cfg?std::max(0,cfg->spiralRadiusSubvoxels):6*qPerMetre; const std::int64_t spiralTurnsQ16=cfg?std::max(16384,cfg->spiralTurnsQ16):2ll<<16; const std::int64_t verticalWobble=cfg?std::max(0,cfg->verticalWobbleSubvoxels):0; const std::uint32_t connectionCount=cfg?static_cast(std::clamp(cfg->connections,1,12)):1; for(std::uint32_t connection=0;connection upperMouth{},lowerMouth{}; const bool hasUpperRoute=connection==0&&routeAnchor(upper,true,upperMouth); const bool hasLowerRoute=connection==0&&routeAnchor(lower,false,lowerMouth); const bool surfaceGroundDeparture=hasUpperRoute&&upperIsSurfaceWorld; const std::int64_t upperPlayerCenterAboveFloorQ=surfaceGroundDeparture ?surfacePlayerCenterAboveFloorQ:playerCenterAboveFloorQ; const ResolvedLayoutAnchorV2* passageAnchor=nullptr; if(connection==0) for(const auto& anchor:upper.anchors) if(anchor.kind=="passage_anchor"&&anchor.values.size()>=7) {passageAnchor=&anchor;break;} const bool anchoredIslandPassage=passageAnchor!=nullptr &&upper.generatorKind=="FloatingIslands"; const bool anchoredSpiral=style==2&&anchoredIslandPassage; if(!hasUpperRoute||!hasLowerRoute) { const auto xy=PassageRandomXY(PassageSeed(seed,upper.strateIndex,lower.strateIndex, connection,0x504c4143u),inner,placementRadius); if(!hasUpperRoute) { upperMouth[0]=xy[0];upperMouth[1]=xy[1]; const auto requestedReach=reachValue(PassageSeed(seed,upper.strateIndex, lower.strateIndex,connection,0x55505248u), requestedReachMinimum,requestedReachMaximum); const auto reach=std::min(requestedReach,upperReachLimit); upperMouth[2]=contentZQ(upper.content.bottomMicrometres)+reach; if(requestedReach>upperReachLimit) std::printf("[ResolvedPassageReachClamp] id=%s side=upper sampled_m=%.3f usable_max_m=%.3f selected_m=%.3f status=CLAMPED\n", passage.stableId.c_str(),requestedReach/static_cast(qPerMetre), upperReachLimit/static_cast(qPerMetre), reach/static_cast(qPerMetre)); } if(!hasLowerRoute) { lowerMouth[0]=xy[0];lowerMouth[1]=xy[1]; const auto requestedReach=reachValue(PassageSeed(seed,upper.strateIndex, lower.strateIndex,connection,0x4c4f5752u), requestedReachMinimum,requestedReachMaximum); const auto reach=std::min(requestedReach,lowerReachLimit); lowerMouth[2]=contentZQ(lower.content.topMicrometres)-reach; if(requestedReach>lowerReachLimit) std::printf("[ResolvedPassageReachClamp] id=%s side=lower sampled_m=%.3f usable_max_m=%.3f selected_m=%.3f status=CLAMPED\n", passage.stableId.c_str(),requestedReach/static_cast(qPerMetre), lowerReachLimit/static_cast(qPerMetre), reach/static_cast(qPerMetre)); } } if(!radialWithin(upperMouth,placementRadius)||!radialWithin(lowerMouth,placementRadius)) {error=passage.stableId+" route mouth is outside the resolved PassageConfig.DistanceMax radius";return false;} passage.upperMouthSubvoxels=upperMouth;passage.lowerMouthSubvoxels=lowerMouth; std::array upperTangent{},lowerTangent{}; const bool hasUpperTangent=hasUpperRoute &&routeTangent(upper,true,upperTangent); const bool hasPassageArrival=std::any_of(lower.anchors.begin(),lower.anchors.end(), [](const auto& anchor){return anchor.kind=="passage_arrival" &&anchor.values.size()>=3;}); // The native cavern graph starts at passage_arrival, but its regenerated // route points still define the receiving tangent and floor grade. Other // generators keep the prior landing behavior when they carry this marker. const bool hasLowerTangent=hasLowerRoute &&(!hasPassageArrival||IsNativeCaveFieldStratum(lower)) &&routeTangent(lower,false,lowerTangent); const bool profiledSurfaceJoin=hasLowerTangent &&std::any_of(lower.anchors.begin(),lower.anchors.end(), [](const auto& anchor){return anchor.kind=="heightfield_profile" &&anchor.values.size()>=2&&anchor.values[0]>0;}); const std::int64_t outerRingRadius=std::max(1, placementRadius-mouthRadius-qPerMetre/2); const std::uint32_t pathStyle=anchoredSpiral?1u:style; const bool generatedWormEntry=!hasUpperRoute&&!hasUpperTangent &&!anchoredSpiral&&pathStyle==1u; std::int64_t ringRadius=outerRingRadius; if(pathStyle==1u) ringRadius=std::min(outerRingRadius, std::max(mouthRadius+2*qPerMetre,placementRadius*5/8)); else if(pathStyle==2u) ringRadius=std::clamp(spiralRadius, std::min(outerRingRadius,mouthRadius+qPerMetre/2),outerRingRadius); else if(pathStyle==3u) ringRadius=std::clamp(outerRingRadius -std::min(outerRingRadius/3, std::max(0,cfg?cfg->cascadeLedgeSubvoxels:0) *static_cast(cfg?cfg->cascadeSteps:4)), mouthRadius+2*qPerMetre,outerRingRadius); if(generatedWormEntry) ringRadius=outerRingRadius; if(surfaceGroundDeparture) { const double departureRadius=std::hypot(static_cast(upperMouth[0]), static_cast(upperMouth[1])); const std::int64_t clearRingRadius=static_cast(std::ceil( departureRadius+mouthRadius+0.5*qPerMetre)); const std::int64_t maximumRingRadius=std::max(1, placementRadius-qPerMetre); ringRadius=std::min(maximumRingRadius, std::max(ringRadius,clearRingRadius)); } if(anchoredIslandPassage) { const std::int64_t craterRimRouteRadius=VoxelForgeCore::WorldAssemblyEvaluatorVersion>=185 &&style==0&&!anchoredSpiral&&passageAnchor&&passageAnchor->values.size()>3 ?std::max(midRadius, passageAnchor->values[3]+2*qPerMetre) :mouthRadius+qPerMetre/2; ringRadius=std::min(ringRadius,anchoredSpiral ?mouthRadius+qPerMetre/2:craterRimRouteRadius); } std::uint32_t phase=static_cast(PassageSeed(seed, upper.strateIndex,lower.strateIndex,connection,0x50484153u)%16); // Authored tangents and island anchors already determine the phase. // A generated mouth in a native cave uses its radial approach; an // island landing uses the tangent fit below to ease off the crater rim. if(hasUpperRoute||(anchoredIslandPassage&&!anchoredSpiral) ||(!hasUpperRoute&&!anchoredIslandPassage&&!anchoredSpiral &&upper.generatorKind!="FloatingIslands")) { std::int64_t bestAlignment=std::numeric_limits::min(); const std::uint32_t hashedPhase=phase; const std::int64_t phaseX=style==0&&anchoredIslandPassage &&!anchoredSpiral&&passageAnchor ?upperMouth[0]-passageAnchor->values[0] :(hasUpperTangent?upperTangent[0]:upperMouth[0] -(anchoredIslandPassage&&passageAnchor ?passageAnchor->values[0]:0)); const std::int64_t phaseY=style==0&&anchoredIslandPassage &&!anchoredSpiral&&passageAnchor ?upperMouth[1]-passageAnchor->values[1] :(hasUpperTangent?upperTangent[1]:upperMouth[1] -(anchoredIslandPassage&&passageAnchor ?passageAnchor->values[1]:0)); for(std::uint32_t offset=0;offset<16;++offset) { const std::uint32_t candidate=static_cast( (hashedPhase+offset)%16); const std::int64_t alignment=phaseX*directions[candidate][0] +phaseY*directions[candidate][1]; if(style==0&&anchoredIslandPassage&&!anchoredSpiral&&passageAnchor) { const std::int64_t targetProjection=ringRadius*65536; if(alignment<=0)continue; const std::int64_t tangentError=std::abs(alignment-targetProjection); if(tangentError::min()) {bestAlignment=tangentError;phase=candidate;} } else if(alignment>bestAlignment) {bestAlignment=alignment;phase=candidate;} } } bool clockwise=(PassageSeed(seed,upper.strateIndex,lower.strateIndex, connection,0x44495245u)&1u)!=0; if(style==0&&anchoredIslandPassage&&!anchoredSpiral&&passageAnchor) { const auto& ringDirection=directions[phase]; const double radiusX=static_cast(ringRadius)*ringDirection[0]/65536.0; const double radiusY=static_cast(ringRadius)*ringDirection[1]/65536.0; const double anchorX=static_cast(passageAnchor->values[0]); const double anchorY=static_cast(passageAnchor->values[1]); const double connectorX=anchorX+radiusX-upperMouth[0]; const double connectorY=anchorY+radiusY-upperMouth[1]; clockwise=connectorX*(-radiusY)+connectorY*radiusX>=0.0; } else if(anchoredSpiral&&passageAnchor&&hasUpperTangent) { const double radialX=static_cast(upperMouth[0]-passageAnchor->values[0]); const double radialY=static_cast(upperMouth[1]-passageAnchor->values[1]); const double clockwiseTangentX=-radialY,clockwiseTangentY=radialX; clockwise=clockwiseTangentX*upperTangent[0] +clockwiseTangentY*upperTangent[1]>=0.0; } int direction=clockwise?1:-1; constexpr std::int64_t TargetGradeQ16=12500; // leave margin below the 15-degree helix contract. const std::int64_t wormRadialWobble=std::min( std::max(0,wander/3),2*qPerMetre); const std::int64_t wormCenterWobble=std::min( std::max(0,wander/6),qPerMetre); const auto ringPoint=[&](std::uint32_t directionIndex,std::uint32_t loopIndex=0) { const double angle=static_cast(directionIndex)* (3.14159265358979323846/8.0); double radius=static_cast(ringRadius); double centerX=0.0,centerY=0.0; if(pathStyle==1u) { const double loop=static_cast(loopIndex); const double phaseA=loop*0.6180339887498948+0.37; const double phaseB=loop*0.4142135623730950+1.11; const double jitterX=static_cast(wormCenterWobble)*std::sin(phaseB); const double jitterY=static_cast(wormCenterWobble)*std::cos(phaseB); radius+=static_cast(wormRadialWobble) *std::sin(angle+phaseA); centerX=jitterX+(anchoredIslandPassage&&passageAnchor ?static_cast(passageAnchor->values[0]):0.0); centerY=jitterY+(anchoredIslandPassage&&passageAnchor ?static_cast(passageAnchor->values[1]):0.0); } else if(pathStyle==3u) { const std::uint32_t steps=std::max(1, cfg?cfg->cascadeSteps:4u); const double ledge=std::min(ringRadius/4, static_cast(cfg?std::max(0, cfg->cascadeLedgeSubvoxels):0)); // Give each turn a seeded, shallow angular profile so the same cosine // scallop cannot repeat at full ledge depth on every revolution. const std::uint32_t controlCount=std::clamp(steps*2u,8u, segments/2u); const double coordinate=angle*controlCount /(2.0*3.14159265358979323846); const std::uint32_t control0=static_cast(std::floor(coordinate)); const std::uint32_t control1=(control0+1u)%controlCount; const double blend=coordinate-std::floor(coordinate); const auto controlValue=[&](std::uint32_t control) { const std::uint32_t salt=0x43415343u ^(loopIndex*0x9e3779b9u)^(control*0x85ebca6bu); const std::uint32_t bits=static_cast(PassageSeed(seed, upper.strateIndex,lower.strateIndex,connection,salt)>>40); return 2.0*static_cast(bits)/16777215.0-1.0; }; const double smooth=blend*blend*(3.0-2.0*blend); const double irregular=std::lerp(controlValue(control0), controlValue(control1),smooth); const std::uint32_t loopHash=static_cast(PassageSeed(seed, upper.strateIndex,lower.strateIndex,connection, 0x4c454447u+loopIndex)>>40); const double amplitude=0.60+0.40 *static_cast(loopHash)/16777215.0; radius-=ledge*amplitude*(0.24+0.10*irregular); } if(pathStyle!=1u&&anchoredIslandPassage&&passageAnchor) { centerX=static_cast(passageAnchor->values[0]); centerY=static_cast(passageAnchor->values[1]); } const double placementBudget=std::max(0.0, static_cast(placementRadius) -std::hypot(centerX,centerY)-2.0); radius=std::clamp(radius,0.0,placementBudget); return std::array{ static_cast(std::llround(centerX+radius*directions[directionIndex][0]/65536.0)), static_cast(std::llround(centerY+radius*directions[directionIndex][1]/65536.0))}; }; if(generatedWormEntry) { struct FPhaseScore { std::uint32_t phase=0; int direction=1; std::uint64_t routeLength=0; double clearance=0.0; double tangent=0.0; }; std::array phaseScores{}; std::size_t phaseScoreCount=0; const std::uint32_t hashedPhase=phase; const int hashedDirection=direction; const std::int64_t entryVerticalDistance=std::abs( lowerMouth[2]-upperMouth[2]); const std::int64_t entryRouteGradeQ16=profiledSurfaceJoin?8000 :style==0&&anchoredIslandPassage&&!anchoredSpiral?15500 :TargetGradeQ16; const auto candidateRouteLength=[&](std::uint32_t candidate,int candidateDirection) { std::vector> candidateXY; const auto append=[&](std::array point) {if(candidateXY.empty()||candidateXY.back()!=point) candidateXY.push_back(point);}; append({upperMouth[0],upperMouth[1]}); append(ringPoint(candidate)); const auto measure=[&]() { std::uint64_t length=0; for(std::size_t i=1;i(entryRouteGradeQ16) >=static_cast(entryVerticalDistance)*65536ull)break; for(int step=1;step<=16;++step) { int directionIndex=static_cast(candidate) +candidateDirection*step; directionIndex=(directionIndex%16+16)%16; append(ringPoint(static_cast(directionIndex), loops+(step==16?1u:0u))); } ++loops; } if(loops>=256)return std::uint64_t{0}; const std::int64_t mouthPlanSeparation=distanceQ( lowerMouth[0]-upperMouth[0],lowerMouth[1]-upperMouth[1]); if(!hasLowerTangent&&!anchoredSpiral&&pathStyle==1u&&loops<=1 &&mouthPlanSeparation<=2*mouthRadius &&entryVerticalDistance<=2*mouthRadius+qPerMetre/2) { double bestExitScore=-std::numeric_limits::infinity(); std::uint32_t lowerPhase=candidate; const auto firstRing=ringPoint(candidate); for(std::uint32_t exitCandidate=0;exitCandidate<16;++exitCandidate) { if(exitCandidate==candidate)continue; const std::uint32_t previous=static_cast( (static_cast(exitCandidate)-candidateDirection+16)%16); const auto terminal=ringPoint(exitCandidate,loops); const auto beforeTerminal=ringPoint(previous,loops); const double incomingX=static_cast(terminal[0]-beforeTerminal[0]); const double incomingY=static_cast(terminal[1]-beforeTerminal[1]); const double outgoingX=static_cast(lowerMouth[0]-terminal[0]); const double outgoingY=static_cast(lowerMouth[1]-terminal[1]); const double incomingLength=std::hypot(incomingX,incomingY); const double outgoingLength=std::hypot(outgoingX,outgoingY); if(incomingLength<=1.0e-6||outgoingLength<=1.0e-6)continue; const double alignment=(incomingX*outgoingX+incomingY*outgoingY) /(incomingLength*outgoingLength); const double separation=std::hypot( static_cast(terminal[0]-firstRing[0]), static_cast(terminal[1]-firstRing[1])); const double score=alignment+0.20*std::clamp( separation/(2.0*qPerMetre),0.0,2.0); if(score>bestExitScore) {bestExitScore=score;lowerPhase=exitCandidate;} } for(int step=1;step<=16;++step) { int directionIndex=static_cast(candidate) +candidateDirection*step; directionIndex=(directionIndex%16+16)%16; append(ringPoint(static_cast(directionIndex),loops)); if(static_cast(directionIndex)==lowerPhase)break; } } return measure(); }; for(std::uint32_t offset=0;offset<16;++offset) { const std::uint32_t candidate=static_cast( (hashedPhase+16-offset)%16); const auto point=ringPoint(candidate); const double entryX=static_cast(point[0]-upperMouth[0]); const double entryY=static_cast(point[1]-upperMouth[1]); const double entryLength=std::hypot(entryX,entryY); if(entryLength<=1.0e-6)continue; const auto entryDistance=distanceQ(point[0]-upperMouth[0], point[1]-upperMouth[1]); for(const int candidateDirection:{hashedDirection,-hashedDirection}) { const std::uint64_t routeLength=candidateRouteLength(candidate, candidateDirection); if(routeLength==0)continue; const std::uint32_t next=static_cast( (static_cast(candidate)+candidateDirection+16)%16); const auto nextPoint=ringPoint(next); const double tangentX=static_cast(nextPoint[0]-point[0]); const double tangentY=static_cast(nextPoint[1]-point[1]); const double tangentLength=std::hypot(tangentX,tangentY); if(tangentLength<=1.0e-6)continue; const double tangentAlignment=(entryX*tangentX+entryY*tangentY) /(entryLength*tangentLength); const std::int64_t firstAxisZ=upperMouth[2]+mouthRadius -upperPlayerCenterAboveFloorQ; const std::int64_t secondAxisZ=firstAxisZ+PassageRoundRatio( lowerMouth[2]-upperMouth[2],static_cast(entryDistance), static_cast(routeLength)); double candidateTreadClearance=std::numeric_limits::infinity(); const int entrySamples=std::max(1,static_cast(std::ceil( entryLength/(0.25*qPerMetre)))); for(int sample=0;sample<=entrySamples;++sample) { const double t=static_cast(sample)/entrySamples; const double qx=upperMouth[0]+t*entryX; const double qy=upperMouth[1]+t*entryY; const double qz=firstAxisZ+t*(secondAxisZ-firstAxisZ) -mouthRadius+0.20*qPerMetre; for(const auto& previousPassage:layout.passages) { if(previousPassage.stableId==passage.stableId ||previousPassage.controlPoints.size()<2)continue; for(std::size_t previousSegment=0; previousSegment+1(previousA.positionSubvoxels[0]); const double ay=static_cast(previousA.positionSubvoxels[1]); const double az=static_cast(previousA.positionSubvoxels[2]); const double dx=static_cast(previousB.positionSubvoxels[0])-ax; const double dy=static_cast(previousB.positionSubvoxels[1])-ay; const double dz=static_cast(previousB.positionSubvoxels[2])-az; const double horizontalLengthSquared=dx*dx+dy*dy; const double projection=horizontalLengthSquared>0.0 ?std::clamp(((qx-ax)*dx+(qy-ay)*dy) /horizontalLengthSquared,0.0,1.0):0.0; const double centerX=ax+projection*dx; const double centerY=ay+projection*dy; const double centerZ=az+projection*dz -(previousA.radiusSubvoxels+projection*( static_cast(previousB.radiusSubvoxels) -previousA.radiusSubvoxels)) +1.13*qPerMetre; const double vertical=std::max(0.0, std::abs(qz-centerZ)-0.54*qPerMetre); const double clearance=std::sqrt( (qx-centerX)*(qx-centerX)+(qy-centerY)*(qy-centerY) +vertical*vertical)-0.34*qPerMetre; candidateTreadClearance=std::min(candidateTreadClearance, clearance); } } } double previousTreadClearance=std::numeric_limits::infinity(); const double entryXQ=entryX,entryYQ=entryY; const double entryZQ=static_cast(secondAxisZ-firstAxisZ); const double entryLengthSquared=entryXQ*entryXQ+entryYQ*entryYQ +entryZQ*entryZQ; const double mouthStartT=std::clamp( mouthRadius/entryLength,0.0,1.0); for(const auto& previousPassage:layout.passages) { if(previousPassage.stableId==passage.stableId ||previousPassage.controlPoints.size()<2)continue; for(std::size_t previousSegment=0; previousSegment+1(previousA.positionSubvoxels[0]); const double ay=static_cast(previousA.positionSubvoxels[1]); const double az=static_cast(previousA.positionSubvoxels[2]); const double dx=static_cast(previousB.positionSubvoxels[0])-ax; const double dy=static_cast(previousB.positionSubvoxels[1])-ay; const double dz=static_cast(previousB.positionSubvoxels[2])-az; for(int sample=0;sample<=8;++sample) { const double t=static_cast(sample)/8.0; const double px=ax+t*dx; const double py=ay+t*dy; const double pz=az+t*dz -(previousA.radiusSubvoxels+t*( static_cast(previousB.radiusSubvoxels) -previousA.radiusSubvoxels)) +0.20*qPerMetre; const double projection=entryLengthSquared>0.0 ?std::clamp(((px-upperMouth[0])*entryXQ +(py-upperMouth[1])*entryYQ +(pz-firstAxisZ)*entryZQ)/entryLengthSquared, mouthStartT,1.0):mouthStartT; const double ex=px-(upperMouth[0]+projection*entryXQ); const double ey=py-(upperMouth[1]+projection*entryYQ); const double ez=pz-(firstAxisZ+projection*entryZQ); const double clearance=std::sqrt(ex*ex+ey*ey+ez*ez) -mouthRadius; previousTreadClearance=std::min(previousTreadClearance, clearance); } } } const double clearanceScore=std::isfinite(candidateTreadClearance) &&std::isfinite(previousTreadClearance) ?std::min(candidateTreadClearance,previousTreadClearance)/qPerMetre :0.0; if(phaseScoreCount0) { double bestClearance=-std::numeric_limits::infinity(); for(std::size_t index=0;indexselected->tangent+1.0e-6; const bool tangentTie=selected &&std::abs(candidate.tangent-selected->tangent)<=1.0e-6; const bool stableTangentTie=tangentTie&&( candidate.routeLengthrouteLength ||(candidate.routeLength==selected->routeLength &&candidate.clearance>selected->clearance+1.0e-6)); if(smoother||stableTangentTie)selected=&candidate; } if(!selected) for(std::size_t index=0;indexselected->clearance ||(candidate.clearance==selected->clearance &&candidate.routeLengthrouteLength)) selected=&candidate; } if(selected) { phase=selected->phase; direction=selected->direction; clockwise=direction>0; } } } direction=clockwise?1:-1; std::vector> xy; const auto pushXY=[&](std::array p) {if(xy.empty()||xy.back()!=p)xy.push_back(p);}; std::size_t helixEndIndex=0; std::int64_t helixBedrockZ=0; bool hasAnchorHelix=false; pushXY({upperMouth[0],upperMouth[1]}); if(anchoredSpiral&&passageAnchor&&upper.bedrock.presentInCurrentField &&spiralRadius>0) { const std::int64_t anchorX=passageAnchor->values[0]; const std::int64_t anchorY=passageAnchor->values[1]; const std::int64_t rootRadius=passageAnchor->values[2]; const std::int64_t rootBottom=passageAnchor->values[5]; const std::int64_t bedrockTop=rootBottom!=0?rootBottom :contentZQ(upper.bedrock.topMicrometres); // Passage route heights are player-center positions. End one capsule // half-height above the bedrock top so the final ramp step is walkable. helixBedrockZ=bedrockTop+qPerMetre; const std::int64_t helixRadius=spiralRadius; if(rootRadius(upperMouth[0]-anchorX), static_cast(upperMouth[1]-anchorY)) >static_cast(rootRadius+qPerMetre/2) ||upperMouth[2]<=helixBedrockZ) {error=passage.stableId+" anchor root cannot contain its authored spiral mouth or bedrock descent";return false;} const double drop=static_cast(upperMouth[2]-helixBedrockZ); const double pointsPerTurn=static_cast(segments); const double polygonCircumference=2.0*3.14159265358979323846 *static_cast(helixRadius) *(std::sin(3.14159265358979323846/pointsPerTurn) /(3.14159265358979323846/pointsPerTurn)); const double horizontalPerTurn=polygonCircumference *static_cast(TargetGradeQ16)/65536.0; const std::uint32_t authoredTurns=static_cast(std::max(1, (spiralTurnsQ16+65535)/65536)); const std::uint32_t gradeTurns=static_cast(std::max(1.0, std::ceil(drop/std::max(1.0,horizontalPerTurn)))); // The first coil grows from the island mouth offset to the full authored // radius, so reserve one extra turn for that shorter opening revolution. const std::uint32_t turns=std::max(authoredTurns,gradeTurns+1); if(turns>512) {error=passage.stableId+" authored spiral radius needs more than 512 turns to meet walkable grade";return false;} const double startAngle=std::atan2(static_cast(upperMouth[1]-anchorY), static_cast(upperMouth[0]-anchorX)); const std::uint32_t helixPoints=turns*segments; for(std::uint32_t point=1;point<=helixPoints;++point) { const double t=static_cast(point)/helixPoints; const double angle=startAngle+static_cast(clockwise?1:-1) *2.0*3.14159265358979323846*t*turns; const double radialT=std::min(1.0,static_cast(point)/segments); const double mouthOffset=std::clamp(static_cast(passageAnchor->values[3]), 0.0,static_cast(helixRadius)); const double radial=mouthOffset+(static_cast(helixRadius)-mouthOffset) *radialT; const std::array p{ static_cast(std::llround(anchorX +radial*std::cos(angle))), static_cast(std::llround(anchorY +radial*std::sin(angle)))}; pushXY(p); } helixEndIndex=xy.size()-1; hasAnchorHelix=helixEndIndex>0; } const std::int64_t routeGradeQ16=profiledSurfaceJoin?8000:hasAnchorHelix &&lower.generatorKind=="SurfaceWorld"?8000 :style==0&&anchoredIslandPassage&&!anchoredSpiral?15500:TargetGradeQ16; const std::array firstRing=ringPoint(phase); if(hasAnchorHelix&&pathStyle==1u) { // Let the anchored helix widen into the winding inter-strate route // over a full, smooth coil. A direct jump from the root radius to the // outer worm ring creates a sharp support seam inside the rock neck. const double anchorX=static_cast(passageAnchor->values[0]); const double anchorY=static_cast(passageAnchor->values[1]); const double startAngle=std::atan2(static_cast(upperMouth[1])-anchorY, static_cast(upperMouth[0])-anchorX); const double targetX=static_cast(firstRing[0])-anchorX; const double targetY=static_cast(firstRing[1])-anchorY; const double targetAngle=std::atan2(targetY,targetX); const double angleDelta=static_cast(direction)*2.0 *3.14159265358979323846 +std::remainder(targetAngle-startAngle,2.0*3.14159265358979323846); const double startRadius=static_cast(spiralRadius); const std::int64_t targetDx=firstRing[0]-passageAnchor->values[0]; const std::int64_t targetDy=firstRing[1]-passageAnchor->values[1]; const double targetRadius=static_cast(PassageIsqrt( static_cast(targetDx*targetDx+targetDy*targetDy))); const std::uint32_t transitionSegments=static_cast( std::clamp(std::ceil(std::abs(angleDelta)*(startRadius+targetRadius) /(2.0*0.75*qPerMetre)),static_cast(segments),256.0)); for(std::uint32_t step=1;step<=transitionSegments;++step) { const double t=static_cast(step)/transitionSegments; const double smooth=t*t*(3.0-2.0*t); const double radius=startRadius+(targetRadius-startRadius)*smooth; const double angle=startAngle+angleDelta*t; const std::array point=step==transitionSegments?firstRing: std::array{ static_cast(std::llround(anchorX+radius*std::cos(angle))), static_cast(std::llround(anchorY+radius*std::sin(angle)))}; pushXY(point); } } if(generatedWormEntry) { // Join the portal lead to the winding route with a circular arc whose // radius remains wider than the bore. A short quadratic corner still // makes the passage sweep swell on the inside of this turn. const auto nextRing=ringPoint(static_cast( (static_cast(phase)+direction+16)%16)); const double incomingX=static_cast(firstRing[0]-upperMouth[0]); const double incomingY=static_cast(firstRing[1]-upperMouth[1]); const double outgoingX=static_cast(nextRing[0]-firstRing[0]); const double outgoingY=static_cast(nextRing[1]-firstRing[1]); const double incomingLength=std::hypot(incomingX,incomingY); const double outgoingLength=std::hypot(outgoingX,outgoingY); const double turnCross=incomingX*outgoingY-incomingY*outgoingX; const double turnDot=incomingX*outgoingX+incomingY*outgoingY; const double turnAngle=std::atan2(turnCross,turnDot); const double halfTangent=std::tan(std::abs(turnAngle)*0.5); if(incomingLength>1.0e-6&&outgoingLength>1.0e-6 &&std::abs(halfTangent)>1.0e-6) { const double maximumCut=0.80*std::min(incomingLength,outgoingLength); const double requestedRadius=std::max(mouthRadius,midRadius) +0.75*qPerMetre; const double filletRadius=std::min(static_cast(requestedRadius), maximumCut/std::abs(halfTangent)); const double cut=filletRadius*std::abs(halfTangent); const double incomingUnitX=incomingX/incomingLength; const double incomingUnitY=incomingY/incomingLength; const double outgoingUnitX=outgoingX/outgoingLength; const double outgoingUnitY=outgoingY/outgoingLength; const std::array start={ firstRing[0]-incomingUnitX*cut, firstRing[1]-incomingUnitY*cut}; const std::array end={ firstRing[0]+outgoingUnitX*cut, firstRing[1]+outgoingUnitY*cut}; const double turnSign=turnCross>=0.0?1.0:-1.0; const std::array center={ start[0]-turnSign*incomingUnitY*filletRadius, start[1]+turnSign*incomingUnitX*filletRadius}; const double startAngle=std::atan2(start[1]-center[1], start[0]-center[0]); const int arcSegments=std::max(1,static_cast(std::ceil( filletRadius*std::abs(turnAngle)/(0.75*qPerMetre)))); pushXY({static_cast(std::llround(start[0])), static_cast(std::llround(start[1]))}); for(int sample=1;sample<=arcSegments;++sample) { const double t=static_cast(sample)/arcSegments; const double angle=startAngle+turnAngle*t; const std::array point=sample==arcSegments?end: std::array{center[0]+filletRadius*std::cos(angle), center[1]+filletRadius*std::sin(angle)}; pushXY({static_cast(std::llround(point[0])), static_cast(std::llround(point[1]))}); } } else pushXY(firstRing); } else pushXY(firstRing); std::uint64_t totalHorizontal=0;std::uint32_t loops=0; const std::int64_t verticalDistance=hasAnchorHelix ?std::abs(lowerMouth[2]-helixBedrockZ):std::abs(lowerMouth[2]-upperMouth[2]); auto measurePath=[&]() { std::uint64_t total=0; const std::size_t start=hasAnchorHelix?helixEndIndex:0; for(std::size_t i=start+1;i(routeGradeQ16) >=static_cast(verticalDistance)*65536ull)break; for(int step=1;step<=16;++step) { int directionIndex=static_cast(phase)+direction*step; directionIndex=(directionIndex%16+16)%16; pushXY(ringPoint(static_cast(directionIndex), loops+(step==16?1u:0u))); } ++loops; } const std::int64_t mouthPlanSeparation=distanceQ( lowerMouth[0]-upperMouth[0],lowerMouth[1]-upperMouth[1]); const bool shortGeneratedClosure=!hasLowerTangent&&!hasAnchorHelix &&pathStyle==1u&&loops<=1 &&mouthPlanSeparation<=2*mouthRadius &&verticalDistance<=2*mouthRadius+qPerMetre/2; if(hasLowerTangent) { std::uint32_t lowerPhase=static_cast(PassageSeed(seed, upper.strateIndex,lower.strateIndex,connection,0x4c505248u)%16); std::int64_t bestAlignment=std::numeric_limits::min(); bool lowerRouteReversed=false; const std::uint32_t hashedLowerPhase=lowerPhase; for(std::uint32_t offset=0;offset<16;++offset) { const std::uint32_t candidate=static_cast( (hashedLowerPhase+offset)%16); const auto terminal=ringPoint(candidate,loops); const std::int64_t dx=lowerMouth[0]-terminal[0]; const std::int64_t dy=lowerMouth[1]-terminal[1]; const std::int64_t alignment=dx*lowerTangent[0]+dy*lowerTangent[1]; const std::int64_t cross=std::abs(dx*lowerTangent[1]-dy*lowerTangent[0]); if(alignment<=0||cross==0)continue; const auto length=distanceQ(dx,dy); const std::int64_t score=alignment*65536/static_cast(length); if(score>bestAlignment) {bestAlignment=score;lowerPhase=candidate;} } // A route is walkable in either direction. If the approach ring lies on // the wrong side of its first route tangent, attach to the same endpoint // while traversing the route backwards instead of rejecting the layout. if(bestAlignment==std::numeric_limits::min()) { for(std::uint32_t offset=0;offset<16;++offset) { const std::uint32_t candidate=static_cast( (hashedLowerPhase+offset)%16); const auto terminal=ringPoint(candidate,loops); const std::int64_t dx=lowerMouth[0]-terminal[0]; const std::int64_t dy=lowerMouth[1]-terminal[1]; const std::int64_t alignment=dx*lowerTangent[0]+dy*lowerTangent[1]; const std::int64_t cross=std::abs(dx*lowerTangent[1]-dy*lowerTangent[0]); if(alignment>=0||cross==0)continue; const auto length=distanceQ(dx,dy); const std::int64_t score=-alignment*65536/static_cast(length); if(score>bestAlignment) {bestAlignment=score;lowerPhase=candidate;lowerRouteReversed=true;} } } if(bestAlignment==std::numeric_limits::min()) {error=passage.stableId+" could not find a non-overlapping lower landing approach";return false;} std::printf("[ResolvedPassageLowerApproach] id=%s direction=%s alignment_q16=%lld status=PASS\n", passage.stableId.c_str(),lowerRouteReversed?"reverse":"forward", static_cast(bestAlignment)); if(phase!=lowerPhase) for(int step=1;step<=16;++step) { int directionIndex=static_cast(phase)+direction*step; directionIndex=(directionIndex%16+16)%16; pushXY(ringPoint(static_cast(directionIndex),loops)); if(static_cast(directionIndex)==lowerPhase)break; } pushXY(ringPoint(lowerPhase)); } else if(shortGeneratedClosure) { // A one-turn generated route whose mouths share a plan location // must leave and rejoin from different ring tangents. Returning on // the entry chord creates a hairpin that overlaps its own tread. double bestExitScore=-std::numeric_limits::infinity(); std::uint32_t lowerPhase=phase; const auto firstRing=ringPoint(phase,loops); for(std::uint32_t candidate=0;candidate<16;++candidate) { if(candidate==phase)continue; const std::uint32_t previous=static_cast( (static_cast(candidate)-direction+16)%16); const auto terminal=ringPoint(candidate,loops); const auto beforeTerminal=ringPoint(previous,loops); const double incomingX=static_cast(terminal[0]-beforeTerminal[0]); const double incomingY=static_cast(terminal[1]-beforeTerminal[1]); const double outgoingX=static_cast(lowerMouth[0]-terminal[0]); const double outgoingY=static_cast(lowerMouth[1]-terminal[1]); const double incomingLength=std::hypot(incomingX,incomingY); const double outgoingLength=std::hypot(outgoingX,outgoingY); if(incomingLength<=1.0e-6||outgoingLength<=1.0e-6)continue; const double tangentAlignment=(incomingX*outgoingX+incomingY*outgoingY) /(incomingLength*outgoingLength); const double separation=std::hypot( static_cast(terminal[0]-firstRing[0]), static_cast(terminal[1]-firstRing[1])); const double score=tangentAlignment +0.20*std::clamp(separation/(2.0*qPerMetre),0.0,2.0); if(score>bestExitScore) {bestExitScore=score;lowerPhase=candidate;} } for(int step=1;step<=16;++step) { int directionIndex=static_cast(phase)+direction*step; directionIndex=(directionIndex%16+16)%16; pushXY(ringPoint(static_cast(directionIndex),loops)); if(static_cast(directionIndex)==lowerPhase)break; } std::printf("[ResolvedPassageGeneratedExitApproach] id=%s turns=%u phase=%u exit_phase=%u tangent_score=%.3f status=PASS\n", passage.stableId.c_str(),loops,phase,lowerPhase,bestExitScore); } pushXY({lowerMouth[0],lowerMouth[1]});totalHorizontal=measurePath(); std::size_t lowerMouthIndex=xy.size()-1; if(VoxelForgeCore::WorldAssemblyEvaluatorVersion>=188 &&style==0&&anchoredIslandPassage&&!anchoredSpiral&&xy.size()>4) { std::vector> refined; refined.reserve(xy.size()*2); for(std::size_t index=0;index+1(std::llround(0.5*( static_cast(xy[index][0]) +static_cast(xy[index+1][0])))), static_cast(std::llround(0.5*( static_cast(xy[index][1]) +static_cast(xy[index+1][1]))))}); } refined.push_back(xy.back()); if(lowerMouthIndex>3)lowerMouthIndex+=lowerMouthIndex-3; xy.swap(refined); } totalHorizontal=measurePath(); if(loops>=256||totalHorizontal==0) {error=passage.stableId+" could not fit a supported grade inside the resolved placement radius";return false;} std::vector cumulative(xy.size(),0); for(std::size_t i=1;i(std::llround(1.5*qPerMetre))) :mouthRadius; const std::uint64_t surfaceMouthBlendLength=static_cast( std::max(8*qPerMetre,2*mouthRadius)); for(std::size_t i=0;i(cumulative[i]) /surfaceMouthBlendLength,0.0,1.0); const double smooth=t*t*(3.0-2.0*t); radius=surfacePortalRadius+static_cast(std::llround( (midRadius-surfacePortalRadius)*smooth)); } else if(i( std::max(UpperMouthRadiusBlendLengthMetres*qPerMetre, 2*mouthRadius)); if(cumulative[i](cumulative[i])/blendLength; const double smooth=t*t*(3.0-2.0*t); radius=mouthRadius+static_cast(std::llround( (midRadius-mouthRadius)*smooth)); } } // Grow the lower bore into its mouth over fixed route distance. Assigning // the full mouth radius to the penultimate point creates a segment-length // flare whose cubic-detrended profile can read as a short-period wall beat. if(i<=lowerMouthIndex) { constexpr std::int64_t LowerMouthRadiusBlendLengthMetres=12; const std::uint64_t blendLength=static_cast( std::max(LowerMouthRadiusBlendLengthMetres*qPerMetre, 2*mouthRadius)); const std::uint64_t distanceToLowerMouth=mouthHorizontal-cumulative[i]; if(distanceToLowerMouth(distanceToLowerMouth)/blendLength; const double smooth=t*t*(3.0-2.0*t); radius=midRadius+static_cast(std::llround( (mouthRadius-midRadius)*(1.0-smooth))); } } std::int64_t playerZ=0; if(hasAnchorHelix&&i<=helixEndIndex) playerZ=upperMouth[2]+PassageRoundRatio(helixBedrockZ-upperMouth[2], static_cast(cumulative[i]), static_cast(helixHorizontal)); else { const std::uint64_t segmentStart=hasAnchorHelix?helixHorizontal:0; const std::int64_t startZ=hasAnchorHelix?helixBedrockZ:upperMouth[2]; const std::uint64_t segmentLength=mouthHorizontal-segmentStart; playerZ=startZ+PassageRoundRatio(lowerMouth[2]-startZ, static_cast(cumulative[i]-segmentStart), static_cast(segmentLength)); } ResolvedLayoutPassagePointV1 point; std::int64_t pointPlayerCenterAboveFloorQ=playerCenterAboveFloorQ; if(surfaceGroundDeparture) { const double t=std::clamp(static_cast(cumulative[i]) /surfaceMouthBlendLength,0.0,1.0); const double smooth=t*t*(3.0-2.0*t); pointPlayerCenterAboveFloorQ=upperPlayerCenterAboveFloorQ +static_cast(std::llround( (playerCenterAboveFloorQ-upperPlayerCenterAboveFloorQ)*smooth)); } point.positionSubvoxels={xy[i][0],xy[i][1], playerZ+radius-pointPlayerCenterAboveFloorQ}; point.radiusSubvoxels=radius;passage.controlPoints.push_back(point); } if(surfaceGroundDeparture) std::printf("[ResolvedSurfaceWorldDepartureMouth] passage=%s ground_floor_z_m=%.3f portal_radius_m=%.3f tunnel_radius_m=%.3f blend_length_m=%.3f departure_xy_m=(%.3f,%.3f) first_tunnel_xy_m=(%.3f,%.3f) first_tunnel_floor_z_m=%.3f first_tunnel_radius_m=%.3f\n", passage.stableId.c_str(),(upperMouth[2]-upperPlayerCenterAboveFloorQ)/static_cast(qPerMetre), surfacePortalRadius/static_cast(qPerMetre), midRadius/static_cast(qPerMetre), surfaceMouthBlendLength/static_cast(qPerMetre), upperMouth[0]/static_cast(qPerMetre), upperMouth[1]/static_cast(qPerMetre), passage.controlPoints.size()>1?passage.controlPoints[1].positionSubvoxels[0] /static_cast(qPerMetre):0.0, passage.controlPoints.size()>1?passage.controlPoints[1].positionSubvoxels[1] /static_cast(qPerMetre):0.0, passage.controlPoints.size()>1? (passage.controlPoints[1].positionSubvoxels[2] -passage.controlPoints[1].radiusSubvoxels+playerCenterAboveFloorQ -static_cast(std::llround(1.13*qPerMetre))) /static_cast(qPerMetre):0.0, passage.controlPoints.size()>1?passage.controlPoints[1].radiusSubvoxels /static_cast(qPerMetre):0.0); passage.controlPoints.front().positionSubvoxels[2]=upperMouth[2] +passage.controlPoints.front().radiusSubvoxels-upperPlayerCenterAboveFloorQ; passage.controlPoints[lowerMouthIndex].positionSubvoxels[2]=lowerMouth[2] +passage.controlPoints[lowerMouthIndex].radiusSubvoxels -playerCenterAboveFloorQ; std::vector chamberFloorControls; std::vector chamberFloorProfile; chamberFloorControls.reserve(passage.controlPoints.size()); chamberFloorProfile.reserve(passage.controlPoints.size()); for(const auto& point:passage.controlPoints) { chamberFloorControls.push_back({ static_cast(point.positionSubvoxels[0]), static_cast(point.positionSubvoxels[1]), static_cast(point.positionSubvoxels[2]), static_cast(point.radiusSubvoxels)}); chamberFloorProfile.push_back(static_cast( point.positionSubvoxels[2]-point.radiusSubvoxels)); } const auto routePoints=[&](const ResolvedLayoutStratumV2& host) { std::vector> route; for(const auto& anchor:host.anchors) if(anchor.kind=="route_point"&&anchor.values.size()>=3) route.push_back({static_cast(anchor.values[0]), static_cast(anchor.values[1]), static_cast(anchor.values[2])}); return route; }; if(upper.generatorKind=="TunnelNetwork" ||upper.generatorKind=="SurfaceWorld") { auto route=routePoints(upper); if(upper.generatorKind=="TunnelNetwork"&&route.size()>2) route.erase(route.begin(),route.end()-2); double routeHeight=0.0,distanceSquared=0.0; if(VoxelForgeCore::PassageSpline::ClosestRouteHeightAtXY(route, chamberFloorControls.front()[0],chamberFloorControls.front()[1], routeHeight,distanceSquared) &&distanceSquared<=std::pow(chamberFloorControls.front()[3] +2.0*qPerMetre,2.0)) VoxelForgeCore::PassageSpline::BlendEndpointFloorToRoute( chamberFloorControls,chamberFloorProfile,0,route,qPerMetre, upper.generatorKind=="SurfaceWorld"?0.98:1.13); } if(lower.generatorKind=="TunnelNetwork" ||lower.generatorKind=="VerticalShafts") { auto route=routePoints(lower); if(route.size()>2)route.resize(2); double routeHeight=0.0,distanceSquared=0.0; if(VoxelForgeCore::PassageSpline::ClosestRouteHeightAtXY(route, chamberFloorControls[lowerMouthIndex][0], chamberFloorControls[lowerMouthIndex][1], routeHeight,distanceSquared) &&distanceSquared<=std::pow( chamberFloorControls[lowerMouthIndex][3] +2.0*qPerMetre,2.0)) VoxelForgeCore::PassageSpline::BlendEndpointFloorToRoute( chamberFloorControls,chamberFloorProfile, lowerMouthIndex,route,qPerMetre); } const auto resolveLandingChamber=[&]( ResolvedLayoutPassageLandingChamberV1& chamber, const std::array& mouth,std::size_t endpointIndex, std::size_t neighborIndex,const ResolvedLayoutStratumV2& host, const char* side) { chamber.floorCenterSubvoxels=mouth; chamber.radiusSubvoxels=mouthRadius+3*qPerMetre; chamber.clearHeightSubvoxels=static_cast( std::llround(4.5*qPerMetre)); const auto& endpoint=passage.controlPoints[endpointIndex]; const auto& neighbor=passage.controlPoints[neighborIndex]; const std::int64_t endpointFloor=static_cast( std::llround(chamberFloorProfile[endpointIndex])); const std::int64_t neighborFloor=static_cast( std::llround(chamberFloorProfile[neighborIndex])); chamber.floorCenterSubvoxels[2]=endpointFloor; const std::int64_t dx=endpoint.positionSubvoxels[0] -neighbor.positionSubvoxels[0]; const std::int64_t dy=endpoint.positionSubvoxels[1] -neighbor.positionSubvoxels[1]; const std::int64_t dz=endpointFloor-neighborFloor; const std::int64_t horizontalSquared=dx*dx+dy*dy; if(horizontalSquared<=0) {error=passage.stableId+" "+side+" landing chamber has no endpoint tangent";return false;} chamber.floorGradientQ16={ PassageRoundRatio(dx<0?-dz:dz,std::abs(dx)*65536, horizontalSquared), PassageRoundRatio(dy<0?-dz:dz,std::abs(dy)*65536, horizontalSquared)}; if(host.generatorKind=="SurfaceWorld") { const auto surfaceRoute=routePoints(host); std::vector> nearbyRoute; // Fit the landing floor only to route anchors inside its // chamber footprint; samples beyond the chamber can pull // the floor away from the route at the joint. const double supportRadius=static_cast( chamber.radiusSubvoxels); for(const auto& point:surfaceRoute) { const double routeDx=point[0]-endpoint.positionSubvoxels[0]; const double routeDy=point[1]-endpoint.positionSubvoxels[1]; if(routeDx*routeDx+routeDy*routeDy<=supportRadius*supportRadius) nearbyRoute.push_back(point); } if(nearbyRoute.size()>=2) { std::array mean{}; for(const auto& point:nearbyRoute) for(std::size_t axis=0;axis<3;++axis) mean[axis]+=point[axis]/nearbyRoute.size(); double xx=0.0,xy=0.0,yy=0.0,xz=0.0,yz=0.0; for(const auto& point:nearbyRoute) { const double routeDx=point[0]-mean[0]; const double routeDy=point[1]-mean[1]; const double routeDz=point[2]-mean[2]; xx+=routeDx*routeDx;xy+=routeDx*routeDy; yy+=routeDy*routeDy;xz+=routeDx*routeDz; yz+=routeDy*routeDz; } const double determinant=xx*yy-xy*xy; double gradientX=0.0,gradientY=0.0; if(determinant>1.0e-9) { gradientX=(xz*yy-yz*xy)/determinant; gradientY=(yz*xx-xz*xy)/determinant; } else { // Route anchors can be collinear inside the chamber. // Fit their dominant horizontal direction by least // squares instead of extrapolating from one far point. const double trace=xx+yy; const double discriminant=std::sqrt(std::max(0.0, (xx-yy)*(xx-yy)+4.0*xy*xy)); const double variance=0.5*(trace+discriminant); if(variance>1.0e-9) { double axisX=1.0,axisY=0.0; if(std::abs(xy)>1.0e-12) {axisX=xy;axisY=variance-xx;} else if(yy>xx) {axisX=0.0;axisY=1.0;} const double axisLength=std::hypot(axisX,axisY); axisX/=axisLength;axisY/=axisLength; const double routeSlope=(axisX*xz+axisY*yz)/variance; gradientX=routeSlope*axisX; gradientY=routeSlope*axisY; } } chamber.floorGradientQ16={ static_cast(std::llround(gradientX*65536.0)), static_cast(std::llround(gradientY*65536.0))}; } } const std::uint64_t gradientSquared=static_cast( chamber.floorGradientQ16[0]*chamber.floorGradientQ16[0] +chamber.floorGradientQ16[1]*chamber.floorGradientQ16[1]); const std::int64_t gradientMagnitude=static_cast( PassageIsqrt(gradientSquared)); constexpr std::int64_t MaximumLandingGradeQ16=17560; if(gradientMagnitude>MaximumLandingGradeQ16) {error=passage.stableId+" "+side+" landing chamber floor exceeds 15 degrees";return false;} const std::int64_t planeReachQ=PassageRoundRatio( chamber.radiusSubvoxels,gradientMagnitude,65536); const std::int64_t shellQ=static_cast( std::llround(0.75*qPerMetre)); const auto qToUm=[&](std::int64_t q) {return q*static_cast(layout.coreVoxelSizeMicrometres) /WorldAssemblySubvoxelsPerVoxel;}; if(!host.content.presentInCurrentField ||qToUm(chamber.floorCenterSubvoxels[2]-planeReachQ-shellQ) host.content.topMicrometres) { error=passage.stableId+" "+side +" landing chamber does not fit inside its host content band"; return false; } return true; }; if(!resolveLandingChamber(passage.upperLandingChamber,upperMouth,0,1, upper,"upper") ||!resolveLandingChamber(passage.lowerLandingChamber,lowerMouth, lowerMouthIndex,lowerMouthIndex-1,lower,"lower")) return false; std::printf("[PassageLandingChamberPlan] id=%s upper_radius_m=%.3f lower_radius_m=%.3f clear_height_m=4.500 upper_floor_grade_q16=(%lld,%lld) lower_floor_grade_q16=(%lld,%lld) status=PASS\n", passage.stableId.c_str(), passage.upperLandingChamber.radiusSubvoxels/static_cast(qPerMetre), passage.lowerLandingChamber.radiusSubvoxels/static_cast(qPerMetre), static_cast(passage.upperLandingChamber.floorGradientQ16[0]), static_cast(passage.upperLandingChamber.floorGradientQ16[1]), static_cast(passage.lowerLandingChamber.floorGradientQ16[0]), static_cast(passage.lowerLandingChamber.floorGradientQ16[1])); layout.passages.push_back(std::move(passage)); } } return true; } ResolvedLayoutValidatorAuditV3 AuditResolvedLayoutValidatorV3() { ResolvedLayoutValidatorAuditV3 result; auto make=[](std::string id,std::int32_t order,std::int64_t top,std::int64_t bottom) { ResolvedLayoutStratumV2 s;s.stableId=std::move(id);s.order=order;s.strateIndex=order;s.generatorKind="synthetic"; s.interiorTopMicrometres=top;s.interiorBottomMicrometres=bottom;s.voxelSizeMicrometres=250000; s.transitionMode="Gradient";s.transitionWidthMicrometres=4000000; s.transitionHonouredInCore=true; s.ceiling={"ceiling",top,top+1000000,true,true,false,"closed ceiling band","upper face","synthetic"}; s.content={"content",bottom,top,true,true,false,"generator content","none","none"}; s.bedrock={"bedrock",bottom-1000000,bottom,true,true,false,"breakable bedrock","lower face","none"}; return s; }; const auto expectReject=[&](std::string name,ResolvedLayoutSnapshotV3 candidate) { ++result.controlsRun;std::vector issues;std::string error; if(!candidate.validate(ResolvedLayoutValidationModeV3::strict,issues,error))++result.controlsRejectedAsExpected; else result.failures.push_back({"negative_control_accepted",std::move(name)+" was accepted by strict validator"}); }; auto base=ResolvedLayoutSnapshotV3{};base.worldIdentity="synthetic";base.ownerVoxelSizeMicrometres=125000; base.coreVoxelSizeMicrometres=250000;base.strates={make("a",0,30000000,0),make("b",1,-2000000,-40000000)}; ResolvedLayoutPassageConfigV1 passageConfig;passageConfig.upperStrateIndex=0;passageConfig.connections=1; passageConfig.mouthRadiusSubvoxels=1280;passageConfig.midRadiusSubvoxels=1280; passageConfig.reachMinimumSubvoxels=9216;passageConfig.reachMaximumSubvoxels=9216; passageConfig.distanceMinimumSubvoxels=1024;passageConfig.distanceMaximumSubvoxels=10240; std::string planError; if(!PlanResolvedLayoutPassagesV1(base,19,{passageConfig},planError)) result.failures.push_back({"synthetic_passage_plan_failed",planError}); {std::vector issues;std::string error; if(!base.validate(ResolvedLayoutValidationModeV3::strict,issues,error)) { for(const auto& issue:issues) std::printf("[ResolvedLayoutValidatorPositiveControlIssue] code=%s detail=%s\n", issue.code.c_str(),issue.detail.c_str()); result.failures.push_back({"positive_control_rejected","valid ceiling/content/bedrock layout was rejected"}); }} auto missingPassage=base;missingPassage.passages.clear();expectReject("missing interface passage",missingPassage); auto outsidePassage=base; if(!outsidePassage.passages.empty()) { outsidePassage.passages.front().upperMouthSubvoxels[0] =outsidePassage.passages.front().placementRadiusSubvoxels+1; outsidePassage.passages.front().controlPoints.front().positionSubvoxels[0] =outsidePassage.passages.front().upperMouthSubvoxels[0]; } expectReject("passage mouth outside placement radius",outsidePassage); auto boundaryGap=base;boundaryGap.strates[1].ceiling.topMicrometres-=1; expectReject("bedrock/next-ceiling gap",boundaryGap); auto missingBedrock=base;missingBedrock.strates[0].bedrock.presentInCurrentField=false;expectReject("missing bedrock",missingBedrock); auto missingCeiling=base;missingCeiling.strates[0].ceiling.presentInCurrentField=false;expectReject("missing ceiling",missingCeiling); auto wrongOrder=base;wrongOrder.strates[0].content.kind="bedrock";expectReject("wrong band order",wrongOrder); auto largeOverlap=base;largeOverlap.strates[0].transitionInterpenetrationMicrometres=4000001;expectReject("interpenetration exceeds transition width",largeOverlap); auto thinBand=base; thinBand.strates[0].bedrock.topMicrometres=thinBand.strates[0].bedrock.bottomMicrometres +2ll*base.coreVoxelSizeMicrometres-1; thinBand.strates[0].content.bottomMicrometres=thinBand.strates[0].bedrock.topMicrometres; expectReject("bedrock band below minimum thickness",thinBand); auto genericOverlap=base;genericOverlap.strates[0].bedrock.bottomMicrometres-=1;expectReject("nominal band overlap",genericOverlap); auto genericGap=base;genericGap.strates[0].bedrock.bottomMicrometres+=1;expectReject("nominal band gap",genericGap); result.passed=result.controlsRun==result.controlsRejectedAsExpected&&result.failures.empty();return result; } }