Files
VoxelForge/Standalone/Core/src/ResolvedLayoutInternal.h
T
Fr0zkaandClaude Opus 5.5 ca85e876ad feat(core): ore vein records and a materialAt query, off by default (L50); research R18, R19
Versioned capsule vein records (stable ID, resource ID, endpoints, radius, seeded irregularity) resolved
into the snapshot only when requested, placed beside the route and away from passages, landings and
landmarks (forced-passage control rejected). The bound field table gains materialAt (host + two resource
IDs + Q16 weights) behind a per-tile BVH; mesh export v2 carries per-vertex materials; a measurement-only
yield computes removed resource volume (0 on already-air space). With veins on, every density, mesh,
payload and topology hash is unchanged; demo off: owner hashes identical to L47. fast/medium/full PASS.
Research R18 (cavern-graph planner) and R19 (why FloatingIslands costs ~10x SurfaceWorld).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-24 18:25:03 +02:00

2536 lines
145 KiB
C++

#pragma once
#include "Json.h"
#include "VoxelForgeCore/VoxelForgeCore.h"
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <limits>
#include <sstream>
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<std::uint8_t>(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<std::uint32_t>(seed));
TransitionHashWord(hash, static_cast<std::uint32_t>(interfaceIndex));
TransitionHashWord(hash, static_cast<std::uint64_t>(x));
TransitionHashWord(hash, static_cast<std::uint64_t>(y));
TransitionHashWord(hash, static_cast<std::uint64_t>(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<std::uint32_t>(
TransitionHash(seed, interfaceIndex, x, y, z, salt) >> 32);
return static_cast<std::int64_t>(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<std::uint32_t>(seed));
TransitionHashWord(prefix,static_cast<std::uint32_t>(interfaceIndex));
const auto at = [&](std::int64_t dx, std::int64_t dy)
{
std::uint64_t hash=prefix;
TransitionHashWord(hash,static_cast<std::uint64_t>(ix+dx));
TransitionHashWord(hash,static_cast<std::uint64_t>(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<std::uint32_t>(hash>>32);
return static_cast<std::int64_t>(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<std::int64_t>(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<char>(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<std::uint32_t>(seed));
TransitionHashWord(hash,static_cast<std::uint32_t>(strate));
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<std::uint8_t>(kind);
return value>=static_cast<std::uint8_t>(ResolvedSdfStampKindV1::carveEllipsoid)
&&value<=static_cast<std::uint8_t>(ResolvedSdfStampKindV1::fillDais);
}
double LayoutStampPointSegmentDistance(const std::array<std::int64_t,3>& point,
const std::array<std::int64_t,3>& a,const std::array<std::int64_t,3>& b)
{
double lengthSquared=0.0,dot=0.0;
for(std::size_t axis=0;axis<3;++axis)
{
const double delta=static_cast<double>(b[axis])-a[axis];
lengthSquared+=delta*delta;
dot+=(static_cast<double>(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<double>(a[axis])
+t*(static_cast<double>(b[axis])-a[axis]);
const double delta=static_cast<double>(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<double>(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]<band->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<std::uint32_t>(1,layout.coreVoxelSizeMicrometres);
constexpr double RouteClearanceMicrometres=1000000.0;
const auto routeSafe=[&](const std::array<std::int64_t,3>& a,
const std::array<std::int64_t,3>& b,double radiusSubvoxels)
{
const double radius=radiusSubvoxels*coreMicrometres
/WorldAssemblySubvoxelsPerVoxel;
return LayoutStampPointSegmentDistance(stamp.positionMicrometres,a,b)
>boundRadius+radius+RouteClearanceMicrometres;
};
const auto qToMicrometres=[&](const std::array<std::int64_t,3>& q)
{
std::array<std::int64_t,3> result{};
for(std::size_t axis=0;axis<3;++axis)
result[axis]=static_cast<std::int64_t>(std::llround(
static_cast<double>(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<double>(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<passage.controlPoints.size();++i)
{
const auto a=qToMicrometres(passage.controlPoints[i].positionSubvoxels);
const auto b=qToMicrometres(passage.controlPoints[i+1].positionSubvoxels);
const double radius=std::max(passage.controlPoints[i].radiusSubvoxels,
passage.controlPoints[i+1].radiusSubvoxels);
if(!routeSafe(a,b,radius))
{if(reason)*reason="stamp overlaps a resolved passage walk corridor";return false;}
}
}
std::vector<const ResolvedLayoutAnchorV2*> 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<route.size();++i)
{
const auto makePoint=[&](const ResolvedLayoutAnchorV2& anchor)
{return std::array<std::int64_t,3>{
static_cast<std::int64_t>(std::llround(static_cast<double>(anchor.values[0])
*coreMicrometres/WorldAssemblySubvoxelsPerVoxel)),
static_cast<std::int64_t>(std::llround(static_cast<double>(anchor.values[1])
*coreMicrometres/WorldAssemblySubvoxelsPerVoxel)),
static_cast<std::int64_t>(std::llround(static_cast<double>(anchor.values[2])
*coreMicrometres/WorldAssemblySubvoxelsPerVoxel))};};
const auto a=makePoint(*route[i]);
const auto b=i+1<route.size()?makePoint(*route[i+1]):a;
const double radius=i+1<route.size()
?std::max(route[i]->values.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<double,3>& p0,
const std::array<double,3>& p1,const std::array<double,3>& q0,
const std::array<double,3>& q1)
{
std::array<double,3> 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<double,3> LayoutOrePointUm(const std::array<std::int64_t,3>& point)
{return {static_cast<double>(point[0]),static_cast<double>(point[1]),
static_cast<double>(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]<band->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<double>(vein.radiusMicrometres
+vein.irregularityAmplitudeMicrometres+vein.blendWidthMicrometres);
const double coreMicrometres=std::max<std::uint32_t>(1,layout.coreVoxelSizeMicrometres);
constexpr double RouteClearanceMicrometres=100000.0;
const auto qToUm=[&](const std::array<std::int64_t,3>& q)
{return std::array<double,3>{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<passage.controlPoints.size();++i)
{
const auto a=qToUm(passage.controlPoints[i].positionSubvoxels);
const auto b=qToUm(passage.controlPoints[i+1].positionSubvoxels);
const double radius=std::max(passage.controlPoints[i].radiusSubvoxels,
passage.controlPoints[i+1].radiusSubvoxels)*coreMicrometres
/WorldAssemblySubvoxelsPerVoxel;
if(LayoutSegmentDistance(veinA,veinB,a,b)
<=coreRadius+radius+RouteClearanceMicrometres)
return fail("vein enters a clear passage corridor");
}
}
std::vector<const ResolvedLayoutAnchorV2*> 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<double,3> 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<route.size();++i)
{
const auto qPoint=[&](const ResolvedLayoutAnchorV2& anchor)
{return std::array<double,3>{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<double,3> previous{};
const auto bezier=[&](double t)
{
const double one=1.0-t;
const std::array<double,4> weights={one*one*one,3.0*one*one*t,
3.0*one*t*t,t*t*t};
std::array<double,3> 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<double>(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))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<<std::hex<<std::setfill('0')<<std::setw(16)<<value;return out.str();}
std::string LayoutBandJson(const ResolvedLayoutBandV2& band)
{
std::ostringstream out;out<<"{\"kind\":"<<LayoutEscape(band.kind)
<<",\"bottom_um\":"<<band.bottomMicrometres<<",\"top_um\":"<<band.topMicrometres
<<",\"present\":"<<(band.presentInCurrentField?"true":"false")
<<",\"diggable\":"<<(band.diggable?"true":"false")
<<",\"protected\":"<<(band.protectedBand?"true":"false")
<<",\"solid_density_q16\":"<<band.solidDensityQ16
<<",\"behaviour\":"<<LayoutEscape(band.behaviour)
<<",\"density_side\":"<<LayoutEscape(band.densitySide)
<<",\"cap_path\":"<<LayoutEscape(band.capPath)<<'}';return out.str();
}
ResolvedLayoutBandV2 ParseLayoutBand(const VFJson::Value& value)
{
ResolvedLayoutBandV2 b;b.kind=value.Get("kind").String();
b.bottomMicrometres=value.Get("bottom_um").Integer();b.topMicrometres=value.Get("top_um").Integer();
b.presentInCurrentField=value.Get("present").Boolean();b.diggable=value.Get("diggable").Boolean(true);
b.protectedBand=value.Get("protected").Boolean();b.solidDensityQ16=value.Get("solid_density_q16").Integer(65536);
b.behaviour=value.Get("behaviour").String();
b.densitySide=value.Get("density_side").String();b.capPath=value.Get("cap_path").String();return b;
}
std::string LayoutBodyJson(const ResolvedLayoutSnapshotV3& s)
{
std::ostringstream out;out<<"{\"schema_version\":"<<s.schemaVersion
<<",\"world_identity\":"<<LayoutEscape(s.worldIdentity)
<<",\"owner_voxel_size_um\":"<<s.ownerVoxelSizeMicrometres
<<",\"core_voxel_size_um\":"<<s.coreVoxelSizeMicrometres
<<",\"strate_layout_chunk_size_um\":"<<s.strateLayoutChunkSizeMicrometres
<<",\"strate_layout_chunk_width_subvoxels\":"<<s.strateLayoutChunkWidthSubvoxels
<<",\"strates\":[";
for(std::size_t i=0;i<s.strates.size();++i)
{
if(i)out<<',';const auto& x=s.strates[i];
out<<"{\"stable_id\":"<<LayoutEscape(x.stableId)<<",\"order\":"<<x.order
<<",\"strate_index\":"<<x.strateIndex<<",\"generator_kind\":"<<LayoutEscape(x.generatorKind)
<<",\"recipe_id\":"<<LayoutEscape(x.recipeId)<<",\"recipe_version\":"<<x.recipeVersion
<<",\"interior_bottom_um\":"<<x.interiorBottomMicrometres
<<",\"interior_top_um\":"<<x.interiorTopMicrometres
<<",\"voxel_size_um\":"<<x.voxelSizeMicrometres
<<",\"lattice_origin_um\":["<<x.latticeOriginMicrometres[0]<<','
<<x.latticeOriginMicrometres[1]<<','<<x.latticeOriginMicrometres[2]<<']'
<<",\"ceiling\":"<<LayoutBandJson(x.ceiling)
<<",\"content\":"<<LayoutBandJson(x.content)
<<",\"bedrock\":"<<LayoutBandJson(x.bedrock)
<<",\"transition_mode\":"<<LayoutEscape(x.transitionMode)
<<",\"transition_blend_chunks\":"<<x.transitionBlendChunks
<<",\"transition_width_um\":"<<x.transitionWidthMicrometres
<<",\"transition_warp_amplitude_um\":"<<x.transitionWarpAmplitudeMicrometres
<<",\"transition_interpenetration_um\":"<<x.transitionInterpenetrationMicrometres
<<",\"transition_honoured_in_core\":"<<(x.transitionHonouredInCore?"true":"false")
<<",\"transition_falls_back_to_hard\":"<<(x.transitionFallsBackToHard?"true":"false")
<<",\"transition_dispatch_path\":"<<LayoutEscape(x.transitionDispatchPath)
<<",\"bound_dispatch_path\":"<<LayoutEscape(x.boundDispatchPath)
<<",\"query_dispatch_path\":"<<LayoutEscape(x.queryDispatchPath)
<<",\"manager_lookup\":"<<LayoutEscape(x.managerLookup)
<<",\"manager_chunk_width_um\":"<<x.managerChunkWidthMicrometres
<<",\"dispatch_chunk_width_um\":"<<x.dispatchChunkWidthMicrometres
<<",\"anchors\":[";
for(std::size_t a=0;a<x.anchors.size();++a)
{
if(a)out<<',';const auto& anchor=x.anchors[a];
out<<"{\"kind\":"<<LayoutEscape(anchor.kind)<<",\"stable_id\":"
<<LayoutEscape(anchor.stableId)<<",\"units\":"<<LayoutEscape(anchor.units)<<",\"values\":[";
for(std::size_t v=0;v<anchor.values.size();++v){if(v)out<<',';out<<anchor.values[v];}
out<<"]}";
}
out<<"]}";
}
out<<"],\"passages\":[";
for(std::size_t i=0;i<s.passages.size();++i)
{
if(i)out<<',';const auto& p=s.passages[i];
out<<"{\"stable_id\":"<<LayoutEscape(p.stableId)
<<",\"upper_strate_index\":"<<p.upperStrateIndex
<<",\"lower_strate_index\":"<<p.lowerStrateIndex
<<",\"style\":"<<p.style
<<",\"placement_radius_q\":"<<p.placementRadiusSubvoxels
<<",\"upper_mouth_q\":["<<p.upperMouthSubvoxels[0]<<','
<<p.upperMouthSubvoxels[1]<<','<<p.upperMouthSubvoxels[2]
<<"],\"lower_mouth_q\":["<<p.lowerMouthSubvoxels[0]<<','
<<p.lowerMouthSubvoxels[1]<<','<<p.lowerMouthSubvoxels[2]
<<"],\"mouth_radius_q\":"<<p.mouthRadiusSubvoxels
<<",\"mid_radius_q\":"<<p.midRadiusSubvoxels<<",\"control_points\":[";
for(std::size_t j=0;j<p.controlPoints.size();++j)
{
if(j)out<<',';const auto& point=p.controlPoints[j];
out<<"{\"position_q\":["<<point.positionSubvoxels[0]<<','
<<point.positionSubvoxels[1]<<','<<point.positionSubvoxels[2]
<<"],\"radius_q\":"<<point.radiusSubvoxels<<'}';
}
out<<"]}";
}
out<<']';
if(!s.stamps.empty())
{
out<<",\"stamp_vocabulary_version\":"<<s.stampVocabularyVersion
<<",\"stamps\":[";
for(std::size_t i=0;i<s.stamps.size();++i)
{
if(i)out<<',';const auto& stamp=s.stamps[i];
out<<"{\"stable_id\":"<<LayoutEscape(stamp.stableId)
<<",\"strate_index\":"<<stamp.strateIndex
<<",\"kind\":"<<static_cast<unsigned>(stamp.kind)
<<",\"position_um\":["<<stamp.positionMicrometres[0]<<','
<<stamp.positionMicrometres[1]<<','<<stamp.positionMicrometres[2]
<<"],\"bounds_min_um\":["<<stamp.boundsMinimumMicrometres[0]<<','
<<stamp.boundsMinimumMicrometres[1]<<','<<stamp.boundsMinimumMicrometres[2]
<<"],\"bounds_max_um\":["<<stamp.boundsMaximumMicrometres[0]<<','
<<stamp.boundsMaximumMicrometres[1]<<','<<stamp.boundsMaximumMicrometres[2]
<<"],\"half_extents_um\":["<<stamp.halfExtentsMicrometres[0]<<','
<<stamp.halfExtentsMicrometres[1]<<','<<stamp.halfExtentsMicrometres[2]
<<"],\"smooth_width_um\":"<<stamp.smoothWidthMicrometres
<<",\"irregularity_amplitude_um\":"<<stamp.irregularityAmplitudeMicrometres
<<",\"irregularity_wavelength_um\":"<<stamp.irregularityWavelengthMicrometres
<<",\"irregularity_seed\":"<<stamp.irregularitySeed
<<",\"yaw_quarter_turns\":"<<static_cast<unsigned>(stamp.yawQuarterTurns)<<'}';
}
out<<']';
}
if(!s.cavernRooms.empty()||!s.cavernEdges.empty())
{
out<<",\"cavern_graph_vocabulary_version\":"
<<s.cavernGraphVocabularyVersion<<",\"cavern_rooms\":[";
for(std::size_t i=0;i<s.cavernRooms.size();++i)
{
if(i)out<<',';const auto& room=s.cavernRooms[i];
out<<"{\"stable_id\":"<<LayoutEscape(room.stableId)
<<",\"strate_index\":"<<room.strateIndex
<<",\"center_um\":["<<room.centerMicrometres[0]<<','
<<room.centerMicrometres[1]<<','<<room.centerMicrometres[2]
<<"],\"half_extents_um\":["<<room.halfExtentsMicrometres[0]<<','
<<room.halfExtentsMicrometres[1]<<','<<room.halfExtentsMicrometres[2]
<<"],\"floor_um\":"<<room.floorMicrometres
<<",\"smooth_width_um\":"<<room.smoothWidthMicrometres
<<",\"irregularity_amplitude_um\":"<<room.irregularityAmplitudeMicrometres
<<",\"irregularity_wavelength_um\":"<<room.irregularityWavelengthMicrometres
<<",\"irregularity_seed\":"<<room.irregularitySeed
<<",\"bounds_min_um\":["<<room.boundsMinimumMicrometres[0]<<','
<<room.boundsMinimumMicrometres[1]<<','<<room.boundsMinimumMicrometres[2]
<<"],\"bounds_max_um\":["<<room.boundsMaximumMicrometres[0]<<','
<<room.boundsMaximumMicrometres[1]<<','<<room.boundsMaximumMicrometres[2]<<"]}";
}
out<<"],\"cavern_edges\":[";
for(std::size_t i=0;i<s.cavernEdges.size();++i)
{
if(i)out<<',';const auto& edge=s.cavernEdges[i];
out<<"{\"stable_id\":"<<LayoutEscape(edge.stableId)
<<",\"start_anchor_stable_id\":"<<LayoutEscape(edge.startAnchorStableId)
<<",\"end_room_stable_id\":"<<LayoutEscape(edge.endRoomStableId)
<<",\"strate_index\":"<<edge.strateIndex<<",\"control_points_um\":[";
for(std::size_t point=0;point<edge.controlPointsMicrometres.size();++point)
{
if(point)out<<',';const auto& p=edge.controlPointsMicrometres[point];
out<<'['<<p[0]<<','<<p[1]<<','<<p[2]<<']';
}
out<<"],\"bore_radius_um\":"<<edge.boreRadiusMicrometres
<<",\"bore_height_um\":"<<edge.boreHeightMicrometres
<<",\"floor_support_depth_um\":"<<edge.floorSupportDepthMicrometres
<<",\"smooth_width_um\":"<<edge.smoothWidthMicrometres
<<",\"irregularity_amplitude_um\":"<<edge.irregularityAmplitudeMicrometres
<<",\"irregularity_wavelength_um\":"<<edge.irregularityWavelengthMicrometres
<<",\"irregularity_seed\":"<<edge.irregularitySeed
<<",\"bounds_min_um\":["<<edge.boundsMinimumMicrometres[0]<<','
<<edge.boundsMinimumMicrometres[1]<<','<<edge.boundsMinimumMicrometres[2]
<<"],\"bounds_max_um\":["<<edge.boundsMaximumMicrometres[0]<<','
<<edge.boundsMaximumMicrometres[1]<<','<<edge.boundsMaximumMicrometres[2]<<"]}";
}
out<<']';
}
if(!s.oreVeins.empty())
{
out<<",\"ore_vein_vocabulary_version\":"<<s.oreVeinVocabularyVersion
<<",\"ore_veins\":[";
for(std::size_t i=0;i<s.oreVeins.size();++i)
{
if(i)out<<',';const auto& vein=s.oreVeins[i];
out<<"{\"stable_id\":"<<LayoutEscape(vein.stableId)
<<",\"strate_index\":"<<vein.strateIndex
<<",\"resource_material_id\":"<<vein.resourceMaterialId
<<",\"endpoints_um\":[["<<vein.endpointsMicrometres[0][0]<<','
<<vein.endpointsMicrometres[0][1]<<','<<vein.endpointsMicrometres[0][2]
<<"],["<<vein.endpointsMicrometres[1][0]<<','
<<vein.endpointsMicrometres[1][1]<<','<<vein.endpointsMicrometres[1][2]
<<"]],\"bounds_min_um\":["<<vein.boundsMinimumMicrometres[0]<<','
<<vein.boundsMinimumMicrometres[1]<<','<<vein.boundsMinimumMicrometres[2]
<<"],\"bounds_max_um\":["<<vein.boundsMaximumMicrometres[0]<<','
<<vein.boundsMaximumMicrometres[1]<<','<<vein.boundsMaximumMicrometres[2]
<<"],\"radius_um\":"<<vein.radiusMicrometres
<<",\"irregularity_amplitude_um\":"<<vein.irregularityAmplitudeMicrometres
<<",\"irregularity_wavelength_um\":"<<vein.irregularityWavelengthMicrometres
<<",\"blend_width_um\":"<<vein.blendWidthMicrometres
<<",\"irregularity_seed\":"<<vein.irregularitySeed<<'}';
}
out<<']';
}
out<<'}';return out.str();
}
std::int64_t LayoutInt(const VFJson::Value& obj,std::string_view key)
{return obj.Get(key).Integer();}
std::string LayoutString(const VFJson::Value& obj,std::string_view key)
{return obj.Get(key).String();}
bool LayoutBool(const VFJson::Value& obj,std::string_view key,bool fallback=false)
{return obj.Get(key).Boolean(fallback);}
std::uint64_t PassageHash(std::uint64_t value)
{
value+=0x9e3779b97f4a7c15ull;
value=(value^(value>>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<std::uint32_t>(seed));
h=PassageHash(h^static_cast<std::uint32_t>(upper));
h=PassageHash(h^static_cast<std::uint32_t>(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<std::uint64_t>(negative?-value:value);
const std::uint64_t product=magnitude*static_cast<std::uint64_t>(numerator);
const std::uint64_t rounded=(product+static_cast<std::uint64_t>(denominator)/2)
/static_cast<std::uint64_t>(denominator);
return negative?-static_cast<std::int64_t>(rounded):static_cast<std::int64_t>(rounded);
}
std::array<std::int64_t,2> 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<std::uint64_t>(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<std::int64_t>(hx%span)-outer;
const std::int64_t y=static_cast<std::int64_t>(hy%span)-outer;
const std::uint64_t radiusSquared=static_cast<std::uint64_t>(x*x+y*y);
if(radiusSquared<=static_cast<std::uint64_t>(outer*outer)
&&radiusSquared>=static_cast<std::uint64_t>(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<std::int64_t>(0, upperThickness);
result.lowerCeilingBottomMicrometres = seam - std::max<std::int64_t>(0, lowerThickness);
result.lowerCeilingTopMicrometres = seam;
const std::int64_t width = upper.transitionFallsBackToHard
? 0 : std::max<std::int64_t>(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<std::int64_t>(
1000000, std::max<std::int64_t>(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<std::int64_t>(1000000,
std::max<std::int64_t>(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<b.order;});
std::sort(cavernRooms.begin(),cavernRooms.end(),[](const auto& a,const auto& b)
{return a.stableId<b.stableId;});
std::sort(cavernEdges.begin(),cavernEdges.end(),[](const auto& a,const auto& b)
{return a.stableId<b.stableId;});
std::sort(oreVeins.begin(),oreVeins.end(),[](const auto& a,const auto& b)
{return a.stableId<b.stableId;});
for(std::size_t i=0;i<strates.size();++i)
{
auto& x=strates[i];x.order=static_cast<std::int32_t>(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<strates.size())
{
const auto& lower=strates[i+1];
x.lowerInterfaceCapabilities.preserveUpperBedrockAirGap=
x.generatorKind=="FloatingIslands"&&x.transitionMode=="Gradient";
x.lowerInterfaceCapabilities.surfaceToTunnelLandingSupport=
x.generatorKind=="SurfaceWorld"&&lower.generatorKind=="TunnelNetwork";
}
}
const std::int64_t playerCenterAboveFloorQ=static_cast<std::int64_t>(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<passage.controlPoints.size();++i)
{
const auto& point=passage.controlPoints[i];
if(point.positionSubvoxels[0]==passage.lowerMouthSubvoxels[0]
&&point.positionSubvoxels[1]==passage.lowerMouthSubvoxels[1]
&&point.positionSubvoxels[2]-point.radiusSubvoxels+playerCenterAboveFloorQ
==passage.lowerMouthSubvoxels[2])
passage.lowerMouthControlIndex=i;
}
}
stableIdentity.clear();const std::string body=LayoutBodyJson(*this);
stableIdentity="resolved-layout-v3-fnv64:"+LayoutHex(LayoutHash(body));
std::vector<ResolvedLayoutIssueV2> issues;
return validate(ResolvedLayoutValidationModeV3::compatibility,issues,error);
}
bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
std::vector<ResolvedLayoutIssueV2>& 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;i<strates.size();++i)
{
const auto& s=strates[i];
const std::int64_t effectiveTransitionWidth=LayoutTransitionLimit(s);
if(s.interiorTopMicrometres<=s.interiorBottomMicrometres)
add("invalid_interior",s.stableId+" has non-positive content height");
if(s.transitionInterpenetrationMicrometres>effectiveTransitionWidth
||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<strates.size()&&s.transitionMode!="Hard"&&!s.transitionHonouredInCore
&&!s.transitionFallsBackToHard)
add("transition_not_honoured",s.stableId+" declares a blend/interleave that Core does not apply on this interface");
if(!s.ceiling.presentInCurrentField)add("missing_ceiling",s.stableId+" has no explicit ceiling band in today's field");
if(!s.bedrock.presentInCurrentField)add("missing_bedrock",s.stableId+" has no explicit bedrock floor in today's field");
if(strict)
{
if(s.order!=static_cast<std::int32_t>(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<minimumThickness)
add("band_thinner_than_minimum",s.stableId+" "+std::string(expected)
+" is thinner than the two-Core-voxel minimum");
if(b.protectedBand&&b.diggable)
add("invalid_protection",s.stableId+" band is both protected and diggable");
if(b.solidDensityQ16<=0)
add("invalid_solid_density",s.stableId+" has a non-positive solid band strength");
};
bandShape(s.ceiling,"ceiling");bandShape(s.content,"content");bandShape(s.bedrock,"bedrock");
if(s.ceiling.bottomMicrometres!=s.content.topMicrometres
||s.content.bottomMicrometres!=s.bedrock.topMicrometres)
add("wrong_order",s.stableId+" bands do not follow ceiling -> content -> bedrock");
if(i+1<strates.size()
&&s.bedrock.bottomMicrometres!=strates[i+1].ceiling.topMicrometres)
add(s.bedrock.bottomMicrometres>strates[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.bottomMicrometres<s.content.topMicrometres)
add("band_overlap",s.stableId+" ceiling overlaps content by "+std::to_string(
s.content.topMicrometres-s.ceiling.bottomMicrometres)+" um");
else if(s.ceiling.bottomMicrometres>s.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.topMicrometres<s.content.bottomMicrometres)
add("uncovered_interval",s.stableId+" has a "+std::to_string(
s.content.bottomMicrometres-s.bedrock.topMicrometres)+" um gap above its bedrock");
}
}
for(std::size_t i=0;i+1<strates.size();++i)
{
const auto& a=strates[i];const auto& b=strates[i+1];
const std::int64_t effectiveTransitionWidth=LayoutTransitionLimit(a);
if(a.interiorBottomMicrometres>b.interiorTopMicrometres)
add("uncovered_interval",a.stableId+" to "+b.stableId+" leaves "+std::to_string(
a.interiorBottomMicrometres-b.interiorTopMicrometres)+" um uncovered");
else if(a.interiorBottomMicrometres<b.interiorTopMicrometres)
{
const std::int64_t overlap=b.interiorTopMicrometres-a.interiorBottomMicrometres;
if(overlap>effectiveTransitionWidth)
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.bottomMicrometres<b.ceiling.topMicrometres)
{
const std::int64_t overlap=b.ceiling.topMicrometres-a.bedrock.bottomMicrometres;
if(overlap>effectiveTransitionWidth)
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<std::string> passageIds;
for(std::size_t i=0;strict&&i+1<strates.size();++i)
{
const auto& upper=strates[i];const auto& lower=strates[i+1];
if(std::none_of(passages.begin(),passages.end(),[&](const auto& p)
{return p.upperStrateIndex==upper.strateIndex&&p.lowerStrateIndex==lower.strateIndex;}))
add("missing_passage_interface",upper.stableId+" has no passage to "+lower.stableId);
}
for(const auto& passage:passages)
{
const auto* upper=findStratum(passage.upperStrateIndex);
const auto* lower=findStratum(passage.lowerStrateIndex);
if(passage.stableId.empty()||std::find(passageIds.begin(),passageIds.end(),passage.stableId)!=passageIds.end())
add("passage_identity", "passage has an empty or duplicate stable identity");
else passageIds.push_back(passage.stableId);
if(!upper||!lower||lower->order!=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<std::int64_t,3>& 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<std::int64_t,3>& p)
{
const std::int64_t zMicrometres=p[2]*static_cast<std::int64_t>(coreVoxelSizeMicrometres)
/WorldAssemblySubvoxelsPerVoxel;
return s.content.presentInCurrentField&&zMicrometres>=s.content.bottomMicrometres
&&zMicrometres<s.content.topMicrometres;
};
if(!insidePlacement(passage.upperMouthSubvoxels)||!insidePlacement(passage.lowerMouthSubvoxels))
add("passage_placement",passage.stableId+" mouth lies outside its (0,0) placement radius");
if(!insideContent(*upper,passage.upperMouthSubvoxels)
||!insideContent(*lower,passage.lowerMouthSubvoxels))
add("passage_mouth_content",passage.stableId+" mouth is outside its owned content band");
if(passage.controlPoints.size()<2)continue;
const std::int64_t PlayerCenterAboveFloorQ=coreVoxelSizeMicrometres>0
?static_cast<std::int64_t>(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<passage.controlPoints.size();++i)
{
const auto& candidate=passage.controlPoints[i];
if(candidate.positionSubvoxels[0]==passage.lowerMouthSubvoxels[0]
&&candidate.positionSubvoxels[1]==passage.lowerMouthSubvoxels[1]
&&floorProfile(candidate)==passage.lowerMouthSubvoxels[2])
lowerMouthControlIndex=i;
}
if(lowerMouthControlIndex==passage.controlPoints.size())
{
add("passage_mouth_binding",passage.stableId+" has no control point bound to its lower mouth");
lowerMouthControlIndex=passage.controlPoints.size()-1;
}
for(std::size_t i=0;i<passage.controlPoints.size();++i)
{
const auto& point=passage.controlPoints[i];
if(point.radiusSubvoxels<=0||!insidePlacement(point.positionSubvoxels))
add("passage_control_point",passage.stableId+" has a non-positive radius or leaves its placement disk");
if(i==0||i==lowerMouthControlIndex)
{
const auto& mouth=i==0?passage.upperMouthSubvoxels:passage.lowerMouthSubvoxels;
if(point.positionSubvoxels[0]!=mouth[0]||point.positionSubvoxels[1]!=mouth[1]
||floorProfile(point)!=mouth[2])
add("passage_mouth_binding",passage.stableId+" control point does not bind to its mouth anchor");
}
if(i==0)continue;
const auto& previous=passage.controlPoints[i-1];
const std::int64_t dx=point.positionSubvoxels[0]-previous.positionSubvoxels[0];
const std::int64_t dy=point.positionSubvoxels[1]-previous.positionSubvoxels[1];
const auto horizontal=PassageIsqrt(static_cast<std::uint64_t>(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<std::int64_t>(horizontal)*17560
:signedDz>=0||dz*65536>static_cast<std::int64_t>(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)
add("stamp_vocabulary_version","resolved stamps require supported vocabulary version 1 or 2");
std::vector<std::string> 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);
if(!LayoutStampKindValid(stamp.kind)||stamp.yawQuarterTurns>3)
add("stamp_kind","stamp has an unsupported kind or orientation");
bool validExtents=true;
for(const auto extent:stamp.halfExtentsMicrometres)
validExtents=validExtents&&extent>=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");
const std::int64_t pad=stamp.smoothWidthMicrometres
+stamp.irregularityAmplitudeMicrometres;
const std::array<std::int64_t,3> 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(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)
add("cavern_graph_vocabulary_version",
"cavern graph records require vocabulary version 1");
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<std::string> 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.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 std::int64_t pad=room.smoothWidthMicrometres
+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.floorMicrometres<room.centerMicrometres[2]-room.halfExtentsMicrometres[2]
||room.floorMicrometres>room.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");
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.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");
std::array<std::int64_t,3> low=edge.controlPointsMicrometres[0];
std::array<std::int64_t,3> high=low;
for(const auto& point:edge.controlPointsMicrometres)
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]);}
const std::int64_t xyPad=edge.boreRadiusMicrometres
+edge.smoothWidthMicrometres+edge.irregularityAmplitudeMicrometres;
const std::int64_t zPad=edge.smoothWidthMicrometres
+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();
if(endpoint[0]<room->centerMicrometres[0]-room->halfExtentsMicrometres[0]
||endpoint[0]>room->centerMicrometres[0]+room->halfExtentsMicrometres[0]
||endpoint[1]<room->centerMicrometres[1]-room->halfExtentsMicrometres[1]
||endpoint[1]>room->centerMicrometres[1]+room->halfExtentsMicrometres[1]
||std::abs(endpoint[2]-room->floorMicrometres)>500000)
add("cavern_edge_portal",edge.stableId+" endpoint does not meet its room floor portal");
}
}
}
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<std::string> 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;return false;}
return true;
}
std::optional<ResolvedLayoutSnapshotV3> 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<std::uint32_t>(LayoutInt(root,"schema_version"));
s.worldIdentity=LayoutString(root,"world_identity");
s.ownerVoxelSizeMicrometres=static_cast<std::uint32_t>(LayoutInt(root,"owner_voxel_size_um"));
s.coreVoxelSizeMicrometres=static_cast<std::uint32_t>(LayoutInt(root,"core_voxel_size_um"));
s.strateLayoutChunkSizeMicrometres=static_cast<std::uint32_t>(LayoutInt(root,"strate_layout_chunk_size_um"));
s.strateLayoutChunkWidthSubvoxels=LayoutInt(root,"strate_layout_chunk_width_subvoxels");
s.stampVocabularyVersion=static_cast<std::uint32_t>(LayoutInt(root,"stamp_vocabulary_version"));
s.cavernGraphVocabularyVersion=static_cast<std::uint32_t>(
LayoutInt(root,"cavern_graph_vocabulary_version"));
s.oreVeinVocabularyVersion=static_cast<std::uint32_t>(
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<std::int32_t>(LayoutInt(v,"order"));x.strateIndex=static_cast<std::int32_t>(LayoutInt(v,"strate_index"));
x.generatorKind=LayoutString(v,"generator_kind");x.recipeId=LayoutString(v,"recipe_id");
x.recipeVersion=static_cast<std::uint32_t>(LayoutInt(v,"recipe_version"));
x.interiorBottomMicrometres=LayoutInt(v,"interior_bottom_um");x.interiorTopMicrometres=LayoutInt(v,"interior_top_um");
x.voxelSizeMicrometres=static_cast<std::uint32_t>(LayoutInt(v,"voxel_size_um"));
const auto& origin=v.Get("lattice_origin_um").AsArray();for(std::size_t i=0;i<3&&i<origin.size();++i)x.latticeOriginMicrometres[i]=origin[i].Integer();
x.ceiling=ParseLayoutBand(v.Get("ceiling"));x.content=ParseLayoutBand(v.Get("content"));x.bedrock=ParseLayoutBand(v.Get("bedrock"));
x.transitionMode=LayoutString(v,"transition_mode");x.transitionBlendChunks=static_cast<std::int32_t>(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<std::int32_t>(LayoutInt(v,"upper_strate_index"));
p.lowerStrateIndex=static_cast<std::int32_t>(LayoutInt(v,"lower_strate_index"));
if(v.Get("style").IsNumber())
p.style=static_cast<std::uint32_t>(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<upper.size();++axis)p.upperMouthSubvoxels[axis]=upper[axis].Integer();
for(std::size_t axis=0;axis<3&&axis<lower.size();++axis)p.lowerMouthSubvoxels[axis]=lower[axis].Integer();
for(const auto& q:v.Get("control_points").AsArray())
{
ResolvedLayoutPassagePointV1 point;const auto& position=q.Get("position_q").AsArray();
for(std::size_t axis=0;axis<3&&axis<position.size();++axis)
point.positionSubvoxels[axis]=position[axis].Integer();
point.radiusSubvoxels=LayoutInt(q,"radius_q");p.controlPoints.push_back(point);
}
s.passages.push_back(std::move(p));
}
for(const auto& v:root.Get("stamps").AsArray())
{
ResolvedLayoutStampV1 stamp;stamp.stableId=LayoutString(v,"stable_id");
stamp.strateIndex=static_cast<std::int32_t>(LayoutInt(v,"strate_index"));
stamp.kind=static_cast<ResolvedSdfStampKindV1>(LayoutInt(v,"kind"));
const auto readTriplet=[&](std::string_view key,std::array<std::int64_t,3>& target)
{
const auto& values=v.Get(key).AsArray();
for(std::size_t axis=0;axis<3&&axis<values.size();++axis)
target[axis]=values[axis].Integer();
};
readTriplet("position_um",stamp.positionMicrometres);
readTriplet("bounds_min_um",stamp.boundsMinimumMicrometres);
readTriplet("bounds_max_um",stamp.boundsMaximumMicrometres);
readTriplet("half_extents_um",stamp.halfExtentsMicrometres);
stamp.smoothWidthMicrometres=LayoutInt(v,"smooth_width_um");
stamp.irregularityAmplitudeMicrometres=LayoutInt(v,"irregularity_amplitude_um");
stamp.irregularityWavelengthMicrometres=LayoutInt(v,"irregularity_wavelength_um");
stamp.irregularitySeed=static_cast<std::uint64_t>(LayoutInt(v,"irregularity_seed"));
stamp.yawQuarterTurns=static_cast<std::uint8_t>(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<std::int32_t>(LayoutInt(v,"strate_index"));
const auto readTriplet=[&](std::string_view key,std::array<std::int64_t,3>& target)
{
const auto& values=v.Get(key).AsArray();
for(std::size_t axis=0;axis<3&&axis<values.size();++axis)
target[axis]=values[axis].Integer();
};
readTriplet("center_um",room.centerMicrometres);
readTriplet("half_extents_um",room.halfExtentsMicrometres);
readTriplet("bounds_min_um",room.boundsMinimumMicrometres);
readTriplet("bounds_max_um",room.boundsMaximumMicrometres);
room.floorMicrometres=LayoutInt(v,"floor_um");
room.smoothWidthMicrometres=LayoutInt(v,"smooth_width_um");
room.irregularityAmplitudeMicrometres=LayoutInt(v,"irregularity_amplitude_um");
room.irregularityWavelengthMicrometres=LayoutInt(v,"irregularity_wavelength_um");
room.irregularitySeed=static_cast<std::uint64_t>(LayoutInt(v,"irregularity_seed"));
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<std::int32_t>(LayoutInt(v,"strate_index"));
const auto& points=v.Get("control_points_um").AsArray();
for(std::size_t i=0;i<edge.controlPointsMicrometres.size()&&i<points.size();++i)
{
const auto& point=points[i].AsArray();
for(std::size_t axis=0;axis<3&&axis<point.size();++axis)
edge.controlPointsMicrometres[i][axis]=point[axis].Integer();
}
const auto readTriplet=[&](std::string_view key,std::array<std::int64_t,3>& target)
{
const auto& values=v.Get(key).AsArray();
for(std::size_t axis=0;axis<3&&axis<values.size();++axis)
target[axis]=values[axis].Integer();
};
readTriplet("bounds_min_um",edge.boundsMinimumMicrometres);
readTriplet("bounds_max_um",edge.boundsMaximumMicrometres);
edge.boreRadiusMicrometres=LayoutInt(v,"bore_radius_um");
edge.boreHeightMicrometres=LayoutInt(v,"bore_height_um");
edge.floorSupportDepthMicrometres=LayoutInt(v,"floor_support_depth_um");
edge.smoothWidthMicrometres=LayoutInt(v,"smooth_width_um");
edge.irregularityAmplitudeMicrometres=LayoutInt(v,"irregularity_amplitude_um");
edge.irregularityWavelengthMicrometres=LayoutInt(v,"irregularity_wavelength_um");
edge.irregularitySeed=static_cast<std::uint64_t>(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<std::int32_t>(LayoutInt(v,"strate_index"));
vein.resourceMaterialId=static_cast<std::uint16_t>(LayoutInt(v,"resource_material_id"));
const auto& endpoints=v.Get("endpoints_um").AsArray();
for(std::size_t endpoint=0;endpoint<2&&endpoint<endpoints.size();++endpoint)
{
const auto& values=endpoints[endpoint].AsArray();
for(std::size_t axis=0;axis<3&&axis<values.size();++axis)
vein.endpointsMicrometres[endpoint][axis]=values[axis].Integer();
}
const auto readTriplet=[&](std::string_view key,std::array<std::int64_t,3>& target)
{
const auto& values=v.Get(key).AsArray();
for(std::size_t axis=0;axis<3&&axis<values.size();++axis)
target[axis]=values[axis].Integer();
};
readTriplet("bounds_min_um",vein.boundsMinimumMicrometres);
readTriplet("bounds_max_um",vein.boundsMaximumMicrometres);
vein.radiusMicrometres=LayoutInt(v,"radius_um");
vein.irregularityAmplitudeMicrometres=LayoutInt(v,"irregularity_amplitude_um");
vein.irregularityWavelengthMicrometres=LayoutInt(v,"irregularity_wavelength_um");
vein.blendWidthMicrometres=LayoutInt(v,"blend_width_um");
vein.irregularitySeed=static_cast<std::uint64_t>(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<const ResolvedLayoutStampRequestV1> 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<ResolvedLayoutStampRequestV1> ordered(requests.begin(),requests.end());
std::sort(ordered.begin(),ordered.end(),[](const auto& a,const auto& b)
{return a.stableId<b.stableId;});
ResolvedLayoutSnapshotV3 candidate=layout;
candidate.stampVocabularyVersion=ResolvedLayoutStampVocabularyVersionV1;
candidate.stamps.clear();candidate.stamps.reserve(ordered.size());
for(std::size_t requestIndex=0;requestIndex<ordered.size();++requestIndex)
{
const auto& request=ordered[requestIndex];
if(request.stableId.empty()||(requestIndex>0
&&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<std::uint64_t>(radius)*2+1;
return static_cast<std::int64_t>(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<std::int64_t,3> offset{};
if(request.placementRadiusMicrometres>0)
{
offset={randomOffset(LayoutStampNext(state)),
randomOffset(LayoutStampNext(state)),
randomOffset(LayoutStampNext(state))/3};
const long double radius=static_cast<long double>(request.placementRadiusMicrometres);
const long double distanceSquared=static_cast<long double>(offset[0])*offset[0]
+static_cast<long double>(offset[1])*offset[1]
+static_cast<long double>(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<std::int64_t,3> 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<ResolvedLayoutIssueV2> 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<const ResolvedLayoutStampRequestV1> 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<ResolvedLayoutIssueV2> 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 ResolveResolvedCavernGraphV1(ResolvedLayoutSnapshotV3& layout,std::int32_t seed,
std::span<const ResolvedCavernRoomV1> rooms,
std::span<const ResolvedCavernEdgeV1> 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;
}
candidate.cavernGraphVocabularyVersion=ResolvedLayoutCavernGraphVocabularyVersionV1;
candidate.cavernRooms.assign(rooms.begin(),rooms.end());
candidate.cavernEdges.assign(edges.begin(),edges.end());
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=LayoutStampSeed(seed,room.strateIndex,room.stableId)
&0x001fffffffffffffull;
const std::int64_t pad=room.smoothWidthMicrometres
+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)
{
edge.irregularitySeed=LayoutStampSeed(seed,edge.strateIndex,edge.stableId)
&0x001fffffffffffffull;
std::array<std::int64_t,3> low=edge.controlPointsMicrometres[0];
std::array<std::int64_t,3> high=low;
for(const auto& point:edge.controlPointsMicrometres)
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]);}
const std::int64_t xyPad=edge.boreRadiusMicrometres
+edge.smoothWidthMicrometres+edge.irregularityAmplitudeMicrometres;
const std::int64_t zPad=edge.smoothWidthMicrometres
+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<ResolvedLayoutIssueV2> 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 ResolveResolvedLayoutOreVeinsV1(ResolvedLayoutSnapshotV3& layout,std::int32_t seed,
std::span<const ResolvedLayoutOreVeinRequestV1> 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<ResolvedLayoutOreVeinRequestV1> ordered(requests.begin(),requests.end());
std::sort(ordered.begin(),ordered.end(),[](const auto& a,const auto& b)
{return a.stableId<b.stableId;});
std::vector<std::string> 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<std::uint64_t>(radius)*2+1;
return static_cast<std::int64_t>(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<attempts;++attempt)
{
std::array<std::int64_t,3> 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<long double>(offset[0])*offset[0]
+static_cast<long double>(offset[1])*offset[1]
+static_cast<long double>(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<ResolvedLayoutIssueV2> 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::int64_t>(std::llround(
static_cast<double>(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<ResolvedLayoutIssueV2> 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<std::int64_t,3>& position,
std::int32_t strateIndex,std::array<std::int64_t,3> minimum,
std::array<std::int64_t,3> 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<ResolvedLayoutIssueV2> 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<double>(layout.coreVoxelSizeMicrometres)
/WorldAssemblySubvoxelsPerVoxel;
const std::size_t routeControlIndex=passage->lowerMouthControlIndex>4
?passage->lowerMouthControlIndex-4:0;
std::array<std::int64_t,3> position{};
for(std::size_t axis=0;axis<3;++axis)
position[axis]=static_cast<std::int64_t>(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<std::int64_t,3> 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<ResolvedLayoutPassageConfigV1>& 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<b.order;});
for(std::size_t i=0;i<layout.strates.size();++i)
layout.strates[i].order=static_cast<std::int32_t>(i);
if(layout.coreVoxelSizeMicrometres==0)
{error="passage planner requires the resolved Core voxel size";return false;}
const std::int64_t qPerMetre=static_cast<std::int64_t>(std::llround(
static_cast<double>(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::int64_t>(std::llround(1.13*qPerMetre));
const std::array<std::array<std::int32_t,2>,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<std::int64_t,3>& 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<std::int64_t,2>& 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<std::int64_t,3>& 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(hi<lo)std::swap(hi,lo);
return lo+(hi>lo?static_cast<std::int64_t>(hash%static_cast<std::uint64_t>(hi-lo+1)):0);
};
const auto contentZQ=[&](std::int64_t micrometres)
{return static_cast<std::int64_t>(std::llround(static_cast<double>(micrometres)
*WorldAssemblySubvoxelsPerVoxel/layout.coreVoxelSizeMicrometres));};
const auto distanceQ=[](std::int64_t dx,std::int64_t dy)
{
const auto square=static_cast<std::uint64_t>(dx*dx+dy*dy);
const auto floor=PassageIsqrt(square);return floor*floor==square?floor:floor+1;
};
for(std::size_t interface=0;interface+1<layout.strates.size();++interface)
{
const auto& upper=layout.strates[interface];const auto& lower=layout.strates[interface+1];
const auto* cfg=configFor(upper.strateIndex);
const std::int64_t outer=cfg?std::max(cfg->distanceMinimumSubvoxels,
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<std::int64_t>(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::int64_t>(std::llround(
(upperIsSurfaceWorld?2.0:1.5)*qPerMetre));
const std::int64_t mouthRadius=std::max<std::int64_t>(minimumMouthRadius,
cfg?cfg->mouthRadiusSubvoxels:0);
const std::int64_t midRadius=std::max<std::int64_t>(
static_cast<std::int64_t>(std::llround((upperIsSurfaceWorld?3.5:1.5)*qPerMetre)),
cfg?cfg->midRadiusSubvoxels:mouthRadius);
const std::int64_t reachMinimum=cfg?std::max<std::int64_t>(0,cfg->reachMinimumSubvoxels):8*qPerMetre;
const std::int64_t reachMaximum=cfg?std::max<std::int64_t>(reachMinimum,cfg->reachMaximumSubvoxels):24*qPerMetre;
const std::uint32_t style=cfg?std::min<std::uint32_t>(cfg->style,3u):1u;
const std::uint32_t segments=cfg?std::clamp<std::uint32_t>(cfg->segments,16u,48u):20u;
const std::int64_t wander=cfg?std::max<std::int64_t>(0,cfg->wanderSubvoxels):3*qPerMetre;
const std::int64_t spiralRadius=cfg?std::max<std::int64_t>(0,cfg->spiralRadiusSubvoxels):6*qPerMetre;
const std::int64_t spiralTurnsQ16=cfg?std::max<std::int64_t>(16384,cfg->spiralTurnsQ16):2ll<<16;
const std::int64_t verticalWobble=cfg?std::max<std::int64_t>(0,cfg->verticalWobbleSubvoxels):0;
const std::uint32_t connectionCount=cfg?static_cast<std::uint32_t>(std::clamp(cfg->connections,1,12)):1;
for(std::uint32_t connection=0;connection<connectionCount;++connection)
{
ResolvedLayoutPassageV1 passage;
passage.style=style;
passage.stableId="passage/"+std::to_string(upper.strateIndex)+"-"
+std::to_string(lower.strateIndex)+"/"+std::to_string(connection);
passage.upperStrateIndex=upper.strateIndex;passage.lowerStrateIndex=lower.strateIndex;
passage.placementRadiusSubvoxels=placementRadius;
passage.mouthRadiusSubvoxels=mouthRadius;passage.midRadiusSubvoxels=midRadius;
std::array<std::int64_t,3> 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<std::int64_t,2> 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<std::int64_t>(1,
placementRadius-mouthRadius-qPerMetre/2);
const std::uint32_t pathStyle=anchoredSpiral?1u:style;
std::int64_t ringRadius=outerRingRadius;
if(pathStyle==1u)
ringRadius=std::min<std::int64_t>(outerRingRadius,
std::max<std::int64_t>(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<std::int64_t>(outerRingRadius/3,
std::max<std::int64_t>(0,cfg?cfg->cascadeLedgeSubvoxels:0)
*static_cast<std::int64_t>(cfg?cfg->cascadeSteps:4)),
mouthRadius+2*qPerMetre,outerRingRadius);
if(surfaceGroundDeparture)
{
const double departureRadius=std::hypot(static_cast<double>(upperMouth[0]),
static_cast<double>(upperMouth[1]));
const std::int64_t clearRingRadius=static_cast<std::int64_t>(std::ceil(
departureRadius+mouthRadius+0.5*qPerMetre));
const std::int64_t maximumRingRadius=std::max<std::int64_t>(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<std::int64_t>(midRadius,
passageAnchor->values[3]+2*qPerMetre)
:mouthRadius+qPerMetre/2;
ringRadius=std::min(ringRadius,anchoredSpiral
?mouthRadius+qPerMetre/2:craterRimRouteRadius);
}
std::uint32_t phase=static_cast<std::uint32_t>(PassageSeed(seed,
upper.strateIndex,lower.strateIndex,connection,0x50484153u)%16);
if(hasUpperRoute)
{
std::int64_t bestAlignment=std::numeric_limits<std::int64_t>::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<std::uint32_t>(
(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<bestAlignment
||bestAlignment==std::numeric_limits<std::int64_t>::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<std::int64_t>::min();
for(std::uint32_t candidate=0;candidate<directions.size();++candidate)
{
const std::int64_t alignment=upperMouth[0]*directions[candidate][0]
+upperMouth[1]*directions[candidate][1];
if(alignment>bestOutwardAlignment)
{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<double>(ringRadius)*ringDirection[0]/65536.0;
const double radiusY=static_cast<double>(ringRadius)*ringDirection[1]/65536.0;
const double anchorX=static_cast<double>(passageAnchor->values[0]);
const double anchorY=static_cast<double>(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<double>(upperMouth[0]-passageAnchor->values[0]);
const double radialY=static_cast<double>(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::int64_t>(
std::max<std::int64_t>(0,wander/3),2*qPerMetre);
const std::int64_t wormCenterWobble=std::min<std::int64_t>(
std::max<std::int64_t>(0,wander/6),qPerMetre);
const auto ringPoint=[&](std::uint32_t directionIndex,std::uint32_t loopIndex=0)
{
const double angle=static_cast<double>(directionIndex)*
(3.14159265358979323846/8.0);
double radius=static_cast<double>(ringRadius);
double centerX=0.0,centerY=0.0;
if(pathStyle==1u)
{
const double loop=static_cast<double>(loopIndex);
const double phaseA=loop*0.6180339887498948+0.37;
const double phaseB=loop*0.4142135623730950+1.11;
const double jitterX=static_cast<double>(wormCenterWobble)*std::sin(phaseB);
const double jitterY=static_cast<double>(wormCenterWobble)*std::cos(phaseB);
radius+=static_cast<double>(wormRadialWobble)
*std::sin(angle+phaseA);
centerX=jitterX+(anchoredIslandPassage&&passageAnchor
?static_cast<double>(passageAnchor->values[0]):0.0);
centerY=jitterY+(anchoredIslandPassage&&passageAnchor
?static_cast<double>(passageAnchor->values[1]):0.0);
}
else if(pathStyle==3u)
{
const std::uint32_t steps=std::max<std::uint32_t>(1,
cfg?cfg->cascadeSteps:4u);
const double ledge=std::min<double>(ringRadius/4,
static_cast<double>(cfg?std::max<std::int64_t>(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::uint32_t>(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<std::uint32_t>(PassageSeed(seed,
upper.strateIndex,lower.strateIndex,connection,salt)>>40);
return 2.0*static_cast<double>(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<std::uint32_t>(PassageSeed(seed,
upper.strateIndex,lower.strateIndex,connection,
0x4c454447u+loopIndex)>>40);
const double amplitude=0.60+0.40
*static_cast<double>(loopHash)/16777215.0;
radius-=ledge*amplitude*(0.24+0.10*irregular);
}
if(pathStyle!=1u&&anchoredIslandPassage&&passageAnchor)
{
centerX=static_cast<double>(passageAnchor->values[0]);
centerY=static_cast<double>(passageAnchor->values[1]);
}
return std::array<std::int64_t,2>{
static_cast<std::int64_t>(std::llround(centerX+radius*directions[directionIndex][0]/65536.0)),
static_cast<std::int64_t>(std::llround(centerY+radius*directions[directionIndex][1]/65536.0))};
};
std::vector<std::array<std::int64_t,2>> xy;
const auto pushXY=[&](std::array<std::int64_t,2> 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<helixRadius+midRadius+qPerMetre/4
||std::hypot(static_cast<double>(upperMouth[0]-anchorX),
static_cast<double>(upperMouth[1]-anchorY))
>static_cast<double>(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<double>(upperMouth[2]-helixBedrockZ);
const double pointsPerTurn=static_cast<double>(segments);
const double polygonCircumference=2.0*3.14159265358979323846
*static_cast<double>(helixRadius)
*(std::sin(3.14159265358979323846/pointsPerTurn)
/(3.14159265358979323846/pointsPerTurn));
const double horizontalPerTurn=polygonCircumference
*static_cast<double>(TargetGradeQ16)/65536.0;
const std::uint32_t authoredTurns=static_cast<std::uint32_t>(std::max<std::int64_t>(1,
(spiralTurnsQ16+65535)/65536));
const std::uint32_t gradeTurns=static_cast<std::uint32_t>(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<double>(upperMouth[1]-anchorY),
static_cast<double>(upperMouth[0]-anchorX));
const std::uint32_t helixPoints=turns*segments;
for(std::uint32_t point=1;point<=helixPoints;++point)
{
const double t=static_cast<double>(point)/helixPoints;
const double angle=startAngle+static_cast<double>(clockwise?1:-1)
*2.0*3.14159265358979323846*t*turns;
const double radialT=std::min(1.0,static_cast<double>(point)/segments);
const double mouthOffset=std::clamp(static_cast<double>(passageAnchor->values[3]),
0.0,static_cast<double>(helixRadius));
const double radial=mouthOffset+(static_cast<double>(helixRadius)-mouthOffset)
*radialT;
const std::array<std::int64_t,2> p{
static_cast<std::int64_t>(std::llround(anchorX
+radial*std::cos(angle))),
static_cast<std::int64_t>(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<std::int64_t,2> 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<double>(passageAnchor->values[0]);
const double anchorY=static_cast<double>(passageAnchor->values[1]);
const double startAngle=std::atan2(static_cast<double>(upperMouth[1])-anchorY,
static_cast<double>(upperMouth[0])-anchorX);
const double targetX=static_cast<double>(firstRing[0])-anchorX;
const double targetY=static_cast<double>(firstRing[1])-anchorY;
const double targetAngle=std::atan2(targetY,targetX);
const double angleDelta=static_cast<double>(direction)*2.0
*3.14159265358979323846
+std::remainder(targetAngle-startAngle,2.0*3.14159265358979323846);
const double startRadius=static_cast<double>(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<double>(PassageIsqrt(
static_cast<std::uint64_t>(targetDx*targetDx+targetDy*targetDy)));
const std::uint32_t transitionSegments=static_cast<std::uint32_t>(
std::clamp(std::ceil(std::abs(angleDelta)*(startRadius+targetRadius)
/(2.0*0.75*qPerMetre)),static_cast<double>(segments),256.0));
for(std::uint32_t step=1;step<=transitionSegments;++step)
{
const double t=static_cast<double>(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<std::int64_t,2> point=step==transitionSegments?firstRing:
std::array<std::int64_t,2>{
static_cast<std::int64_t>(std::llround(anchorX+radius*std::cos(angle))),
static_cast<std::int64_t>(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<xy.size();++i)
total+=distanceQ(xy[i][0]-xy[i-1][0],xy[i][1]-xy[i-1][1]);
if(!hasLowerTangent&&!xy.empty())
total+=distanceQ(lowerMouth[0]-xy.back()[0],lowerMouth[1]-xy.back()[1]);
return total;
};
while(loops<256)
{
totalHorizontal=measurePath();
if(totalHorizontal*static_cast<std::uint64_t>(routeGradeQ16)
>=static_cast<std::uint64_t>(verticalDistance)*65536ull)break;
for(int step=1;step<=16;++step)
{
int directionIndex=static_cast<int>(phase)+direction*step;
directionIndex=(directionIndex%16+16)%16;
pushXY(ringPoint(static_cast<std::uint32_t>(directionIndex),
loops+(step==16?1u:0u)));
}
++loops;
}
if(hasLowerTangent)
{
std::uint32_t lowerPhase=static_cast<std::uint32_t>(PassageSeed(seed,
upper.strateIndex,lower.strateIndex,connection,0x4c505248u)%16);
std::int64_t bestAlignment=std::numeric_limits<std::int64_t>::min();
const std::uint32_t hashedLowerPhase=lowerPhase;
for(std::uint32_t offset=0;offset<16;++offset)
{
const std::uint32_t candidate=static_cast<std::uint32_t>(
(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<std::int64_t>(length);
if(score>bestAlignment)
{bestAlignment=score;lowerPhase=candidate;}
}
if(bestAlignment==std::numeric_limits<std::int64_t>::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<int>(phase)+direction*step;
directionIndex=(directionIndex%16+16)%16;
pushXY(ringPoint(static_cast<std::uint32_t>(directionIndex),loops));
if(static_cast<std::uint32_t>(directionIndex)==lowerPhase)break;
}
pushXY(ringPoint(lowerPhase));
}
pushXY({lowerMouth[0],lowerMouth[1]});totalHorizontal=measurePath();
std::size_t lowerMouthIndex=xy.size()-1;
std::vector<std::array<std::int64_t,3>> lowerLandingPoints;
if(hasLowerTangent)
{
std::array<std::int64_t,3> 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<std::int64_t,3> 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<std::int64_t>(remainingQ,
static_cast<std::int64_t>(segmentLength));
std::array<std::int64_t,3> landingPoint{};
for(std::size_t axis=0;axis<3;++axis)
landingPoint[axis]=previousRoutePoint[axis]+PassageRoundRatio(
routePoint[axis]-previousRoutePoint[axis],advance,
static_cast<std::int64_t>(segmentLength));
if(!radialWithin(landingPoint,placementRadius))break;
lowerLandingPoints.push_back(landingPoint);
pushXY({landingPoint[0],landingPoint[1]});
previousRoutePoint=landingPoint;remainingQ-=advance;
if(advance<static_cast<std::int64_t>(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<std::array<std::int64_t,2>> refined;
refined.reserve(xy.size()*2);
for(std::size_t index=0;index+1<xy.size();++index)
{
refined.push_back(xy[index]);
if(index<3)continue;
refined.push_back({
static_cast<std::int64_t>(std::llround(0.5*(
static_cast<double>(xy[index][0])
+static_cast<double>(xy[index+1][0])))),
static_cast<std::int64_t>(std::llround(0.5*(
static_cast<double>(xy[index][1])
+static_cast<double>(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<std::uint64_t> cumulative(xy.size(),0);
for(std::size_t i=1;i<xy.size();++i)
cumulative[i]=cumulative[i-1]+distanceQ(xy[i][0]-xy[i-1][0],xy[i][1]-xy[i-1][1]);
const std::uint64_t mouthHorizontal=cumulative[lowerMouthIndex];
const std::uint64_t helixHorizontal=hasAnchorHelix
?cumulative[helixEndIndex]:0;
if(mouthHorizontal==0)
{error=passage.stableId+" has no horizontal reach before its lower mouth";return false;}
if(hasAnchorHelix&&helixHorizontal==0)
{error=passage.stableId+" anchor spiral has no horizontal reach";return false;}
passage.controlPoints.reserve(xy.size());
const std::int64_t surfacePortalRadius=surfaceGroundDeparture
?std::min(mouthRadius,static_cast<std::int64_t>(std::llround(1.5*qPerMetre)))
:mouthRadius;
const std::uint64_t surfaceMouthBlendLength=static_cast<std::uint64_t>(
std::max<std::int64_t>(8*qPerMetre,2*mouthRadius));
for(std::size_t i=0;i<xy.size();++i)
{
std::int64_t radius=(i<=1||i==lowerMouthIndex
||i+1>=xy.size()-1)?mouthRadius:midRadius;
if(surfaceGroundDeparture&&i<lowerMouthIndex)
{
const double t=std::clamp(static_cast<double>(cumulative[i])
/surfaceMouthBlendLength,0.0,1.0);
const double smooth=t*t*(3.0-2.0*t);
radius=surfacePortalRadius+static_cast<std::int64_t>(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<std::int64_t>(cumulative[i]),
static_cast<std::int64_t>(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<std::int64_t>(cumulative[i]-segmentStart),
static_cast<std::int64_t>(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::int64_t>(std::llround(0.88*qPerMetre)))/static_cast<double>(qPerMetre),
surfacePortalRadius/static_cast<double>(qPerMetre),
midRadius/static_cast<double>(qPerMetre),
surfaceMouthBlendLength/static_cast<double>(qPerMetre),
upperMouth[0]/static_cast<double>(qPerMetre),
upperMouth[1]/static_cast<double>(qPerMetre),
passage.controlPoints.size()>1?passage.controlPoints[1].positionSubvoxels[0]
/static_cast<double>(qPerMetre):0.0,
passage.controlPoints.size()>1?passage.controlPoints[1].positionSubvoxels[1]
/static_cast<double>(qPerMetre):0.0,
passage.controlPoints.size()>1?
(passage.controlPoints[1].positionSubvoxels[2]
-passage.controlPoints[1].radiusSubvoxels+playerCenterAboveFloorQ
-static_cast<std::int64_t>(std::llround(0.88*qPerMetre)))
/static_cast<double>(qPerMetre):0.0,
passage.controlPoints.size()>1?passage.controlPoints[1].radiusSubvoxels
/static_cast<double>(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<ResolvedLayoutIssueV2> 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<ResolvedLayoutIssueV2> 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;
}
}