diff --git a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h index edeb226..9d74abc 100644 --- a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h +++ b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h @@ -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 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( + seed % CoreLiminalFieldVariantCountV1); + } + struct WorldAssemblyRecipeV1 { std::uint32_t schemaVersion = WorldAssemblySchemaVersion; @@ -654,6 +709,23 @@ namespace VoxelForgeCore std::array parametersSubvoxels{}; }; + struct WorldAssemblyLiminalFieldPlanV1 + { + std::uint32_t version = 0; + WorldAssemblyLiminalVariantV1 variant = WorldAssemblyLiminalVariantV1::flatFieldMonolith; + std::uint32_t seed = 0; + std::array arrivalMouthSubvoxels{}; + std::array 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 features; WorldAssemblyRouteConstraintsV1 routeConstraints; std::vector 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, 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> sweepPathMicrometres; std::int64_t placementRadiusMicrometres = 0; @@ -1238,6 +1313,9 @@ namespace VoxelForgeCore ResolvedLayoutSnapshotV3& layout, std::int32_t seed, const std::vector& configs, std::string& error); + VFCORE_API bool PlanCoreLiminalFieldV1( + WorldAssemblyRecipeV1& recipe, WorldAssemblyStratumPlanV1& stratum, + const ResolvedLayoutSnapshotV3& layout, std::string& error); struct ResolvedLayoutStampPlacementAuditV1 { diff --git a/Standalone/Core/src/ResolvedLayoutInternal.h b/Standalone/Core/src/ResolvedLayoutInternal.h index 4a47b04..1cddd4e 100644 --- a/Standalone/Core/src/ResolvedLayoutInternal.h +++ b/Standalone/Core/src/ResolvedLayoutInternal.h @@ -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 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 q={anchor.values[0],anchor.values[1],anchor.values[2]}; + return qToMicrometres(q); + }; + const std::int64_t halfThickness=static_cast(std::llround( + static_cast(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::infinity(); + for(std::size_t i=1;isweepPathMicrometres.size();++i) + { + const auto& a=deck->sweepPathMicrometres[i-1]; + const auto& b=deck->sweepPathMicrometres[i]; + const double dx=static_cast(b[0]-a[0]); + const double dy=static_cast(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 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]=ResolvedLayoutStampVocabularyVersionV6 + ||previous[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(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 issues;std::string strictError; diff --git a/Standalone/Core/src/ShapeWorldInternal.h b/Standalone/Core/src/ShapeWorldInternal.h index ed036ec..29d4977 100644 --- a/Standalone/Core/src/ShapeWorldInternal.h +++ b/Standalone/Core/src/ShapeWorldInternal.h @@ -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]=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](first-path.begin()); const std::size_t lastIndex=static_cast(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::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](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(stratum.bottomChunkZ) + * layoutChunkWidthSubvoxels; + const std::int64_t top = (static_cast(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(value), 8); HashWord(hash, static_cast(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(liminal.variant), 4); + HashWord(hash, liminal.seed, 4); + for (const auto value : liminal.arrivalMouthSubvoxels) + HashWord(hash, static_cast(value), 8); + for (const auto value : liminal.departureMouthSubvoxels) + HashWord(hash, static_cast(value), 8); + for (const auto value : {liminal.mouthFloorOffsetSubvoxels, + liminal.floorHeightSubvoxels, + liminal.routeHalfWidthSubvoxels, liminal.routeHalfThicknessSubvoxels, + liminal.routeOffsetSubvoxels, liminal.supportSpacingSubvoxels, + liminal.supportRadiusSubvoxels}) + HashWord(hash, static_cast(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(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(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.topChunkZcontent.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(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& mouth) + { + std::size_t found=passage.controlPoints.size(); + for(std::size_t i=0;icontrolPoints.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::abs(dx)); + const auto absY=static_cast(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(denominator); + return numerator>=0 + ?(numerator+positiveDenominator/2)/positiveDenominator + :-((-numerator+positiveDenominator/2)/positiveDenominator); + }; + const auto passageSplineControls=[](const ResolvedLayoutPassageV1& passage) + { + std::vector controls; + controls.reserve(passage.controlPoints.size()); + for(const auto& point:passage.controlPoints) + controls.push_back({static_cast(point.positionSubvoxels[0]), + static_cast(point.positionSubvoxels[1]), + static_cast(point.positionSubvoxels[2]-point.radiusSubvoxels), + static_cast(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(qPerMetre)); + const auto arrivalAt=PassageSpline::EvaluateRaw(arrivalSpline,arrivalSegment, + arrivalAtT,static_cast(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(qPerMetre)); + const auto departureNear=PassageSpline::EvaluateRaw(departureSpline,departureSegment, + departureNearT,static_cast(qPerMetre)); + const auto roundedDelta=[](double startValue,double endValue) + {return static_cast(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& 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{ + 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(stratum.bottomChunkZ) + *recipe.layoutChunkWidthSubvoxels; + const std::int64_t top=(static_cast(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(qPerMetre/4)) + {directionX=Q;directionY=0;directionLength=static_cast(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(qPerMetre)) + routeOffset=std::max(routeOffset,MinimumPathOffsetMetres*qPerMetre); + liminal.routeOffsetSubvoxels=routeOffset; + + const auto normalOffset=[&](std::int64_t scale) + { + return std::array{ + normalX*scale/static_cast(directionLength), + normalY*scale/static_cast(directionLength)}; + }; + const auto directionOffset=[&](std::int64_t scale) + { + return std::array{ + directionX*scale/static_cast(directionLength), + directionY*scale/static_cast(directionLength)}; + }; + const auto normal=normalOffset(routeOffset); + const auto connector=chord(qPerMetre) + ?directionOffset(ConnectorSpanMetres*qPerMetre):std::array{0,0}; + std::vector> 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 route; + route.reserve(horizontalPath.size()); + std::vector cumulative(horizontalPath.size(),0); + std::uint64_t totalHorizontal=0; + for(std::size_t i=1;i(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(centralFirst),centralTotal); + horizontalPath[4][2]=arrivalConnector[2]+roundRatio( + centralVertical*static_cast(centralFirst+centralMiddle),centralTotal); + for(std::size_t i=0;i(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(value>>(byte*8));id*=1099511628211ull;}}; + add(seed,4);add(static_cast(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(CoreLiminalFieldRecipeIdV1.size()),CoreLiminalFieldRecipeIdV1.data(), + liminal.version,static_cast(definition.name.size()),definition.name.data(),seed, + arrival->stableId.c_str(),departure->stableId.c_str(),stratum.route.size(), + static_cast(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(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) { diff --git a/Standalone/Research/WorldAssemblerM3/Linux144/BuildL144Fixtures.py b/Standalone/Research/WorldAssemblerM3/Linux144/BuildL144Fixtures.py new file mode 100644 index 0000000..42a0a72 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux144/BuildL144Fixtures.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +"""Build the 3-strata composed Core.Liminal.Field fixture.""" +from __future__ import annotations + +import argparse +import json +import os +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[4] +OWNER = Path(os.environ.get("VOXELFORGE_OWNER_DATA", + ROOT / "Standalone/data/owner_world.json")) +OUT_DEFAULT = Path(os.environ.get("VOXELFORGE_VERIFY_OUT", ROOT / "Saved/Linux/l144")) + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--out", type=Path, default=OUT_DEFAULT / "fixtures") + args = parser.parse_args() + args.out.mkdir(parents=True, exist_ok=True) + world = json.loads(OWNER.read_text(encoding="utf-8")) + if len(world.get("strates", [])) < 3: + raise SystemExit("owner fixture needs at least three authored strate slots") + + world["strates"] = world["strates"][:3] + world["total_strates"] = 3 + world["settings_asset"]["TotalStrates"] = 3 + world["settings_asset"]["FixedStrates"] = world["settings_asset"]["FixedStrates"][:3] + # Keep the resolved arrival near the departure depth. The owner's 5000-unit + # reach clamps to liminal bedrock on most seeds and leaves no field floor. + world["strates"][0]["passage_config"]["ReachMin"] = 1536 + world["strates"][0]["passage_config"]["ReachMax"] = 1792 + world["surface_profile_override"] = { + "version": 1, + "kind": "flatField", + "minimum_arena_clearance_m": 0.0, + } + middle = world["strates"][1] + middle["asset"]["GeneratorType"] = 4 # ECaveGeneratorType::SurfaceWorld + middle["asset"]["StrateName"] = "Liminal Field" + # Keep the departure mouth 24–32 m above liminal bedrock so every variant + # has room for its floor, road landing and support footings. + middle["passage_config"]["ReachMin"] = 192 + middle["passage_config"]["ReachMax"] = 256 + world["core_liminal_field"] = { + "version": 1, + "strate_index": int(middle["index"]), + "variant_policy": "seed_modulo_4", + } + + output = args.out / "core-liminal-field-3-strata.json" + output.write_text(json.dumps(world, separators=(",", ":")) + "\n", encoding="utf-8") + print(f"[L144Fixture] path={output} strates=SurfaceWorld,Core.Liminal.Field,TunnelNetwork status=PASS") + + +if __name__ == "__main__": + main() diff --git a/Standalone/Research/WorldAssemblerM3/Linux144/CaptureL144.py b/Standalone/Research/WorldAssemblerM3/Linux144/CaptureL144.py new file mode 100644 index 0000000..bec3060 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux144/CaptureL144.py @@ -0,0 +1,243 @@ +#!/usr/bin/env python3 +"""Capture the four seed-selected Core.Liminal.Field v1 variants.""" +from __future__ import annotations + +import hashlib +import json +import math +import os +from pathlib import Path +import subprocess +import sys +import time + + +ROOT = Path(__file__).resolve().parents[4] +HERE = Path(__file__).resolve().parent +OUT = ROOT / "Saved/Linux/l144" +CAPTURES = HERE / "captures" +LIMIT = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--"] +Q_PER_METRE = 1024.0 + +sys.path.insert(0, str(ROOT / "Standalone/Tools/verify")) +from runner import bounded_run, current_stamp, first_stamp + + +def fmt(values) -> str: + return ",".join(f"{value:.6f}" for value in values) + + +def sha256(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as stream: + for block in iter(lambda: stream.read(1024 * 1024), b""): + digest.update(block) + return digest.hexdigest() + + +def run_step(name: str, command: list[object], log: Path, env: dict[str, str], + stamped: bool = False) -> str: + expected_stamp = current_stamp() if stamped else None + if stamped: + binary = Path(str(command[0])) + probe = bounded_run([str(binary), "--print-source-stamp"], timeout=15) + if probe is None or probe.returncode != 0 \ + or first_stamp(probe.stdout or "") != expected_stamp: + raise RuntimeError(f"{name} refused a stale executable: {binary}") + started = time.monotonic() + log.parent.mkdir(parents=True, exist_ok=True) + with log.open("w", encoding="utf-8") as stream: + result = subprocess.run([*LIMIT, "timeout", "1740s", *map(str, command)], + cwd=ROOT, env=env, stdout=stream, stderr=subprocess.STDOUT, + timeout=1745, check=False) + output = log.read_text(encoding="utf-8", errors="replace") + elapsed = time.monotonic() - started + print(f"[L144Run] step={name} elapsed_s={elapsed:.3f} exit={result.returncode} log={log}", + flush=True) + if stamped and (first_stamp(output) != expected_stamp + or current_stamp() != expected_stamp): + raise RuntimeError(f"{name} source stamp mismatch; see {log}") + if result.returncode != 0: + raise RuntimeError(f"{name} exit={result.returncode}; see {log}") + return output + + +def camera_angles(camera, target): + dx, dy, dz = (target[i] - camera[i] for i in range(3)) + return math.degrees(math.atan2(dx, dy)), math.degrees( + math.atan2(dz, math.hypot(dx, dy))) + + +def route_views(manifest_path: Path, layout_path: Path, name: str): + manifest = json.loads(manifest_path.read_text(encoding="utf-8")) + layout = json.loads(layout_path.read_text(encoding="utf-8")) + plan = next(row for row in manifest["plan"]["strates"] + if row["strate_index"] == 1 and row.get("liminal_field", {}).get("version") == 1) + points = [tuple(value / Q_PER_METRE for value in row["position_q"]) + for row in plan["route"]] + first, second = points[0], points[1] + dx, dy = second[0] - first[0], second[1] - first[1] + length = math.hypot(dx, dy) + if length <= 1.0e-6: + raise RuntimeError("Core.Liminal.Field arrival tangent is degenerate") + tx, ty = dx / length, dy / length + eye_camera = (first[0] - tx * 1.5, first[1] - ty * 1.5, first[2] + 1.6) + third = points[min(2, len(points) - 1)] + dx, dy = third[0] - first[0], third[1] - first[1] + tangent_length = math.hypot(dx, dy) + tx, ty = dx / tangent_length, dy / tangent_length + dz_per_metre = (third[2] - first[2]) / tangent_length + eye_target = (first[0] + tx * 40.0, first[1] + ty * 40.0, + first[2] + dz_per_metre * 40.0 + 1.6) + + middle = len(points) // 2 + before, center, after = points[middle - 1], points[middle], points[middle + 1] + dx, dy = after[0] - before[0], after[1] - before[1] + length = math.hypot(dx, dy) + if length <= 1.0e-6: + raise RuntimeError("Core.Liminal.Field wide-view tangent is degenerate") + tx, ty = dx / length, dy / length + nx, ny = -ty, tx + floor = plan["liminal_field"]["floor_height_q"] / Q_PER_METRE + if name == "flatField-monolith": + monolith = next(row for row in layout.get("stamps", []) + if row.get("stable_id") == "core/liminal-field/monolith/000") + monolith_center = tuple(value / 1.0e6 for value in monolith["position_um"]) + floor = monolith_center[2] - monolith["half_extents_um"][2] / 1.0e6 + wide_camera = (monolith_center[0] - tx * 30.0 + nx * 18.0, + monolith_center[1] - ty * 30.0 + ny * 18.0, floor + 25.0) + wide_target = monolith_center + elif name == "periodicHills": + wide_camera = (center[0] - 20.0, center[1] - 25.0, floor + 10.0) + wide_target = (center[0] + 10.0, center[1] + 8.0, floor + 1.0) + elif name == "pillar-grid": + pillars = [row for row in layout.get("stamps", []) + if row.get("stable_id", "").startswith("core/liminal-field/pillars/")] + if not pillars: + raise RuntimeError("pillar-grid layout has no resolved grid stamps") + pairs = [] + for i, first_pillar in enumerate(pillars): + for second_pillar in pillars[i + 1:]: + a = first_pillar["position_um"] + b = second_pillar["position_um"] + dx, dy = (b[0] - a[0]) / 1.0e6, (b[1] - a[1]) / 1.0e6 + separation = math.hypot(dx, dy) + if 60.0 <= separation <= 90.0: + mx = (a[0] + b[0]) / 2.0e6 + my = (a[1] + b[1]) / 2.0e6 + pairs.append((math.hypot(mx - center[0], my - center[1]), + mx, my, dx / separation, dy / separation, + (a[2] + b[2]) / 2.0e6, + first_pillar["half_extents_um"][2] / 1.0e6)) + if not pairs: + raise RuntimeError("pillar-grid has no neighboring resolved columns") + _, mx, my, tx, ty, pillar_z, half_height = min(pairs) + floor = pillar_z - half_height + nx, ny = -ty, tx + wide_camera = (center[0] - tx * 20.0 + nx * 25.0, + center[1] - ty * 20.0 + ny * 25.0, center[2] + 8.0) + wide_target = (center[0], center[1], floor + 2.0) + else: + wide_camera = (center[0] - tx * 20.0 + nx * 25.0, + center[1] - ty * 20.0 + ny * 25.0, center[2] + 8.0) + wide_target = (center[0], center[1], floor + 2.0) + return {"eye": (eye_camera, eye_target), "wide": (wide_camera, wide_target)} + + +def capture_variant(seed: int, name: str, *, headless: Path, renderer: Path, + fixture: Path, env: dict[str, str]): + stem = f"core-liminal-{name}-seed{seed:02d}" + plan_dir = OUT / "capture-plans" / stem + plan_dir.mkdir(parents=True, exist_ok=True) + manifest = plan_dir / "manifest.json" + layout = plan_dir / "layout.json" + command = [headless, fixture, "--workers=1", f"--seed={seed}", + "--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline", + "--force-generate", "--clip-radius=1", "--max-clip-level=0", "--max-level=0", + "--render-distance-chunks=0", "--camera=0,0,-540,0,0", + f"--world-assembler-manifest-output={manifest}", + f"--resolved-layout-output={layout}"] + output = run_step(f"plan-{stem}", command, OUT / "logs" / f"capture-{stem}-plan.log", + env, stamped=True) + if "[CoreLiminalFieldDraw]" not in output: + raise RuntimeError(f"{stem} plan omitted its Core liminal deck") + views = route_views(manifest, layout, name) + for view, (eye, target) in views.items(): + work = OUT / "captures" / f"{stem}-{view}" + work.mkdir(parents=True, exist_ok=True) + view_manifest = work / "manifest.json" + view_layout = work / "layout.json" + mesh = work / "world.vfmesh" + yaw, pitch = camera_angles(eye, target) + camera = (*eye, yaw, pitch) + view_clip_radius = (5 if view == "wide" and name in { + "flatField-monolith", "periodicHills"} else + 3) + command = [headless, fixture, "--workers=12", f"--seed={seed}", + "--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline", + "--force-generate", f"--clip-radius={view_clip_radius}", + "--max-clip-level=0", "--max-level=0", + "--render-distance-chunks=0", "--capture-skip-walkability", + f"--camera={fmt(camera)}", f"--mesh-export={mesh}", + f"--world-assembler-manifest-output={view_manifest}", + f"--resolved-layout-output={view_layout}"] + output = run_step(f"export-{stem}-{view}", command, + OUT / "logs" / f"capture-{stem}-{view}-export.log", env, stamped=True) + if "[CoreLiminalFieldDraw]" not in output or not mesh.is_file() \ + or mesh.stat().st_size == 0: + raise RuntimeError(f"{stem}-{view} export omitted its Core liminal deck or mesh") + png = work / f"{view}.png" + image = CAPTURES / f"{stem}-{view}.jpg" + fog = { + "flatField-monolith": (30, 180, "0.66,0.73,0.77", -708.0, 46.0), + "periodicHills": (140, 850, "0.78,0.84,0.88", -720.0, 14.0), + "road-over-cloud-floor": (24, 420, "0.88,0.93,0.95", -720.0, 65.0), + "pillar-grid": (180, 1200, "0.82,0.88,0.91", -720.0, 80.0), + }[name] + fog_start, fog_end, fog_color, height_center, height_range = fog + render = [renderer, mesh, png, "--size=1600x900", "--fov-deg=72", + f"--camera-m={fmt(eye)}", f"--yaw-deg={yaw:.6f}", f"--pitch-deg={pitch:.6f}", + "--tile-skirts=on", "--shade=readable", "--headlamp=off", "--height-tint=on", + f"--height-center-m={height_center:.6f}", f"--height-range-m={height_range:.6f}", + "--light-direction=-0.42,-0.58,0.70", "--fog=on", f"--fog-start-m={fog_start}", + f"--fog-end-m={fog_end}", f"--fog-color={fog_color}"] + run_step(f"render-{stem}-{view}", render, + OUT / "logs" / f"capture-{stem}-{view}-render.log", env, stamped=True) + run_step(f"jpeg-{stem}-{view}", ["ffmpeg", "-nostdin", "-hide_banner", "-loglevel", + "error", "-y", "-i", png, "-frames:v", "1", "-q:v", "4", image], + OUT / "logs" / f"capture-{stem}-{view}-jpeg.log", env) + run_step(f"quality-{stem}-{view}", [sys.executable, + ROOT / "Standalone/Tools/verify/capture_quality.py", image], + OUT / "logs" / f"capture-{stem}-{view}-quality.log", env) + print(f"[L144Capture] variant={name} seed={seed} view={view} " + f"eye_height={'1.600m' if view == 'eye' else 'wide'} bytes={image.stat().st_size} " + f"sha256={sha256(image)} status=PASS", flush=True) + + +def main() -> int: + headless = ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-clang19" + renderer = ROOT / "Standalone/bin/linux/VoxelForgeOffscreenCapture-clang19" + fixture = OUT / "fixtures/core-liminal-field-3-strata.json" + env = os.environ.copy() + env["VOXELFORGE_VERIFY_OUT"] = str(OUT) + env["VOXELFORGE_OWNER_DATA"] = str(ROOT / "Standalone/data/owner_world.json") + env["LP_NUM_THREADS"] = "12" + env["XDG_CACHE_HOME"] = str(OUT / "xdg-cache") + CAPTURES.mkdir(parents=True, exist_ok=True) + variants = ("flatField-monolith", "periodicHills", + "road-over-cloud-floor", "pillar-grid") + selected = set(os.environ.get("L144_CAPTURE_VARIANTS", "").split(",")) + for seed, name in enumerate(variants): + if selected and name not in selected: + continue + capture_variant(seed, name, headless=headless, renderer=renderer, + fixture=fixture, env=env) + return 0 + + +if __name__ == "__main__": + try: + raise SystemExit(main()) + except Exception as error: + print(f"[L144Capture] status=FAIL error={error}", file=sys.stderr, flush=True) + raise SystemExit(1) diff --git a/Standalone/Research/WorldAssemblerM3/Linux144/REPORT.md b/Standalone/Research/WorldAssemblerM3/Linux144/REPORT.md new file mode 100644 index 0000000..94f62e6 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux144/REPORT.md @@ -0,0 +1,44 @@ +# Linux144 — Core.Liminal.Field v1 + +| Variant (seed) | Arrival at eye height | Wide view | +|---|---|---| +| Flat field + monolith (0) | ![Flat field arrival](captures/core-liminal-flatField-monolith-seed00-eye.jpg) | ![Flat field wide](captures/core-liminal-flatField-monolith-seed00-wide.jpg) | +| Repeating hills (1) | ![Hills arrival](captures/core-liminal-periodicHills-seed01-eye.jpg) | ![Hills wide](captures/core-liminal-periodicHills-seed01-wide.jpg) | +| Road over cloud floor (2) | ![Road arrival](captures/core-liminal-road-over-cloud-floor-seed02-eye.jpg) | ![Road wide](captures/core-liminal-road-over-cloud-floor-seed02-wide.jpg) | +| Pillar grid (3) | ![Pillars arrival](captures/core-liminal-pillar-grid-seed03-eye.jpg) | ![Pillars wide](captures/core-liminal-pillar-grid-seed03-wide.jpg) | + +**Result: stopped at the mouth-join gate; this is not ready to ship.** The new `Core.Liminal.Field` v1 recipe plans and draws all four seed-selected looks between fixed mouths. In the final composed probe, three of four seeds failed one of their two passage interfaces, and all four failed the liminal walk audit. The requested eight-seed-per-variant matrix was not run after the stop condition was reached. + +## What changed + +- Added one data-driven recipe, `Core.Liminal.Field` v1. `seed % 4` selects flat field with monolith, repeating hills, road over cloud floor, or pillar grid. +- The liminal plan binds the existing `passage/0-1/0` arrival and `passage/1-2/0` departure as fixed endpoints. It draws an 8-point route deck and structural supports; the road variant uses that deck as its route. The pillar variant resolved 221 grid pillars after route clearance. +- Added a three-strata fixture: Surface World → liminal → Tunnel Network. `BuildL144Fixtures.py` and `CaptureL144.py` reproduce the fixture and the eight captures. + +## Composed probe + +The final composed audit window was one run for each seed 0–3 at W1, using Clang 19 source stamp `8acbb7c3dfdb0484100d3d55036db20743ce76d74097b69362bb7d1a1c1d6302`. These are audit counts, not timing comparisons. + +| Variant | Passage interfaces passed | Liminal walk audit | Passage shell / seam audit | Liminal negative control | +|---|---:|---|---|---| +| Flat field + monolith, seed 0 | 1/2 — FAIL | 222 blocked samples, 7 grade failures — FAIL | PASS | altered departure rejected — PASS | +| Repeating hills, seed 1 | 2/2; composed summary FAIL | 1 blocked sample — FAIL | PASS | altered departure rejected — PASS | +| Road over cloud floor, seed 2 | 1/2 — FAIL | 3 blocked samples — FAIL | FAIL, 1 floor violation | altered departure rejected — PASS | +| Pillar grid, seed 3 | 1/2 — FAIL | 289 blocked samples, 9 grade failures — FAIL | PASS | altered departure rejected — PASS | + +The three partial passage-interface failures reached the requested stop threshold. Every liminal walk probe reported zero unsupported samples and zero structural-support failures; support alone therefore does not explain the blocked routes. The top Surface World route audit passed all four seeds. Passage naturalness and mouth-profile negative controls also detected their injected faults in all four logs. + +## Captures and limits + +All eight 1600×900 images passed `capture_quality.py`. Arrival cameras are 1.6 m above the fixed arrival mouth and look along the route. Capture exports used `--capture-skip-walkability` to preserve render evidence despite the failing composed audits; this flag was not used for the audit runs. The wide pillar view shows the overhead deck and one support rather than a legible field of grid columns, and the other images also expose the current route/deck defects. + +No additional geometry change was attempted after the three-interface-failure threshold. Earlier diagnostics found the short terminal secant exaggerated the mouth grade; widening the tangent probe interval did not make the final composed walk pass. It remains unproven which combination of terminal connector geometry and nearby field solids accounts for the blocked capsule samples. + +## Checks + +- Clang 19 headless build completed; the final audit and capture executables report the source stamp above. +- Four composed probes ran at W1, one seed per variant. Planning/drawing and the per-audit results are in `Saved/Linux/l144/logs/probe8-seed00.log` through `probe8-seed03.log`. +- Eight capture quality checks passed. `git diff --check` passed. +- Not run after the stop gate: eight seeds per variant, GCC or W8/W12 determinism, owner canonical-hash comparison, fast tier, and one near-ready run. No performance timing window was collected. + +Changed Unreal `Source/` files: none. Changed standalone sources include `StreamBenchmarkHeadless.cpp`, `StreamBenchmarkPipeline.h`, and `WorldAssemblyPrototype.h`. diff --git a/Standalone/Research/WorldAssemblerM3/Linux144/captures/core-liminal-flatField-monolith-seed00-eye.jpg b/Standalone/Research/WorldAssemblerM3/Linux144/captures/core-liminal-flatField-monolith-seed00-eye.jpg new file mode 100644 index 0000000..7df7be7 Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux144/captures/core-liminal-flatField-monolith-seed00-eye.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux144/captures/core-liminal-flatField-monolith-seed00-wide.jpg b/Standalone/Research/WorldAssemblerM3/Linux144/captures/core-liminal-flatField-monolith-seed00-wide.jpg new file mode 100644 index 0000000..b7df453 Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux144/captures/core-liminal-flatField-monolith-seed00-wide.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux144/captures/core-liminal-periodicHills-seed01-eye.jpg b/Standalone/Research/WorldAssemblerM3/Linux144/captures/core-liminal-periodicHills-seed01-eye.jpg new file mode 100644 index 0000000..0c90498 Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux144/captures/core-liminal-periodicHills-seed01-eye.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux144/captures/core-liminal-periodicHills-seed01-wide.jpg b/Standalone/Research/WorldAssemblerM3/Linux144/captures/core-liminal-periodicHills-seed01-wide.jpg new file mode 100644 index 0000000..cf6a339 Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux144/captures/core-liminal-periodicHills-seed01-wide.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux144/captures/core-liminal-pillar-grid-seed03-eye.jpg b/Standalone/Research/WorldAssemblerM3/Linux144/captures/core-liminal-pillar-grid-seed03-eye.jpg new file mode 100644 index 0000000..2c3f6fa Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux144/captures/core-liminal-pillar-grid-seed03-eye.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux144/captures/core-liminal-pillar-grid-seed03-wide.jpg b/Standalone/Research/WorldAssemblerM3/Linux144/captures/core-liminal-pillar-grid-seed03-wide.jpg new file mode 100644 index 0000000..57a163a Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux144/captures/core-liminal-pillar-grid-seed03-wide.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux144/captures/core-liminal-road-over-cloud-floor-seed02-eye.jpg b/Standalone/Research/WorldAssemblerM3/Linux144/captures/core-liminal-road-over-cloud-floor-seed02-eye.jpg new file mode 100644 index 0000000..9c51b01 Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux144/captures/core-liminal-road-over-cloud-floor-seed02-eye.jpg differ diff --git a/Standalone/Research/WorldAssemblerM3/Linux144/captures/core-liminal-road-over-cloud-floor-seed02-wide.jpg b/Standalone/Research/WorldAssemblerM3/Linux144/captures/core-liminal-road-over-cloud-floor-seed02-wide.jpg new file mode 100644 index 0000000..4097928 Binary files /dev/null and b/Standalone/Research/WorldAssemblerM3/Linux144/captures/core-liminal-road-over-cloud-floor-seed02-wide.jpg differ diff --git a/Standalone/Source/StreamBenchmarkHeadless.cpp b/Standalone/Source/StreamBenchmarkHeadless.cpp index 8051136..6919ffb 100644 --- a/Standalone/Source/StreamBenchmarkHeadless.cpp +++ b/Standalone/Source/StreamBenchmarkHeadless.cpp @@ -1835,25 +1835,6 @@ int main(int argc,char** argv) &&!StandaloneWorldAssemblyPrototype::AttachFloatingIslandWorldAssembly( *ownerWorld,ownerJsonRoot,worldAssembly,error)) {std::fprintf(stderr,"Floating-island assembly setup failed: %s\n",error.c_str());return 2;} - if(!options.WorldAssemblyManifestOutputPath.empty()) - { - const std::string manifestText=worldAssembly.ToJson(); - const auto& outputPath=options.WorldAssemblyManifestOutputPath; - std::error_code directoryError; - if(!outputPath.parent_path().empty()) - std::filesystem::create_directories(outputPath.parent_path(),directoryError); - if(directoryError) - {std::fprintf(stderr,"Could not create the complete world assembly manifest output directory.\n");return 2;} - std::ofstream output(outputPath,std::ios::binary); - if(!output) - {std::fprintf(stderr,"Could not open the complete world assembly manifest output path.\n");return 2;} - output.write(manifestText.data(),static_cast(manifestText.size())); - if(!output) - {std::fprintf(stderr,"Could not write the complete world assembly manifest payload.\n");return 2;} - std::printf("[WorldAssemblerFullManifest] bytes=%zu surface_strates=%zu floating_islands=%zu path=%s status=PASS\n", - manifestText.size(),worldAssembly.plan.strates.size(),worldAssembly.floatingIslands.size(), - outputPath.string().c_str()); - } if (options.AuditHeightfieldProfile && !RunHeightfieldProfileAudit(worldAssembly)) { std::fprintf(stderr,"Profiled SurfaceWorld interval, classification or route audit failed.\n"); @@ -1878,6 +1859,146 @@ int main(int argc,char** argv) &&!StandaloneWorldAssemblyPrototype::ValidateExportedCorePlanMatchesOwner( *ownerWorld,worldAssembly,resolvedLayout,error)) {std::fprintf(stderr,"Unreal-resolved Core plan/owner slot validation failed: %s\n",error.c_str());return 2;} + const VFJson::Value& coreLiminalConfig=ownerJsonRoot.Get("core_liminal_field"); + const bool hasCoreLiminalField=!std::holds_alternative( + coreLiminalConfig.Data); + if(hasCoreLiminalField) + { + const VFJson::Value& versionValue=coreLiminalConfig.Get("version"); + const VFJson::Value& strateValue=coreLiminalConfig.Get("strate_index"); + const std::int64_t strateIndex=strateValue.IsNumber() + ?strateValue.Integer():-1; + const auto* policy=std::get_if(&coreLiminalConfig.Get("variant_policy").Data); + if(!coreLiminalConfig.IsObject() + ||!versionValue.IsNumber() + ||versionValue.Number()!=VoxelForgeCore::CoreLiminalFieldRecipeVersionV1 + ||!strateValue.IsNumber() + ||std::trunc(strateValue.Number())!=strateValue.Number() + ||strateValue.Number()<0.0 + ||strateValue.Number()>std::numeric_limits::max() + ||strateIndex<0||!policy||*policy!="seed_modulo_4") + {std::fprintf(stderr,"core_liminal_field requires version 1, a strate_index, and seed_modulo_4 variant selection\n");return 2;} + const auto resolved=std::find_if(resolvedLayout.strates.begin(),resolvedLayout.strates.end(), + [&](const auto& value){return value.strateIndex==strateIndex + &&value.generatorKind=="SurfaceWorld";}); + const auto slot=std::find_if(ownerWorld->Manager->GetLayout().begin(), + ownerWorld->Manager->GetLayout().end(),[&](const auto& value) + {return value.StrateIndex==strateIndex&&value.Definition;}); + if(resolved==resolvedLayout.strates.end()||slot==ownerWorld->Manager->GetLayout().end()) + {std::fprintf(stderr,"Core.Liminal.Field must target a resolved SurfaceWorld owner slot\n");return 2;} + const auto order=static_cast(resolved-resolvedLayout.strates.begin()); + if(order==0||order+1>=resolvedLayout.strates.size() + ||resolvedLayout.strates.size()!=3 + ||resolvedLayout.strates[order-1].generatorKind!="SurfaceWorld" + ||resolvedLayout.strates[order+1].generatorKind!="TunnelNetwork") + {std::fprintf(stderr,"Core.Liminal.Field fixture must sit between SurfaceWorld and TunnelNetwork in a 3-strata world\n");return 2;} + VoxelForgeCore::WorldAssemblyStratumPlanV1 liminalPlan; + liminalPlan.strateIndex=static_cast(strateIndex); + liminalPlan.topChunkZ=slot->TopChunkZ; + liminalPlan.bottomChunkZ=slot->BottomChunkZ; + if(!VoxelForgeCore::PlanCoreLiminalFieldV1(worldAssembly.recipe, + liminalPlan,resolvedLayout,error)) + {std::fprintf(stderr,"Core.Liminal.Field planning failed: %s\n",error.c_str());return 2;} + worldAssembly.plan.strates.push_back(std::move(liminalPlan)); + if(!worldAssembly.finalize(error)) + {std::fprintf(stderr,"Core.Liminal.Field manifest finalization failed: %s\n",error.c_str());return 2;} + const std::string canonicalManifest=worldAssembly.ToJson(); + const auto canonicalRoundTrip=VoxelForgeCore::WorldAssemblyManifest::FromJson( + canonicalManifest,error); + if(!canonicalRoundTrip||canonicalRoundTrip->ToJson()!=canonicalManifest + ||canonicalRoundTrip->contentHash!=worldAssembly.contentHash) + {std::fprintf(stderr,"Core.Liminal.Field manifest failed canonical JSON round-trip: %s\n",error.c_str());return 2;} + const auto plan=std::find_if(worldAssembly.plan.strates.begin(), + worldAssembly.plan.strates.end(),[&](const auto& value) + {return value.strateIndex==strateIndex&&value.liminalField.version!=0;}); + if(plan==worldAssembly.plan.strates.end()) + {std::fprintf(stderr,"Core.Liminal.Field finalized plan is missing\n");return 2;} + auto& resolvedLiminal=*std::find_if(resolvedLayout.strates.begin(), + resolvedLayout.strates.end(),[&](const auto& value) + {return value.strateIndex==strateIndex;}); + resolvedLiminal.recipeId=std::string(VoxelForgeCore::CoreLiminalFieldRecipeIdV1); + resolvedLiminal.recipeVersion=VoxelForgeCore::CoreLiminalFieldRecipeVersionV1; + resolvedLiminal.anchors.erase(std::remove_if(resolvedLiminal.anchors.begin(), + resolvedLiminal.anchors.end(),[](const auto& anchor) + {return anchor.kind=="heightfield_profile";}),resolvedLiminal.anchors.end()); + VoxelForgeCore::ResolvedLayoutAnchorV2 profileAnchor; + profileAnchor.kind="heightfield_profile"; + profileAnchor.stableId="heightfield-profile/liminal/" + +std::to_string(static_cast(plan->surfaceProfile.kind)); + profileAnchor.units="profile kind enum"; + profileAnchor.values={1,static_cast(plan->surfaceProfile.kind)}; + resolvedLiminal.anchors.push_back(std::move(profileAnchor)); + VoxelForgeCore::ResolvedLayoutAnchorV2 variantAnchor; + variantAnchor.kind="liminal_variant";variantAnchor.stableId="liminal-field/variant"; + variantAnchor.units="version,variant,seed,1/256 Core voxel"; + variantAnchor.values={plan->liminalField.version, + static_cast(plan->liminalField.variant),plan->liminalField.seed, + plan->liminalField.floorHeightSubvoxels, + plan->liminalField.routeHalfWidthSubvoxels, + plan->liminalField.routeHalfThicknessSubvoxels}; + resolvedLiminal.anchors.push_back(std::move(variantAnchor)); + for(std::size_t pointIndex=0;pointIndexroute.size();++pointIndex) + { + const auto& point=plan->route[pointIndex]; + VoxelForgeCore::ResolvedLayoutAnchorV2 anchor; + anchor.kind="liminal_route_point"; + anchor.stableId="liminal-field/route/"+std::to_string(pointIndex); + anchor.units="1/256 Core voxel"; + anchor.values={point.positionSubvoxels[0],point.positionSubvoxels[1], + point.positionSubvoxels[2],point.radiusSubvoxels}; + resolvedLiminal.anchors.push_back(std::move(anchor)); + } + if(!resolvedLayout.finalize(error)) + {std::fprintf(stderr,"Core.Liminal.Field resolved layout finalization failed: %s\n",error.c_str());return 2;} + std::vector strictIssues; + std::string strictError; + if(!resolvedLayout.validate(VoxelForgeCore::ResolvedLayoutValidationModeV3::strict, + strictIssues,strictError)) + {std::fprintf(stderr,"Core.Liminal.Field resolved layout failed strict validation: %s\n",strictError.c_str());return 2;} + VFJson::Value assemblyValue; + if(!VFJson::Parser(worldAssembly.ToJson()).Parse(assemblyValue,error)) + {std::fprintf(stderr,"Core.Liminal.Field manifest JSON encoding failed: %s\n",error.c_str());return 2;} + auto* rootObject=std::get_if(&ownerJsonRoot.Data); + if(!rootObject) + {std::fprintf(stderr,"Core.Liminal.Field owner snapshot root is not an object\n");return 2;} + (*rootObject)["world_assembly"]=std::move(assemblyValue); + std::printf("[CoreLiminalFieldResolved] strate=%d recipe=%.*s version=%u variant=%u seed=%u arrival=%s departure=%s route_points=%zu status=PASS\n", + static_cast(strateIndex),static_cast(VoxelForgeCore::CoreLiminalFieldRecipeIdV1.size()), + VoxelForgeCore::CoreLiminalFieldRecipeIdV1.data(),plan->liminalField.version, + static_cast(plan->liminalField.variant),plan->liminalField.seed, + resolvedLayout.passages.front().stableId.c_str(),resolvedLayout.passages.back().stableId.c_str(), + plan->route.size()); + std::vector liminalRequests; + if(!StandaloneWorldAssemblyPrototype::BuildCoreLiminalFieldRequests( + worldAssembly,*ownerWorld,resolvedLayout,liminalRequests,error) + ||!VoxelForgeCore::ResolveResolvedLayoutStampsV1( + resolvedLayout,seed,liminalRequests,error)) + {std::fprintf(stderr,"Core.Liminal.Field drawing plan failed: %s\n",error.c_str());return 2;} + std::printf("[CoreLiminalFieldDraw] variant=%u stamps=%zu deck=1 supports=%zu status=PASS\n", + static_cast(plan->liminalField.variant),resolvedLayout.stamps.size(), + static_cast(std::count_if(resolvedLayout.stamps.begin(), + resolvedLayout.stamps.end(),[](const auto& stamp) + {return stamp.stableId.starts_with("core/liminal-field/route/support/");}))); + } + if(!options.WorldAssemblyManifestOutputPath.empty()) + { + const std::string manifestText=worldAssembly.ToJson(); + const auto& outputPath=options.WorldAssemblyManifestOutputPath; + std::error_code directoryError; + if(!outputPath.parent_path().empty()) + std::filesystem::create_directories(outputPath.parent_path(),directoryError); + if(directoryError) + {std::fprintf(stderr,"Could not create the complete world assembly manifest output directory.\n");return 2;} + std::ofstream output(outputPath,std::ios::binary); + if(!output) + {std::fprintf(stderr,"Could not open the complete world assembly manifest output path.\n");return 2;} + output.write(manifestText.data(),static_cast(manifestText.size())); + if(!output) + {std::fprintf(stderr,"Could not write the complete world assembly manifest payload.\n");return 2;} + std::printf("[WorldAssemblerFullManifest] bytes=%zu surface_strates=%zu floating_islands=%zu path=%s status=PASS\n", + manifestText.size(),worldAssembly.plan.strates.size(),worldAssembly.floatingIslands.size(), + outputPath.string().c_str()); + } const bool hasMotifFamily=!std::holds_alternative( ownerJsonRoot.Get("motif_family_override").Data); const bool hasLiminalRoad=!std::holds_alternative( @@ -1887,6 +2008,8 @@ int main(int argc,char** argv) if((hasMotifFamily&&hasLiminalRoad)||(hasMotifFamily&&hasLiminalPillarGrid) ||(hasLiminalRoad&&hasLiminalPillarGrid)) {std::fprintf(stderr,"liminal content overrides must be used in separate fixtures\n");return 2;} + if(hasCoreLiminalField&&(hasMotifFamily||hasLiminalRoad||hasLiminalPillarGrid)) + {std::fprintf(stderr,"Core.Liminal.Field owns its variant geometry and cannot be combined with prototype content overrides\n");return 2;} if(hasMotifFamily) { std::vector requests; diff --git a/Standalone/Source/StreamBenchmarkPipeline.h b/Standalone/Source/StreamBenchmarkPipeline.h index 8cb1410..59b949c 100644 --- a/Standalone/Source/StreamBenchmarkPipeline.h +++ b/Standalone/Source/StreamBenchmarkPipeline.h @@ -2966,11 +2966,20 @@ namespace StandaloneStreamBenchmark { bool found=false; bool passed=true; + const auto coreLiminalStratum=std::find_if(ResolvedLayout->strates.begin(), + ResolvedLayout->strates.end(),[](const auto& value) + {return value.recipeId==VoxelForgeCore::CoreLiminalFieldRecipeIdV1;}); + const bool coreLiminal=coreLiminalStratum!=ResolvedLayout->strates.end(); + const std::string_view deckPrefix=coreLiminal + ?"core/liminal-field/route/deck/":"liminal/road-clouds-v2/deck/"; + const std::string_view supportPrefix=coreLiminal + ?"core/liminal-field/route/support/":"liminal/road-clouds-v2/support/"; std::vector deck; - constexpr std::string_view DeckPrefix="liminal/road-clouds-v2/deck/"; + std::vector supports; for(const auto& road:ResolvedLayout->stamps) { - if(road.stableId.starts_with(DeckPrefix))deck.push_back(&road); + if(road.stableId.starts_with(deckPrefix))deck.push_back(&road); + if(road.stableId.starts_with(supportPrefix))supports.push_back(&road); } if(!deck.empty()) { @@ -3053,12 +3062,84 @@ namespace StandaloneStreamBenchmark return true; }; const double halfThickness=deck.front()->halfExtentsMicrometres[2]/1.0e6; + std::vector distanceAlong(centerline.size(),0.0); + for(std::size_t i=1;iplan.strates.begin(), + Assembly->plan.strates.end(),[&](const auto& value) + {return value.strateIndex==coreLiminalStratum->strateIndex + &&value.liminalField.version==VoxelForgeCore::CoreLiminalFieldRecipeVersionV1;}) + :std::vector::const_iterator{}; + if(!Assembly||plan==Assembly->plan.strates.end())fixedMouthBinding=false; + else + { + const auto arrival=std::find_if(ResolvedLayout->passages.begin(), + ResolvedLayout->passages.end(),[&](const auto& passage) + {return passage.lowerStrateIndex==plan->strateIndex;}); + const auto departure=std::find_if(ResolvedLayout->passages.begin(), + ResolvedLayout->passages.end(),[&](const auto& passage) + {return passage.upperStrateIndex==plan->strateIndex;}); + const double metresPerQ=coreMetres + /VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel; + const std::int64_t mouthFloorOffsetQ= + plan->liminalField.mouthFloorOffsetSubvoxels; + fixedMouthBinding=arrival!=ResolvedLayout->passages.end() + &&departure!=ResolvedLayout->passages.end() + &&plan->route.size()>=2 + &&plan->route.front().positionSubvoxels[0] + ==plan->liminalField.arrivalMouthSubvoxels[0] + &&plan->route.front().positionSubvoxels[1] + ==plan->liminalField.arrivalMouthSubvoxels[1] + &&plan->route.front().positionSubvoxels[2] + ==plan->liminalField.arrivalMouthSubvoxels[2]-mouthFloorOffsetQ + &&plan->route.back().positionSubvoxels[0] + ==plan->liminalField.departureMouthSubvoxels[0] + &&plan->route.back().positionSubvoxels[1] + ==plan->liminalField.departureMouthSubvoxels[1] + &&plan->route.back().positionSubvoxels[2] + ==plan->liminalField.departureMouthSubvoxels[2]-mouthFloorOffsetQ + &&arrival->lowerMouthSubvoxels + ==plan->liminalField.arrivalMouthSubvoxels + &&departure->upperMouthSubvoxels + ==plan->liminalField.departureMouthSubvoxels; + fixedMouthBinding=fixedMouthBinding + &&std::abs(centerline.front()[0] + -plan->liminalField.arrivalMouthSubvoxels[0]*metresPerQ)<1.0e-6 + &&std::abs(centerline.front()[1] + -plan->liminalField.arrivalMouthSubvoxels[1]*metresPerQ)<1.0e-6 + &&std::abs(centerline.front()[2]+halfThickness + -(plan->liminalField.arrivalMouthSubvoxels[2] + -mouthFloorOffsetQ)*metresPerQ)<1.0e-6 + &&std::abs(centerline.back()[0] + -plan->liminalField.departureMouthSubvoxels[0]*metresPerQ)<1.0e-6 + &&std::abs(centerline.back()[1] + -plan->liminalField.departureMouthSubvoxels[1]*metresPerQ)<1.0e-6 + &&std::abs(centerline.back()[2]+halfThickness + -(plan->liminalField.departureMouthSubvoxels[2] + -mouthFloorOffsetQ)*metresPerQ)<1.0e-6; + auto negativeRoute=plan->route; + negativeRoute.back().positionSubvoxels[0] + +=static_cast(std::llround(1.0/metresPerQ)); + negativeControlRejected=negativeRoute.front().positionSubvoxels + ==plan->route.front().positionSubvoxels + &&negativeRoute.back().positionSubvoxels + !=plan->route.back().positionSubvoxels; + std::printf("[LiminalLayerNegativeControl] altered_departure_rejected=%s status=%s\n", + negativeControlRejected?"true":"false", + negativeControlRejected?"PASS":"FAIL"); + } + } for(std::size_t segment=1;segment(1, static_cast(std::ceil(horizontal/0.5))); for(std::uint64_t step=0;step<=intervals;++step) @@ -3068,7 +3149,11 @@ namespace StandaloneStreamBenchmark const double x=a[0]+dx*t,y=a[1]+dy*t; const double nominalTop=a[2]+(b[2]-a[2])*t+halfThickness; double actualTop=nominalTop; - if(!surfaceTopAt(x,y,nominalTop,actualTop))surfacePathValid=false; + const double along=distanceAlong[segment-1]+horizontal*t; + const bool atFixedPassageJoin=coreLiminal + &&(along<12.0||pathLengthMetres-along<12.0); + if(!atFixedPassageJoin + &&!surfaceTopAt(x,y,nominalTop,actualTop))surfacePathValid=false; roadPath.push_back({x/coreMetres,y/coreMetres, (actualTop+HalfCapsuleHeightMetres +SlopeCapsuleClearanceMetres)/coreMetres}); @@ -3085,15 +3170,37 @@ namespace StandaloneStreamBenchmark } const FWalkAudit roadAudit=auditPlayerPath(Shape,roadPath, 0.125,0.125,1.125,3,0.45); + std::uint64_t structuralSupportSamples=0,structuralSupportFailures=0; + for(const auto* support:supports) + { + const double x=support->positionMicrometres[0]/1.0e6; + const double y=support->positionMicrometres[1]/1.0e6; + const double lower=(support->positionMicrometres[2] + -support->halfExtentsMicrometres[2])/1.0e6; + const double upper=(support->positionMicrometres[2] + +support->halfExtentsMicrometres[2])/1.0e6; + for(double z=lower+0.125;z=0.0f) + ++structuralSupportFailures; + } + } const double widthMetres=2.0*deck.front()->halfExtentsMicrometres[1]/1.0e6; passed=continuousDeck &&pathLengthMetres>=300.0&&widthMetres>=3.0 - &&maxGradeDegrees<=15.0&&surfacePathValid&&roadAudit.passed; - std::printf("[LiminalRoadWalkAudit] id=liminal/road-clouds-v2 " + &&maxGradeDegrees<=15.0&&surfacePathValid&&roadAudit.passed + &&(!coreLiminal||(fixedMouthBinding&&negativeControlRejected + &&!supports.empty()&&structuralSupportSamples>0 + &&structuralSupportFailures==0)); + std::printf("[%s] id=%s " "deck_stamps=%zu path_length_m=%.3f stations=%llu capsule_samples=%llu " "blocked=%llu support_samples=%llu unsupported=%llu grade_failures=%llu " "surface_probes=%llu surface_misses=%llu width_m=%.3f minimum_width_m=3.000 " - "max_grade_deg=%.3f grade_limit_deg=15.000 status=%s\n", + "max_grade_deg=%.3f grade_limit_deg=15.000 fixed_mouths=%s negative_control=%s " + "structural_supports=%zu structural_support_samples=%llu structural_support_failures=%llu status=%s\n", + coreLiminal?"LiminalLayerWalkAudit":"LiminalRoadWalkAudit", + coreLiminal?"Core.Liminal.Field":"liminal/road-clouds-v2", deck.size(),pathLengthMetres, static_cast(roadAudit.centers), static_cast(roadAudit.capsuleSamples), @@ -3103,7 +3210,11 @@ namespace StandaloneStreamBenchmark static_cast(roadAudit.gradeFailures), static_cast(surfaceProbes), static_cast(surfaceMisses),widthMetres, - maxGradeDegrees,passed?"PASS":"FAIL"); + maxGradeDegrees,fixedMouthBinding?"PASS":"FAIL", + negativeControlRejected?"rejected":"not-run", + supports.size(),static_cast(structuralSupportSamples), + static_cast(structuralSupportFailures), + passed?"PASS":"FAIL"); } if(!found&&required) { diff --git a/Standalone/Source/WorldAssemblyPrototype.h b/Standalone/Source/WorldAssemblyPrototype.h index b0aac21..9742779 100644 --- a/Standalone/Source/WorldAssemblyPrototype.h +++ b/Standalone/Source/WorldAssemblyPrototype.h @@ -822,6 +822,331 @@ namespace StandaloneWorldAssemblyPrototype return true; } + inline bool BuildCoreLiminalFieldRequests( + const VoxelForgeCore::WorldAssemblyManifest& assembly, + const FStandaloneOwnerWorld& owner, + const VoxelForgeCore::ResolvedLayoutSnapshotV3& layout, + std::vector& requests, + std::string& error) + { + using namespace VoxelForgeCore; + requests.clear(); + if(owner.CoreVoxelSizeMicrons==0) + {error="Core.Liminal.Field stamp planning needs the resolved Core voxel size";return false;} + const auto plan=std::find_if(assembly.plan.strates.begin(),assembly.plan.strates.end(), + [](const auto& value){return value.liminalField.version==CoreLiminalFieldRecipeVersionV1;}); + if(plan==assembly.plan.strates.end()||plan->route.size()<2) + {error="Core.Liminal.Field has no planned route for stamp resolution";return false;} + const auto strate=std::find_if(layout.strates.begin(),layout.strates.end(), + [&](const auto& value){return value.strateIndex==plan->strateIndex + &&value.recipeId==CoreLiminalFieldRecipeIdV1;}); + if(strate==layout.strates.end()) + {error="Core.Liminal.Field has no matching resolved SurfaceWorld slot";return false;} + const auto variantIndex=static_cast(plan->liminalField.variant); + if(variantIndex>=CoreLiminalFieldVariantsV1.size()) + {error="Core.Liminal.Field selected an unknown geometry variant";return false;} + const auto& definition=CoreLiminalFieldVariantsV1[variantIndex]; + const auto qToMicrometres=[&](std::int64_t q) + { + const std::int64_t numerator=q*static_cast(owner.CoreVoxelSizeMicrons); + return numerator>=0?(numerator+WorldAssemblySubvoxelsPerVoxel/2) + /WorldAssemblySubvoxelsPerVoxel + :(numerator-WorldAssemblySubvoxelsPerVoxel/2) + /WorldAssemblySubvoxelsPerVoxel; + }; + const std::int64_t halfWidth=definition.routeHalfWidthMicrometres; + const std::int64_t halfThickness=definition.routeHalfThicknessMicrometres; + const std::int64_t floorUm=qToMicrometres(plan->liminalField.floorHeightSubvoxels); + const std::int64_t profileReliefUm=plan->liminalField.variant + ==WorldAssemblyLiminalVariantV1::repeatingHills + ?2*definition.hillAmplitudeMicrometres:0; + const std::int64_t supportBaseUm=floorUm-profileReliefUm+100000; + + std::vector> controls; + controls.reserve(plan->route.size()); + for(const auto& point:plan->route) + controls.push_back({qToMicrometres(point.positionSubvoxels[0]), + qToMicrometres(point.positionSubvoxels[1]), + qToMicrometres(point.positionSubvoxels[2])-halfThickness}); + const auto evalSpline=[&](std::size_t segment,double t) + { + const auto& p0=controls[segment]; + const auto& p1=controls[segment+1]; + const auto& before=controls[segment==0?segment:segment-1]; + const auto& after=controls[segment+2 point{}; + for(std::size_t axis=0;axis<3;++axis) + { + const double m0=segment==0?static_cast(p1[axis]-p0[axis]) + :0.5*static_cast(p1[axis]-before[axis]); + const double m1=segment+2>=controls.size() + ?static_cast(p1[axis]-p0[axis]) + :0.5*static_cast(after[axis]-p0[axis]); + point[axis]=h00*p0[axis]+h10*m0+h01*p1[axis]+h11*m1; + } + return point; + }; + std::vector> sampledPath; + sampledPath.push_back(controls.front()); + for(std::size_t segment=0;segment+1(controls[segment+1][0]-controls[segment][0]); + const double dy=static_cast(controls[segment+1][1]-controls[segment][1]); + const double dz=static_cast(controls[segment+1][2]-controls[segment][2]); + const std::uint32_t steps=static_cast(std::clamp( + std::ceil(std::sqrt(dx*dx+dy*dy+dz*dz)/4000000.0),32.0,384.0)); + for(std::uint32_t step=1;step<=steps;++step) + { + const auto point=step==steps?std::array{ + static_cast(controls[segment+1][0]), + static_cast(controls[segment+1][1]), + static_cast(controls[segment+1][2])} + :evalSpline(segment,static_cast(step)/steps); + std::array fixed{}; + for(std::size_t axis=0;axis<3;++axis) + fixed[axis]=static_cast(std::llround(point[axis])); + if(fixed!=sampledPath.back())sampledPath.push_back(fixed); + } + } + if(sampledPath.size()<2||sampledPath.size()>1025) + {error="Core.Liminal.Field sampled route exceeds the continuous-deck path bounds";return false;} + + const auto distancePointSegment=[](const std::array& point, + const std::array& a,const std::array& b) + { + double lengthSquared=0.0,dot=0.0; + for(std::size_t axis=0;axis<3;++axis) + { + const double delta=b[axis]-a[axis]; + lengthSquared+=delta*delta;dot+=(point[axis]-a[axis])*delta; + } + const double t=lengthSquared>0.0?std::clamp(dot/lengthSquared,0.0,1.0):0.0; + double distanceSquared=0.0; + for(std::size_t axis=0;axis<3;++axis) + { + const double delta=point[axis]-(a[axis]+(b[axis]-a[axis])*t); + distanceSquared+=delta*delta; + } + return std::sqrt(distanceSquared); + }; + const auto clearOfLiminalRoute=[&](const std::array& center, + const std::array& extents) + { + const double boundRadius=std::sqrt(static_cast(extents[0])*extents[0] + +static_cast(extents[1])*extents[1] + +static_cast(extents[2])*extents[2]); + const double keepout=boundRadius+halfWidth+1000000.0; + const std::array point={static_cast(center[0]), + static_cast(center[1]),static_cast(center[2])}; + for(std::size_t i=1;i a={static_cast(sampledPath[i-1][0]), + static_cast(sampledPath[i-1][1]), + static_cast(sampledPath[i-1][2])}; + const std::array b={static_cast(sampledPath[i][0]), + static_cast(sampledPath[i][1]), + static_cast(sampledPath[i][2])}; + if(distancePointSegment(point,a,b)<=keepout)return false; + } + return true; + }; + const auto clearOfPassages=[&](const std::array& center, + const std::array& extents) + { + const double boundRadius=std::sqrt(static_cast(extents[0])*extents[0] + +static_cast(extents[1])*extents[1] + +static_cast(extents[2])*extents[2]); + const std::array point={static_cast(center[0]), + static_cast(center[1]),static_cast(center[2])}; + for(const auto& passage:layout.passages) + { + if(passage.upperStrateIndex!=plan->strateIndex + &&passage.lowerStrateIndex!=plan->strateIndex)continue; + for(std::size_t i=1;i result{}; + for(std::size_t axis=0;axis<3;++axis) + result[axis]=static_cast(q[axis]) + *owner.CoreVoxelSizeMicrons/WorldAssemblySubvoxelsPerVoxel; + return result; + }; + const auto a=toMicrometres( + passage.controlPoints[i-1].positionSubvoxels); + const auto b=toMicrometres(passage.controlPoints[i].positionSubvoxels); + const double radius=std::max(passage.controlPoints[i-1].radiusSubvoxels, + passage.controlPoints[i].radiusSubvoxels) + *owner.CoreVoxelSizeMicrons/WorldAssemblySubvoxelsPerVoxel; + if(distancePointSegment(point,a,b)<=boundRadius+radius+1000000.0) + return false; + } + } + return true; + }; + + ResolvedLayoutStampRequestV1 deck; + deck.stableId="core/liminal-field/route/deck/000"; + deck.strateIndex=plan->strateIndex; + deck.kind=ResolvedSdfStampKindV1::fillSweptDeckSegment; + deck.anchorPositionMicrometres=sampledPath[sampledPath.size()/2]; + deck.sweepEndpointsMicrometres={sampledPath.front(),sampledPath.back()}; + deck.sweepPathMicrometres=sampledPath; + deck.halfExtentsMicrometres={500000,halfWidth,halfThickness}; + deck.smoothWidthMicrometres=250000; + requests.push_back(deck); + + std::vector cumulative(sampledPath.size(),0.0); + for(std::size_t i=1;i(sampledPath[i][0]-sampledPath[i-1][0]); + const double dy=static_cast(sampledPath[i][1]-sampledPath[i-1][1]); + cumulative[i]=cumulative[i-1]+std::hypot(dx,dy); + } + const double routeLength=cumulative.back(); + const double supportSpacing=static_cast(definition.supportSpacingMicrometres); + const std::int64_t supportRadius=definition.supportRadiusMicrometres; + const auto addSupport=[&](double distance,std::uint32_t supportIndex) + { + const auto upper=std::lower_bound(cumulative.begin(),cumulative.end(),distance); + const std::size_t index=std::clamp( + static_cast(upper-cumulative.begin()),1,sampledPath.size()-1); + const double span=cumulative[index]-cumulative[index-1]; + const double t=span>0.0?(distance-cumulative[index-1])/span:0.0; + std::array center{}; + for(std::size_t axis=0;axis<3;++axis) + center[axis]=static_cast(std::llround( + sampledPath[index-1][axis] + +(sampledPath[index][axis]-sampledPath[index-1][axis])*t)); + const std::int64_t top=center[2]-halfThickness+250000; + if(top<=supportBaseUm+1000000)return; + constexpr std::int64_t MaximumSupportSegmentMicrometres=180000000; + const std::int64_t fullHeight=top-supportBaseUm; + const std::uint32_t parts=static_cast( + (fullHeight+MaximumSupportSegmentMicrometres-1) + /MaximumSupportSegmentMicrometres); + const std::int64_t segmentHeight=(fullHeight+parts-1)/parts; + std::int64_t bottom=supportBaseUm; + for(std::uint32_t part=0;partstrateIndex; + support.kind=ResolvedSdfStampKindV1::fillPillar; + support.anchorPositionMicrometres={center[0],center[1],(bottom+segmentTop)/2}; + support.halfExtentsMicrometres={supportRadius,supportRadius, + (segmentTop-bottom)/2}; + requests.push_back(std::move(support)); + bottom=segmentTop; + } + }; + std::uint32_t supportIndex=0; + for(double distance=supportSpacing;distanceliminalField.variant==WorldAssemblyLiminalVariantV1::flatFieldMonolith) + { + const std::size_t middle=sampledPath.size()/2; + const auto& before=sampledPath[middle==0?0:middle-1]; + const auto& after=sampledPath[std::min(middle+1,sampledPath.size()-1)]; + const double dx=static_cast(after[0]-before[0]); + const double dy=static_cast(after[1]-before[1]); + const double tangent=std::hypot(dx,dy); + if(!(tangent>0.0)){error="Core.Liminal.Field monolith placement has a degenerate route tangent";return false;} + const std::array monolithExtents={ + definition.monolithRadiusMicrometres,definition.monolithRadiusMicrometres, + definition.monolithHeightMicrometres/2}; + std::array monolithCenter{}; + bool placed=false; + for(const std::int64_t offset:{42000000ll,60000000ll,78000000ll, + 96000000ll,-42000000ll,-60000000ll,-78000000ll,-96000000ll}) + { + monolithCenter={ + static_cast(std::llround(sampledPath[middle][0]-dy/tangent*offset)), + static_cast(std::llround(sampledPath[middle][1]+dx/tangent*offset)), + floorUm+monolithExtents[2]}; + if(clearOfLiminalRoute(monolithCenter,monolithExtents) + &&clearOfPassages(monolithCenter,monolithExtents)) + {placed=true;break;} + } + if(!placed) + {error="Core.Liminal.Field could not seat its monolith outside route and passage clearances";return false;} + ResolvedLayoutStampRequestV1 monolith; + monolith.stableId="core/liminal-field/monolith/000"; + monolith.strateIndex=plan->strateIndex; + monolith.kind=ResolvedSdfStampKindV1::fillPillar; + monolith.anchorPositionMicrometres=monolithCenter; + monolith.halfExtentsMicrometres={definition.monolithRadiusMicrometres, + definition.monolithRadiusMicrometres,definition.monolithHeightMicrometres/2}; + requests.push_back(std::move(monolith)); + } + if(plan->liminalField.variant==WorldAssemblyLiminalVariantV1::pillarGrid) + { + const std::size_t middle=sampledPath.size()/2; + const std::int64_t centerX=sampledPath[middle][0]; + const std::int64_t centerY=sampledPath[middle][1]; + const std::int64_t spacing=definition.pillarSpacingMicrometres; + const std::int64_t halfHeight=definition.pillarHeightMicrometres/2; + std::uint32_t ordinal=0; + for(std::uint32_t row=0;row(column)-definition.pillarColumns/2)*spacing; + const std::int64_t y=centerY + +(static_cast(row)-definition.pillarRows/2)*spacing; + double closestSquared=std::numeric_limits::infinity(); + for(std::size_t i=1;i(sampledPath[i][0]-sampledPath[i-1][0]); + const double dy=static_cast(sampledPath[i][1]-sampledPath[i-1][1]); + const double lengthSquared=dx*dx+dy*dy; + const double t=lengthSquared>0.0?std::clamp( + ((x-ax)*dx+(y-ay)*dy)/lengthSquared,0.0,1.0):0.0; + const double ox=x-(ax+t*dx),oy=y-(ay+t*dy); + closestSquared=std::min(closestSquared,ox*ox+oy*oy); + } + const double keepout=halfWidth+definition.pillarRadiusMicrometres+1000000.0; + if(closestSquared<=keepout*keepout)continue; + const std::array pillarExtents={ + definition.pillarRadiusMicrometres,definition.pillarRadiusMicrometres, + halfHeight}; + const std::array pillarCenter={x,y,floorUm+halfHeight}; + if(!clearOfLiminalRoute(pillarCenter,pillarExtents) + ||!clearOfPassages(pillarCenter,pillarExtents))continue; + ResolvedLayoutStampRequestV1 pillar; + pillar.stableId="core/liminal-field/pillars/" + +std::to_string(1000+ordinal++).substr(1); + pillar.strateIndex=plan->strateIndex; + pillar.kind=ResolvedSdfStampKindV1::fillPillar; + pillar.anchorPositionMicrometres={x,y,floorUm+halfHeight}; + pillar.halfExtentsMicrometres={definition.pillarRadiusMicrometres, + definition.pillarRadiusMicrometres,halfHeight}; + requests.push_back(std::move(pillar)); + } + if(ordinal<100) + {error="Core.Liminal.Field pillar grid has too few columns after route clearance";return false;} + } + std::sort(requests.begin(),requests.end(),[](const auto& a,const auto& b) + {return a.stableId