#pragma once #include "Json.h" #include "VoxelForgeCore/VoxelForgeCore.h" #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::string LayoutHex(std::uint64_t value) {std::ostringstream out;out<>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(x.generatorKind=="FloatingIslands") { x.passageCapabilities.hasFloatingIslandLanding=true; x.passageCapabilities.usePassageAnchorForColumns=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 &&zMicrometres0 ?static_cast(std::llround(1.13*WorldAssemblySubvoxelsPerVoxel *1000000.0/coreVoxelSizeMicrometres)):1157; const auto floorProfile=[&](const ResolvedLayoutPassagePointV1& p) {return p.positionSubvoxels[2]-p.radiusSubvoxels+PlayerCenterAboveFloorQ;}; std::size_t lowerMouthControlIndex=passage.controlPoints.size(); for(std::size_t i=1;i(dx*dx+dy*dy)); const std::int64_t signedDz=floorProfile(point)-floorProfile(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(strict&&!issues.empty()){error="strict resolved-layout validation rejected "+issues.front().code;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"); 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& 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::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 reachMinimum=cfg?std::max(0,cfg->reachMinimumSubvoxels):8*qPerMetre; const std::int64_t reachMaximum=cfg?std::max(reachMinimum,cfg->reachMaximumSubvoxels):24*qPerMetre; 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 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 reach=reachValue(PassageSeed(seed,upper.strateIndex,lower.strateIndex, connection,0x55505248u),reachMinimum,reachMaximum); upperMouth[2]=contentZQ(upper.content.bottomMicrometres)+reach; } if(!hasLowerRoute) { lowerMouth[0]=xy[0];lowerMouth[1]=xy[1]; const auto reach=reachValue(PassageSeed(seed,upper.strateIndex,lower.strateIndex, connection,0x4c4f5752u),reachMinimum,reachMaximum); lowerMouth[2]=contentZQ(lower.content.topMicrometres)-reach; } } 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; constexpr std::int64_t LandingExtensionQ=8; 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;}); const bool hasLowerTangent=hasLowerRoute&&!hasPassageArrival &&routeTangent(lower,false,lowerTangent); const std::int64_t outerRingRadius=std::max(1, placementRadius-mouthRadius-qPerMetre/2); const std::uint32_t pathStyle=anchoredSpiral?1u:style; 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(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); if(hasUpperRoute) { 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]); const std::int64_t phaseY=style==0&&anchoredIslandPassage &&!anchoredSpiral&&passageAnchor ?upperMouth[1]-passageAnchor->values[1] :(hasUpperTangent?upperTangent[1]:upperMouth[1]); 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;} } } if(hasUpperRoute&&upperIsSurfaceWorld) { const auto& directionAtPhase=directions[phase]; const std::int64_t radialAlignment=upperMouth[0]*directionAtPhase[0] +upperMouth[1]*directionAtPhase[1]; if(radialAlignment<0) { std::int64_t bestOutwardAlignment=std::numeric_limits::min(); for(std::uint32_t candidate=0;candidatebestOutwardAlignment) {bestOutwardAlignment=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; } const 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]); } 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))}; }; 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=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); } } 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; } 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(); 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;} } if(bestAlignment==std::numeric_limits::min()) {error=passage.stableId+" could not find a non-overlapping lower landing approach";return false;} 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)); } pushXY({lowerMouth[0],lowerMouth[1]});totalHorizontal=measurePath(); std::size_t lowerMouthIndex=xy.size()-1; std::vector> lowerLandingPoints; if(hasLowerTangent) { std::array previousRoutePoint=lowerMouth; std::int64_t remainingQ=LandingExtensionQ*qPerMetre; bool skippedMouth=false; for(const auto& anchor:lower.anchors) if(anchor.kind=="route_point"&&anchor.values.size()>=3) { const std::array routePoint{{anchor.values[0], anchor.values[1],anchor.values[2]}}; if(!skippedMouth){skippedMouth=true;continue;} if(remainingQ<=0)break; const std::int64_t dx=routePoint[0]-previousRoutePoint[0]; const std::int64_t dy=routePoint[1]-previousRoutePoint[1]; const std::uint64_t segmentLength=distanceQ(dx,dy); if(segmentLength==0)continue; const std::int64_t advance=std::min(remainingQ, static_cast(segmentLength)); std::array landingPoint{}; for(std::size_t axis=0;axis<3;++axis) landingPoint[axis]=previousRoutePoint[axis]+PassageRoundRatio( routePoint[axis]-previousRoutePoint[axis],advance, static_cast(segmentLength)); if(!radialWithin(landingPoint,placementRadius))break; lowerLandingPoints.push_back(landingPoint); pushXY({landingPoint[0],landingPoint[1]}); previousRoutePoint=landingPoint;remainingQ-=advance; if(advance(segmentLength))break; } if(lowerLandingPoints.empty()) {error=passage.stableId+" lower route has no landing length inside PassageConfig.DistanceMax";return false;} } 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=xy.size()-1)?mouthRadius:midRadius; if(surfaceGroundDeparture&&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)); } std::int64_t playerZ=0; if(i>lowerMouthIndex) playerZ=lowerLandingPoints[i-lowerMouthIndex-1][2]; else 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; point.positionSubvoxels={xy[i][0],xy[i][1],playerZ+radius-playerCenterAboveFloorQ}; 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]-static_cast(std::llround(0.88*qPerMetre)))/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(0.88*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-playerCenterAboveFloorQ; passage.controlPoints[lowerMouthIndex].positionSubvoxels[2]=lowerMouth[2] +passage.controlPoints[lowerMouthIndex].radiusSubvoxels -playerCenterAboveFloorQ; 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,10000000,0),make("b",1,-2000000,-12000000)}; ResolvedLayoutPassageConfigV1 passageConfig;passageConfig.upperStrateIndex=0;passageConfig.connections=1; passageConfig.mouthRadiusSubvoxels=1280;passageConfig.midRadiusSubvoxels=1280; passageConfig.reachMinimumSubvoxels=2048;passageConfig.reachMaximumSubvoxels=2048; 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)) 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; } }