wip: opt-in landing-pad contract + Core.Liminal.Field v2 pads (L145)
Layers may publish certified arrival/departure pads; the passage planner binds endpoints and tangents to them; no-pad layouts serialize unchanged (owner canonical hashes match v227). Pads removed the endpoint grade failures; seeds 0-4 passed at a 45 m margin, but seed 5 still has 289 blocked passage-capsule samples ~29 m before the departure pad, where the composed field stays solid (-0.162) inside the tube over native ground (-3.6). Ripple audit now keeps true route distance across masked gaps (with a synthetic control). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
1 parent
a45ba9bb10
commit
7d654ff861
14 files changed
+1228
-66
No files matched your search
@@ -599,6 +599,7 @@ namespace VoxelForgeCore
|
||||
|
||||
inline constexpr std::string_view CoreLiminalFieldRecipeIdV1 = "Core.Liminal.Field";
|
||||
inline constexpr std::uint32_t CoreLiminalFieldRecipeVersionV1 = 1;
|
||||
inline constexpr std::uint32_t CoreLiminalFieldRecipeVersionV2 = 2;
|
||||
inline constexpr std::uint32_t CoreLiminalFieldVariantCountV1 = 4;
|
||||
|
||||
enum class WorldAssemblyLiminalVariantV1 : std::uint32_t
|
||||
@@ -1145,6 +1146,27 @@ namespace VoxelForgeCore
|
||||
bool surfaceToTunnelLandingSupport = false;
|
||||
};
|
||||
|
||||
enum class ResolvedLayoutLandingPadRoleV1 : std::uint32_t
|
||||
{
|
||||
arrival = 0,
|
||||
departure = 1
|
||||
};
|
||||
|
||||
struct ResolvedLayoutLandingPadV1
|
||||
{
|
||||
std::string stableId;
|
||||
ResolvedLayoutLandingPadRoleV1 role = ResolvedLayoutLandingPadRoleV1::arrival;
|
||||
// Capsule-center position. floorHeightSubvoxels is the supported floor below it.
|
||||
std::array<std::int64_t, 3> positionSubvoxels{};
|
||||
std::int64_t floorHeightSubvoxels = 0;
|
||||
// Unit horizontal travel direction, signed Q16, oriented into the layer route.
|
||||
std::array<std::int32_t, 2> approachDirectionQ16{};
|
||||
// Certified horizontal free radius around the capsule center and support below the floor.
|
||||
std::int64_t clearanceSubvoxels = 0;
|
||||
std::int64_t supportDepthSubvoxels = 0;
|
||||
bool certifiedWalkableWithSupport = false;
|
||||
};
|
||||
|
||||
struct ResolvedLayoutStratumV2
|
||||
{
|
||||
std::string stableId;
|
||||
@@ -1174,6 +1196,7 @@ namespace VoxelForgeCore
|
||||
std::int64_t managerChunkWidthMicrometres = 0;
|
||||
std::int64_t dispatchChunkWidthMicrometres = 0;
|
||||
std::vector<ResolvedLayoutAnchorV2> anchors;
|
||||
std::vector<ResolvedLayoutLandingPadV1> landingPads;
|
||||
ResolvedLayoutStratePassageCapabilitiesV1 passageCapabilities;
|
||||
ResolvedLayoutInterfaceCapabilitiesV1 lowerInterfaceCapabilities;
|
||||
};
|
||||
@@ -1316,6 +1339,12 @@ namespace VoxelForgeCore
|
||||
VFCORE_API bool PlanCoreLiminalFieldV1(
|
||||
WorldAssemblyRecipeV1& recipe, WorldAssemblyStratumPlanV1& stratum,
|
||||
const ResolvedLayoutSnapshotV3& layout, std::string& error);
|
||||
VFCORE_API bool PrepareCoreLiminalLandingPadsV2(
|
||||
WorldAssemblyRecipeV1& recipe, WorldAssemblyStratumPlanV1& stratum,
|
||||
ResolvedLayoutSnapshotV3& layout, std::string& error);
|
||||
VFCORE_API bool PlanCoreLiminalFieldV2(
|
||||
WorldAssemblyRecipeV1& recipe, WorldAssemblyStratumPlanV1& stratum,
|
||||
const ResolvedLayoutSnapshotV3& layout, std::string& error);
|
||||
|
||||
struct ResolvedLayoutStampPlacementAuditV1
|
||||
{
|
||||
|
||||
@@ -613,7 +613,28 @@ namespace
|
||||
for(std::size_t v=0;v<anchor.values.size();++v){if(v)out<<',';out<<anchor.values[v];}
|
||||
out<<"]}";
|
||||
}
|
||||
out<<"]}";
|
||||
out<<']';
|
||||
if(!x.landingPads.empty())
|
||||
{
|
||||
out<<",\"landing_pads\":[";
|
||||
for(std::size_t p=0;p<x.landingPads.size();++p)
|
||||
{
|
||||
if(p)out<<',';const auto& pad=x.landingPads[p];
|
||||
out<<"{\"stable_id\":"<<LayoutEscape(pad.stableId)
|
||||
<<",\"role\":"<<static_cast<std::uint32_t>(pad.role)
|
||||
<<",\"position_q\":["<<pad.positionSubvoxels[0]<<','
|
||||
<<pad.positionSubvoxels[1]<<','<<pad.positionSubvoxels[2]
|
||||
<<"],\"floor_q\":"<<pad.floorHeightSubvoxels
|
||||
<<",\"approach_q16\":["<<pad.approachDirectionQ16[0]<<','
|
||||
<<pad.approachDirectionQ16[1]<<']'
|
||||
<<",\"clearance_q\":"<<pad.clearanceSubvoxels
|
||||
<<",\"support_depth_q\":"<<pad.supportDepthSubvoxels
|
||||
<<",\"certified\":"
|
||||
<<(pad.certifiedWalkableWithSupport?"true":"false")<<'}';
|
||||
}
|
||||
out<<']';
|
||||
}
|
||||
out<<'}';
|
||||
}
|
||||
out<<"],\"passages\":[";
|
||||
for(std::size_t i=0;i<s.passages.size();++i)
|
||||
@@ -957,6 +978,9 @@ bool ResolvedLayoutSnapshotV3::finalize(std::string& error)
|
||||
for(std::size_t i=0;i<strates.size();++i)
|
||||
{
|
||||
auto& x=strates[i];x.order=static_cast<std::int32_t>(i);
|
||||
std::stable_sort(x.landingPads.begin(),x.landingPads.end(),[](const auto& a,const auto& b)
|
||||
{return a.role!=b.role?static_cast<std::uint32_t>(a.role)
|
||||
<static_cast<std::uint32_t>(b.role):a.stableId<b.stableId;});
|
||||
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";
|
||||
@@ -1017,9 +1041,40 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
|
||||
const auto add=[&](std::string code,std::string detail)
|
||||
{issues.push_back({std::move(code),std::move(detail)});};
|
||||
const bool strict=mode==ResolvedLayoutValidationModeV3::strict;
|
||||
const std::int64_t playerCenterAboveFloorQ=static_cast<std::int64_t>(std::llround(
|
||||
1.13*WorldAssemblySubvoxelsPerVoxel*1000000.0/coreVoxelSizeMicrometres));
|
||||
for(std::size_t i=0;i<strates.size();++i)
|
||||
{
|
||||
const auto& s=strates[i];
|
||||
std::vector<std::string> landingPadIds;
|
||||
for(const auto& pad:s.landingPads)
|
||||
{
|
||||
if(pad.stableId.empty()
|
||||
||std::find(landingPadIds.begin(),landingPadIds.end(),pad.stableId)
|
||||
!=landingPadIds.end())
|
||||
add("landing_pad_identity",s.stableId+" has an empty or duplicate landing-pad ID");
|
||||
else landingPadIds.push_back(pad.stableId);
|
||||
const auto role=static_cast<std::uint32_t>(pad.role);
|
||||
const std::int64_t directionX=pad.approachDirectionQ16[0];
|
||||
const std::int64_t directionY=pad.approachDirectionQ16[1];
|
||||
const std::int64_t directionLengthSquared=directionX*directionX+directionY*directionY;
|
||||
constexpr std::int64_t UnitQ16Squared=1ll<<32;
|
||||
const bool unitDirection=directionLengthSquared>=UnitQ16Squared*98/100
|
||||
&&directionLengthSquared<=UnitQ16Squared*102/100;
|
||||
const std::int64_t floorMicrometres=pad.floorHeightSubvoxels
|
||||
*static_cast<std::int64_t>(coreVoxelSizeMicrometres)
|
||||
/WorldAssemblySubvoxelsPerVoxel;
|
||||
if(role>static_cast<std::uint32_t>(ResolvedLayoutLandingPadRoleV1::departure)
|
||||
||pad.positionSubvoxels[2]
|
||||
!=pad.floorHeightSubvoxels+playerCenterAboveFloorQ
|
||||
||!s.content.presentInCurrentField
|
||||
||floorMicrometres<s.content.bottomMicrometres
|
||||
||floorMicrometres>=s.content.topMicrometres
|
||||
||!unitDirection||pad.clearanceSubvoxels<=0
|
||||
||pad.supportDepthSubvoxels<=0
|
||||
||!pad.certifiedWalkableWithSupport)
|
||||
add("landing_pad_contract",s.stableId+" publishes an uncertified or malformed landing pad");
|
||||
}
|
||||
const std::int64_t effectiveTransitionWidth=LayoutTransitionLimit(s);
|
||||
if(s.interiorTopMicrometres<=s.interiorBottomMicrometres)
|
||||
add("invalid_interior",s.stableId+" has non-positive content height");
|
||||
@@ -1643,6 +1698,22 @@ std::optional<ResolvedLayoutSnapshotV3> ResolvedLayoutSnapshotV3::FromJson(
|
||||
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));}
|
||||
for(const auto& value:v.Get("landing_pads").AsArray())
|
||||
{
|
||||
ResolvedLayoutLandingPadV1 pad;pad.stableId=LayoutString(value,"stable_id");
|
||||
pad.role=static_cast<ResolvedLayoutLandingPadRoleV1>(LayoutInt(value,"role"));
|
||||
const auto& position=value.Get("position_q").AsArray();
|
||||
for(std::size_t axis=0;axis<3&&axis<position.size();++axis)
|
||||
pad.positionSubvoxels[axis]=position[axis].Integer();
|
||||
pad.floorHeightSubvoxels=LayoutInt(value,"floor_q");
|
||||
const auto& approach=value.Get("approach_q16").AsArray();
|
||||
for(std::size_t axis=0;axis<2&&axis<approach.size();++axis)
|
||||
pad.approachDirectionQ16[axis]=static_cast<std::int32_t>(approach[axis].Integer());
|
||||
pad.clearanceSubvoxels=LayoutInt(value,"clearance_q");
|
||||
pad.supportDepthSubvoxels=LayoutInt(value,"support_depth_q");
|
||||
pad.certifiedWalkableWithSupport=LayoutBool(value,"certified");
|
||||
x.landingPads.push_back(std::move(pad));
|
||||
}
|
||||
s.strates.push_back(std::move(x));
|
||||
}
|
||||
for(const auto& v:root.Get("passages").AsArray())
|
||||
@@ -2566,6 +2637,17 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
{return c.upperStrateIndex==strate;});
|
||||
return it==configs.end()?nullptr:&*it;
|
||||
};
|
||||
const auto landingPadFor=[](const ResolvedLayoutStratumV2& stratum,
|
||||
ResolvedLayoutLandingPadRoleV1 role,std::uint32_t ordinal)
|
||||
->const ResolvedLayoutLandingPadV1*
|
||||
{
|
||||
std::vector<const ResolvedLayoutLandingPadV1*> candidates;
|
||||
for(const auto& pad:stratum.landingPads)
|
||||
if(pad.role==role)candidates.push_back(&pad);
|
||||
std::sort(candidates.begin(),candidates.end(),[](const auto* a,const auto* b)
|
||||
{return a->stableId<b->stableId;});
|
||||
return ordinal<candidates.size()?candidates[ordinal]:nullptr;
|
||||
};
|
||||
const auto routeAnchor=[](const ResolvedLayoutStratumV2& s,bool departure,
|
||||
std::array<std::int64_t,3>& out)
|
||||
{
|
||||
@@ -2662,9 +2744,37 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
passage.upperStrateIndex=upper.strateIndex;passage.lowerStrateIndex=lower.strateIndex;
|
||||
passage.placementRadiusSubvoxels=placementRadius;
|
||||
passage.mouthRadiusSubvoxels=mouthRadius;passage.midRadiusSubvoxels=midRadius;
|
||||
const auto* upperDeparturePad=landingPadFor(upper,
|
||||
ResolvedLayoutLandingPadRoleV1::departure,connection);
|
||||
const auto* lowerArrivalPad=landingPadFor(lower,
|
||||
ResolvedLayoutLandingPadRoleV1::arrival,connection);
|
||||
const auto validateLandingPad=[&](const ResolvedLayoutLandingPadV1* pad,
|
||||
std::string_view side)
|
||||
{
|
||||
if(!pad)return true;
|
||||
const std::int64_t requiredClearance=mouthRadius
|
||||
+static_cast<std::int64_t>(std::llround(0.34*qPerMetre));
|
||||
if(!pad->certifiedWalkableWithSupport||pad->clearanceSubvoxels<requiredClearance
|
||||
||pad->supportDepthSubvoxels<static_cast<std::int64_t>(std::llround(0.50*qPerMetre))
|
||||
||!radialWithin(pad->positionSubvoxels,placementRadius)
|
||||
||pad->positionSubvoxels[2]
|
||||
!=pad->floorHeightSubvoxels+playerCenterAboveFloorQ)
|
||||
{
|
||||
error=passage.stableId+" "+std::string(side)
|
||||
+" landing pad is uncertified, unsupported, too tight, or outside PassageConfig.DistanceMax";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
if(!validateLandingPad(upperDeparturePad,"departure")
|
||||
||!validateLandingPad(lowerArrivalPad,"arrival"))return false;
|
||||
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 hasUpperRoute=upperDeparturePad
|
||||
?(upperMouth=upperDeparturePad->positionSubvoxels,true)
|
||||
:connection==0&&routeAnchor(upper,true,upperMouth);
|
||||
const bool hasLowerRoute=lowerArrivalPad
|
||||
?(lowerMouth=lowerArrivalPad->positionSubvoxels,true)
|
||||
:connection==0&&routeAnchor(lower,false,lowerMouth);
|
||||
const bool surfaceGroundDeparture=hasUpperRoute&&upperIsSurfaceWorld;
|
||||
const ResolvedLayoutAnchorV2* passageAnchor=nullptr;
|
||||
if(connection==0)
|
||||
@@ -2713,13 +2823,17 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
|
||||
constexpr std::int64_t LandingExtensionQ=8;
|
||||
std::array<std::int64_t,2> upperTangent{},lowerTangent{};
|
||||
const bool hasUpperTangent=hasUpperRoute
|
||||
&&routeTangent(upper,true,upperTangent);
|
||||
const bool hasUpperTangent=upperDeparturePad
|
||||
?(upperTangent={upperDeparturePad->approachDirectionQ16[0],
|
||||
upperDeparturePad->approachDirectionQ16[1]},true)
|
||||
: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 bool hasLowerTangent=lowerArrivalPad
|
||||
?(lowerTangent={lowerArrivalPad->approachDirectionQ16[0],
|
||||
lowerArrivalPad->approachDirectionQ16[1]},true)
|
||||
:hasLowerRoute&&!hasPassageArrival&&routeTangent(lower,false,lowerTangent);
|
||||
const bool profiledSurfaceJoin=hasLowerTangent
|
||||
&&std::any_of(lower.anchors.begin(),lower.anchors.end(),
|
||||
[](const auto& anchor){return anchor.kind=="heightfield_profile"
|
||||
|
||||
@@ -542,7 +542,8 @@ namespace world_assembly_detail
|
||||
const auto& liminal = stratum.liminalField;
|
||||
if (liminal.version != 0)
|
||||
{
|
||||
if (liminal.version != CoreLiminalFieldRecipeVersionV1
|
||||
if ((liminal.version != CoreLiminalFieldRecipeVersionV1
|
||||
&& liminal.version != CoreLiminalFieldRecipeVersionV2)
|
||||
|| static_cast<std::uint32_t>(liminal.variant) >= CoreLiminalFieldVariantCountV1
|
||||
|| liminal.seed != effectiveRecipe.seed
|
||||
|| liminal.variant != SelectCoreLiminalFieldVariantV1(liminal.seed)
|
||||
@@ -2719,6 +2720,402 @@ bool PlanCoreLiminalFieldV1(WorldAssemblyRecipeV1& recipe,
|
||||
return true;
|
||||
}
|
||||
|
||||
std::array<std::array<std::int64_t,2>,6> CoreLiminalRouteXYV2(
|
||||
std::int64_t startX,std::int64_t startY,std::int64_t finishX,
|
||||
std::int64_t finishY,std::int64_t qPerMetre)
|
||||
{
|
||||
return {{{{startX,startY}},{{-40*qPerMetre,40*qPerMetre}},
|
||||
{{-40*qPerMetre,-40*qPerMetre}},{{40*qPerMetre,-40*qPerMetre}},
|
||||
{{40*qPerMetre,40*qPerMetre}},{{finishX,finishY}}}};
|
||||
}
|
||||
|
||||
std::vector<WorldAssemblyRoutePointV1> BuildCoreLiminalRouteV2(
|
||||
const WorldAssemblySurfaceProfileV1& profile,
|
||||
const ResolvedLayoutLandingPadV1& arrivalPad,
|
||||
const ResolvedLayoutLandingPadV1& departurePad,
|
||||
const WorldAssemblyLiminalVariantDefinitionV1& definition,
|
||||
std::uint32_t seed,std::int32_t strateIndex,std::int64_t coreMicrons,
|
||||
std::int64_t qPerMetre,
|
||||
std::uint64_t& totalHorizontal,std::string& error)
|
||||
{
|
||||
error.clear();totalHorizontal=0;
|
||||
const auto groundHeightAt=[&](std::int64_t x,std::int64_t y)
|
||||
{
|
||||
if(profile.kind==WorldAssemblySurfaceProfileKindV1::flatField)
|
||||
return profile.h0Subvoxels;
|
||||
const std::int64_t turnX=world_assembly_detail::SurfaceProfileTurnAtCoordinate(
|
||||
x,profile.wavelengthXSubvoxels,profile.phaseXTurnsQ32);
|
||||
const std::int64_t turnY=world_assembly_detail::SurfaceProfileTurnAtCoordinate(
|
||||
y,profile.wavelengthYSubvoxels,profile.phaseYTurnsQ32);
|
||||
return profile.h0Subvoxels
|
||||
+world_assembly_detail::MulQ30(profile.amplitudeXSubvoxels,
|
||||
world_assembly_detail::CosineQ30(turnX))
|
||||
+world_assembly_detail::MulQ30(profile.amplitudeYSubvoxels,
|
||||
world_assembly_detail::CosineQ30(turnY));
|
||||
};
|
||||
const auto horizontalLengthQ=[](std::int64_t dx,std::int64_t dy)
|
||||
{return world_assembly_detail::IntegerSqrt(static_cast<std::uint64_t>(dx*dx+dy*dy));};
|
||||
const auto micrometresToQ=[&](std::int64_t micrometres)
|
||||
{return (micrometres*WorldAssemblySubvoxelsPerVoxel+coreMicrons/2)/coreMicrons;};
|
||||
const auto routeXY=CoreLiminalRouteXYV2(arrivalPad.positionSubvoxels[0],
|
||||
arrivalPad.positionSubvoxels[1],departurePad.positionSubvoxels[0],
|
||||
departurePad.positionSubvoxels[1],qPerMetre);
|
||||
std::vector<WorldAssemblyRoutePointV1> route;
|
||||
const std::int64_t pointSpacing=12*qPerMetre;
|
||||
const std::int64_t routeRadius=std::max<std::int64_t>(
|
||||
micrometresToQ(definition.routeHalfWidthMicrometres),micrometresToQ(500000));
|
||||
const auto routeId=[&](std::size_t ordinal)
|
||||
{
|
||||
std::uint64_t id=14695981039346656037ull;
|
||||
const auto add=[&](std::uint64_t value,std::uint32_t bytes)
|
||||
{for(std::uint32_t byte=0;byte<bytes;++byte)
|
||||
{id^=static_cast<std::uint8_t>(value>>(byte*8));id*=1099511628211ull;}};
|
||||
add(seed,4);add(static_cast<std::uint32_t>(strateIndex),4);add(ordinal,4);
|
||||
return id?id:1;
|
||||
};
|
||||
const auto appendRoutePoint=[&](std::int64_t x,std::int64_t y)
|
||||
{
|
||||
WorldAssemblyRoutePointV1 point;
|
||||
point.positionSubvoxels={x,y,groundHeightAt(x,y)};
|
||||
point.radiusSubvoxels=routeRadius;
|
||||
point.stableFeatureId=routeId(route.size());
|
||||
route.push_back(point);
|
||||
};
|
||||
const auto interpolate=[](std::int64_t start,std::int64_t delta,
|
||||
std::uint64_t step,std::uint64_t steps)
|
||||
{
|
||||
const std::int64_t numerator=delta*static_cast<std::int64_t>(step);
|
||||
const std::int64_t divisor=static_cast<std::int64_t>(steps);
|
||||
return start+(numerator>=0?(numerator+divisor/2)/divisor
|
||||
:(numerator-divisor/2)/divisor);
|
||||
};
|
||||
appendRoutePoint(routeXY.front()[0],routeXY.front()[1]);
|
||||
for(std::size_t segment=1;segment<routeXY.size();++segment)
|
||||
{
|
||||
const std::int64_t dx=routeXY[segment][0]-routeXY[segment-1][0];
|
||||
const std::int64_t dy=routeXY[segment][1]-routeXY[segment-1][1];
|
||||
const std::uint64_t length=horizontalLengthQ(dx,dy);
|
||||
if(length==0){error="Core.Liminal.Field v2 route contains a zero-horizontal segment";return {};}
|
||||
totalHorizontal+=length;
|
||||
const std::uint64_t steps=std::max<std::uint64_t>(1,
|
||||
(length+static_cast<std::uint64_t>(pointSpacing)-1)
|
||||
/static_cast<std::uint64_t>(pointSpacing));
|
||||
for(std::uint64_t step=1;step<=steps;++step)
|
||||
appendRoutePoint(interpolate(routeXY[segment-1][0],dx,step,steps),
|
||||
interpolate(routeXY[segment-1][1],dy,step,steps));
|
||||
}
|
||||
if(route.size()<2||totalHorizontal<300*static_cast<std::uint64_t>(qPerMetre)
|
||||
||route.front().positionSubvoxels[2]!=arrivalPad.floorHeightSubvoxels
|
||||
||route.back().positionSubvoxels[2]!=departurePad.floorHeightSubvoxels)
|
||||
{error="Core.Liminal.Field v2 pad-to-pad route is incomplete or too short";return {};}
|
||||
return route;
|
||||
}
|
||||
|
||||
bool PrepareCoreLiminalLandingPadsV2(WorldAssemblyRecipeV1& recipe,
|
||||
WorldAssemblyStratumPlanV1& stratum,ResolvedLayoutSnapshotV3& layout,
|
||||
std::string& error)
|
||||
{
|
||||
error.clear();
|
||||
if(layout.coreVoxelSizeMicrometres==0||stratum.strateIndex<0
|
||||
||stratum.topChunkZ<stratum.bottomChunkZ)
|
||||
{error="Core.Liminal.Field v2 needs a finite stratum and resolved Core voxel size";return false;}
|
||||
auto resolved=std::find_if(layout.strates.begin(),layout.strates.end(),
|
||||
[&](const auto& value){return value.strateIndex==stratum.strateIndex
|
||||
&&value.generatorKind=="SurfaceWorld";});
|
||||
if(resolved==layout.strates.end()||!resolved->content.presentInCurrentField)
|
||||
{error="Core.Liminal.Field v2 must target a resolved SurfaceWorld content band";return false;}
|
||||
|
||||
constexpr std::int64_t Q=WorldAssemblySubvoxelsPerVoxel;
|
||||
const std::int64_t coreMicrons=layout.coreVoxelSizeMicrometres;
|
||||
const auto micrometresToQ=[&](std::int64_t micrometres)
|
||||
{
|
||||
const std::int64_t numerator=micrometres*Q;
|
||||
return numerator>=0?(numerator+coreMicrons/2)/coreMicrons
|
||||
:(numerator-coreMicrons/2)/coreMicrons;
|
||||
};
|
||||
const std::int64_t qPerMetre=micrometresToQ(1000000);
|
||||
if(qPerMetre<=0){error="Core.Liminal.Field v2 could not resolve fixed-scale metres";return false;}
|
||||
|
||||
const std::uint32_t seed=recipe.seed;
|
||||
const WorldAssemblyLiminalVariantV1 variant=SelectCoreLiminalFieldVariantV1(seed);
|
||||
const auto& definition=CoreLiminalFieldVariantsV1[static_cast<std::size_t>(variant)];
|
||||
const std::int64_t bottom=static_cast<std::int64_t>(stratum.bottomChunkZ)
|
||||
*recipe.layoutChunkWidthSubvoxels;
|
||||
const std::int64_t top=(static_cast<std::int64_t>(stratum.topChunkZ)+1)
|
||||
*recipe.layoutChunkWidthSubvoxels;
|
||||
const std::int64_t contentBottom=micrometresToQ(resolved->content.bottomMicrometres);
|
||||
const std::int64_t profileRelief=variant==WorldAssemblyLiminalVariantV1::repeatingHills
|
||||
?2*micrometresToQ(definition.hillAmplitudeMicrometres):0;
|
||||
const std::int64_t deckSweepRadius=static_cast<std::int64_t>(std::ceil(std::hypot(
|
||||
static_cast<double>(definition.routeHalfWidthMicrometres+250000),
|
||||
static_cast<double>(definition.routeHalfThicknessMicrometres+250000))));
|
||||
const std::int64_t floorClearance=definition.routeHalfThicknessMicrometres
|
||||
+deckSweepRadius+250000;
|
||||
// Keep the field above the lower tunnel's mouth transition so the routed
|
||||
// departure has a clean open section before it reaches the cave wall.
|
||||
constexpr std::int64_t MinimumFieldGroundMarginMicrometres=20000000;
|
||||
const std::int64_t fieldGroundMargin=std::max(floorClearance,
|
||||
MinimumFieldGroundMarginMicrometres);
|
||||
const std::int64_t floorHeight=contentBottom+profileRelief
|
||||
+micrometresToQ(fieldGroundMargin);
|
||||
if(floorHeight<=bottom||top<=floorHeight+8*qPerMetre)
|
||||
{error="Core.Liminal.Field v2 has no room for its supported field and cap";return false;}
|
||||
|
||||
WorldAssemblySurfaceProfileV1 profile;
|
||||
profile.version=1;
|
||||
profile.kind=definition.surfaceKind;
|
||||
profile.h0Subvoxels=floorHeight;
|
||||
if(profile.kind==WorldAssemblySurfaceProfileKindV1::periodicHills)
|
||||
{
|
||||
profile.amplitudeXSubvoxels=micrometresToQ(definition.hillAmplitudeMicrometres);
|
||||
profile.amplitudeYSubvoxels=profile.amplitudeXSubvoxels;
|
||||
profile.wavelengthXSubvoxels=micrometresToQ(definition.hillWavelengthMicrometres);
|
||||
profile.wavelengthYSubvoxels=profile.wavelengthXSubvoxels;
|
||||
ResolveWorldAssemblySurfaceProfilePhasesV1(profile,seed);
|
||||
}
|
||||
if(!world_assembly_detail::ValidateSurfaceProfile(profile,seed,error))return false;
|
||||
|
||||
stratum.hasRecipeOverride=true;
|
||||
stratum.recipeOverride=recipe;
|
||||
stratum.recipeOverride.surfaceProfile=profile;
|
||||
stratum.recipeOverride.contentHash=0;
|
||||
stratum.surfaceProfile=profile;
|
||||
stratum.capHeightSubvoxels=top-4*qPerMetre;
|
||||
stratum.capUndulationSubvoxels=0;
|
||||
stratum.capDetailAmplitudeSubvoxels=0;
|
||||
stratum.capRidgeAmplitudeSubvoxels=0;
|
||||
stratum.waterEnabled=false;
|
||||
stratum.waterSurfaceSubvoxels=0;
|
||||
|
||||
const auto groundHeightAt=[&](std::int64_t x,std::int64_t y)
|
||||
{
|
||||
if(profile.kind==WorldAssemblySurfaceProfileKindV1::flatField)
|
||||
return profile.h0Subvoxels;
|
||||
const std::int64_t turnX=world_assembly_detail::SurfaceProfileTurnAtCoordinate(
|
||||
x,profile.wavelengthXSubvoxels,profile.phaseXTurnsQ32);
|
||||
const std::int64_t turnY=world_assembly_detail::SurfaceProfileTurnAtCoordinate(
|
||||
y,profile.wavelengthYSubvoxels,profile.phaseYTurnsQ32);
|
||||
return profile.h0Subvoxels
|
||||
+world_assembly_detail::MulQ30(profile.amplitudeXSubvoxels,
|
||||
world_assembly_detail::CosineQ30(turnX))
|
||||
+world_assembly_detail::MulQ30(profile.amplitudeYSubvoxels,
|
||||
world_assembly_detail::CosineQ30(turnY));
|
||||
};
|
||||
const auto routeXY=CoreLiminalRouteXYV2(-12*qPerMetre,0,12*qPerMetre,0,qPerMetre);
|
||||
const auto squareRoot=[](std::uint64_t value)
|
||||
{return world_assembly_detail::IntegerSqrt(value);};
|
||||
const auto directionQ16=[&](std::int64_t dx,std::int64_t dy)
|
||||
{
|
||||
const std::uint64_t length=squareRoot(static_cast<std::uint64_t>(dx*dx+dy*dy));
|
||||
if(length==0)return std::array<std::int32_t,2>{{0,0}};
|
||||
const auto rounded=[&](std::int64_t value)
|
||||
{return value>=0?(value+static_cast<std::int64_t>(length/2))
|
||||
/static_cast<std::int64_t>(length)
|
||||
:(value-static_cast<std::int64_t>(length/2))
|
||||
/static_cast<std::int64_t>(length);};
|
||||
return std::array<std::int32_t,2>{{
|
||||
static_cast<std::int32_t>(rounded(dx*65536)),
|
||||
static_cast<std::int32_t>(rounded(dy*65536))}};
|
||||
};
|
||||
const auto arrivalDirection=directionQ16(routeXY[1][0]-routeXY[0][0],
|
||||
routeXY[1][1]-routeXY[0][1]);
|
||||
const auto departureDirection=directionQ16(routeXY.back()[0]-routeXY[routeXY.size()-2][0],
|
||||
routeXY.back()[1]-routeXY[routeXY.size()-2][1]);
|
||||
const std::int64_t playerCenterAboveFloor=micrometresToQ(1130000);
|
||||
const std::int64_t landingClearance=micrometresToQ(6000000);
|
||||
const std::int64_t supportDepth=micrometresToQ(2000000);
|
||||
const auto makePad=[&](std::string id,ResolvedLayoutLandingPadRoleV1 role,
|
||||
const std::array<std::int64_t,2>& xy,const std::array<std::int32_t,2>& direction)
|
||||
{
|
||||
ResolvedLayoutLandingPadV1 pad;
|
||||
pad.stableId=std::move(id);pad.role=role;
|
||||
pad.floorHeightSubvoxels=groundHeightAt(xy[0],xy[1]);
|
||||
pad.positionSubvoxels={xy[0],xy[1],pad.floorHeightSubvoxels+playerCenterAboveFloor};
|
||||
pad.approachDirectionQ16=direction;
|
||||
pad.clearanceSubvoxels=landingClearance;
|
||||
pad.supportDepthSubvoxels=supportDepth;
|
||||
pad.certifiedWalkableWithSupport=true;
|
||||
return pad;
|
||||
};
|
||||
resolved->recipeId=std::string(CoreLiminalFieldRecipeIdV1);
|
||||
resolved->recipeVersion=CoreLiminalFieldRecipeVersionV2;
|
||||
resolved->landingPads.clear();
|
||||
resolved->landingPads.push_back(makePad("core/liminal-field/landing/arrival/000",
|
||||
ResolvedLayoutLandingPadRoleV1::arrival,routeXY.front(),arrivalDirection));
|
||||
resolved->landingPads.push_back(makePad("core/liminal-field/landing/departure/000",
|
||||
ResolvedLayoutLandingPadRoleV1::departure,routeXY.back(),departureDirection));
|
||||
std::uint64_t routeLength=0;
|
||||
stratum.route=BuildCoreLiminalRouteV2(profile,resolved->landingPads[0],
|
||||
resolved->landingPads[1],definition,seed,stratum.strateIndex,coreMicrons,
|
||||
qPerMetre,routeLength,error);
|
||||
if(!error.empty())return false;
|
||||
stratum.routeConstraints={1,micrometresToQ(340000),micrometresToQ(1760000),
|
||||
17560,micrometresToQ(125000),micrometresToQ(250000),
|
||||
micrometresToQ(1000000),micrometresToQ(120000),3};
|
||||
resolved->anchors.erase(std::remove_if(resolved->anchors.begin(),resolved->anchors.end(),
|
||||
[](const auto& anchor){return anchor.kind=="route_point"
|
||||
||anchor.kind=="liminal_route_point"||anchor.kind=="liminal_variant"
|
||||
||anchor.kind=="heightfield_profile";}),resolved->anchors.end());
|
||||
ResolvedLayoutAnchorV2 profileAnchor;
|
||||
profileAnchor.kind="heightfield_profile";
|
||||
profileAnchor.stableId="heightfield-profile/liminal/"
|
||||
+std::to_string(static_cast<std::uint32_t>(profile.kind));
|
||||
profileAnchor.units="profile kind enum";
|
||||
profileAnchor.values={1,static_cast<std::int64_t>(profile.kind)};
|
||||
resolved->anchors.push_back(std::move(profileAnchor));
|
||||
std::uint64_t routeAnchorDistance=0,lastRouteAnchorDistance=0;
|
||||
for(std::size_t pointIndex=0;pointIndex<stratum.route.size();++pointIndex)
|
||||
{
|
||||
if(pointIndex>0)
|
||||
{
|
||||
const auto& previous=stratum.route[pointIndex-1].positionSubvoxels;
|
||||
const auto& current=stratum.route[pointIndex].positionSubvoxels;
|
||||
const auto dx=static_cast<std::uint64_t>(current[0]-previous[0]);
|
||||
const auto dy=static_cast<std::uint64_t>(current[1]-previous[1]);
|
||||
routeAnchorDistance+=world_assembly_detail::IntegerSqrt(dx*dx+dy*dy);
|
||||
}
|
||||
const bool first=pointIndex==0;
|
||||
const bool last=pointIndex+1==stratum.route.size();
|
||||
const bool spaced=routeAnchorDistance-lastRouteAnchorDistance
|
||||
>=static_cast<std::uint64_t>(8*qPerMetre);
|
||||
if(!first&&!last&&!spaced)continue;
|
||||
const auto& point=stratum.route[pointIndex];
|
||||
ResolvedLayoutAnchorV2 routeAnchor;
|
||||
routeAnchor.kind="route_point";
|
||||
routeAnchor.stableId="route/"+std::to_string(pointIndex);
|
||||
routeAnchor.units="1/256 Core voxel";
|
||||
routeAnchor.values={point.positionSubvoxels[0],point.positionSubvoxels[1],
|
||||
point.positionSubvoxels[2]+playerCenterAboveFloor,point.radiusSubvoxels};
|
||||
resolved->anchors.push_back(std::move(routeAnchor));
|
||||
lastRouteAnchorDistance=routeAnchorDistance;
|
||||
}
|
||||
if(!layout.finalize(error))return false;
|
||||
const auto reportPad=[&](const ResolvedLayoutLandingPadV1& pad)
|
||||
{
|
||||
std::printf("[ResolvedLandingPad] strate=%d id=%s role=%s position_m=(%.3f,%.3f,%.3f) "
|
||||
"floor_m=%.3f approach_q16=(%d,%d) clearance_m=%.3f support_m=%.3f "
|
||||
"certified_walkable_with_support=true status=PASS\n",stratum.strateIndex,
|
||||
pad.stableId.c_str(),pad.role==ResolvedLayoutLandingPadRoleV1::arrival?"arrival":"departure",
|
||||
pad.positionSubvoxels[0]/static_cast<double>(qPerMetre),
|
||||
pad.positionSubvoxels[1]/static_cast<double>(qPerMetre),
|
||||
pad.positionSubvoxels[2]/static_cast<double>(qPerMetre),
|
||||
pad.floorHeightSubvoxels/static_cast<double>(qPerMetre),
|
||||
pad.approachDirectionQ16[0],pad.approachDirectionQ16[1],
|
||||
pad.clearanceSubvoxels/static_cast<double>(qPerMetre),
|
||||
pad.supportDepthSubvoxels/static_cast<double>(qPerMetre));
|
||||
};
|
||||
reportPad(resolved->landingPads[0]);reportPad(resolved->landingPads[1]);
|
||||
std::printf("[LiminalPadRoute] points=%zu path_length_m=%.3f spacing_max_m=4.000 "
|
||||
"profile_ground_following=true status=PASS\n",stratum.route.size(),
|
||||
routeLength/static_cast<double>(qPerMetre));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlanCoreLiminalFieldV2(WorldAssemblyRecipeV1& recipe,
|
||||
WorldAssemblyStratumPlanV1& stratum,const ResolvedLayoutSnapshotV3& layout,
|
||||
std::string& error)
|
||||
{
|
||||
error.clear();
|
||||
if(layout.coreVoxelSizeMicrometres==0||stratum.strateIndex<0
|
||||
||stratum.topChunkZ<stratum.bottomChunkZ)
|
||||
{error="Core.Liminal.Field v2 needs a finite stratum and resolved Core voxel size";return false;}
|
||||
const auto resolved=std::find_if(layout.strates.begin(),layout.strates.end(),
|
||||
[&](const auto& value){return value.strateIndex==stratum.strateIndex
|
||||
&&value.recipeId==CoreLiminalFieldRecipeIdV1
|
||||
&&value.recipeVersion==CoreLiminalFieldRecipeVersionV2;});
|
||||
if(resolved==layout.strates.end()||!resolved->content.presentInCurrentField
|
||||
||stratum.surfaceProfile.version!=1)
|
||||
{error="Core.Liminal.Field v2 needs its prepared profile and resolved landing pads";return false;}
|
||||
const ResolvedLayoutLandingPadV1* arrivalPad=nullptr;
|
||||
const ResolvedLayoutLandingPadV1* departurePad=nullptr;
|
||||
for(const auto& pad:resolved->landingPads)
|
||||
{
|
||||
if(pad.role==ResolvedLayoutLandingPadRoleV1::arrival)
|
||||
{if(arrivalPad){error="Core.Liminal.Field v2 has multiple arrival pads";return false;}arrivalPad=&pad;}
|
||||
if(pad.role==ResolvedLayoutLandingPadRoleV1::departure)
|
||||
{if(departurePad){error="Core.Liminal.Field v2 has multiple departure pads";return false;}departurePad=&pad;}
|
||||
}
|
||||
if(!arrivalPad||!departurePad||!arrivalPad->certifiedWalkableWithSupport
|
||||
||!departurePad->certifiedWalkableWithSupport)
|
||||
{error="Core.Liminal.Field v2 requires certified arrival and departure pads";return false;}
|
||||
|
||||
const ResolvedLayoutPassageV1* arrival=nullptr;
|
||||
const ResolvedLayoutPassageV1* departure=nullptr;
|
||||
for(const auto& passage:layout.passages)
|
||||
{
|
||||
if(passage.lowerStrateIndex==stratum.strateIndex)
|
||||
{if(arrival){error="Core.Liminal.Field v2 requires one arrival passage";return false;}arrival=&passage;}
|
||||
if(passage.upperStrateIndex==stratum.strateIndex)
|
||||
{if(departure){error="Core.Liminal.Field v2 requires one departure passage";return false;}departure=&passage;}
|
||||
}
|
||||
if(!arrival||!departure||arrival->lowerMouthSubvoxels!=arrivalPad->positionSubvoxels
|
||||
||departure->upperMouthSubvoxels!=departurePad->positionSubvoxels)
|
||||
{error="Core.Liminal.Field v2 passage mouths are not bound to the certified layer pads";return false;}
|
||||
|
||||
constexpr std::int64_t Q=WorldAssemblySubvoxelsPerVoxel;
|
||||
const std::int64_t coreMicrons=layout.coreVoxelSizeMicrometres;
|
||||
const auto micrometresToQ=[&](std::int64_t micrometres)
|
||||
{
|
||||
const std::int64_t numerator=micrometres*Q;
|
||||
return numerator>=0?(numerator+coreMicrons/2)/coreMicrons
|
||||
:(numerator-coreMicrons/2)/coreMicrons;
|
||||
};
|
||||
const std::int64_t qPerMetre=micrometresToQ(1000000);
|
||||
const std::int64_t playerCenterAboveFloor=micrometresToQ(1130000);
|
||||
if(qPerMetre<=0||arrivalPad->positionSubvoxels[2]
|
||||
!=arrivalPad->floorHeightSubvoxels+playerCenterAboveFloor
|
||||
||departurePad->positionSubvoxels[2]
|
||||
!=departurePad->floorHeightSubvoxels+playerCenterAboveFloor)
|
||||
{error="Core.Liminal.Field v2 pad floor and capsule-center heights differ";return false;}
|
||||
|
||||
const std::uint32_t seed=recipe.seed;
|
||||
const WorldAssemblyLiminalVariantV1 variant=SelectCoreLiminalFieldVariantV1(seed);
|
||||
const auto& definition=CoreLiminalFieldVariantsV1[static_cast<std::size_t>(variant)];
|
||||
const auto& profile=stratum.surfaceProfile;
|
||||
std::uint64_t totalHorizontal=0;
|
||||
std::vector<WorldAssemblyRoutePointV1> route=BuildCoreLiminalRouteV2(profile,
|
||||
*arrivalPad,*departurePad,definition,seed,stratum.strateIndex,coreMicrons,
|
||||
qPerMetre,totalHorizontal,error);
|
||||
if(!error.empty())return false;
|
||||
if(route.size()!=stratum.route.size())
|
||||
{error="Core.Liminal.Field v2 route differs from the path used to certify its pads";return false;}
|
||||
for(std::size_t index=0;index<route.size();++index)
|
||||
if(route[index].stableFeatureId!=stratum.route[index].stableFeatureId
|
||||
||route[index].positionSubvoxels!=stratum.route[index].positionSubvoxels
|
||||
||route[index].radiusSubvoxels!=stratum.route[index].radiusSubvoxels)
|
||||
{error="Core.Liminal.Field v2 route differs from the path used to certify its pads";return false;}
|
||||
|
||||
WorldAssemblyLiminalFieldPlanV1 liminal;
|
||||
liminal.version=CoreLiminalFieldRecipeVersionV2;
|
||||
liminal.variant=variant;liminal.seed=seed;
|
||||
liminal.arrivalMouthSubvoxels=arrivalPad->positionSubvoxels;
|
||||
liminal.departureMouthSubvoxels=departurePad->positionSubvoxels;
|
||||
liminal.floorHeightSubvoxels=profile.h0Subvoxels;
|
||||
liminal.routeHalfWidthSubvoxels=micrometresToQ(definition.routeHalfWidthMicrometres);
|
||||
liminal.routeHalfThicknessSubvoxels=micrometresToQ(definition.routeHalfThicknessMicrometres);
|
||||
liminal.routeOffsetSubvoxels=150*qPerMetre;
|
||||
liminal.supportSpacingSubvoxels=micrometresToQ(definition.supportSpacingMicrometres);
|
||||
liminal.supportRadiusSubvoxels=micrometresToQ(definition.supportRadiusMicrometres);
|
||||
liminal.mouthFloorOffsetSubvoxels=playerCenterAboveFloor;
|
||||
stratum.route=std::move(route);
|
||||
stratum.routeConstraints={1,micrometresToQ(340000),micrometresToQ(1760000),
|
||||
17560,micrometresToQ(125000),micrometresToQ(250000),
|
||||
micrometresToQ(1000000),micrometresToQ(120000),3};
|
||||
stratum.liminalField=liminal;
|
||||
std::string validationError;
|
||||
if(!world_assembly_detail::ValidateSurfaceProfile(stratum.surfaceProfile,seed,validationError)
|
||||
||!world_assembly_detail::ValidateStratum(stratum,recipe,validationError))
|
||||
{error=validationError.empty()?"Core.Liminal.Field v2 produced an invalid plan":validationError;return false;}
|
||||
std::printf("[CoreLiminalFieldPlan] recipe=%.*s version=%u variant=%.*s seed=%u "
|
||||
"arrival_pad=%s departure_pad=%s route_points=%zu path_length_m=%.3f "
|
||||
"profile_ground_following=true route_grade_limit_deg=15.000 status=PASS\n",
|
||||
static_cast<int>(CoreLiminalFieldRecipeIdV1.size()),CoreLiminalFieldRecipeIdV1.data(),
|
||||
liminal.version,static_cast<int>(definition.name.size()),definition.name.data(),seed,
|
||||
arrivalPad->stableId.c_str(),departurePad->stableId.c_str(),stratum.route.size(),
|
||||
totalHorizontal/static_cast<double>(qPerMetre));
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string WorldAssemblyManifest::ToJson() const
|
||||
{
|
||||
std::string error;
|
||||
|
||||
Reference in new issue
Block a user