wip: Core.Liminal.Field v1 recipe, 4 seed-selected variants between fixed mouths (L144, fails mouth joins)
Plans and draws flat field+monolith, repeating hills, road over cloud floor and pillar grid in a 3-strata fixture, binding passage/0-1 and 1-2 as fixed endpoints. Composed W1 probes: 3/4 seeds fail one passage interface, 4/4 fail the liminal walk audit near the mouths (support itself passes). Stopped per AGENTS.md. Same mouth-ownership failure class as FI (L133/L140) and VS seed 2 (L139). 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
a4e214561f
commit
a45ba9bb10
18 files changed
+1547
-43
No files matched your search
@@ -597,6 +597,61 @@ namespace VoxelForgeCore
|
||||
}
|
||||
}
|
||||
|
||||
inline constexpr std::string_view CoreLiminalFieldRecipeIdV1 = "Core.Liminal.Field";
|
||||
inline constexpr std::uint32_t CoreLiminalFieldRecipeVersionV1 = 1;
|
||||
inline constexpr std::uint32_t CoreLiminalFieldVariantCountV1 = 4;
|
||||
|
||||
enum class WorldAssemblyLiminalVariantV1 : std::uint32_t
|
||||
{
|
||||
flatFieldMonolith = 0,
|
||||
repeatingHills = 1,
|
||||
roadOverCloudFloor = 2,
|
||||
pillarGrid = 3
|
||||
};
|
||||
|
||||
struct WorldAssemblyLiminalVariantDefinitionV1
|
||||
{
|
||||
WorldAssemblyLiminalVariantV1 variant = WorldAssemblyLiminalVariantV1::flatFieldMonolith;
|
||||
std::string_view name;
|
||||
WorldAssemblySurfaceProfileKindV1 surfaceKind = WorldAssemblySurfaceProfileKindV1::flatField;
|
||||
std::int64_t hillAmplitudeMicrometres = 0;
|
||||
std::int64_t hillWavelengthMicrometres = 0;
|
||||
std::int64_t routeHalfWidthMicrometres = 0;
|
||||
std::int64_t routeHalfThicknessMicrometres = 0;
|
||||
std::int64_t supportSpacingMicrometres = 0;
|
||||
std::int64_t supportRadiusMicrometres = 0;
|
||||
std::int64_t monolithRadiusMicrometres = 0;
|
||||
std::int64_t monolithHeightMicrometres = 0;
|
||||
std::uint32_t pillarRows = 0;
|
||||
std::uint32_t pillarColumns = 0;
|
||||
std::int64_t pillarSpacingMicrometres = 0;
|
||||
std::int64_t pillarRadiusMicrometres = 0;
|
||||
std::int64_t pillarHeightMicrometres = 0;
|
||||
};
|
||||
|
||||
inline constexpr std::array<WorldAssemblyLiminalVariantDefinitionV1,
|
||||
CoreLiminalFieldVariantCountV1> CoreLiminalFieldVariantsV1{{
|
||||
{WorldAssemblyLiminalVariantV1::flatFieldMonolith, "flatField-monolith",
|
||||
WorldAssemblySurfaceProfileKindV1::flatField, 0, 0, 1750000, 500000,
|
||||
36000000, 550000, 1600000, 26000000, 0, 0, 0, 0, 0},
|
||||
{WorldAssemblyLiminalVariantV1::repeatingHills, "periodicHills",
|
||||
WorldAssemblySurfaceProfileKindV1::periodicHills, 2200000, 80000000,
|
||||
1750000, 500000, 36000000, 550000, 0, 0, 0, 0, 0, 0, 0},
|
||||
{WorldAssemblyLiminalVariantV1::roadOverCloudFloor, "road-over-cloud-floor",
|
||||
WorldAssemblySurfaceProfileKindV1::flatField, 0, 0, 2000000, 500000,
|
||||
24000000, 550000, 0, 0, 0, 0, 0, 0, 0},
|
||||
{WorldAssemblyLiminalVariantV1::pillarGrid, "pillar-grid",
|
||||
WorldAssemblySurfaceProfileKindV1::flatField, 0, 0, 1750000, 500000,
|
||||
36000000, 550000, 0, 0, 15, 15, 76000000, 5500000, 40000000}
|
||||
}};
|
||||
|
||||
inline constexpr WorldAssemblyLiminalVariantV1 SelectCoreLiminalFieldVariantV1(
|
||||
std::uint32_t seed) noexcept
|
||||
{
|
||||
return static_cast<WorldAssemblyLiminalVariantV1>(
|
||||
seed % CoreLiminalFieldVariantCountV1);
|
||||
}
|
||||
|
||||
struct WorldAssemblyRecipeV1
|
||||
{
|
||||
std::uint32_t schemaVersion = WorldAssemblySchemaVersion;
|
||||
@@ -654,6 +709,23 @@ namespace VoxelForgeCore
|
||||
std::array<std::int64_t, 4> parametersSubvoxels{};
|
||||
};
|
||||
|
||||
struct WorldAssemblyLiminalFieldPlanV1
|
||||
{
|
||||
std::uint32_t version = 0;
|
||||
WorldAssemblyLiminalVariantV1 variant = WorldAssemblyLiminalVariantV1::flatFieldMonolith;
|
||||
std::uint32_t seed = 0;
|
||||
std::array<std::int64_t, 3> arrivalMouthSubvoxels{};
|
||||
std::array<std::int64_t, 3> departureMouthSubvoxels{};
|
||||
// Fixed distance from each passage player-center anchor to its walk surface.
|
||||
std::int64_t mouthFloorOffsetSubvoxels = 0;
|
||||
std::int64_t floorHeightSubvoxels = 0;
|
||||
std::int64_t routeHalfWidthSubvoxels = 0;
|
||||
std::int64_t routeHalfThicknessSubvoxels = 0;
|
||||
std::int64_t routeOffsetSubvoxels = 0;
|
||||
std::int64_t supportSpacingSubvoxels = 0;
|
||||
std::int64_t supportRadiusSubvoxels = 0;
|
||||
};
|
||||
|
||||
struct WorldAssemblyStratumPlanV1
|
||||
{
|
||||
std::int32_t strateIndex = -1;
|
||||
@@ -678,6 +750,7 @@ namespace VoxelForgeCore
|
||||
std::vector<WorldAssemblyFeatureV1> features;
|
||||
WorldAssemblyRouteConstraintsV1 routeConstraints;
|
||||
std::vector<WorldAssemblyRoutePointV1> route;
|
||||
WorldAssemblyLiminalFieldPlanV1 liminalField;
|
||||
std::uint64_t contentHash = 0;
|
||||
};
|
||||
|
||||
@@ -893,6 +966,7 @@ namespace VoxelForgeCore
|
||||
inline constexpr std::uint32_t ResolvedLayoutStampVocabularyVersionV3 = 3;
|
||||
inline constexpr std::uint32_t ResolvedLayoutStampVocabularyVersionV4 = 4;
|
||||
inline constexpr std::uint32_t ResolvedLayoutStampVocabularyVersionV5 = 5;
|
||||
inline constexpr std::uint32_t ResolvedLayoutStampVocabularyVersionV6 = 6;
|
||||
inline constexpr std::uint32_t ResolvedLayoutCavernGraphVocabularyVersionV1 = 1;
|
||||
inline constexpr std::uint32_t ResolvedLayoutOreVeinVocabularyVersionV1 = 1;
|
||||
enum class ResolvedLayoutValidationModeV3 : std::uint8_t { compatibility, strict };
|
||||
@@ -920,6 +994,7 @@ namespace VoxelForgeCore
|
||||
// Present only for fillSweptDeckSegment; defines its directed centerline.
|
||||
std::array<std::array<std::int64_t, 3>, 2> sweepEndpointsMicrometres{};
|
||||
// V5 permits one continuous swept deck to carry its complete sampled centerline.
|
||||
// V6 also permits a path whose X coordinates are not strictly increasing.
|
||||
// Empty retains the V4 single-segment interpretation.
|
||||
std::vector<std::array<std::int64_t, 3>> sweepPathMicrometres;
|
||||
std::int64_t placementRadiusMicrometres = 0;
|
||||
@@ -1238,6 +1313,9 @@ namespace VoxelForgeCore
|
||||
ResolvedLayoutSnapshotV3& layout, std::int32_t seed,
|
||||
const std::vector<ResolvedLayoutPassageConfigV1>& configs,
|
||||
std::string& error);
|
||||
VFCORE_API bool PlanCoreLiminalFieldV1(
|
||||
WorldAssemblyRecipeV1& recipe, WorldAssemblyStratumPlanV1& stratum,
|
||||
const ResolvedLayoutSnapshotV3& layout, std::string& error);
|
||||
|
||||
struct ResolvedLayoutStampPlacementAuditV1
|
||||
{
|
||||
|
||||
@@ -249,6 +249,71 @@ namespace
|
||||
/WorldAssemblySubvoxelsPerVoxel));
|
||||
return result;
|
||||
};
|
||||
const bool liminalRouteStamp=strate->recipeId==CoreLiminalFieldRecipeIdV1
|
||||
&&stamp.stableId.starts_with("core/liminal-field/route/")
|
||||
&&(stamp.kind==ResolvedSdfStampKindV1::fillSweptDeckSegment
|
||||
||stamp.kind==ResolvedSdfStampKindV1::fillPillar);
|
||||
if(liminalRouteStamp)
|
||||
{
|
||||
const auto variant=std::find_if(strate->anchors.begin(),strate->anchors.end(),
|
||||
[](const auto& anchor){return anchor.kind=="liminal_variant"
|
||||
&&anchor.values.size()>=6;});
|
||||
std::vector<const ResolvedLayoutAnchorV2*> points;
|
||||
for(const auto& anchor:strate->anchors)
|
||||
if(anchor.kind=="liminal_route_point"&&anchor.values.size()>=4)
|
||||
points.push_back(&anchor);
|
||||
if(variant==strate->anchors.end()||points.size()<2)
|
||||
{if(reason)*reason="Core.Liminal.Field route stamp has no versioned fixed-mouth route";return false;}
|
||||
const auto routePoint=[&](const ResolvedLayoutAnchorV2& anchor)
|
||||
{
|
||||
std::array<std::int64_t,3> q={anchor.values[0],anchor.values[1],anchor.values[2]};
|
||||
return qToMicrometres(q);
|
||||
};
|
||||
const std::int64_t halfThickness=static_cast<std::int64_t>(std::llround(
|
||||
static_cast<double>(variant->values[5])*coreMicrometres
|
||||
/WorldAssemblySubvoxelsPerVoxel));
|
||||
if(stamp.kind==ResolvedSdfStampKindV1::fillSweptDeckSegment)
|
||||
{
|
||||
const auto first=routePoint(*points.front());
|
||||
const auto last=routePoint(*points.back());
|
||||
if(stamp.sweepPathMicrometres.size()<2
|
||||
||stamp.sweepPathMicrometres.front()[0]!=first[0]
|
||||
||stamp.sweepPathMicrometres.front()[1]!=first[1]
|
||||
||stamp.sweepPathMicrometres.front()[2]!=first[2]-halfThickness
|
||||
||stamp.sweepPathMicrometres.back()[0]!=last[0]
|
||||
||stamp.sweepPathMicrometres.back()[1]!=last[1]
|
||||
||stamp.sweepPathMicrometres.back()[2]!=last[2]-halfThickness)
|
||||
{if(reason)*reason="Core.Liminal.Field deck does not terminate at its fixed passage mouths";return false;}
|
||||
return true;
|
||||
}
|
||||
const auto deck=std::find_if(layout.stamps.begin(),layout.stamps.end(),
|
||||
[&](const auto& existing){return existing.strateIndex==stamp.strateIndex
|
||||
&&existing.stableId=="core/liminal-field/route/deck/000"
|
||||
&&existing.kind==ResolvedSdfStampKindV1::fillSweptDeckSegment;});
|
||||
if(deck==layout.stamps.end()||deck->sweepPathMicrometres.size()<2)
|
||||
{if(reason)*reason="Core.Liminal.Field support has no previously resolved route deck";return false;}
|
||||
double distance=std::numeric_limits<double>::infinity();
|
||||
for(std::size_t i=1;i<deck->sweepPathMicrometres.size();++i)
|
||||
{
|
||||
const auto& a=deck->sweepPathMicrometres[i-1];
|
||||
const auto& b=deck->sweepPathMicrometres[i];
|
||||
const double dx=static_cast<double>(b[0]-a[0]);
|
||||
const double dy=static_cast<double>(b[1]-a[1]);
|
||||
const double lengthSquared=dx*dx+dy*dy;
|
||||
const double t=lengthSquared>0.0?std::clamp(
|
||||
((stamp.positionMicrometres[0]-a[0])*dx
|
||||
+(stamp.positionMicrometres[1]-a[1])*dy)/lengthSquared,
|
||||
0.0,1.0):0.0;
|
||||
const double offsetX=stamp.positionMicrometres[0]-(a[0]+t*dx);
|
||||
const double offsetY=stamp.positionMicrometres[1]-(a[1]+t*dy);
|
||||
distance=std::min(distance,std::hypot(offsetX,offsetY));
|
||||
}
|
||||
const double corridorRadius=deck->halfExtentsMicrometres[1]
|
||||
+stamp.halfExtentsMicrometres[0]+RouteClearanceMicrometres;
|
||||
if(distance>corridorRadius)
|
||||
{if(reason)*reason="Core.Liminal.Field route stamp is outside its versioned route corridor";return false;}
|
||||
return true;
|
||||
}
|
||||
for(const auto& passage:layout.passages)
|
||||
{
|
||||
if(passage.upperStrateIndex!=stamp.strateIndex
|
||||
@@ -1158,8 +1223,9 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
|
||||
&&stampVocabularyVersion!=ResolvedLayoutStampVocabularyVersionV2
|
||||
&&stampVocabularyVersion!=ResolvedLayoutStampVocabularyVersionV3
|
||||
&&stampVocabularyVersion!=ResolvedLayoutStampVocabularyVersionV4
|
||||
&&stampVocabularyVersion!=ResolvedLayoutStampVocabularyVersionV5)
|
||||
add("stamp_vocabulary_version","resolved stamps require supported vocabulary version 1 through 5");
|
||||
&&stampVocabularyVersion!=ResolvedLayoutStampVocabularyVersionV5
|
||||
&&stampVocabularyVersion!=ResolvedLayoutStampVocabularyVersionV6)
|
||||
add("stamp_vocabulary_version","resolved stamps require supported vocabulary version 1 through 6");
|
||||
std::vector<std::string> stampIds;
|
||||
for(const auto& stamp:stamps)
|
||||
{
|
||||
@@ -1217,11 +1283,15 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
|
||||
validPath=validPath&&coordinate>=-1000000000000ll
|
||||
&&coordinate<=1000000000000ll;
|
||||
if(pointIndex>0)
|
||||
validPath=validPath
|
||||
&&stamp.sweepPathMicrometres[pointIndex-1][0]<point[0];
|
||||
{
|
||||
const auto& previous=stamp.sweepPathMicrometres[pointIndex-1];
|
||||
validPath=validPath&&previous!=point
|
||||
&&(stampVocabularyVersion>=ResolvedLayoutStampVocabularyVersionV6
|
||||
||previous[0]<point[0]);
|
||||
}
|
||||
}
|
||||
if(!validPath)add("stamp_sweep_path",stamp.stableId
|
||||
+" has an invalid V5 path or non-increasing longitudinal axis");
|
||||
+" has an invalid swept-deck path for its vocabulary version");
|
||||
}
|
||||
}
|
||||
if(motif)
|
||||
@@ -1749,10 +1819,20 @@ bool ResolveResolvedLayoutStampsV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
const bool hasContinuousSweptDeck=std::any_of(ordered.begin(),ordered.end(),[](const auto& request)
|
||||
{return request.kind==ResolvedSdfStampKindV1::fillSweptDeckSegment
|
||||
&&!request.sweepPathMicrometres.empty();});
|
||||
const bool hasNonMonotoneSweptDeck=std::any_of(ordered.begin(),ordered.end(),[](const auto& request)
|
||||
{
|
||||
if(request.kind!=ResolvedSdfStampKindV1::fillSweptDeckSegment
|
||||
||request.sweepPathMicrometres.empty())return false;
|
||||
for(std::size_t i=1;i<request.sweepPathMicrometres.size();++i)
|
||||
if(request.sweepPathMicrometres[i-1][0]>=request.sweepPathMicrometres[i][0])
|
||||
return true;
|
||||
return false;
|
||||
});
|
||||
const bool hasSweptDeck=std::any_of(ordered.begin(),ordered.end(),[](const auto& request)
|
||||
{return request.kind==ResolvedSdfStampKindV1::fillSweptDeckSegment;});
|
||||
candidate.stampVocabularyVersion=hasContinuousSweptDeck
|
||||
?ResolvedLayoutStampVocabularyVersionV5
|
||||
?(hasNonMonotoneSweptDeck?ResolvedLayoutStampVocabularyVersionV6
|
||||
:ResolvedLayoutStampVocabularyVersionV5)
|
||||
:hasSweptDeck?ResolvedLayoutStampVocabularyVersionV4
|
||||
:ResolvedLayoutStampVocabularyVersionV1;
|
||||
candidate.stamps.clear();candidate.stamps.reserve(ordered.size());
|
||||
@@ -1818,8 +1898,8 @@ bool ResolveResolvedLayoutStampsV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
for(const auto coordinate:point)
|
||||
if(coordinate<-1000000000000ll||coordinate>1000000000000ll)
|
||||
{error="stamp request "+request.stableId+" has an unbounded path point";return false;}
|
||||
if(pointIndex>0&&request.sweepPathMicrometres[pointIndex-1][0]>=point[0])
|
||||
{error="stamp request "+request.stableId+" path X coordinates must increase strictly";return false;}
|
||||
if(pointIndex>0&&request.sweepPathMicrometres[pointIndex-1]==point)
|
||||
{error="stamp request "+request.stableId+" path contains a zero-length segment";return false;}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1836,6 +1916,7 @@ bool ResolveResolvedLayoutStampsV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
return static_cast<std::int64_t>(bits%span)-radius;
|
||||
};
|
||||
bool placed=false;
|
||||
std::string lastPlacementFailure;
|
||||
for(std::uint32_t attempt=0;attempt<(request.placementRadiusMicrometres==0?1u:512u);++attempt)
|
||||
{
|
||||
ResolvedLayoutStampV1 stamp;stamp.stableId=request.stableId;
|
||||
@@ -1919,11 +2000,14 @@ bool ResolveResolvedLayoutStampsV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
stamp.boundsMaximumMicrometres[axis]=stamp.positionMicrometres[axis]+extents[axis];
|
||||
}
|
||||
}
|
||||
if(!LayoutStampSafe(candidate,stamp))continue;
|
||||
std::string safetyReason;
|
||||
if(!LayoutStampSafe(candidate,stamp,&safetyReason))
|
||||
{lastPlacementFailure=std::move(safetyReason);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;}
|
||||
{error="stamp request "+request.stableId+" has no deterministic passage-safe content placement"
|
||||
+(lastPlacementFailure.empty()?std::string{}:": "+lastPlacementFailure);return false;}
|
||||
}
|
||||
if(!candidate.finalize(error))return false;
|
||||
std::vector<ResolvedLayoutIssueV2> issues;std::string strictError;
|
||||
|
||||
@@ -1523,6 +1523,8 @@ public:
|
||||
&nativeLatticeFloorSupport);
|
||||
}
|
||||
const auto* bound=FindBoundStratumForManagerZ(Z);
|
||||
const bool liminalRouteComposition=bound&&bound->Resolved
|
||||
&&bound->Resolved->recipeId==VoxelForgeCore::CoreLiminalFieldRecipeIdV1;
|
||||
const bool nativeLattice=bound&&bound->Resolved
|
||||
&&VoxelForgeCore::IsNativeLatticeFieldStratum(*bound->Resolved)
|
||||
&&bound->Resolved->passageCapabilities.hasNativeLatticeField;
|
||||
@@ -1546,7 +1548,9 @@ public:
|
||||
VoxelForgeCore::Linux77Profiler::FScopedPhase l77StampPhase(
|
||||
VoxelForgeCore::Linux77Profiler::EPhase::Stamps);
|
||||
FScopedLegacyFieldTermTimer TermTimer(ELegacyFieldTerm::ComposerStamps);
|
||||
stamped=ApplyResolvedSdfStamps(composed,X,Y,Z);
|
||||
stamped=ApplyResolvedSdfStamps(composed,X,Y,Z,
|
||||
liminalRouteComposition?std::string_view("core/liminal-field/route/")
|
||||
:std::string_view{});
|
||||
}
|
||||
static const bool bL75TraceTnComposition=[]
|
||||
{
|
||||
@@ -1598,6 +1602,12 @@ public:
|
||||
ELegacyFieldTerm::ComposerPassage);
|
||||
final=ApplyResolvedStratePassageCarves(final,X,Y,Z);
|
||||
}
|
||||
// A liminal route is authored to terminate at fixed passage mouths. The
|
||||
// passage carve owns the surrounding bore, while the deck/support stamps
|
||||
// restore its walkable floor so the composed route crosses the interface.
|
||||
if(liminalRouteComposition)
|
||||
final=ApplyResolvedSdfStamps(final,X,Y,Z,{},
|
||||
"core/liminal-field/route/");
|
||||
if(bTraceTnComposition)
|
||||
std::printf("[L75TNCompositionTrace] input=%.9g graph=%.9g boundary=%.9g passage=%.9g stamp=%.9g final=%.9g native=0\n",
|
||||
density,graphDensity,boundary,composed,stamped,final);
|
||||
@@ -7156,10 +7166,16 @@ private:
|
||||
const float halfThickness=stamp.halfExtentsVoxels[2];
|
||||
const float round=std::min(0.35f,std::min(halfWidth,halfThickness)*0.15f);
|
||||
const float searchMargin=halfWidth+halfThickness+2.0f;
|
||||
const auto first=std::lower_bound(path.begin(),path.end(),x-searchMargin,
|
||||
[](const auto& point,float value){return point[0]<value;});
|
||||
const auto last=std::upper_bound(first,path.end(),x+searchMargin,
|
||||
[](float value,const auto& point){return value<point[0];});
|
||||
const bool increasingX=std::adjacent_find(path.begin(),path.end(),
|
||||
[](const auto& a,const auto& b){return a[0]>=b[0];})==path.end();
|
||||
const auto first=increasingX
|
||||
?std::lower_bound(path.begin(),path.end(),x-searchMargin,
|
||||
[](const auto& point,float value){return point[0]<value;})
|
||||
:path.begin();
|
||||
const auto last=increasingX
|
||||
?std::upper_bound(first,path.end(),x+searchMargin,
|
||||
[](float value,const auto& point){return value<point[0];})
|
||||
:path.end();
|
||||
const std::size_t firstIndex=static_cast<std::size_t>(first-path.begin());
|
||||
const std::size_t lastIndex=static_cast<std::size_t>(last-path.begin());
|
||||
const std::size_t segmentBegin=std::min(path.size()-2,
|
||||
@@ -7426,7 +7442,9 @@ private:
|
||||
std::nextafter(maximum+outwardPadding,
|
||||
std::numeric_limits<double>::infinity()),true};
|
||||
}
|
||||
float ApplyResolvedSdfStamps(float density,float X,float Y,float Z) const
|
||||
float ApplyResolvedSdfStamps(float density,float X,float Y,float Z,
|
||||
std::string_view excludedStableIdPrefix={},
|
||||
std::string_view includedStableIdPrefix={}) const
|
||||
{
|
||||
if(StampRuntime.empty()||!Owner||Owner->CoreVoxelSizeMicrons==0)return density;
|
||||
const auto* tileQueries=ActivePassageTileQueries;
|
||||
@@ -7443,6 +7461,10 @@ private:
|
||||
if(index>=StampRuntime.size())return;
|
||||
const auto& runtime=StampRuntime[index];const auto& record=*runtime.record;
|
||||
if(record.strateIndex!=bound->StrateIndex)return;
|
||||
if(!excludedStableIdPrefix.empty()
|
||||
&&record.stableId.starts_with(excludedStableIdPrefix))return;
|
||||
if(!includedStableIdPrefix.empty()
|
||||
&&!record.stableId.starts_with(includedStableIdPrefix))return;
|
||||
if(UsesCoarsePillarProxy(runtime,CurrentStampSamplingStep()))return;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
if(worldMicrometres[axis]<record.boundsMinimumMicrometres[axis]
|
||||
|
||||
@@ -539,6 +539,37 @@ namespace world_assembly_detail
|
||||
if (squared == 0)
|
||||
{ error = "world assembly route contains a zero-length segment"; return false; }
|
||||
}
|
||||
const auto& liminal = stratum.liminalField;
|
||||
if (liminal.version != 0)
|
||||
{
|
||||
if (liminal.version != CoreLiminalFieldRecipeVersionV1
|
||||
|| static_cast<std::uint32_t>(liminal.variant) >= CoreLiminalFieldVariantCountV1
|
||||
|| liminal.seed != effectiveRecipe.seed
|
||||
|| liminal.variant != SelectCoreLiminalFieldVariantV1(liminal.seed)
|
||||
|| stratum.route.size() < 2
|
||||
|| liminal.mouthFloorOffsetSubvoxels <= 0
|
||||
|| stratum.route.front().positionSubvoxels[0] != liminal.arrivalMouthSubvoxels[0]
|
||||
|| stratum.route.front().positionSubvoxels[1] != liminal.arrivalMouthSubvoxels[1]
|
||||
|| stratum.route.front().positionSubvoxels[2]
|
||||
!= liminal.arrivalMouthSubvoxels[2]-liminal.mouthFloorOffsetSubvoxels
|
||||
|| stratum.route.back().positionSubvoxels[0] != liminal.departureMouthSubvoxels[0]
|
||||
|| stratum.route.back().positionSubvoxels[1] != liminal.departureMouthSubvoxels[1]
|
||||
|| stratum.route.back().positionSubvoxels[2]
|
||||
!= liminal.departureMouthSubvoxels[2]-liminal.mouthFloorOffsetSubvoxels
|
||||
|| liminal.routeHalfWidthSubvoxels < stratum.routeConstraints.capsuleRadiusSubvoxels
|
||||
|| liminal.routeHalfThicknessSubvoxels <= 0
|
||||
|| liminal.routeOffsetSubvoxels <= 0
|
||||
|| liminal.supportSpacingSubvoxels <= 0
|
||||
|| liminal.supportRadiusSubvoxels <= 0
|
||||
|| stratum.surfaceProfile.version != 1)
|
||||
{ error = "Core.Liminal.Field plan has invalid version, seed-selected variant, route mouths or dimensions"; return false; }
|
||||
const std::int64_t bottom = static_cast<std::int64_t>(stratum.bottomChunkZ)
|
||||
* layoutChunkWidthSubvoxels;
|
||||
const std::int64_t top = (static_cast<std::int64_t>(stratum.topChunkZ) + 1)
|
||||
* layoutChunkWidthSubvoxels;
|
||||
if (liminal.floorHeightSubvoxels <= bottom || liminal.floorHeightSubvoxels >= top)
|
||||
{ error = "Core.Liminal.Field floor escapes its finite stratum bounds"; return false; }
|
||||
}
|
||||
const auto& constraints = stratum.routeConstraints;
|
||||
if (constraints.version == 0)
|
||||
{
|
||||
@@ -682,6 +713,24 @@ namespace world_assembly_detail
|
||||
for (const auto value : point.positionSubvoxels) HashWord(hash, static_cast<std::uint64_t>(value), 8);
|
||||
HashWord(hash, static_cast<std::uint64_t>(point.radiusSubvoxels), 8);
|
||||
}
|
||||
if (stratum.liminalField.version != 0)
|
||||
{
|
||||
const auto& liminal = stratum.liminalField;
|
||||
HashWord(hash, 0x4c494d494e414c31ull, 8);
|
||||
HashWord(hash, liminal.version, 4);
|
||||
HashWord(hash, static_cast<std::uint32_t>(liminal.variant), 4);
|
||||
HashWord(hash, liminal.seed, 4);
|
||||
for (const auto value : liminal.arrivalMouthSubvoxels)
|
||||
HashWord(hash, static_cast<std::uint64_t>(value), 8);
|
||||
for (const auto value : liminal.departureMouthSubvoxels)
|
||||
HashWord(hash, static_cast<std::uint64_t>(value), 8);
|
||||
for (const auto value : {liminal.mouthFloorOffsetSubvoxels,
|
||||
liminal.floorHeightSubvoxels,
|
||||
liminal.routeHalfWidthSubvoxels, liminal.routeHalfThicknessSubvoxels,
|
||||
liminal.routeOffsetSubvoxels, liminal.supportSpacingSubvoxels,
|
||||
liminal.supportRadiusSubvoxels})
|
||||
HashWord(hash, static_cast<std::uint64_t>(value), 8);
|
||||
}
|
||||
if (stratum.surfaceProfile.version != 0)
|
||||
HashSurfaceProfile(hash, stratum.surfaceProfile);
|
||||
return hash;
|
||||
@@ -1059,6 +1108,30 @@ namespace world_assembly_detail
|
||||
{ error = "world assembly route point is missing a fixed-scale field"; return false; }
|
||||
stratum.route.push_back(point);
|
||||
}
|
||||
const VFJson::Value& liminalValue = item.Get("liminal_field");
|
||||
if (!std::holds_alternative<std::nullptr_t>(liminalValue.Data))
|
||||
{
|
||||
auto& liminal = stratum.liminalField;
|
||||
std::uint32_t variant = 0;
|
||||
if (!liminalValue.IsObject()
|
||||
|| !ReadU32(liminalValue.Get("version"), liminal.version)
|
||||
|| !ReadU32(liminalValue.Get("variant"), variant)
|
||||
|| !ReadU32(liminalValue.Get("seed"), liminal.seed)
|
||||
|| !ReadArray(liminalValue.Get("arrival_mouth_q"),
|
||||
liminal.arrivalMouthSubvoxels.data(), 3)
|
||||
|| !ReadArray(liminalValue.Get("departure_mouth_q"),
|
||||
liminal.departureMouthSubvoxels.data(), 3)
|
||||
|| !ReadInteger(liminalValue.Get("mouth_floor_offset_q"),
|
||||
liminal.mouthFloorOffsetSubvoxels)
|
||||
|| !ReadInteger(liminalValue.Get("floor_height_q"), liminal.floorHeightSubvoxels)
|
||||
|| !ReadInteger(liminalValue.Get("route_half_width_q"), liminal.routeHalfWidthSubvoxels)
|
||||
|| !ReadInteger(liminalValue.Get("route_half_thickness_q"), liminal.routeHalfThicknessSubvoxels)
|
||||
|| !ReadInteger(liminalValue.Get("route_offset_q"), liminal.routeOffsetSubvoxels)
|
||||
|| !ReadInteger(liminalValue.Get("support_spacing_q"), liminal.supportSpacingSubvoxels)
|
||||
|| !ReadInteger(liminalValue.Get("support_radius_q"), liminal.supportRadiusSubvoxels))
|
||||
{ error = "Core.Liminal.Field plan is missing a fixed-scale field"; return false; }
|
||||
liminal.variant = static_cast<WorldAssemblyLiminalVariantV1>(variant);
|
||||
}
|
||||
plan.strates.push_back(std::move(stratum));
|
||||
}
|
||||
return true;
|
||||
@@ -2324,6 +2397,328 @@ bool WorldAssemblyManifest::validate(std::string& error) const
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlanCoreLiminalFieldV1(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 needs a finite stratum and resolved Core voxel size";return false;}
|
||||
const auto resolvedStratum=std::find_if(layout.strates.begin(),layout.strates.end(),
|
||||
[&](const auto& value){return value.strateIndex==stratum.strateIndex
|
||||
&&value.generatorKind=="SurfaceWorld";});
|
||||
if(resolvedStratum==layout.strates.end()||!resolvedStratum->content.presentInCurrentField)
|
||||
{error="Core.Liminal.Field must target a resolved SurfaceWorld content band";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 requires one fixed arrival passage mouth";return false;}
|
||||
arrival=&passage;
|
||||
}
|
||||
if(passage.upperStrateIndex==stratum.strateIndex)
|
||||
{
|
||||
if(departure){error="Core.Liminal.Field requires one fixed departure passage mouth";return false;}
|
||||
departure=&passage;
|
||||
}
|
||||
}
|
||||
if(!arrival||!departure)
|
||||
{error="Core.Liminal.Field requires an adjacent passage on both sides";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 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 auto& start=arrival->lowerMouthSubvoxels;
|
||||
const auto& finish=departure->upperMouthSubvoxels;
|
||||
constexpr std::int64_t MaximumMouthCoordinateQ=1000000000ll;
|
||||
if(std::abs(start[0])>MaximumMouthCoordinateQ||std::abs(start[1])>MaximumMouthCoordinateQ
|
||||
||std::abs(start[2])>MaximumMouthCoordinateQ||std::abs(finish[0])>MaximumMouthCoordinateQ
|
||||
||std::abs(finish[1])>MaximumMouthCoordinateQ||std::abs(finish[2])>MaximumMouthCoordinateQ)
|
||||
{error="Core.Liminal.Field passage mouth exceeds the supported fixed-coordinate range";return false;}
|
||||
const std::int64_t mouthPlayerCenterHeight=micrometresToQ(1130000);
|
||||
auto routeStart=start;
|
||||
auto routeFinish=finish;
|
||||
routeStart[2]-=mouthPlayerCenterHeight;
|
||||
routeFinish[2]-=mouthPlayerCenterHeight;
|
||||
const auto findMouthControl=[&](const ResolvedLayoutPassageV1& passage,
|
||||
const std::array<std::int64_t,3>& mouth)
|
||||
{
|
||||
std::size_t found=passage.controlPoints.size();
|
||||
for(std::size_t i=0;i<passage.controlPoints.size();++i)
|
||||
{
|
||||
const auto& point=passage.controlPoints[i];
|
||||
if(point.positionSubvoxels[0]==mouth[0]
|
||||
&&point.positionSubvoxels[1]==mouth[1]
|
||||
&&point.positionSubvoxels[2]-point.radiusSubvoxels
|
||||
+mouthPlayerCenterHeight==mouth[2])
|
||||
{
|
||||
if(found!=passage.controlPoints.size())return passage.controlPoints.size();
|
||||
found=i;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
};
|
||||
const std::size_t arrivalControl=findMouthControl(*arrival,start);
|
||||
const std::size_t departureControl=findMouthControl(*departure,finish);
|
||||
if(arrival->controlPoints.size()<2||departure->controlPoints.size()<2
|
||||
||arrivalControl>=arrival->controlPoints.size()
|
||||
||departureControl>=departure->controlPoints.size())
|
||||
{error="Core.Liminal.Field needs exact passage-control bindings at both fixed mouths";return false;}
|
||||
const auto horizontalLengthQ=[](std::int64_t dx,std::int64_t dy)
|
||||
{
|
||||
const auto absX=static_cast<std::uint64_t>(std::abs(dx));
|
||||
const auto absY=static_cast<std::uint64_t>(std::abs(dy));
|
||||
return world_assembly_detail::IntegerSqrt(absX*absX+absY*absY);
|
||||
};
|
||||
const auto roundRatio=[](std::int64_t numerator,std::uint64_t denominator)
|
||||
{
|
||||
if(denominator==0)return std::int64_t{0};
|
||||
const auto positiveDenominator=static_cast<std::int64_t>(denominator);
|
||||
return numerator>=0
|
||||
?(numerator+positiveDenominator/2)/positiveDenominator
|
||||
:-((-numerator+positiveDenominator/2)/positiveDenominator);
|
||||
};
|
||||
const auto passageSplineControls=[](const ResolvedLayoutPassageV1& passage)
|
||||
{
|
||||
std::vector<PassageSpline::FPoint> controls;
|
||||
controls.reserve(passage.controlPoints.size());
|
||||
for(const auto& point:passage.controlPoints)
|
||||
controls.push_back({static_cast<double>(point.positionSubvoxels[0]),
|
||||
static_cast<double>(point.positionSubvoxels[1]),
|
||||
static_cast<double>(point.positionSubvoxels[2]-point.radiusSubvoxels),
|
||||
static_cast<double>(point.radiusSubvoxels)});
|
||||
return controls;
|
||||
};
|
||||
const auto arrivalSpline=passageSplineControls(*arrival);
|
||||
const auto departureSpline=passageSplineControls(*departure);
|
||||
constexpr double MouthTangentProbeParameter=0.5;
|
||||
const std::size_t arrivalSegment=arrivalControl==0?0:arrivalControl-1;
|
||||
const double arrivalAtT=arrivalControl==0?0.0:1.0;
|
||||
const double arrivalNearT=arrivalControl==0?MouthTangentProbeParameter
|
||||
:1.0-MouthTangentProbeParameter;
|
||||
const auto arrivalNear=PassageSpline::EvaluateRaw(arrivalSpline,arrivalSegment,
|
||||
arrivalNearT,static_cast<double>(qPerMetre));
|
||||
const auto arrivalAt=PassageSpline::EvaluateRaw(arrivalSpline,arrivalSegment,
|
||||
arrivalAtT,static_cast<double>(qPerMetre));
|
||||
const std::size_t departureSegment=departureControl+1==departure->controlPoints.size()
|
||||
?departureControl-1:departureControl;
|
||||
const double departureAtT=departureControl+1==departure->controlPoints.size()?1.0:0.0;
|
||||
const double departureNearT=departureControl+1==departure->controlPoints.size()
|
||||
?1.0-MouthTangentProbeParameter:MouthTangentProbeParameter;
|
||||
const auto departureAt=PassageSpline::EvaluateRaw(departureSpline,departureSegment,
|
||||
departureAtT,static_cast<double>(qPerMetre));
|
||||
const auto departureNear=PassageSpline::EvaluateRaw(departureSpline,departureSegment,
|
||||
departureNearT,static_cast<double>(qPerMetre));
|
||||
const auto roundedDelta=[](double startValue,double endValue)
|
||||
{return static_cast<std::int64_t>(std::llround(endValue-startValue));};
|
||||
const std::int64_t arrivalDx=roundedDelta(arrivalNear[0],arrivalAt[0]);
|
||||
const std::int64_t arrivalDy=roundedDelta(arrivalNear[1],arrivalAt[1]);
|
||||
const std::int64_t arrivalFloorDelta=roundedDelta(arrivalNear[2],arrivalAt[2]);
|
||||
const std::uint64_t arrivalLength=horizontalLengthQ(arrivalDx,arrivalDy);
|
||||
const std::int64_t departureDx=roundedDelta(departureAt[0],departureNear[0]);
|
||||
const std::int64_t departureDy=roundedDelta(departureAt[1],departureNear[1]);
|
||||
const std::int64_t departureFloorDelta=roundedDelta(departureAt[2],departureNear[2]);
|
||||
const std::uint64_t departureLength=horizontalLengthQ(departureDx,departureDy);
|
||||
if(arrivalLength==0||departureLength==0)
|
||||
{error="Core.Liminal.Field passage mouth tangent is too short for a stable connector";return false;}
|
||||
const auto extendMouth=[&](const std::array<std::int64_t,3>& mouth,
|
||||
std::int64_t dx,std::int64_t dy,std::int64_t dz,std::uint64_t horizontalLength,
|
||||
std::int64_t distance,bool forward)
|
||||
{
|
||||
const std::int64_t sign=forward?1:-1;
|
||||
return std::array<std::int64_t,3>{
|
||||
mouth[0]+roundRatio(sign*dx*distance,horizontalLength),
|
||||
mouth[1]+roundRatio(sign*dy*distance,horizontalLength),
|
||||
mouth[2]+roundRatio(sign*dz*distance,horizontalLength)};
|
||||
};
|
||||
constexpr std::int64_t MouthConnectorMidMetres=5;
|
||||
constexpr std::int64_t MouthConnectorFullMetres=10;
|
||||
const auto arrivalConnectorMid=extendMouth(routeStart,arrivalDx,arrivalDy,
|
||||
arrivalFloorDelta,arrivalLength,MouthConnectorMidMetres*qPerMetre,true);
|
||||
const auto arrivalConnector=extendMouth(routeStart,arrivalDx,arrivalDy,
|
||||
arrivalFloorDelta,arrivalLength,MouthConnectorFullMetres*qPerMetre,true);
|
||||
const auto departureConnector=extendMouth(routeFinish,departureDx,departureDy,
|
||||
departureFloorDelta,departureLength,MouthConnectorFullMetres*qPerMetre,false);
|
||||
const auto departureConnectorMid=extendMouth(routeFinish,departureDx,departureDy,
|
||||
departureFloorDelta,departureLength,MouthConnectorMidMetres*qPerMetre,false);
|
||||
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 minRouteFloor=std::min({routeStart[2],routeFinish[2],
|
||||
arrivalConnectorMid[2],arrivalConnector[2],departureConnector[2],
|
||||
departureConnectorMid[2]});
|
||||
const std::int64_t contentBottom=micrometresToQ(
|
||||
resolvedStratum->content.bottomMicrometres);
|
||||
const std::int64_t profileRelief=variant==WorldAssemblyLiminalVariantV1::repeatingHills
|
||||
?2*micrometresToQ(definition.hillAmplitudeMicrometres):0;
|
||||
const std::int64_t floorHeight=std::max(contentBottom+profileRelief+qPerMetre/4,
|
||||
minRouteFloor-8*qPerMetre);
|
||||
if(floorHeight>=minRouteFloor-2*qPerMetre||floorHeight<=bottom||top<=floorHeight+8*qPerMetre)
|
||||
{error="Core.Liminal.Field has no room for its floor and passage route";return false;}
|
||||
|
||||
stratum.hasRecipeOverride=true;
|
||||
stratum.recipeOverride=recipe;
|
||||
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);
|
||||
}
|
||||
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;
|
||||
|
||||
WorldAssemblyLiminalFieldPlanV1 liminal;
|
||||
liminal.version=CoreLiminalFieldRecipeVersionV1;
|
||||
liminal.variant=variant;
|
||||
liminal.seed=seed;
|
||||
liminal.arrivalMouthSubvoxels=start;
|
||||
liminal.departureMouthSubvoxels=finish;
|
||||
liminal.floorHeightSubvoxels=floorHeight;
|
||||
liminal.routeHalfWidthSubvoxels=micrometresToQ(definition.routeHalfWidthMicrometres);
|
||||
liminal.routeHalfThicknessSubvoxels=micrometresToQ(definition.routeHalfThicknessMicrometres);
|
||||
liminal.supportSpacingSubvoxels=micrometresToQ(definition.supportSpacingMicrometres);
|
||||
liminal.supportRadiusSubvoxels=micrometresToQ(definition.supportRadiusMicrometres);
|
||||
liminal.mouthFloorOffsetSubvoxels=mouthPlayerCenterHeight;
|
||||
|
||||
constexpr std::int64_t GradeQ16=17560;
|
||||
constexpr std::int64_t MinimumPathOffsetMetres=150;
|
||||
constexpr std::int64_t ConnectorSpanMetres=16;
|
||||
const std::int64_t dx=departureConnector[0]-arrivalConnector[0];
|
||||
const std::int64_t dy=departureConnector[1]-arrivalConnector[1];
|
||||
const std::uint64_t chord=horizontalLengthQ(dx,dy);
|
||||
std::int64_t directionX=dx,directionY=dy;
|
||||
std::uint64_t directionLength=chord;
|
||||
if(directionLength<static_cast<std::uint64_t>(qPerMetre/4))
|
||||
{directionX=Q;directionY=0;directionLength=static_cast<std::uint64_t>(Q);}
|
||||
const std::int64_t normalX=-directionY;
|
||||
const std::int64_t normalY=directionX;
|
||||
const std::int64_t vertical=std::abs(departureConnector[2]-arrivalConnector[2]);
|
||||
const std::int64_t requiredHorizontal=(vertical*65536+GradeQ16-1)/GradeQ16;
|
||||
std::int64_t routeOffset=std::max(MinimumPathOffsetMetres*qPerMetre,
|
||||
(requiredHorizontal+1)/2+20*qPerMetre);
|
||||
if(chord<ConnectorSpanMetres*static_cast<std::uint64_t>(qPerMetre))
|
||||
routeOffset=std::max(routeOffset,MinimumPathOffsetMetres*qPerMetre);
|
||||
liminal.routeOffsetSubvoxels=routeOffset;
|
||||
|
||||
const auto normalOffset=[&](std::int64_t scale)
|
||||
{
|
||||
return std::array<std::int64_t,2>{
|
||||
normalX*scale/static_cast<std::int64_t>(directionLength),
|
||||
normalY*scale/static_cast<std::int64_t>(directionLength)};
|
||||
};
|
||||
const auto directionOffset=[&](std::int64_t scale)
|
||||
{
|
||||
return std::array<std::int64_t,2>{
|
||||
directionX*scale/static_cast<std::int64_t>(directionLength),
|
||||
directionY*scale/static_cast<std::int64_t>(directionLength)};
|
||||
};
|
||||
const auto normal=normalOffset(routeOffset);
|
||||
const auto connector=chord<ConnectorSpanMetres*static_cast<std::uint64_t>(qPerMetre)
|
||||
?directionOffset(ConnectorSpanMetres*qPerMetre):std::array<std::int64_t,2>{0,0};
|
||||
std::vector<std::array<std::int64_t,3>> horizontalPath;
|
||||
horizontalPath.reserve(8);
|
||||
horizontalPath.push_back(routeStart);
|
||||
horizontalPath.push_back(arrivalConnectorMid);
|
||||
horizontalPath.push_back(arrivalConnector);
|
||||
horizontalPath.push_back({arrivalConnector[0]+normal[0],
|
||||
arrivalConnector[1]+normal[1],0});
|
||||
horizontalPath.push_back({departureConnector[0]+normal[0]+connector[0],
|
||||
departureConnector[1]+normal[1]+connector[1],0});
|
||||
horizontalPath.push_back(departureConnector);
|
||||
horizontalPath.push_back(departureConnectorMid);
|
||||
horizontalPath.push_back(routeFinish);
|
||||
|
||||
std::vector<WorldAssemblyRoutePointV1> route;
|
||||
route.reserve(horizontalPath.size());
|
||||
std::vector<std::uint64_t> cumulative(horizontalPath.size(),0);
|
||||
std::uint64_t totalHorizontal=0;
|
||||
for(std::size_t i=1;i<horizontalPath.size();++i)
|
||||
{
|
||||
const std::int64_t sx=horizontalPath[i][0]-horizontalPath[i-1][0];
|
||||
const std::int64_t sy=horizontalPath[i][1]-horizontalPath[i-1][1];
|
||||
const std::uint64_t segment=horizontalLengthQ(sx,sy);
|
||||
if(segment==0){error="Core.Liminal.Field route contains a zero-horizontal segment";return false;}
|
||||
totalHorizontal+=segment;
|
||||
cumulative[i]=totalHorizontal;
|
||||
}
|
||||
if(totalHorizontal<static_cast<std::uint64_t>(requiredHorizontal))
|
||||
{error="Core.Liminal.Field route cannot meet the 15-degree grade bound";return false;}
|
||||
const std::uint64_t centralFirst=horizontalLengthQ(
|
||||
horizontalPath[3][0]-horizontalPath[2][0],
|
||||
horizontalPath[3][1]-horizontalPath[2][1]);
|
||||
const std::uint64_t centralMiddle=horizontalLengthQ(
|
||||
horizontalPath[4][0]-horizontalPath[3][0],
|
||||
horizontalPath[4][1]-horizontalPath[3][1]);
|
||||
const std::uint64_t centralLast=horizontalLengthQ(
|
||||
horizontalPath[5][0]-horizontalPath[4][0],
|
||||
horizontalPath[5][1]-horizontalPath[4][1]);
|
||||
const std::uint64_t centralTotal=centralFirst+centralMiddle+centralLast;
|
||||
if(centralTotal==0)
|
||||
{error="Core.Liminal.Field central route has no horizontal span";return false;}
|
||||
const std::int64_t centralVertical=departureConnector[2]-arrivalConnector[2];
|
||||
horizontalPath[3][2]=arrivalConnector[2]+roundRatio(
|
||||
centralVertical*static_cast<std::int64_t>(centralFirst),centralTotal);
|
||||
horizontalPath[4][2]=arrivalConnector[2]+roundRatio(
|
||||
centralVertical*static_cast<std::int64_t>(centralFirst+centralMiddle),centralTotal);
|
||||
for(std::size_t i=0;i<horizontalPath.size();++i)
|
||||
{
|
||||
auto point=horizontalPath[i];
|
||||
WorldAssemblyRoutePointV1 routePoint;
|
||||
routePoint.positionSubvoxels=point;
|
||||
routePoint.radiusSubvoxels=std::max<std::int64_t>(liminal.routeHalfWidthSubvoxels,
|
||||
micrometresToQ(500000));
|
||||
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>(stratum.strateIndex),4);add(i,4);
|
||||
routePoint.stableFeatureId=id?id:1;
|
||||
route.push_back(routePoint);
|
||||
}
|
||||
stratum.route=std::move(route);
|
||||
stratum.routeConstraints={1,micrometresToQ(340000),micrometresToQ(1760000),
|
||||
GradeQ16,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 produced an invalid plan":validationError;return false;}
|
||||
std::printf("[CoreLiminalFieldPlan] recipe=%.*s version=%u variant=%.*s seed=%u arrival=%s departure=%s route_points=%zu route_offset_m=%.3f 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,
|
||||
arrival->stableId.c_str(),departure->stableId.c_str(),stratum.route.size(),
|
||||
static_cast<double>(routeOffset)/qPerMetre);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string WorldAssemblyManifest::ToJson() const
|
||||
{
|
||||
std::string error;
|
||||
@@ -2391,6 +2786,27 @@ std::string WorldAssemblyManifest::ToJson() const
|
||||
<< ",\"minimum_clearance_q\":" << constraints.minimumClearanceSubvoxels
|
||||
<< ",\"minimum_support_probes\":" << constraints.minimumSupportProbes << '}';
|
||||
}
|
||||
if (stratum.liminalField.version != 0)
|
||||
{
|
||||
const auto& liminal = stratum.liminalField;
|
||||
out << ",\"liminal_field\":{\"version\":" << liminal.version
|
||||
<< ",\"variant\":" << static_cast<std::uint32_t>(liminal.variant)
|
||||
<< ",\"seed\":" << liminal.seed << ",\"arrival_mouth_q\":["
|
||||
<< liminal.arrivalMouthSubvoxels[0] << ','
|
||||
<< liminal.arrivalMouthSubvoxels[1] << ','
|
||||
<< liminal.arrivalMouthSubvoxels[2] << "],\"departure_mouth_q\":["
|
||||
<< liminal.departureMouthSubvoxels[0] << ','
|
||||
<< liminal.departureMouthSubvoxels[1] << ','
|
||||
<< liminal.departureMouthSubvoxels[2]
|
||||
<< "],\"mouth_floor_offset_q\":" << liminal.mouthFloorOffsetSubvoxels
|
||||
<< ",\"floor_height_q\":"
|
||||
<< liminal.floorHeightSubvoxels
|
||||
<< ",\"route_half_width_q\":" << liminal.routeHalfWidthSubvoxels
|
||||
<< ",\"route_half_thickness_q\":" << liminal.routeHalfThicknessSubvoxels
|
||||
<< ",\"route_offset_q\":" << liminal.routeOffsetSubvoxels
|
||||
<< ",\"support_spacing_q\":" << liminal.supportSpacingSubvoxels
|
||||
<< ",\"support_radius_q\":" << liminal.supportRadiusSubvoxels << '}';
|
||||
}
|
||||
out << ",\"route\":[";
|
||||
for (std::size_t p = 0; p < stratum.route.size(); ++p)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user