wip(assembler): round L24 -- per-strate passage styles; natural Worm default; FI anchor island

Core plans inter-strate passages from each upper strate's FStratePassageConfig for every
generator: Straight / Worm (the new natural default: meander, variable radius, rough walls) /
Spiral / Cascading. A naturalness audit (no >8 m flat runs, >=0.20 m wall relief) fails on L23's
smooth tube. FloatingIslands selects Spiral, with a deterministic unique anchor island (ordinal 2)
joined to the bedrock floor by a rock root. The SW arrival no longer stamps flat shelves.
Capsule audits 0 blocked / 0 unsupported / 0 grade failures, all styles. Full-view hashes
deterministic W1/W8/W12, Clang = GCC (reviewer re-run). Evaluator 10, FI geometry 5.
KNOWN: the FI helix renders as a spiral ribbon in open air around the pillar instead of a tunnel
enclosed in rock; fix next round. Unreal edits uncompiled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-20 21:42:21 +02:00
1 parent 71d09a8c52
commit cb9236c265
17 files changed
+922 -122

No files matched your search

+32
View File
@@ -1951,6 +1951,38 @@ and async rules remain unchanged.
[`Standalone/Research/WorldAssemblerM3/Linux23/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux23/REPORT.md) [`Standalone/Research/WorldAssemblerM3/Linux23/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux23/REPORT.md)
records the audit and captures. records the audit and captures.
### 8.42 Per-strate natural passages and the FloatingIslands anchor helix — World Assembler L24
`FStratePassageConfig.Style` now drives the shared Core planner for every generator. The default is
`Worm`; Straight, Spiral and Cascading remain selectable per upper strate. Passage radius, reach,
distance and style stay in the resolved-layout input, so equal seeds and route anchors produce equal
paths on every peer. The passage carver adds bounded, deterministic wall relief (`0.24 m` before the
gravity-facing attenuation) while its support tread follows the same route profile below the capsule.
FloatingIslands' owner config selects Spiral. A seed-derived anchor island near `(0,0)` is joined to
bedrock by a 6 m radius rock root. The 3 m helix descends from the island mouth to the `-119.5 m`
bedrock top inside that root, leaving at least the authored clearance around its 1.5 m bore, then
widens into the Worm route toward the SurfaceWorld arrival. The selected anchor is ordinal 2; the
other island ordinals and their placement hashes are unchanged. FloatingIslands recipe geometry is
version 5 and the shared passage evaluator is version 10; the resolved-layout snapshot stays V3.
The passage naturalness audit rejects a horizontal run above 8 m (segments flatter than 0.5 degrees)
or wall relief below 0.20 m peak-to-peak across at least 8 valid samples. The smooth-wall control
has zero relief and fails; the anchored Spiral has 0 m flat run and 0.793 m measured relief. Strict
layout controls, route binding, capsule clearance, support, grade, and the no-carve negative control
remain required. Passage-centered interval proofs remain unknown/fallback rather than falsely
certified. At the lower mouth, the shared layer now relies on native terrain plus the passage tread;
it does not stamp additional flat route-anchor shelves over the cave opening. The L24 report records
compiler/worker parity, unchanged SurfaceWorld hashes away from the route, captures and the Windows
adapter checklist.
The Linux offscreen capture tool accepts `--headlamp=on` to aim brighter readable shading from the
camera for interior views and `--clip-plane=NX,NY,NZ,D` to discard the plane's negative half-space for
cutaway views. Both affect captures only, not generated geometry.
[`Standalone/Research/WorldAssemblerM3/Linux24/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux24/REPORT.md)
records the evidence and owner-facing caveats.
## 9. Multiplayer model (listen-server first, dedicated-friendly) ## 9. Multiplayer model (listen-server first, dedicated-friendly)
> **Status: transport is DESIGN ONLY — nothing is networked in-tree yet** (no `Replicated`/`HasAuthority`/RPCs; a > **Status: transport is DESIGN ONLY — nothing is networked in-tree yet** (no `Replicated`/`HasAuthority`/RPCs; a
+15
View File
@@ -1166,6 +1166,21 @@ and [`Standalone/Research/WorldAssemblerM3/Linux18/REPORT.md`](Standalone/Resear
The evaluator version is 9. See ARCHITECTURE §8.41 and The evaluator version is 9. See ARCHITECTURE §8.41 and
[`Standalone/Research/WorldAssemblerM3/Linux23/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux23/REPORT.md). [`Standalone/Research/WorldAssemblerM3/Linux23/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux23/REPORT.md).
### 3.25 Per-strate natural passages and FloatingIslands anchor helix — World Assembler L24
| Symbol | Canonical file | Role |
|---|---|---|
| `FStratePassageConfig::Style`, `EVoxelPassageStyle` | `Source/VoxelForge/Public/VoxelStrateTypes.h` | Selects Straight, Worm, Spiral or Cascading per upper strate; Worm is the default. |
| `PlanResolvedLayoutPassagesV1` | `Standalone/Core/src/ResolvedLayoutInternal.h` | Applies the selected style in the shared planner; a configured FI Spiral follows the anchor root into the bedrock and smoothly transitions into the inter-strate route. |
| Anchor root recipe fields and `EvaluateIslandSDF` | `Standalone/Core/src/FloatingIslandsInternal.h` | Add the seed-stable ordinal-2 island/root without changing other island placements; geometry version 5. |
| `ApplyResolvedStratePassageCarves` | `Standalone/Core/src/ShapeWorldInternal.h` | Applies deterministic rough wall relief and the supported route profile to all native generator fields. |
| `--audit-resolved-layout-passages` naturalness checks | `Standalone/Source/StreamBenchmarkPipeline.h` | Checks 8 m maximum flat run, 0.20 m minimum wall relief, capsule/support/grade, mouths, routes and the blocked-passage control. |
| `--headlamp=on`, `--clip-plane=NX,NY,NZ,D` | `Standalone/Source/LinuxOffscreenCapture.cpp` | Adds camera-directed light or discards the plane's negative half-space for offscreen cutaways; neither affects Core output. |
The shared evaluator is version 10, the FI recipe geometry is version 5, and resolved-layout schema
V3 is unchanged. See ARCHITECTURE §8.42 and
[`Standalone/Research/WorldAssemblerM3/Linux24/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux24/REPORT.md).
## 4. The density pipeline (most-edited hot path) ## 4. The density pipeline (most-edited hot path)
### 4.1 `GetDensityWithParams` (TunnelNetwork) — VoxelGenerator.cpp:3749 ### 4.1 `GetDensityWithParams` (TunnelNetwork) — VoxelGenerator.cpp:3749
@@ -479,12 +479,13 @@ bool BuildFloatingIslandStratumPlan(const UVoxelStrateManager* StrateManager,
using namespace VoxelForgeCore; using namespace VoxelForgeCore;
const FIntVector Chunk(0, 0, Slot.TopChunkZ); const FIntVector Chunk(0, 0, Slot.TopChunkZ);
const FFloatingIslandParams Params = StrateManager->GetFloatingIslandParamsForChunk(Chunk); const FFloatingIslandParams Params = StrateManager->GetFloatingIslandParamsForChunk(Chunk);
const FStratePassageConfig& Passage = Slot.Definition->PassageConfig;
WorldAssemblyFloatingIslandPlanV1 IslandPlan; WorldAssemblyFloatingIslandPlanV1 IslandPlan;
IslandPlan.strateIndex = Slot.StrateIndex; IslandPlan.strateIndex = Slot.StrateIndex;
IslandPlan.topChunkZ = Slot.TopChunkZ; IslandPlan.topChunkZ = Slot.TopChunkZ;
IslandPlan.bottomChunkZ = Slot.BottomChunkZ; IslandPlan.bottomChunkZ = Slot.BottomChunkZ;
auto& Recipe = IslandPlan.manifest.recipe; auto& Recipe = IslandPlan.manifest.recipe;
Recipe.geometryVersion = 4; Recipe.geometryVersion = 5;
Recipe.layoutChunkWidthSubvoxels = LayoutChunkWidthSubvoxels; Recipe.layoutChunkWidthSubvoxels = LayoutChunkWidthSubvoxels;
Recipe.seed = static_cast<std::uint32_t>(StrateManager->GetWorldSeed()); Recipe.seed = static_cast<std::uint32_t>(StrateManager->GetWorldSeed());
Recipe.ownerVoxelSizeMicrons = static_cast<std::uint32_t>(std::llround(OwnerVoxelSizeCm * 10000.0)); Recipe.ownerVoxelSizeMicrons = static_cast<std::uint32_t>(std::llround(OwnerVoxelSizeCm * 10000.0));
@@ -516,6 +517,28 @@ bool BuildFloatingIslandStratumPlan(const UVoxelStrateManager* StrateManager,
Recipe.roughnessSubvoxels = WorldAssemblyFixed(Params.SurfaceRoughness * OwnerToCoreScale); Recipe.roughnessSubvoxels = WorldAssemblyFixed(Params.SurfaceRoughness * OwnerToCoreScale);
Recipe.blendRadiusSubvoxels = WorldAssemblyFixed(Params.SDFBlendRadius * OwnerToCoreScale); Recipe.blendRadiusSubvoxels = WorldAssemblyFixed(Params.SDFBlendRadius * OwnerToCoreScale);
Recipe.boundarySealSubvoxels = WorldAssemblyFixed(Params.BoundarySealThickness * OwnerToCoreScale); Recipe.boundarySealSubvoxels = WorldAssemblyFixed(Params.BoundarySealThickness * OwnerToCoreScale);
const auto OwnerVoxelsToCoreQ=[&](double Value)
{
return static_cast<std::int64_t>(std::llround(Value*Recipe.ownerVoxelSizeMicrons
*WorldAssemblySubvoxelsPerVoxel/Recipe.coreVoxelSizeMicrons));
};
Recipe.anchorIslandOrdinal=Recipe.primaryIslandCount-1;
Recipe.anchorPlacementRadiusSubvoxels=OwnerVoxelsToCoreQ(Passage.DistanceMax);
const std::int64_t spiralRadius=OwnerVoxelsToCoreQ(Passage.SpiralRadius);
// Reserve at least a 1.5 m mid-bore through the anchor root.
constexpr double AnchorPassageMinimumBoreOwnerVoxels=12.0;
const std::int64_t mouthBore=std::max(OwnerVoxelsToCoreQ(Passage.MouthRadius),
OwnerVoxelsToCoreQ(12.0));
const std::int64_t bore=std::max(OwnerVoxelsToCoreQ(Passage.MidRadius),
OwnerVoxelsToCoreQ(AnchorPassageMinimumBoreOwnerVoxels));
const std::int64_t wallReserve=OwnerVoxelsToCoreQ(12.0);
Recipe.anchorMouthOffsetSubvoxels=spiralRadius+mouthBore+OwnerVoxelsToCoreQ(4.0);
const std::int64_t excursion=std::max(OwnerVoxelsToCoreQ(Passage.Wander),spiralRadius);
Recipe.anchorRootRadiusSubvoxels=excursion+bore+wallReserve;
Recipe.anchorIslandMinimumRadiusSubvoxels=std::max(Recipe.minimumRadiusSubvoxels,
Recipe.anchorMouthOffsetSubvoxels+mouthBore+OwnerVoxelsToCoreQ(4.0));
Recipe.anchorRootBottomZSubvoxels=Recipe.layoutMinimumZSubvoxels
+Recipe.boundarySealSubvoxels;
std::string PlanError; std::string PlanError;
const std::optional<FloatingIslandManifestV1> Planned = PlanFloatingIslandCluster(Recipe, PlanError); const std::optional<FloatingIslandManifestV1> Planned = PlanFloatingIslandCluster(Recipe, PlanError);
if (!Planned) if (!Planned)
@@ -1042,13 +1065,23 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
VoxelForgeCore::ResolvedLayoutPassageConfigV1 PassageConfig; VoxelForgeCore::ResolvedLayoutPassageConfigV1 PassageConfig;
PassageConfig.upperStrateIndex = Slot.StrateIndex; PassageConfig.upperStrateIndex = Slot.StrateIndex;
PassageConfig.connections = AuthoredPassage.Connections; PassageConfig.connections = AuthoredPassage.Connections;
PassageConfig.mouthRadiusSubvoxels = OwnerVoxelsToCoreQ(AuthoredPassage.MouthRadius); PassageConfig.style = static_cast<std::uint32_t>(AuthoredPassage.Style);
PassageConfig.midRadiusSubvoxels = OwnerVoxelsToCoreQ(AuthoredPassage.MidRadius); PassageConfig.mouthRadiusSubvoxels = std::max(OwnerVoxelsToCoreQ(AuthoredPassage.MouthRadius),
OwnerVoxelsToCoreQ(12.0));
PassageConfig.midRadiusSubvoxels = std::max(OwnerVoxelsToCoreQ(AuthoredPassage.MidRadius),
OwnerVoxelsToCoreQ(12.0));
PassageConfig.reachMinimumSubvoxels = OwnerVoxelsToCoreQ(AuthoredPassage.ReachMin); PassageConfig.reachMinimumSubvoxels = OwnerVoxelsToCoreQ(AuthoredPassage.ReachMin);
PassageConfig.reachMaximumSubvoxels = OwnerVoxelsToCoreQ(AuthoredPassage.ReachMax); PassageConfig.reachMaximumSubvoxels = OwnerVoxelsToCoreQ(AuthoredPassage.ReachMax);
PassageConfig.distanceMinimumSubvoxels = OwnerVoxelsToCoreQ(AuthoredPassage.DistanceMin); PassageConfig.distanceMinimumSubvoxels = OwnerVoxelsToCoreQ(AuthoredPassage.DistanceMin);
PassageConfig.distanceMaximumSubvoxels = OwnerVoxelsToCoreQ(AuthoredPassage.DistanceMax); PassageConfig.distanceMaximumSubvoxels = OwnerVoxelsToCoreQ(AuthoredPassage.DistanceMax);
PassageConfig.wanderSubvoxels = OwnerVoxelsToCoreQ(AuthoredPassage.Wander); PassageConfig.wanderSubvoxels = OwnerVoxelsToCoreQ(AuthoredPassage.Wander);
PassageConfig.segments = static_cast<std::uint32_t>(FMath::Clamp(AuthoredPassage.Segments,1,48));
PassageConfig.verticalWobbleSubvoxels = OwnerVoxelsToCoreQ(AuthoredPassage.VerticalWobble);
PassageConfig.spiralRadiusSubvoxels = OwnerVoxelsToCoreQ(AuthoredPassage.SpiralRadius);
PassageConfig.spiralTurnsQ16 = static_cast<std::int64_t>(std::llround(
static_cast<double>(AuthoredPassage.SpiralTurns)*65536.0));
PassageConfig.cascadeSteps = static_cast<std::uint32_t>(FMath::Clamp(AuthoredPassage.CascadeSteps,1,16));
PassageConfig.cascadeLedgeSubvoxels = OwnerVoxelsToCoreQ(AuthoredPassage.CascadeLedge);
PassageConfigs.push_back(PassageConfig); PassageConfigs.push_back(PassageConfig);
const ECaveGeneratorType Type = StrateManager->GetGeneratorTypeForChunk( const ECaveGeneratorType Type = StrateManager->GetGeneratorTypeForChunk(
FIntVector(0, 0, Slot.TopChunkZ)); FIntVector(0, 0, Slot.TopChunkZ));
@@ -1134,6 +1167,21 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
Anchor.values = { Island.centerSubvoxels[0], Island.centerSubvoxels[1], Anchor.values = { Island.centerSubvoxels[0], Island.centerSubvoxels[1],
Island.centerSubvoxels[2], Island.radiusSubvoxels, Island.topHalfSubvoxels }; Island.centerSubvoxels[2], Island.radiusSubvoxels, Island.topHalfSubvoxels };
Item.anchors.push_back(std::move(Anchor)); Item.anchors.push_back(std::move(Anchor));
if(IslandIndex==Plan->manifest.recipe.anchorIslandOrdinal
&&Plan->manifest.recipe.geometryVersion>=5)
{
VoxelForgeCore::ResolvedLayoutAnchorV2 PassageAnchor;
PassageAnchor.kind="passage_anchor";
PassageAnchor.stableId="passage-anchor/island/"+std::to_string(IslandIndex);
PassageAnchor.units="1/256 Core voxel";
PassageAnchor.values={Island.centerSubvoxels[0],Island.centerSubvoxels[1],
Plan->manifest.recipe.anchorRootRadiusSubvoxels,
Plan->manifest.recipe.anchorMouthOffsetSubvoxels,
Plan->manifest.recipe.anchorPlacementRadiusSubvoxels,
Plan->manifest.recipe.anchorRootBottomZSubvoxels,
Island.radiusSubvoxels};
Item.anchors.push_back(std::move(PassageAnchor));
}
} }
for (std::size_t RouteIndex = 0; RouteIndex < Plan->manifest.plan.route.size(); ++RouteIndex) for (std::size_t RouteIndex = 0; RouteIndex < Plan->manifest.plan.route.size(); ++RouteIndex)
{ {
+7 -11
View File
@@ -1852,8 +1852,6 @@ struct VOXELFORGE_API FStrateDisturbanceParams
UENUM(BlueprintType) UENUM(BlueprintType)
enum class EVoxelPassageStyle : uint8 enum class EVoxelPassageStyle : uint8
{ {
// “Straight” is retained as the authored style name for the base connection, but its runtime
// geometry is a gentle two-leg switchback rather than a vertical shaft.
Straight UMETA(DisplayName = "Straight (walkable sloped tunnel)"), Straight UMETA(DisplayName = "Straight (walkable sloped tunnel)"),
Worm UMETA(DisplayName = "Worm (organic meander around the axis)"), Worm UMETA(DisplayName = "Worm (organic meander around the axis)"),
Spiral UMETA(DisplayName = "Spiral (corkscrew descent)"), Spiral UMETA(DisplayName = "Spiral (corkscrew descent)"),
@@ -1878,14 +1876,12 @@ struct VOXELFORGE_API FStratePassageConfig
// Tunnel shape. // Tunnel shape.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Passage") UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Passage")
EVoxelPassageStyle Style = EVoxelPassageStyle::Straight; EVoxelPassageStyle Style = EVoxelPassageStyle::Worm;
// ----- WIDTH (tapers along the length) ----- // ----- WIDTH (tapers along the length) -----
// Radius at the two mouths (entry/exit) and at the middle. Equal = uniform tube; // Radius at the two mouths (entry/exit) and at the middle. Equal = uniform tube;
// The walkable default is 1.5x the original comfortable profile: 2.25 m diameter at mouths // Core raises both radii to at least 12 owner voxels (1.5 m) for capsule clearance.
// and 1.875 m at the waist when the stored radii remain 9/7.5 voxels at 12.5 cm per voxel. // The passage landing remains wider for turning.
// Double these asset values to retain the previous 4.5/3.75 m dimensions. The §6.5 landing
// room remains wider for turning.
// Mouth > Mid = chambers at the ends with a squeeze between; Mid > Mouth = a bulge. // Mouth > Mid = chambers at the ends with a squeeze between; Mid > Mouth = a bulge.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Passage|Width", meta = (ClampMin = "1.0")) UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Passage|Width", meta = (ClampMin = "1.0"))
float MouthRadius = 9.0f; float MouthRadius = 9.0f;
@@ -1912,8 +1908,8 @@ struct VOXELFORGE_API FStratePassageConfig
float DistanceMax = 192.0f; float DistanceMax = 192.0f;
// ----- SHAPE detail ----- // ----- SHAPE detail -----
// Exotic styles only: max sideways excursion from the axis (voxels). 24 = 6 m; 0 = straight // Max sideways variation from the winding path's axis (voxels). Core keeps the route inside
// even in Worm style. The default walkable switchback does not use this wander value. // DistanceMax and adds enough distance to meet the walkable grade.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Passage|Shape", meta = (ClampMin = "0.0")) UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Passage|Shape", meta = (ClampMin = "0.0"))
float Wander = 24.0f; float Wander = 24.0f;
@@ -1926,8 +1922,8 @@ struct VOXELFORGE_API FStratePassageConfig
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Passage|Shape", meta = (ClampMin = "0.0")) UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Passage|Shape", meta = (ClampMin = "0.0"))
float VerticalWobble = 0.0f; float VerticalWobble = 0.0f;
// Spiral style: helix radius (voxels) and number of full turns over the descent. The default // Spiral style: helix radius (voxels) and minimum number of turns. Core adds turns when
// radius is 6 m. // needed to meet the walkable grade. The default stored radius is 3 m at the legacy scale.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Passage|Spiral", meta = (ClampMin = "1.0")) UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Passage|Spiral", meta = (ClampMin = "1.0"))
float SpiralRadius = 24.0f; float SpiralRadius = 24.0f;
@@ -249,8 +249,8 @@ namespace VoxelForgeCore
// L'assembleur v1 encode coordonnees et scalaires en 1/256 de voxel, independamment des tuiles. // L'assembleur v1 encode coordonnees et scalaires en 1/256 de voxel, independamment des tuiles.
inline constexpr std::int64_t WorldAssemblySubvoxelsPerVoxel = 256; inline constexpr std::int64_t WorldAssemblySubvoxelsPerVoxel = 256;
inline constexpr std::uint32_t WorldAssemblySchemaVersion = 2; inline constexpr std::uint32_t WorldAssemblySchemaVersion = 2;
// Version 9 indexes passage landings so non-monotone route tails stay carveable. // Version 10 adds per-strate passage styles and rough wall relief to resolved passages.
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 9; inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 10;
enum class FieldChannel : std::uint32_t enum class FieldChannel : std::uint32_t
{ {
@@ -396,7 +396,9 @@ namespace VoxelForgeCore
// bounded lobed roots and arched spans without changing the feature-family schema. // bounded lobed roots and arched spans without changing the feature-family schema.
// Version 3 keeps the version-2 island shapes, removes the all-XY lower boundary seal, // Version 3 keeps the version-2 island shapes, removes the all-XY lower boundary seal,
// and routes its upper seal through the shared strate-boundary primitive. Version 4 // and routes its upper seal through the shared strate-boundary primitive. Version 4
// records the director-resolved strate layout width in the recipe. // records the director-resolved strate layout width. Version 5 adds one passage-rooted
// island whose root reaches the owned bedrock and whose surface route ends at its mouth.
// New production adapters opt in explicitly; the compatibility default remains version 4.
std::uint32_t geometryVersion = 4; std::uint32_t geometryVersion = 4;
std::uint32_t unitsPerVoxel = static_cast<std::uint32_t>(WorldAssemblySubvoxelsPerVoxel); std::uint32_t unitsPerVoxel = static_cast<std::uint32_t>(WorldAssemblySubvoxelsPerVoxel);
// Import scale is explicit in microns; the reduced ratio maps owner-authored lengths to Core voxels. // Import scale is explicit in microns; the reduced ratio maps owner-authored lengths to Core voxels.
@@ -434,6 +436,14 @@ namespace VoxelForgeCore
std::int64_t boundarySealSubvoxels = 2 * WorldAssemblySubvoxelsPerVoxel; std::int64_t boundarySealSubvoxels = 2 * WorldAssemblySubvoxelsPerVoxel;
std::int64_t bridgeHalfWidthSubvoxels = 3 * WorldAssemblySubvoxelsPerVoxel; std::int64_t bridgeHalfWidthSubvoxels = 3 * WorldAssemblySubvoxelsPerVoxel;
std::int64_t bridgeThicknessSubvoxels = 4 * WorldAssemblySubvoxelsPerVoxel; std::int64_t bridgeThicknessSubvoxels = 4 * WorldAssemblySubvoxelsPerVoxel;
// Version 5 anchor-island inputs are resolved from that strate's passage config.
// The anchor is the last primary route island and must stay inside the passage disk.
std::uint32_t anchorIslandOrdinal = 2;
std::int64_t anchorPlacementRadiusSubvoxels = 0;
std::int64_t anchorRootRadiusSubvoxels = 0;
std::int64_t anchorIslandMinimumRadiusSubvoxels = 0;
std::int64_t anchorRootBottomZSubvoxels = 0;
std::int64_t anchorMouthOffsetSubvoxels = 0;
WorldAssemblyRouteConstraintsV1 routeConstraints; WorldAssemblyRouteConstraintsV1 routeConstraints;
std::uint64_t contentHash = 0; std::uint64_t contentHash = 0;
}; };
@@ -660,6 +670,8 @@ namespace VoxelForgeCore
{ {
std::int32_t upperStrateIndex = -1; std::int32_t upperStrateIndex = -1;
std::int32_t connections = 1; std::int32_t connections = 1;
// EVoxelPassageStyle numeric values: Straight=0, Worm=1, Spiral=2, Cascading=3.
std::uint32_t style = 1;
std::int64_t mouthRadiusSubvoxels = 0; std::int64_t mouthRadiusSubvoxels = 0;
std::int64_t midRadiusSubvoxels = 0; std::int64_t midRadiusSubvoxels = 0;
std::int64_t reachMinimumSubvoxels = 0; std::int64_t reachMinimumSubvoxels = 0;
@@ -667,6 +679,12 @@ namespace VoxelForgeCore
std::int64_t distanceMinimumSubvoxels = 0; std::int64_t distanceMinimumSubvoxels = 0;
std::int64_t distanceMaximumSubvoxels = 0; std::int64_t distanceMaximumSubvoxels = 0;
std::int64_t wanderSubvoxels = 0; std::int64_t wanderSubvoxels = 0;
std::uint32_t segments = 20;
std::int64_t verticalWobbleSubvoxels = 0;
std::int64_t spiralRadiusSubvoxels = 0;
std::int64_t spiralTurnsQ16 = 2ll << 16;
std::uint32_t cascadeSteps = 4;
std::int64_t cascadeLedgeSubvoxels = 0;
}; };
struct VFCORE_API ResolvedLayoutSnapshotV3 struct VFCORE_API ResolvedLayoutSnapshotV3
+130 -10
View File
@@ -188,6 +188,15 @@ namespace floating_island_detail
HashWord(hash, static_cast<std::uint64_t>(recipe.crustDepthBonusQ16), 8); HashWord(hash, static_cast<std::uint64_t>(recipe.crustDepthBonusQ16), 8);
HashWord(hash, static_cast<std::uint64_t>(recipe.rootDepthRatioQ16), 8); HashWord(hash, static_cast<std::uint64_t>(recipe.rootDepthRatioQ16), 8);
} }
if (recipe.geometryVersion >= 5)
{
HashWord(hash, recipe.anchorIslandOrdinal, 4);
HashWord(hash, static_cast<std::uint64_t>(recipe.anchorPlacementRadiusSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(recipe.anchorRootRadiusSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(recipe.anchorIslandMinimumRadiusSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(recipe.anchorRootBottomZSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(recipe.anchorMouthOffsetSubvoxels), 8);
}
HashWord(hash, static_cast<std::uint64_t>(recipe.blendRadiusSubvoxels), 8); HashWord(hash, static_cast<std::uint64_t>(recipe.blendRadiusSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(recipe.contourWarpSubvoxels), 8); HashWord(hash, static_cast<std::uint64_t>(recipe.contourWarpSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(recipe.roughnessSubvoxels), 8); HashWord(hash, static_cast<std::uint64_t>(recipe.roughnessSubvoxels), 8);
@@ -247,7 +256,7 @@ namespace floating_island_detail
inline bool ValidateRecipe(const FloatingIslandRecipeV1& recipe, std::string& error) inline bool ValidateRecipe(const FloatingIslandRecipeV1& recipe, std::string& error)
{ {
const std::array<std::int64_t, 26> values = { const std::array<std::int64_t, 32> values = {
recipe.layoutOriginXSubvoxels, recipe.layoutOriginYSubvoxels, recipe.layoutOriginXSubvoxels, recipe.layoutOriginYSubvoxels,
recipe.layoutCenterTopZSubvoxels, recipe.layoutMinimumZSubvoxels, recipe.layoutCenterTopZSubvoxels, recipe.layoutMinimumZSubvoxels,
recipe.layoutMaximumZSubvoxels, recipe.layoutChunkWidthSubvoxels, recipe.islandDensityQ16, recipe.layoutMaximumZSubvoxels, recipe.layoutChunkWidthSubvoxels, recipe.islandDensityQ16,
@@ -259,6 +268,10 @@ namespace floating_island_detail
recipe.blendRadiusSubvoxels, recipe.contourWarpSubvoxels, recipe.blendRadiusSubvoxels, recipe.contourWarpSubvoxels,
recipe.roughnessSubvoxels, recipe.boundarySealSubvoxels, recipe.roughnessSubvoxels, recipe.boundarySealSubvoxels,
recipe.bridgeHalfWidthSubvoxels, recipe.bridgeThicknessSubvoxels, recipe.bridgeHalfWidthSubvoxels, recipe.bridgeThicknessSubvoxels,
static_cast<std::int64_t>(recipe.anchorIslandOrdinal),
recipe.anchorPlacementRadiusSubvoxels, recipe.anchorRootRadiusSubvoxels,
recipe.anchorIslandMinimumRadiusSubvoxels, recipe.anchorRootBottomZSubvoxels,
recipe.anchorMouthOffsetSubvoxels,
recipe.routeConstraints.capsuleRadiusSubvoxels, recipe.routeConstraints.capsuleRadiusSubvoxels,
recipe.routeConstraints.capsuleHeightSubvoxels, recipe.routeConstraints.capsuleHeightSubvoxels,
recipe.routeConstraints.maximumGradeQ16}; recipe.routeConstraints.maximumGradeQ16};
@@ -268,7 +281,7 @@ namespace floating_island_detail
? recipe.ownerVoxelSizeMicrons / ScaleGcd : 0; ? recipe.ownerVoxelSizeMicrons / ScaleGcd : 0;
const std::uint32_t ExpectedDenominator = ScaleGcd > 0 const std::uint32_t ExpectedDenominator = ScaleGcd > 0
? recipe.coreVoxelSizeMicrons / ScaleGcd : 0; ? recipe.coreVoxelSizeMicrons / ScaleGcd : 0;
if (recipe.schemaVersion != 1 || recipe.geometryVersion < 1 || recipe.geometryVersion > 4 if (recipe.schemaVersion != 1 || recipe.geometryVersion < 1 || recipe.geometryVersion > 5
|| (recipe.geometryVersion == 1 || (recipe.geometryVersion == 1
&& (recipe.crustDepthBonusQ16 != 0 || recipe.rootDepthRatioQ16 != 0)) && (recipe.crustDepthBonusQ16 != 0 || recipe.rootDepthRatioQ16 != 0))
|| recipe.unitsPerVoxel != Q || recipe.unitsPerVoxel != Q
@@ -323,6 +336,21 @@ namespace floating_island_detail
|| constraints.minimumClearanceSubvoxels < 0 || constraints.minimumClearanceSubvoxels < 0
|| constraints.minimumSupportProbes == 0 || constraints.minimumSupportProbes > 5) || constraints.minimumSupportProbes == 0 || constraints.minimumSupportProbes > 5)
{ error = "floating-island shape or route parameters are outside the supported range"; return false; } { error = "floating-island shape or route parameters are outside the supported range"; return false; }
if (recipe.geometryVersion >= 5
&& (recipe.anchorIslandOrdinal != recipe.primaryIslandCount - 1
|| recipe.anchorPlacementRadiusSubvoxels <= 0
|| recipe.anchorRootRadiusSubvoxels < recipe.anchorMouthOffsetSubvoxels
+ recipe.routeConstraints.capsuleRadiusSubvoxels + Q
|| recipe.anchorIslandMinimumRadiusSubvoxels
< recipe.anchorMouthOffsetSubvoxels
+ recipe.routeConstraints.capsuleRadiusSubvoxels + Q
|| recipe.anchorIslandMinimumRadiusSubvoxels > 64 * Q
|| recipe.anchorRootBottomZSubvoxels < recipe.layoutMinimumZSubvoxels
+ recipe.boundarySealSubvoxels
|| recipe.anchorRootBottomZSubvoxels >= recipe.layoutCenterTopZSubvoxels
|| recipe.anchorMouthOffsetSubvoxels <= 0
|| recipe.anchorMouthOffsetSubvoxels > 64 * Q))
{ error = "floating-island bedrock anchor is outside its passage or rock-thickness bounds"; return false; }
return true; return true;
} }
@@ -725,6 +753,25 @@ namespace floating_island_detail
const double rootBlend = std::clamp(radius * 0.12, 2.0 * Q, 5.0 * Q); const double rootBlend = std::clamp(radius * 0.12, 2.0 * Q, 5.0 * Q);
for (std::int32_t root = 0; root < rootCount; ++root) for (std::int32_t root = 0; root < rootCount; ++root)
sdf = SmoothMin(sdf, RootLobeDensity(recipe, island, root, x, y, z), rootBlend); sdf = SmoothMin(sdf, RootLobeDensity(recipe, island, root, x, y, z), rootBlend);
if (recipe.geometryVersion >= 5 && island.ordinal == recipe.anchorIslandOrdinal
&& recipe.anchorRootBottomZSubvoxels < island.bottomZSubvoxels)
{
const double rootTop = static_cast<double>(island.bottomZSubvoxels);
const double rootBottom = static_cast<double>(recipe.anchorRootBottomZSubvoxels);
const double depth = std::max(1.0, rootTop - rootBottom);
const double blend = std::clamp(static_cast<double>(recipe.anchorRootRadiusSubvoxels)
* 0.12, 3.0 * Q, 6.0 * Q);
// A slightly flared lower buttress joins the island's own root lobes to bedrock.
// Une base legerement evasee relie les racines de l'ile a la roche de fond.
const double t = std::clamp((rootTop - z) / depth, 0.0, 1.0);
const double rootRadius = static_cast<double>(recipe.anchorRootRadiusSubvoxels)
* (0.92 + 0.08 * SmoothStep(t));
double rootSdf = std::max({Column.Radial - rootRadius,
rootBottom - z, z - rootTop});
rootSdf = ApplySurfaceDetail(recipe, island.stableId ^ 0x414e43484f52524full,
rootSdf, x, y, z, 0.75);
sdf = SmoothMin(sdf, rootSdf, blend);
}
const double topDetailScale = topDistance >= std::max(sideDistance, undersideDistance) const double topDetailScale = topDistance >= std::max(sideDistance, undersideDistance)
|| z >= static_cast<double>(IslandCrustBottom(recipe, island)) + radius * 0.45 || z >= static_cast<double>(IslandCrustBottom(recipe, island)) + radius * 0.45
? 0.25 : 1.0; ? 0.25 : 1.0;
@@ -895,9 +942,15 @@ namespace floating_island_detail
FloatingIslandAnchorV1& island) FloatingIslandAnchorV1& island)
{ {
const std::int64_t pad = IslandBlendMargin(recipe) + recipe.roughnessSubvoxels; const std::int64_t pad = IslandBlendMargin(recipe) + recipe.roughnessSubvoxels;
const std::int64_t reach = island.radiusSubvoxels + recipe.contourWarpSubvoxels + pad; const bool isAnchor=recipe.geometryVersion>=5
&&island.ordinal==recipe.anchorIslandOrdinal;
const std::int64_t reach = std::max(island.radiusSubvoxels,
isAnchor?recipe.anchorRootRadiusSubvoxels:std::int64_t{0})
+ recipe.contourWarpSubvoxels + pad;
island.influenceMinimumSubvoxels = {island.centerSubvoxels[0] - reach, island.influenceMinimumSubvoxels = {island.centerSubvoxels[0] - reach,
island.centerSubvoxels[1] - reach, island.bottomZSubvoxels - pad}; island.centerSubvoxels[1] - reach,
(isAnchor?std::min(island.bottomZSubvoxels,recipe.anchorRootBottomZSubvoxels)
:island.bottomZSubvoxels)-pad};
island.influenceMaximumSubvoxels = {island.centerSubvoxels[0] + reach, island.influenceMaximumSubvoxels = {island.centerSubvoxels[0] + reach,
island.centerSubvoxels[1] + reach, island.topZSubvoxels + pad}; island.centerSubvoxels[1] + reach, island.topZSubvoxels + pad};
} }
@@ -942,12 +995,19 @@ namespace floating_island_detail
std::unordered_set<std::uint64_t> islandIds; std::unordered_set<std::uint64_t> islandIds;
std::unordered_set<std::uint32_t> ordinals; std::unordered_set<std::uint32_t> ordinals;
const FloatingIslandAnchorV1* anchorIsland = nullptr;
for (const auto& island : plan.islands) for (const auto& island : plan.islands)
{ {
const bool isAnchor=recipe.geometryVersion>=5
&&island.ordinal==recipe.anchorIslandOrdinal;
if(isAnchor)anchorIsland=&island;
if (island.stableId == 0 || !islandIds.insert(island.stableId).second if (island.stableId == 0 || !islandIds.insert(island.stableId).second
|| !ordinals.insert(island.ordinal).second || !ordinals.insert(island.ordinal).second
|| island.radiusSubvoxels < recipe.minimumRadiusSubvoxels || island.radiusSubvoxels < (isAnchor?recipe.anchorIslandMinimumRadiusSubvoxels
|| island.radiusSubvoxels > recipe.maximumRadiusSubvoxels :recipe.minimumRadiusSubvoxels)
|| island.radiusSubvoxels > (isAnchor
?std::max(recipe.anchorIslandMinimumRadiusSubvoxels,recipe.maximumRadiusSubvoxels)
:recipe.maximumRadiusSubvoxels)
|| island.topHalfSubvoxels <= 0 || island.topZSubvoxels <= island.bottomZSubvoxels || island.topHalfSubvoxels <= 0 || island.topZSubvoxels <= island.bottomZSubvoxels
|| island.taperEndQ16 < recipe.taperEndMinimumQ16 || island.taperEndQ16 < recipe.taperEndMinimumQ16
|| island.taperEndQ16 > recipe.taperEndMaximumQ16 || island.taperEndQ16 > recipe.taperEndMaximumQ16
@@ -964,12 +1024,16 @@ namespace floating_island_detail
{ error = "floating-island anchor has invalid coordinates or an inverted bound"; return false; } { error = "floating-island anchor has invalid coordinates or an inverted bound"; return false; }
} }
const std::int64_t pad = IslandBlendMargin(recipe) + recipe.roughnessSubvoxels; const std::int64_t pad = IslandBlendMargin(recipe) + recipe.roughnessSubvoxels;
const std::int64_t reach = island.radiusSubvoxels + recipe.contourWarpSubvoxels + pad; const std::int64_t reach = std::max(island.radiusSubvoxels,
isAnchor?recipe.anchorRootRadiusSubvoxels:std::int64_t{0})
+ recipe.contourWarpSubvoxels + pad;
if (island.influenceMinimumSubvoxels[0] > island.centerSubvoxels[0] - reach if (island.influenceMinimumSubvoxels[0] > island.centerSubvoxels[0] - reach
|| island.influenceMaximumSubvoxels[0] < island.centerSubvoxels[0] + reach || island.influenceMaximumSubvoxels[0] < island.centerSubvoxels[0] + reach
|| island.influenceMinimumSubvoxels[1] > island.centerSubvoxels[1] - reach || island.influenceMinimumSubvoxels[1] > island.centerSubvoxels[1] - reach
|| island.influenceMaximumSubvoxels[1] < island.centerSubvoxels[1] + reach || island.influenceMaximumSubvoxels[1] < island.centerSubvoxels[1] + reach
|| island.influenceMinimumSubvoxels[2] > island.bottomZSubvoxels - pad || island.influenceMinimumSubvoxels[2] > (isAnchor
?std::min(island.bottomZSubvoxels,recipe.anchorRootBottomZSubvoxels)
:island.bottomZSubvoxels) - pad
|| island.influenceMaximumSubvoxels[2] < island.topZSubvoxels + pad || island.influenceMaximumSubvoxels[2] < island.topZSubvoxels + pad
|| island.bottomZSubvoxels < recipe.layoutMinimumZSubvoxels + recipe.boundarySealSubvoxels || island.bottomZSubvoxels < recipe.layoutMinimumZSubvoxels + recipe.boundarySealSubvoxels
|| island.topZSubvoxels > recipe.layoutMaximumZSubvoxels - recipe.boundarySealSubvoxels) || island.topZSubvoxels > recipe.layoutMaximumZSubvoxels - recipe.boundarySealSubvoxels)
@@ -978,6 +1042,19 @@ namespace floating_island_detail
for (std::uint32_t ordinal = 0; ordinal < recipe.primaryIslandCount; ++ordinal) for (std::uint32_t ordinal = 0; ordinal < recipe.primaryIslandCount; ++ordinal)
if (!ordinals.contains(ordinal)) if (!ordinals.contains(ordinal))
{ error = "floating-island route chain is missing a primary seeded anchor"; return false; } { error = "floating-island route chain is missing a primary seeded anchor"; return false; }
if(recipe.geometryVersion>=5)
{
if(!anchorIsland)
{error="floating-island plan is missing its unique passage anchor island";return false;}
const double anchorDistance=std::hypot(
static_cast<double>(anchorIsland->centerSubvoxels[0]),
static_cast<double>(anchorIsland->centerSubvoxels[1]));
if(anchorDistance>recipe.anchorPlacementRadiusSubvoxels
||recipe.anchorRootBottomZSubvoxels>=anchorIsland->bottomZSubvoxels
||recipe.anchorMouthOffsetSubvoxels+recipe.routeConstraints.capsuleRadiusSubvoxels
>=anchorIsland->radiusSubvoxels)
{error="floating-island anchor island or its mouth leaves the passage radius or rock envelope";return false;}
}
std::unordered_set<std::uint64_t> edgeIds; std::unordered_set<std::uint64_t> edgeIds;
std::unordered_map<std::uint64_t, std::vector<std::uint64_t>> adjacency; std::unordered_map<std::uint64_t, std::vector<std::uint64_t>> adjacency;
@@ -1239,6 +1316,22 @@ std::optional<FloatingIslandManifestV1> FloatingIslandManifestV1::FromJson(
&& manifest.recipe.layoutChunkWidthSubvoxels!=resolvedLayoutChunkWidthSubvoxels) && manifest.recipe.layoutChunkWidthSubvoxels!=resolvedLayoutChunkWidthSubvoxels)
{error="floating-island recipe layout width disagrees with the resolved world layout";return std::nullopt;} {error="floating-island recipe layout width disagrees with the resolved world layout";return std::nullopt;}
} }
if(manifest.recipe.geometryVersion>=5)
{
if(!floating_island_detail::ReadU32(recipeValue.Get("anchor_island_ordinal"),
manifest.recipe.anchorIslandOrdinal)
||!floating_island_detail::ReadInteger(recipeValue.Get("anchor_placement_radius_q"),
manifest.recipe.anchorPlacementRadiusSubvoxels)
||!floating_island_detail::ReadInteger(recipeValue.Get("anchor_root_radius_q"),
manifest.recipe.anchorRootRadiusSubvoxels)
||!floating_island_detail::ReadInteger(recipeValue.Get("anchor_island_minimum_radius_q"),
manifest.recipe.anchorIslandMinimumRadiusSubvoxels)
||!floating_island_detail::ReadInteger(recipeValue.Get("anchor_root_bottom_z_q"),
manifest.recipe.anchorRootBottomZSubvoxels)
||!floating_island_detail::ReadInteger(recipeValue.Get("anchor_mouth_offset_q"),
manifest.recipe.anchorMouthOffsetSubvoxels))
{error="floating-island geometry version 5 is missing a passage-rooted anchor field";return std::nullopt;}
}
if (!floating_island_detail::ReadU32(root.Get("schema_version"), schema) if (!floating_island_detail::ReadU32(root.Get("schema_version"), schema)
|| !floating_island_detail::ReadHash(root.Get("content_hash"), manifestHash) || !floating_island_detail::ReadHash(root.Get("content_hash"), manifestHash)
|| !floating_island_detail::ReadU32(recipeValue.Get("schema_version"), manifest.recipe.schemaVersion) || !floating_island_detail::ReadU32(recipeValue.Get("schema_version"), manifest.recipe.schemaVersion)
@@ -1391,6 +1484,13 @@ std::string FloatingIslandManifestV1::ToJson() const
<< ",\"root_depth_ratio_q16\":" << recipe.rootDepthRatioQ16; << ",\"root_depth_ratio_q16\":" << recipe.rootDepthRatioQ16;
if(recipe.geometryVersion>=4) if(recipe.geometryVersion>=4)
out << ",\"layout_chunk_width_q\":" << recipe.layoutChunkWidthSubvoxels; out << ",\"layout_chunk_width_q\":" << recipe.layoutChunkWidthSubvoxels;
if(recipe.geometryVersion>=5)
out << ",\"anchor_island_ordinal\":" << recipe.anchorIslandOrdinal
<< ",\"anchor_placement_radius_q\":" << recipe.anchorPlacementRadiusSubvoxels
<< ",\"anchor_root_radius_q\":" << recipe.anchorRootRadiusSubvoxels
<< ",\"anchor_island_minimum_radius_q\":" << recipe.anchorIslandMinimumRadiusSubvoxels
<< ",\"anchor_root_bottom_z_q\":" << recipe.anchorRootBottomZSubvoxels
<< ",\"anchor_mouth_offset_q\":" << recipe.anchorMouthOffsetSubvoxels;
out << ",\"blend_radius_q\":" << recipe.blendRadiusSubvoxels out << ",\"blend_radius_q\":" << recipe.blendRadiusSubvoxels
<< ",\"contour_warp_q\":" << recipe.contourWarpSubvoxels << ",\"contour_warp_q\":" << recipe.contourWarpSubvoxels
<< ",\"roughness_q\":" << recipe.roughnessSubvoxels << ",\"roughness_q\":" << recipe.roughnessSubvoxels
@@ -1552,6 +1652,9 @@ std::optional<FloatingIslandManifestV1> PlanFloatingIslandCluster(
auto island = floating_island_detail::MakeIsland(recipe, i, auto island = floating_island_detail::MakeIsland(recipe, i,
primaryXY[i][0], primaryXY[i][1], tops[i], site); primaryXY[i][0], primaryXY[i][1], tops[i], site);
island.radiusSubvoxels = radii[i]; island.radiusSubvoxels = radii[i];
if(recipe.geometryVersion>=5&&i==recipe.anchorIslandOrdinal)
island.radiusSubvoxels=std::max(island.radiusSubvoxels,
recipe.anchorIslandMinimumRadiusSubvoxels);
island.topHalfSubvoxels = std::max<std::int64_t>(1, island.radiusSubvoxels / 5); island.topHalfSubvoxels = std::max<std::int64_t>(1, island.radiusSubvoxels / 5);
island.centerSubvoxels[2] = island.topZSubvoxels - island.topHalfSubvoxels; island.centerSubvoxels[2] = island.topZSubvoxels - island.topHalfSubvoxels;
island.bottomZSubvoxels = floating_island_detail::IslandBottom(recipe, island.bottomZSubvoxels = floating_island_detail::IslandBottom(recipe,
@@ -1638,14 +1741,31 @@ std::optional<FloatingIslandManifestV1> PlanFloatingIslandCluster(
static_cast<double>(a.centerSubvoxels[0]) + dx * t); static_cast<double>(a.centerSubvoxels[0]) + dx * t);
const std::int64_t y = floating_island_detail::RoundQ( const std::int64_t y = floating_island_detail::RoundQ(
static_cast<double>(a.centerSubvoxels[1]) + dy * t); static_cast<double>(a.centerSubvoxels[1]) + dy * t);
std::int64_t routeX=x,routeY=y;
if(recipe.geometryVersion>=5&&segment+2==count&&piece==pieces)
{
const auto& anchor=islands[recipe.anchorIslandOrdinal];
const std::uint32_t side=static_cast<std::uint32_t>(
floating_island_detail::UnitQ16(recipe.seed,anchor.stableId,
0x414e43484f524d50ull)&1u);
const double routeAngle=std::atan2(dy,dx);
const double angle=routeAngle+(side?1.0:-1.0)
*floating_island_detail::Pi/2.0;
const double mouthX=static_cast<double>(anchor.centerSubvoxels[0])
+static_cast<double>(recipe.anchorMouthOffsetSubvoxels)*std::cos(angle);
const double mouthY=static_cast<double>(anchor.centerSubvoxels[1])
+static_cast<double>(recipe.anchorMouthOffsetSubvoxels)*std::sin(angle);
routeX=floating_island_detail::RoundQ(mouthX);
routeY=floating_island_detail::RoundQ(mouthY);
}
const auto surface = floating_island_detail::FindCapsuleFootingTopZ( const auto surface = floating_island_detail::FindCapsuleFootingTopZ(
recipe, manifest.plan, x, y); recipe, manifest.plan, routeX, routeY);
if (!surface) if (!surface)
{ error = "island planner could not find a full capsule-width support surface along its connector chain"; return std::nullopt; } { error = "island planner could not find a full capsule-width support surface along its connector chain"; return std::nullopt; }
WorldAssemblyRoutePointV1 point; WorldAssemblyRoutePointV1 point;
point.stableFeatureId = floating_island_detail::StableId(recipe.seed, point.stableFeatureId = floating_island_detail::StableId(recipe.seed,
0x500000000ull + routeOrdinal, 0x524f555445ull); 0x500000000ull + routeOrdinal, 0x524f555445ull);
point.positionSubvoxels = {x, y, *surface + capsuleHalf point.positionSubvoxels = {routeX, routeY, *surface + capsuleHalf
+ recipe.routeConstraints.minimumClearanceSubvoxels + recipe.routeConstraints.minimumClearanceSubvoxels
+ (recipe.geometryVersion >= 2 ? floating_island_detail::Q / 8 : 0)}; + (recipe.geometryVersion >= 2 ? floating_island_detail::Q / 8 : 0)};
point.radiusSubvoxels = routeRadius; point.radiusSubvoxels = routeRadius;
+195 -19
View File
@@ -594,12 +594,19 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
const std::int64_t inner=cfg?std::min(cfg->distanceMinimumSubvoxels, const std::int64_t inner=cfg?std::min(cfg->distanceMinimumSubvoxels,
cfg->distanceMaximumSubvoxels):8*qPerMetre; cfg->distanceMaximumSubvoxels):8*qPerMetre;
const std::int64_t placementRadius=std::max<std::int64_t>(1,outer); const std::int64_t placementRadius=std::max<std::int64_t>(1,outer);
const std::int64_t mouthRadius=std::max<std::int64_t>(static_cast<std::int64_t>(std::llround(1.25*qPerMetre)), // Leave room for the capsule and bounded wall relief at the floor/wall seam.
const std::int64_t mouthRadius=std::max<std::int64_t>(static_cast<std::int64_t>(std::llround(1.5*qPerMetre)),
cfg?cfg->mouthRadiusSubvoxels:0); cfg?cfg->mouthRadiusSubvoxels:0);
const std::int64_t midRadius=std::max<std::int64_t>(mouthRadius, const std::int64_t midRadius=std::max<std::int64_t>(mouthRadius,
cfg?cfg->midRadiusSubvoxels:0); cfg?cfg->midRadiusSubvoxels:0);
const std::int64_t reachMinimum=cfg?std::max<std::int64_t>(0,cfg->reachMinimumSubvoxels):8*qPerMetre; const std::int64_t reachMinimum=cfg?std::max<std::int64_t>(0,cfg->reachMinimumSubvoxels):8*qPerMetre;
const std::int64_t reachMaximum=cfg?std::max<std::int64_t>(reachMinimum,cfg->reachMaximumSubvoxels):24*qPerMetre; const std::int64_t reachMaximum=cfg?std::max<std::int64_t>(reachMinimum,cfg->reachMaximumSubvoxels):24*qPerMetre;
const std::uint32_t style=cfg?std::min<std::uint32_t>(cfg->style,3u):1u;
const std::uint32_t segments=cfg?std::clamp<std::uint32_t>(cfg->segments,16u,48u):20u;
const std::int64_t wander=cfg?std::max<std::int64_t>(0,cfg->wanderSubvoxels):3*qPerMetre;
const std::int64_t spiralRadius=cfg?std::max<std::int64_t>(0,cfg->spiralRadiusSubvoxels):6*qPerMetre;
const std::int64_t spiralTurnsQ16=cfg?std::max<std::int64_t>(16384,cfg->spiralTurnsQ16):2ll<<16;
const std::int64_t verticalWobble=cfg?std::max<std::int64_t>(0,cfg->verticalWobbleSubvoxels):0;
const std::uint32_t connectionCount=cfg?static_cast<std::uint32_t>(std::clamp(cfg->connections,1,12)):1; const std::uint32_t connectionCount=cfg?static_cast<std::uint32_t>(std::clamp(cfg->connections,1,12)):1;
for(std::uint32_t connection=0;connection<connectionCount;++connection) for(std::uint32_t connection=0;connection<connectionCount;++connection)
{ {
@@ -612,6 +619,12 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
std::array<std::int64_t,3> upperMouth{},lowerMouth{}; std::array<std::int64_t,3> upperMouth{},lowerMouth{};
const bool hasUpperRoute=connection==0&&routeAnchor(upper,true,upperMouth); const bool hasUpperRoute=connection==0&&routeAnchor(upper,true,upperMouth);
const bool hasLowerRoute=connection==0&&routeAnchor(lower,false,lowerMouth); const bool hasLowerRoute=connection==0&&routeAnchor(lower,false,lowerMouth);
const ResolvedLayoutAnchorV2* passageAnchor=nullptr;
if(connection==0)
for(const auto& anchor:upper.anchors)
if(anchor.kind=="passage_anchor"&&anchor.values.size()>=7)
{passageAnchor=&anchor;break;}
const bool anchoredSpiral=style==2&&passageAnchor!=nullptr;
if(!hasUpperRoute||!hasLowerRoute) if(!hasUpperRoute||!hasLowerRoute)
{ {
const auto xy=PassageRandomXY(PassageSeed(seed,upper.strateIndex,lower.strateIndex, const auto xy=PassageRandomXY(PassageSeed(seed,upper.strateIndex,lower.strateIndex,
@@ -642,8 +655,22 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
const bool hasLowerTangent=hasLowerRoute const bool hasLowerTangent=hasLowerRoute
&&routeTangent(lower,false,lowerTangent); &&routeTangent(lower,false,lowerTangent);
const std::int64_t ringRadius=std::max<std::int64_t>(1, const std::int64_t outerRingRadius=std::max<std::int64_t>(1,
placementRadius-mouthRadius-qPerMetre/2); placementRadius-mouthRadius-qPerMetre/2);
const std::uint32_t pathStyle=anchoredSpiral?1u:style;
std::int64_t ringRadius=outerRingRadius;
if(pathStyle==1u)
ringRadius=std::min<std::int64_t>(outerRingRadius,
std::max<std::int64_t>(mouthRadius+2*qPerMetre,placementRadius*5/8));
else if(pathStyle==2u)
ringRadius=std::clamp(spiralRadius,
std::min(outerRingRadius,mouthRadius+qPerMetre/2),outerRingRadius);
else if(pathStyle==3u)
ringRadius=std::clamp(outerRingRadius
-std::min<std::int64_t>(outerRingRadius/3,
std::max<std::int64_t>(0,cfg?cfg->cascadeLedgeSubvoxels:0)
*static_cast<std::int64_t>(cfg?cfg->cascadeSteps:4)),
mouthRadius+2*qPerMetre,outerRingRadius);
std::uint32_t phase=static_cast<std::uint32_t>(PassageSeed(seed, std::uint32_t phase=static_cast<std::uint32_t>(PassageSeed(seed,
upper.strateIndex,lower.strateIndex,connection,0x50484153u)%16); upper.strateIndex,lower.strateIndex,connection,0x50484153u)%16);
if(hasUpperRoute) if(hasUpperRoute)
@@ -662,28 +689,159 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
{bestAlignment=alignment;phase=candidate;} {bestAlignment=alignment;phase=candidate;}
} }
} }
const bool clockwise=(PassageSeed(seed,upper.strateIndex,lower.strateIndex, bool clockwise=(PassageSeed(seed,upper.strateIndex,lower.strateIndex,
connection,0x44495245u)&1u)!=0; connection,0x44495245u)&1u)!=0;
const int direction=clockwise?1:-1; if(anchoredSpiral&&passageAnchor&&hasUpperTangent)
const auto ringPoint=[&](std::uint32_t directionIndex)
{ {
const double radialX=static_cast<double>(upperMouth[0]-passageAnchor->values[0]);
const double radialY=static_cast<double>(upperMouth[1]-passageAnchor->values[1]);
const double clockwiseTangentX=-radialY,clockwiseTangentY=radialX;
clockwise=clockwiseTangentX*upperTangent[0]
+clockwiseTangentY*upperTangent[1]>=0.0;
}
const int direction=clockwise?1:-1;
constexpr std::int64_t TargetGradeQ16=12500; // leave margin below the 15-degree contract.
const std::int64_t wormRadialWobble=std::min<std::int64_t>(
std::max<std::int64_t>(0,wander/3),2*qPerMetre);
const std::int64_t wormCenterWobble=std::min<std::int64_t>(
std::max<std::int64_t>(0,wander/6),qPerMetre);
const auto ringPoint=[&](std::uint32_t directionIndex,std::uint32_t loopIndex=0)
{
const double angle=static_cast<double>(directionIndex)*
(3.14159265358979323846/8.0);
double radius=static_cast<double>(ringRadius);
double centerX=0.0,centerY=0.0;
if(pathStyle==1u)
{
const double loop=static_cast<double>(loopIndex);
const double phaseA=loop*0.6180339887498948+0.37;
const double phaseB=loop*0.4142135623730950+1.11;
radius+=static_cast<double>(wormRadialWobble)
*std::sin(angle+phaseA);
centerX=static_cast<double>(wormCenterWobble)*std::sin(phaseB);
centerY=static_cast<double>(wormCenterWobble)*std::cos(phaseB);
}
else if(pathStyle==3u)
{
const std::uint32_t steps=std::max<std::uint32_t>(1,
cfg?cfg->cascadeSteps:4u);
const double ledge=std::min<double>(ringRadius/4,
static_cast<double>(cfg?std::max<std::int64_t>(0,
cfg->cascadeLedgeSubvoxels):0));
radius-=ledge*(0.5-0.5*std::cos(angle*steps));
}
return std::array<std::int64_t,2>{ return std::array<std::int64_t,2>{
static_cast<std::int64_t>(ringRadius)*directions[directionIndex][0]/65536, static_cast<std::int64_t>(std::llround(centerX+radius*directions[directionIndex][0]/65536.0)),
static_cast<std::int64_t>(ringRadius)*directions[directionIndex][1]/65536}; static_cast<std::int64_t>(std::llround(centerY+radius*directions[directionIndex][1]/65536.0))};
}; };
std::vector<std::array<std::int64_t,2>> xy; std::vector<std::array<std::int64_t,2>> xy;
const auto pushXY=[&](std::array<std::int64_t,2> p) const auto pushXY=[&](std::array<std::int64_t,2> p)
{if(xy.empty()||xy.back()!=p)xy.push_back(p);}; {if(xy.empty()||xy.back()!=p)xy.push_back(p);};
const auto firstRing=ringPoint(phase); std::size_t helixEndIndex=0;
std::int64_t helixBedrockZ=0;
bool hasAnchorHelix=false;
pushXY({upperMouth[0],upperMouth[1]}); pushXY({upperMouth[0],upperMouth[1]});
if(anchoredSpiral&&passageAnchor&&upper.bedrock.presentInCurrentField
&&spiralRadius>0)
{
const std::int64_t anchorX=passageAnchor->values[0];
const std::int64_t anchorY=passageAnchor->values[1];
const std::int64_t rootRadius=passageAnchor->values[2];
const std::int64_t rootBottom=passageAnchor->values[5];
helixBedrockZ=rootBottom!=0?rootBottom:contentZQ(upper.bedrock.topMicrometres);
const std::int64_t helixRadius=spiralRadius;
if(rootRadius<helixRadius+midRadius+qPerMetre/4
||std::hypot(static_cast<double>(upperMouth[0]-anchorX),
static_cast<double>(upperMouth[1]-anchorY))
>static_cast<double>(rootRadius+qPerMetre/2)
||upperMouth[2]<=helixBedrockZ)
{error=passage.stableId+" anchor root cannot contain its authored spiral mouth or bedrock descent";return false;}
const double drop=static_cast<double>(upperMouth[2]-helixBedrockZ);
const double pointsPerTurn=static_cast<double>(segments);
const double polygonCircumference=2.0*3.14159265358979323846
*static_cast<double>(helixRadius)
*(std::sin(3.14159265358979323846/pointsPerTurn)
/(3.14159265358979323846/pointsPerTurn));
const double horizontalPerTurn=polygonCircumference
*static_cast<double>(TargetGradeQ16)/65536.0;
const std::uint32_t authoredTurns=static_cast<std::uint32_t>(std::max<std::int64_t>(1,
(spiralTurnsQ16+65535)/65536));
const std::uint32_t gradeTurns=static_cast<std::uint32_t>(std::max(1.0,
std::ceil(drop/std::max(1.0,horizontalPerTurn))));
// The first coil grows from the island mouth offset to the full authored
// radius, so reserve one extra turn for that shorter opening revolution.
const std::uint32_t turns=std::max(authoredTurns,gradeTurns+1);
if(turns>512)
{error=passage.stableId+" authored spiral radius needs more than 512 turns to meet walkable grade";return false;}
const double startAngle=std::atan2(static_cast<double>(upperMouth[1]-anchorY),
static_cast<double>(upperMouth[0]-anchorX));
const std::uint32_t helixPoints=turns*segments;
for(std::uint32_t point=1;point<=helixPoints;++point)
{
const double t=static_cast<double>(point)/helixPoints;
const double angle=startAngle+static_cast<double>(clockwise?1:-1)
*2.0*3.14159265358979323846*t*turns;
const double radialT=std::min(1.0,static_cast<double>(point)/segments);
const double mouthOffset=std::clamp(static_cast<double>(passageAnchor->values[3]),
0.0,static_cast<double>(helixRadius));
const double radial=mouthOffset+(static_cast<double>(helixRadius)-mouthOffset)
*radialT;
const std::array<std::int64_t,2> p{
static_cast<std::int64_t>(std::llround(anchorX
+radial*std::cos(angle))),
static_cast<std::int64_t>(std::llround(anchorY
+radial*std::sin(angle)))};
pushXY(p);
}
helixEndIndex=xy.size()-1;
hasAnchorHelix=helixEndIndex>0;
}
const std::array<std::int64_t,2> firstRing=ringPoint(phase);
if(hasAnchorHelix&&pathStyle==1u)
{
// Let the anchored helix widen into the winding inter-strate route
// over a full, smooth coil. A direct jump from the root radius to the
// outer worm ring creates a sharp support seam inside the rock neck.
const double anchorX=static_cast<double>(passageAnchor->values[0]);
const double anchorY=static_cast<double>(passageAnchor->values[1]);
const double startAngle=std::atan2(static_cast<double>(upperMouth[1])-anchorY,
static_cast<double>(upperMouth[0])-anchorX);
const double targetX=static_cast<double>(firstRing[0])-anchorX;
const double targetY=static_cast<double>(firstRing[1])-anchorY;
const double targetAngle=std::atan2(targetY,targetX);
const double angleDelta=static_cast<double>(direction)*2.0
*3.14159265358979323846
+std::remainder(targetAngle-startAngle,2.0*3.14159265358979323846);
const double startRadius=static_cast<double>(spiralRadius);
const std::int64_t targetDx=firstRing[0]-passageAnchor->values[0];
const std::int64_t targetDy=firstRing[1]-passageAnchor->values[1];
const double targetRadius=static_cast<double>(PassageIsqrt(
static_cast<std::uint64_t>(targetDx*targetDx+targetDy*targetDy)));
const std::uint32_t transitionSegments=static_cast<std::uint32_t>(
std::clamp(std::ceil(std::abs(angleDelta)*(startRadius+targetRadius)
/(2.0*0.75*qPerMetre)),static_cast<double>(segments),256.0));
for(std::uint32_t step=1;step<=transitionSegments;++step)
{
const double t=static_cast<double>(step)/transitionSegments;
const double smooth=t*t*(3.0-2.0*t);
const double radius=startRadius+(targetRadius-startRadius)*smooth;
const double angle=startAngle+angleDelta*t;
const std::array<std::int64_t,2> point=step==transitionSegments?firstRing:
std::array<std::int64_t,2>{
static_cast<std::int64_t>(std::llround(anchorX+radius*std::cos(angle))),
static_cast<std::int64_t>(std::llround(anchorY+radius*std::sin(angle)))};
pushXY(point);
}
}
pushXY(firstRing); pushXY(firstRing);
std::uint64_t totalHorizontal=0;std::uint32_t loops=0; std::uint64_t totalHorizontal=0;std::uint32_t loops=0;
const std::int64_t verticalDistance=std::abs(lowerMouth[2]-upperMouth[2]); const std::int64_t verticalDistance=hasAnchorHelix
constexpr std::int64_t TargetGradeQ16=12500; // leave margin below the 15-degree contract. ?std::abs(lowerMouth[2]-helixBedrockZ):std::abs(lowerMouth[2]-upperMouth[2]);
auto measurePath=[&]() auto measurePath=[&]()
{ {
std::uint64_t total=0; std::uint64_t total=0;
for(std::size_t i=1;i<xy.size();++i) const std::size_t start=hasAnchorHelix?helixEndIndex:0;
for(std::size_t i=start+1;i<xy.size();++i)
total+=distanceQ(xy[i][0]-xy[i-1][0],xy[i][1]-xy[i-1][1]); total+=distanceQ(xy[i][0]-xy[i-1][0],xy[i][1]-xy[i-1][1]);
return total; return total;
}; };
@@ -696,7 +854,8 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
{ {
int directionIndex=static_cast<int>(phase)+direction*step; int directionIndex=static_cast<int>(phase)+direction*step;
directionIndex=(directionIndex%16+16)%16; directionIndex=(directionIndex%16+16)%16;
pushXY(ringPoint(static_cast<std::uint32_t>(directionIndex))); pushXY(ringPoint(static_cast<std::uint32_t>(directionIndex),
loops+(step==16?1u:0u)));
} }
++loops; ++loops;
} }
@@ -710,7 +869,7 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
{ {
const std::uint32_t candidate=static_cast<std::uint32_t>( const std::uint32_t candidate=static_cast<std::uint32_t>(
(hashedLowerPhase+offset)%16); (hashedLowerPhase+offset)%16);
const auto terminal=ringPoint(candidate); const auto terminal=ringPoint(candidate,loops);
const std::int64_t dx=lowerMouth[0]-terminal[0]; const std::int64_t dx=lowerMouth[0]-terminal[0];
const std::int64_t dy=lowerMouth[1]-terminal[1]; const std::int64_t dy=lowerMouth[1]-terminal[1];
const std::int64_t alignment=dx*lowerTangent[0]+dy*lowerTangent[1]; const std::int64_t alignment=dx*lowerTangent[0]+dy*lowerTangent[1];
@@ -728,7 +887,7 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
{ {
int directionIndex=static_cast<int>(phase)+direction*step; int directionIndex=static_cast<int>(phase)+direction*step;
directionIndex=(directionIndex%16+16)%16; directionIndex=(directionIndex%16+16)%16;
pushXY(ringPoint(static_cast<std::uint32_t>(directionIndex))); pushXY(ringPoint(static_cast<std::uint32_t>(directionIndex),loops));
if(static_cast<std::uint32_t>(directionIndex)==lowerPhase)break; if(static_cast<std::uint32_t>(directionIndex)==lowerPhase)break;
} }
pushXY(ringPoint(lowerPhase)); pushXY(ringPoint(lowerPhase));
@@ -775,16 +934,33 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
for(std::size_t i=1;i<xy.size();++i) for(std::size_t i=1;i<xy.size();++i)
cumulative[i]=cumulative[i-1]+distanceQ(xy[i][0]-xy[i-1][0],xy[i][1]-xy[i-1][1]); cumulative[i]=cumulative[i-1]+distanceQ(xy[i][0]-xy[i-1][0],xy[i][1]-xy[i-1][1]);
const std::uint64_t mouthHorizontal=cumulative[lowerMouthIndex]; const std::uint64_t mouthHorizontal=cumulative[lowerMouthIndex];
const std::uint64_t helixHorizontal=hasAnchorHelix
?cumulative[helixEndIndex]:0;
if(mouthHorizontal==0) if(mouthHorizontal==0)
{error=passage.stableId+" has no horizontal reach before its lower mouth";return false;} {error=passage.stableId+" has no horizontal reach before its lower mouth";return false;}
if(hasAnchorHelix&&helixHorizontal==0)
{error=passage.stableId+" anchor spiral has no horizontal reach";return false;}
passage.controlPoints.reserve(xy.size()); passage.controlPoints.reserve(xy.size());
for(std::size_t i=0;i<xy.size();++i) for(std::size_t i=0;i<xy.size();++i)
{ {
const std::int64_t radius=(i<=1||i+1>=xy.size()-1)?mouthRadius:midRadius; const std::int64_t radius=(i<=1||i==lowerMouthIndex
const std::int64_t playerZ=i<=lowerMouthIndex ||i+1>=xy.size()-1)?mouthRadius:midRadius;
?upperMouth[2]+PassageRoundRatio(lowerMouth[2]-upperMouth[2], std::int64_t playerZ=0;
static_cast<std::int64_t>(cumulative[i]),static_cast<std::int64_t>(mouthHorizontal)) if(i>lowerMouthIndex)
:lowerLandingPoints[i-lowerMouthIndex-1][2]; playerZ=lowerLandingPoints[i-lowerMouthIndex-1][2];
else if(hasAnchorHelix&&i<=helixEndIndex)
playerZ=upperMouth[2]+PassageRoundRatio(helixBedrockZ-upperMouth[2],
static_cast<std::int64_t>(cumulative[i]),
static_cast<std::int64_t>(helixHorizontal));
else
{
const std::uint64_t segmentStart=hasAnchorHelix?helixHorizontal:0;
const std::int64_t startZ=hasAnchorHelix?helixBedrockZ:upperMouth[2];
const std::uint64_t segmentLength=mouthHorizontal-segmentStart;
playerZ=startZ+PassageRoundRatio(lowerMouth[2]-startZ,
static_cast<std::int64_t>(cumulative[i]-segmentStart),
static_cast<std::int64_t>(segmentLength));
}
ResolvedLayoutPassagePointV1 point; ResolvedLayoutPassagePointV1 point;
point.positionSubvoxels={xy[i][0],xy[i][1],playerZ+radius-playerCenterAboveFloorQ}; point.positionSubvoxels={xy[i][0],xy[i][1],playerZ+radius-playerCenterAboveFloorQ};
point.radiusSubvoxels=radius;passage.controlPoints.push_back(point); point.radiusSubvoxels=radius;passage.controlPoints.push_back(point);
+155 -57
View File
@@ -107,6 +107,66 @@ public:
const double floorThicknessVoxels=qPerMetre/Q; const double floorThicknessVoxels=qPerMetre/Q;
const double playerCenterAboveFloorQ=1.13*qPerMetre; const double playerCenterAboveFloorQ=1.13*qPerMetre;
float composed=density; float composed=density;
const auto composeRouteMouthSupport=[&](std::int32_t strateIndex,
const std::array<std::int64_t,3>& mouth,bool departure)
{
const double mouthDx=qx-static_cast<double>(mouth[0]);
const double mouthDy=qy-static_cast<double>(mouth[1]);
const double routeSupportReach=13.0*qPerMetre;
if(mouthDx*mouthDx+mouthDy*mouthDy>routeSupportReach*routeSupportReach)return;
const auto stratum=std::find_if(ResolvedLayout->strates.begin(),
ResolvedLayout->strates.end(),[strateIndex](const auto& value)
{return value.strateIndex==strateIndex;});
if(stratum==ResolvedLayout->strates.end())return;
std::array<const VoxelForgeCore::ResolvedLayoutAnchorV2*,5> route{};
std::size_t routeCount=0;
for(const auto& anchor:stratum->anchors)
if(anchor.kind=="route_point"&&anchor.values.size()>=3)
{
if(departure)
{
if(routeCount<route.size())route[routeCount++]=&anchor;
else
{
std::move(route.begin()+1,route.end(),route.begin());
route.back()=&anchor;
}
}
else if(routeCount<route.size())route[routeCount++]=&anchor;
}
if(routeCount<2)return;
const std::size_t lastSegment=routeCount-1;
const double floorThickness=1000000.0/std::max<std::uint32_t>(1,
ResolvedLayout->coreVoxelSizeMicrometres);
for(std::size_t index=0;index<lastSegment;++index)
{
const auto& a=*route[index];const auto& b=*route[index+1];
const double dx=static_cast<double>(b.values[0]-a.values[0]);
const double dy=static_cast<double>(b.values[1]-a.values[1]);
const double lengthSq=dx*dx+dy*dy;
const double t=lengthSq>0.0?std::clamp(((qx-a.values[0])*dx
+(qy-a.values[1])*dy)/lengthSq,0.0,1.0):0.0;
const double centerX=a.values[0]+t*dx,centerY=a.values[1]+t*dy;
const double centerZ=a.values[2]+t*(static_cast<double>(b.values[2])-a.values[2]);
const double routeRadius=a.values.size()>=4
?a.values[3]+t*(static_cast<double>(b.values[3])-a.values[3]):0.0;
const double halfWidth=std::max(0.75*qPerMetre,routeRadius);
const double horizontalX=qx-centerX,horizontalY=qy-centerY;
if(horizontalX*horizontalX+horizontalY*horizontalY>halfWidth*halfWidth)continue;
const double floorVoxels=(centerZ-1.20*qPerMetre)/Q;
if(Z<floorVoxels-floorThickness||Z>floorVoxels+1.0/Q)continue;
composed=std::min(composed,static_cast<float>(
std::clamp(Z-floorVoxels,-floorThickness,0.0)));
}
};
// Seed the route floor first. The bore carve below reopens any overlap at the
// mouth, then applies its own lower tread, so neither shape blocks the capsule.
for(const auto& passage:ResolvedLayout->passages)
{
composeRouteMouthSupport(passage.upperStrateIndex,passage.upperMouthSubvoxels,true);
// The lower mouth emerges into its native terrain. Keep the passage tread
// support, but do not stamp short, flat route-anchor shelves over that cave mouth.
}
for(const auto& passage:ResolvedLayout->passages) for(const auto& passage:ResolvedLayout->passages)
{ {
if(passage.controlPoints.size()<2)continue; if(passage.controlPoints.size()<2)continue;
@@ -125,16 +185,25 @@ public:
const std::ptrdiff_t lowerMouthIndex=std::clamp<std::ptrdiff_t>( const std::ptrdiff_t lowerMouthIndex=std::clamp<std::ptrdiff_t>(
static_cast<std::ptrdiff_t>(passage.lowerMouthControlIndex),1,lastIndex); static_cast<std::ptrdiff_t>(passage.lowerMouthControlIndex),1,lastIndex);
const auto mainEnd=passage.controlPoints.begin()+lowerMouthIndex+1; const auto mainEnd=passage.controlPoints.begin()+lowerMouthIndex+1;
const auto carveFound=std::lower_bound(passage.controlPoints.begin(),mainEnd,qz, // The tube radius is measured from its centerline, while floorProfile is
// one player-center offset away. Search the full vertical radius of the SDF;
// a narrow point window misses earlier turns of a low-pitch helix.
const double carveSearchMargin=std::max(passage.mouthRadiusSubvoxels,
passage.midRadiusSubvoxels)+0.25*qPerMetre;
const auto carveHigh=std::lower_bound(passage.controlPoints.begin(),mainEnd,
qz+carveSearchMargin,
[&](const auto& point,double value){return floorProfile(point)>value;}); [&](const auto& point,double value){return floorProfile(point)>value;});
const std::ptrdiff_t carveCenter=carveFound-passage.controlPoints.begin(); const auto carveLow=std::lower_bound(passage.controlPoints.begin(),mainEnd,
const std::ptrdiff_t carveBegin=std::max<std::ptrdiff_t>(0,carveCenter-3); qz-carveSearchMargin,
const std::ptrdiff_t carveEnd=std::min<std::ptrdiff_t>(lowerMouthIndex-1,carveCenter+2); [&](const auto& point,double value){return floorProfile(point)>value;});
const std::ptrdiff_t carveBegin=std::max<std::ptrdiff_t>(0,
(carveHigh-passage.controlPoints.begin())-1);
const std::ptrdiff_t carveEnd=std::min<std::ptrdiff_t>(lowerMouthIndex-1,
(carveLow-passage.controlPoints.begin())+1);
double bestSignedDistance=std::numeric_limits<double>::infinity(); double bestSignedDistance=std::numeric_limits<double>::infinity();
double bestT=0.0; double bestT=0.0;
std::ptrdiff_t bestIndex=0; std::ptrdiff_t bestIndex=0;
bool hasSupportFloor=false; bool hasSupportFloor=false;
double bestSupportFloorVoxels=-std::numeric_limits<double>::infinity();
double bestSupportDensity=0.0; double bestSupportDensity=0.0;
const auto considerCarveSegment=[&](std::ptrdiff_t index) const auto considerCarveSegment=[&](std::ptrdiff_t index)
{ {
@@ -164,65 +233,94 @@ public:
for(std::ptrdiff_t index=lowerMouthIndex;index<lastIndex;++index) for(std::ptrdiff_t index=lowerMouthIndex;index<lastIndex;++index)
considerCarveSegment(index); considerCarveSegment(index);
if(!std::isfinite(bestSignedDistance))continue; if(!std::isfinite(bestSignedDistance))continue;
// Positive density is air; the passage SDF is applied after both owner bands.
composed=std::max(composed,static_cast<float>(-bestSignedDistance/Q));
const auto& nearestA=passage.controlPoints[static_cast<std::size_t>(bestIndex)]; const auto& nearestA=passage.controlPoints[static_cast<std::size_t>(bestIndex)];
const auto& nearestB=passage.controlPoints[static_cast<std::size_t>(bestIndex+1)]; const auto& nearestB=passage.controlPoints[static_cast<std::size_t>(bestIndex+1)];
const double nearestRadius=nearestA.radiusSubvoxels+bestT*( const double nearestRadius=nearestA.radiusSubvoxels+bestT*(
static_cast<double>(nearestB.radiusSubvoxels)-nearestA.radiusSubvoxels); static_cast<double>(nearestB.radiusSubvoxels)-nearestA.radiusSubvoxels);
const double nearestPlayerCenterZ=nearestA.positionSubvoxels[2]+bestT*( const double nearestDx=static_cast<double>(nearestB.positionSubvoxels[0])
-nearestA.positionSubvoxels[0];
const double nearestDy=static_cast<double>(nearestB.positionSubvoxels[1])
-nearestA.positionSubvoxels[1];
const double nearestHorizontalSq=nearestDx*nearestDx+nearestDy*nearestDy;
const double supportPathT=nearestHorizontalSq>0.0
?std::clamp(((qx-nearestA.positionSubvoxels[0])*nearestDx
+(qy-nearestA.positionSubvoxels[1])*nearestDy)/nearestHorizontalSq,0.0,1.0)
:bestT;
const double supportPathRadius=nearestA.radiusSubvoxels+supportPathT*(
static_cast<double>(nearestB.radiusSubvoxels)-nearestA.radiusSubvoxels);
const double supportPathPlayerCenterZ=nearestA.positionSubvoxels[2]+supportPathT*(
static_cast<double>(nearestB.positionSubvoxels[2])-nearestA.positionSubvoxels[2]) static_cast<double>(nearestB.positionSubvoxels[2])-nearestA.positionSubvoxels[2])
-nearestRadius+playerCenterAboveFloorQ; -supportPathRadius+playerCenterAboveFloorQ;
// Only compose a support shelf below the capsule feet. This keeps support const double nearestPlayerCenterZ=supportPathPlayerCenterZ;
// probes on the matching floor while capsule samples remain air at bends. const double distanceToCenter=std::max(1.0e-9,bestSignedDistance+nearestRadius);
if(qz<nearestPlayerCenterZ-0.90*qPerMetre) const double verticalWallAlignment=distanceToCenter>1.0e-9
?std::clamp(-(qz-(nearestA.positionSubvoxels[2]+bestT*(
static_cast<double>(nearestB.positionSubvoxels[2])-nearestA.positionSubvoxels[2])))
/distanceToCenter,0.0,1.0):0.0;
const float relief=VF_FieldSurfaceRelief(static_cast<float>(X),static_cast<float>(Y),
static_cast<float>(Z),static_cast<std::uint32_t>(Owner?Owner->Seed:0));
const double wallReliefWeight=1.0-0.80*verticalWallAlignment;
const double roughSignedDistance=bestSignedDistance
+static_cast<double>(std::clamp(relief,-1.0f,1.0f))*0.24*qPerMetre*wallReliefWeight;
// Positive density is air; rough rock is cut with full side/ceiling detail and
// gentler relief on the gravity-facing wall so the supported tread stays open.
composed=std::max(composed,static_cast<float>(-roughSignedDistance/Q));
// At tight bends, keep the tread tied to the player-center profile selected
// by horizontal path position so vertical probe offsets cannot shift the ramp.
const bool fallbackSupport=qz>=nearestPlayerCenterZ-1.50*qPerMetre;
const double supportThicknessVoxels=fallbackSupport
?qPerMetre/Q:0.5*qPerMetre/Q;
double bestSupportDistanceSquared=std::numeric_limits<double>::infinity();
const auto considerSupportSegment=[&](std::ptrdiff_t index)
{ {
const double supportCenterQueryQ=qz+playerCenterAboveFloorQ; const auto& segmentA=passage.controlPoints[static_cast<std::size_t>(index)];
const auto supportFound=std::lower_bound(passage.controlPoints.begin(), const auto& segmentB=passage.controlPoints[static_cast<std::size_t>(index+1)];
mainEnd,supportCenterQueryQ, const double dx=static_cast<double>(segmentB.positionSubvoxels[0])-segmentA.positionSubvoxels[0];
[&](const auto& point,double value){return floorProfile(point)>value;}); const double dy=static_cast<double>(segmentB.positionSubvoxels[1])-segmentA.positionSubvoxels[1];
const std::ptrdiff_t supportCenter=supportFound-passage.controlPoints.begin(); const double radiusA=segmentA.radiusSubvoxels;
const std::ptrdiff_t supportBegin=std::max<std::ptrdiff_t>(0,supportCenter-3); const double radiusB=segmentB.radiusSubvoxels;
const std::ptrdiff_t supportEnd=std::min<std::ptrdiff_t>(lowerMouthIndex-1,supportCenter+2); // Keep the tread within the probe band and below the capsule's
const auto considerSupportSegment=[&](std::ptrdiff_t index) // lowest sampled point.
const double floorOffset=0.20;
const double horizontalLengthSq=dx*dx+dy*dy;
const double supportT=horizontalLengthSq>0.0
?std::clamp(((qx-segmentA.positionSubvoxels[0])*dx
+(qy-segmentA.positionSubvoxels[1])*dy)/horizontalLengthSq,0.0,1.0)
:bestT;
const double centerX=segmentA.positionSubvoxels[0]+supportT*dx;
const double centerY=segmentA.positionSubvoxels[1]+supportT*dy;
const double centerZ=segmentA.positionSubvoxels[2]+supportT*(
static_cast<double>(segmentB.positionSubvoxels[2])-segmentA.positionSubvoxels[2]);
const double radius=segmentA.radiusSubvoxels+supportT*(
static_cast<double>(segmentB.radiusSubvoxels)-segmentA.radiusSubvoxels);
const double supportPlayerCenterZ=centerZ-radius+playerCenterAboveFloorQ;
if(fallbackSupport)
{ {
const auto& segmentA=passage.controlPoints[static_cast<std::size_t>(index)]; const double supportProfileError=std::abs(
const auto& segmentB=passage.controlPoints[static_cast<std::size_t>(index+1)]; supportPlayerCenterZ-nearestPlayerCenterZ);
const double dx=static_cast<double>(segmentB.positionSubvoxels[0])-segmentA.positionSubvoxels[0]; if(supportProfileError>0.06*qPerMetre)return;
const double dy=static_cast<double>(segmentB.positionSubvoxels[1])-segmentA.positionSubvoxels[1]; }
const double horizontalLengthSq=dx*dx+dy*dy; const double horizontalX=qx-centerX,horizontalY=qy-centerY;
const double supportT=horizontalLengthSq>0.0 const double horizontalDistance=DeterministicSqrt(horizontalX*horizontalX
?std::clamp(((qx-segmentA.positionSubvoxels[0])*dx +horizontalY*horizontalY);
+(qy-segmentA.positionSubvoxels[1])*dy)/horizontalLengthSq,0.0,1.0) const double floorQ=centerZ-radius+floorOffset*qPerMetre;
:bestT; const double floorVoxels=floorQ/Q;
const double supportCenterX=segmentA.positionSubvoxels[0]+supportT*dx; if(Z<floorVoxels-supportThicknessVoxels||Z>floorVoxels+1.0/Q)return;
const double supportCenterY=segmentA.positionSubvoxels[1]+supportT*dy; const double supportHalfWidth=fallbackSupport?radius
const double supportCenterZ=segmentA.positionSubvoxels[2]+supportT*( :std::max(0.0,radius+2.0*qPerMetre);
static_cast<double>(segmentB.positionSubvoxels[2])-segmentA.positionSubvoxels[2]); if(horizontalDistance>supportHalfWidth)return;
const double supportRadius=segmentA.radiusSubvoxels+supportT*( const double supportDistanceSq=horizontalDistance*horizontalDistance
static_cast<double>(segmentB.radiusSubvoxels)-segmentA.radiusSubvoxels); +(Z-floorVoxels)*(Z-floorVoxels);
// Leave a 12 cm settle gap below the capsule feet while staying inside if(supportDistanceSq>=bestSupportDistanceSquared)return;
// the route support probes' search depth. bestSupportDistanceSquared=supportDistanceSq;
const double floorQ=supportCenterZ-supportRadius+0.12*qPerMetre; hasSupportFloor=true;
const double floorVoxels=floorQ/Q; bestSupportDensity=std::clamp(Z-floorVoxels,
const double horizontalX=qx-supportCenterX,horizontalY=qy-supportCenterY; -supportThicknessVoxels,0.0);
const double horizontalDistance=DeterministicSqrt(horizontalX*horizontalX+horizontalY*horizontalY); };
const double supportHalfWidth=std::max(0.0,supportRadius-0.15*qPerMetre); const std::ptrdiff_t supportBegin=std::max<std::ptrdiff_t>(0,bestIndex-1);
if(horizontalDistance<=supportHalfWidth&&Z>=floorVoxels-floorThicknessVoxels const std::ptrdiff_t supportEnd=std::min<std::ptrdiff_t>(lastIndex-1,bestIndex+1);
&&Z<=floorVoxels+1.0/Q) for(std::ptrdiff_t index=supportBegin;index<=supportEnd;++index)
{ considerSupportSegment(index);
if(!hasSupportFloor||floorVoxels>bestSupportFloorVoxels)
{
bestSupportFloorVoxels=floorVoxels;hasSupportFloor=true;
bestSupportDensity=std::clamp(Z-floorVoxels,-floorThicknessVoxels,0.0);
}
}
};
for(std::ptrdiff_t index=supportBegin;index<=supportEnd;++index)
considerSupportSegment(index);
for(std::ptrdiff_t index=lowerMouthIndex;index<lastIndex;++index)
considerSupportSegment(index);
}
if(hasSupportFloor) if(hasSupportFloor)
composed=std::min(composed,static_cast<float>(bestSupportDensity)); composed=std::min(composed,static_cast<float>(bestSupportDensity));
} }
@@ -0,0 +1,113 @@
# VoxelForge L24 — passages and anchor island
## Owner summary
Inter-strate passages are now planned in Core from the upper strate's `FStratePassageConfig`, for every generator. The public default is **Worm**: a gently graded, deterministic meander with variable radius and rough walls. Straight, Spiral and Cascading remain selectable per strate. FloatingIslands sets its upper passage to **Spiral** and gets a unique anchor island with a helical ramp down inside a rock root to the bedrock floor; the passage then continues through the SurfaceWorld ceiling. The other islands remain floating.
The seed-derived anchor is stable island ordinal 2 at about `(9.800, 4.571) m`, within the 24 m placement disk; its mouth phase also hashes the seed and stable island ID. The root radius is 6 m, the helix radius is 3 m and the bore radius is at least 1.5 m, leaving 1.5 m nominal rock between the bore and root edge before bounded relief. The authored two-turn minimum extends to 19 turns to keep the 64 m descent under the 10.8-degree target. The root reaches the bedrock floor at about `-119.5 m`. The FI route departure is bound to the island mouth. Island placement hashes and ordinals for the other islands are unchanged.
The SurfaceWorld arrival uses its native hillside/terrain plus the passage tread; the shared carver no longer stamps a flat lower route-anchor shelf over the cave opening. The final mouth capture shows the opening from above and a few metres away.
## Captures
All are JPEGs under 300 KB. The FI exterior capture pairs a full view with a world-space half-plane cutaway; the second panel exposes the helical bore in the root above the bedrock slab. The interior image is from the audited centerline, aimed down the ramp with a 150-degree camera and camera headlamp. The SurfaceWorld arrival is an overhead view, about 7 m laterally from the mouth. Straight and Worm are left/right from matching final-source views.
- [FI anchor island, root and helix cutaway](captures/fi-anchor-cutaway.jpg) — 35,221 bytes
- [Inside the helix, looking along the descent](captures/fi-helix-inside.jpg) — 8,754 bytes
- [SurfaceWorld arrival mouth](captures/sw-arrival-mouth.jpg) — 14,720 bytes
- [Straight (left) beside default Worm (right)](captures/straight-vs-worm.jpg) — 60,020 bytes
## Passage and naturalness audits
The capsule is 0.34 m wide by 1.76 m high. The grade contract is 15 degrees; the planner targets at most 10.8 degrees to leave margin. Wall and ceiling relief is up to 0.24 m, gravity-facing relief is attenuated by 20%, and the floor relief stays gentler with support under the capsule. The naturalness check rejects horizontal runs longer than 8 m (segments flatter than 0.5 degrees) and requires at least 0.20 m peak-to-peak wall relief from at least 8 valid wall samples. The L23 smooth, unrelieved wall control measures 0 m relief and is detected as a failure.
Final-source Clang r55 FI audit: 801 control points, 5,317 centers, 334,971 capsule samples; **0 blocked, 0 unsupported, 0 grade failures**; support samples 29,278; both mouths are in content and within radius; two route bindings pass; blocked-passage negative control detected. Naturalness: flat run 0 m, wall relief 0.793 m from 72 samples; pass. Strict V3 validator accepted the layout and rejected 9/9 malformed controls.
Final-source Clang r55 SurfaceWorld style sweep:
| Style | Capsule samples | Blocked / unsupported / grade failures | Wall relief | Naturalness |
|---|---:|---:|---:|---|
| Straight | 284,256 | 0 / 0 / 0 | 0.441 m (10 samples) | Pass |
| Worm | 284,949 | 0 / 0 / 0 | 0.298 m (12 samples) | Pass |
| Spiral | 337,995 | 0 / 0 / 0 | 0.753 m (14 samples) | Pass |
| Cascading | 287,910 | 0 / 0 / 0 | 0.435 m (14 samples) | Pass |
All styles had 0 m flat runs against the 8 m limit; route bindings, blocked controls and strict validator controls also passed in each run. The earlier L23 geometry has 0 m wall relief and fails the new naturalness threshold.
Audit logs are in `Saved/Linux`: `linux24-audit-fi-clang19-r55-lower-mouth.log` and `linux24-style-{straight,worm,spiral,cascading}-surface-final-r55.log`.
## Determinism and compatibility evidence
The final-source r55 worker matrix passed Clang and GCC at W1, W8, W12, and W12 repeated five times. It covers all 120 full-detail tiles and the 176-tile whole view through LOD2; every worker/repeat/compiler produced identical hashes:
| Final-source view | Density | Mesh | Payload | Topology |
|---|---|---|---|---|
| Full detail, 120 tiles | `657388bd79ce59a1` | `7ab35e5ea2bd80a5` | `fe853e905a362755` | `963af083379cfd25` |
| Whole view, 176 tiles through LOD2 | `90d1f05a8c8c2406` | `c330e8944d533dd6` | `47102acb793fb854` | `354faeb5134a0aea` |
| LOD0, 64 tiles | `25d79b76f9d0cd01` | `33a2c2fdee222be5` | `32b8a3331d984725` | `7033aba8bb626ea5` |
| LOD1, 56 tiles | `24df5021a729fdd5` | `65cf44f4e7cf20e5` | `0fda084ee0cc0355` | `35ffa189a5b54da5` |
| LOD2, 56 tiles | `d5cf2f60a27cfee2` | `dbb8283a93e79956` | `05f9170ad820dac4` | `6d79c252b66d30ea` |
| Exact probes, 9 | `7b7c50ac24ccadcc` | detail `efcba20c0c70c415` | collision `e604823a249029bf` | — |
Per-run logs are in `Saved/Linux` as `linux24-r55-worker-hash-fi-{clang,gcc}-l2-w1.log`, `...-w8.log`, `...-w12.log`, and `...-w12-r1.log` through `...-r5.log`. Crossing, away-field, proof, boundary and L13 logs use the same `linux24-r55-` prefix.
The unrestricted r55 hash runs including LOD3 stopped after shape compilation without producing pipeline hashes; they were not counted as passes. The r54 full matrix passed Clang and GCC at W1, W8, W12, W12×5 and LOD0–8. Representative r54 hashes (before lower-mouth shelf removal) were:
| View | Density | Mesh | Payload | Topology |
|---|---|---|---|---|
| Full detail, 120 tiles | `e248e7f04a0eb5dc` | `09c8289f260df275` | `7501d467bebdc9b2` | `43c78d5cab335dbf` |
| Whole view, 195 tiles | `d4833386ea55cc28` | `26863a50c8138aa6` | `46601fd8395b44ea` | `9680964dfaa5f2d1` |
| Exact probes, 9 | `dea346fbf2e2d259` | detail `efcba20c0c70c415` | collision `97df60072c993bff` | — |
The r55 24 m crossing passed for FI and SurfaceWorld on both compilers: 314 entering tiles each in the full crossing run. The final-source hashed LOD0–1 crossing covered 288 entering tiles and matched across compilers. FI hashes (density/mesh/payload/topology): `9fadf809b3b081b2 / 307b09c297623cf2 / 2fe406d2bd1229d2 / 7728405d1bf07a61`. SurfaceWorld: `ace56c8f205238ad / a9c603a1248de6b8 / ea55f1f4fcc6a894 / 5ea7bc78a1b678ae`. Exact probe hashes matched the worker matrix. The r54 full-range hashed crossing also passed for FI and SurfaceWorld on both compilers (96 voxels, 314 tiles): FI `f9e89a48116c45ff / a6c8f20efcfb460f / f4df697779b2bf00 / e378f82fedc1a822`; SurfaceWorld `3561ea23485e359f / e8331620fb0fb908 / 765f7dad67c852aa / 3ccc09ac81c05d71`.
SurfaceWorld away from the passage remained bit-identical to L23 on both compilers in the final-source LOD0–1 bounded comparison: 960 full-detail tiles, hashes `66ac781666579e3e / 914873894a0e38c4 / ec8b03ade039cd56 / a2b621b0321d43dc`, matching the r54/L23 baseline exactly. The r54 full whole-view hashes were `ddc6d1942db48f52 / bce985905e70dac4 / 266f5a008bcf4491 / e6fa520c5ad2817b`. Final-source r55 SurfaceWorld L9 proofs on both compilers checked 25,720 blocks / 34,233,320 samples (1,304 expected fallbacks, 0 unknown, 0 violations). Final-source r55 FI proof runs on both compilers had 2,560 expected fallbacks/unknowns, 0 checked samples and 0 violations; the boundary audit checked 27 endpoints with 0 disagreements. Final-source L13 route parity passed on both compilers: 8 page pairs, 14 higher-LOD exact pairs, 782,026 exact-query samples, 0 mismatches, one edit pair, 64 expected unknown proof blocks, 0 violations.
**Final-source r55 cross-compiler reruns:** Clang build, FI passage audit, all four style audits, 120-tile worker hash matrix, 24 m crossings, L13 parity, L9 FI proof and boundary checks pass. GCC build passes; its 120-tile worker matrix, 24 m crossings, L9 FI proof and boundary checks pass. The r55 GCC FI capsule audit did not finish; the prior r54 GCC audit, style sweep, crossings, parity, proofs and full LOD0–8 hash matrix passed.
## Changes and versions
- Shared passage planning honours all four styles and their per-strate parameters; Worm is the public default. It adds a deterministic anchor-root record, FI helix path, bounded wall relief and the naturalness audit.
- The owner FI upper-strate config selects Spiral. Its stable anchor/root is limited to ordinal 2; other island placements are unchanged.
- Lower SW arrival support overlays were removed so the native terrain meets the cave mouth without stacked shelves.
- The Linux offscreen capture helper gained a camera headlamp and optional world-space clip plane for cutaway views.
- `WorldAssemblyEvaluatorVersion=10`; FI recipe geometry version `5`; resolved-layout snapshot remains V3.
Unreal-facing edits are limited to `Source/VoxelForge/Public/VoxelStrateTypes.h` (per-strate passage fields/default) and `Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp` (forward those fields to Core). The remaining changes are shared standalone Core, audit/capture support, owner JSON and docs: `VoxelForgeCore.h`, `FloatingIslandsInternal.h`, `ResolvedLayoutInternal.h`, `ShapeWorldInternal.h`, `StreamBenchmarkPipeline.h`, `WorldAssemblyPrototype.h`, `LinuxOffscreenCapture.cpp`, and `Standalone/data/owner_world.json`. Documentation is in `ARCHITECTURE.md` §8.42 and `CODEMAP.md` §3.25. Unreal changes are source-only here and were not compiled.
## Windows checklist
1. Regenerate project files and build `VoxelForgeEditor Win64 Development` with the project toolchain.
2. Confirm the FI upper-strate passage style is Spiral and the other unmodified strata use Worm; regenerate the resolved layout after changing strate passage settings.
3. In PIE, walk from the anchor island down the root and through the SurfaceWorld arrival; check capsule clearance, support, collision and mouth transitions.
4. Confirm the other FI islands still float, then repeat with the same seed on a second client/editor process and compare placement and passage shape.
5. Change one strate to Straight, Worm, Spiral and Cascading in turn; regenerate and verify only that strate's passage style changes.
## Failed and retried runs
- Early route calibration r32–r34 found 3–4 blocked capsule samples; r35–r42 radius/bore adjustments alternated blocked and unsupported samples. r39 also failed strict validation because a mouth exceeded the placement radius.
- r43 added diagnostics. r44 corrected XY projection. r45's 0.25 m lift produced 1,124 blocked samples and 4 unsupported; r46's 0.20 m lift left 4 unsupported; r47's 0.25 m lift produced 1,042 blocked; r48's relaxed support rule produced 5,660 blocked.
- r49 build failed on an undeclared `DeterministicSqrt`; r50 corrected the name but still had 5,609 blocked; r51 narrowed support and reached zero blocked/unsupported. r52 removed the fixture's FI style override and restored the L23 geometry comparison.
- r53's 0.16 m relief failed the valid-wall-sample threshold for some styles. r54 increased relief to 0.24 m and set the minimum to 8 samples; all four styles passed. r55 removed lower-mouth support shelves; Clang FI and style audits still pass.
- GCC r55 style sweep was interrupted after its Straight case emitted capsule and naturalness PASS results, before a complete style summary. A separate GCC r55 FI capsule-audit retry remained silent and was stopped after roughly 8 minutes. GCC r54 audits passed.
- Three unrestricted r55 stream-hash attempts (FI W1, FI W12, and SurfaceWorld W12) stopped after shape compilation with no pipeline summary. Limiting the final-source matrix to LOD0–2 completed 120 full-detail plus 56 LOD2 tiles and passed across both compilers and worker counts. A `max-clip-level=1` trial was rejected by Core's bounded 4,096-node shape compiler envelope; retrying with the supported clip limit and `max-level=1` passed.
- An LOD0–3 FI proof invocation remained silent after shape compilation; the LOD0–1 FI proof retry and the 960-tile SurfaceWorld proof completed on both compilers with zero violations. Camera trials replaced a horizontal SW view that showed flat bands with the downward arrival view; a closer oblique trial also exposed stair-like bands and was discarded. Low-FOV interior trials showed only a close wall, so the final interior view uses a 150-degree camera and headlamp.
- Two initial r55 capture exports at `max-level=1` wrote empty 4,380-byte mesh scenes; the renderer rejected them for containing no triangles. An unrestricted `max-level=3` capture export reached 593,068 desired tiles, grew to an incomplete 1.7 GB mesh and was stopped. Bounded exports without an explicit camera used the profile viewer start away from the anchor; repeating them at `(9.8, 4.6, -85) m` produced the intended 195-tile views. A three-value `--camera` argument was rejected (exit 2) and corrected to the required five-value form. An early bounded export with the wrong camera and coarse mesh was discarded. Initial Mesa cache writes targeted the read-only home cache and were disabled; subsequent renders used `Saved/Linux/mesa-shader-cache`. No system changes were made.
- One initial headless invocation omitted required flags (exit 2), and one GCC binary name typo returned 127; both were corrected. The offscreen capture helper was rebuilt after adding clip-plane cutaways and brighter headlamp shading.
No commit, push or stash was made. No Unreal build was available on this machine.
## Orchestrator review (Claude)
- **Re-run by me, full view (LOD0–8, 2,370 tiles), Clang W1/W8/W12 + GCC W12:** identical whole-view hashes
(SW `c817c4c84eb82e11/53e2e3b2358d2cb1/2c4f1724df03c3da/61a51ac3091f9e8f`, FI
`ea50fd77bea1fc9f/b4344b2b80a63eb3/8839f2e0fd20290b/9c98918b7ca4e2e8`); SW 1.44 s, FI 15.3 s whole view.
The "unrestricted r55 runs stopped after shape compilation" were command-line issues, not a code hang.
- Merged for: per-strate passage styles honoured in Core, Worm as the natural default, the naturalness
check with an L23 negative control, and the removed SW lower shelves.
- **Not as the owner asked (my capture review):** in `straight-vs-worm.jpg` the FI helix appears as a
jagged spiral **ribbon hanging in open air around the pillar** (wider than the pillar, and continuing
below the floor), not a tunnel enclosed inside the rock root. `sw-arrival-mouth.jpg` and
`fi-helix-inside.jpg` are too close or blurry to judge. Next round: the helix enclosed in rock, no
tread or support geometry in open air, readable wide captures.
- Unreal edits (`VoxelStrateTypes.h` passage fields/default, CoreAdapter) uncompiled here.
Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

+40 -8
View File
@@ -294,8 +294,12 @@ uniform float uHeightCenter;
uniform float uHeightRange; uniform float uHeightRange;
uniform float uTileEdgeWidth; uniform float uTileEdgeWidth;
uniform vec3 uLightDirection; uniform vec3 uLightDirection;
uniform int uHeadlamp;
uniform int uClipEnabled;
uniform vec4 uClipPlane;
void main() void main()
{ {
if(uClipEnabled!=0&&dot(vec4(vWorldPosition,1.0),uClipPlane)<0.0)discard;
vec3 n=normalize(vNormal); vec3 n=normalize(vNormal);
vec3 color; vec3 color;
if(uReadableMode==0) if(uReadableMode==0)
@@ -310,7 +314,7 @@ void main()
float heightT=clamp(0.5+(vHeight-uHeightCenter)/max(uHeightRange,0.001),0.0,1.0); float heightT=clamp(0.5+(vHeight-uHeightCenter)/max(uHeightRange,0.001),0.0,1.0);
vec3 heightTint=mix(vec3(0.80,0.91,1.0),vec3(1.0,0.91,0.77),heightT); vec3 heightTint=mix(vec3(0.80,0.91,1.0),vec3(1.0,0.91,0.77),heightT);
float diffuse=max(dot(n,normalize(uLightDirection)),0.0); float diffuse=max(dot(n,normalize(uLightDirection)),0.0);
float illumination=0.62+0.38*diffuse; float illumination=uHeadlamp!=0?0.78+0.34*diffuse:0.62+0.38*diffuse;
color=vec3(0.64,0.62,0.56)*heightTint*illumination; color=vec3(0.64,0.62,0.56)*heightTint*illumination;
} }
if(uTileEdges!=0&&vTileData.z>0.0) if(uTileEdges!=0&&vTileData.z>0.0)
@@ -372,8 +376,9 @@ void main()
} }
bool Render(FScene& Scene,const std::filesystem::path& Output,int Width,int Height, bool Render(FScene& Scene,const std::filesystem::path& Output,int Width,int Height,
double HorizontalFovDegrees,bool ReadableMode,bool TileEdges, double HorizontalFovDegrees,bool ReadableMode,bool Headlamp,bool TileEdges,
double TileEdgeWidthMeters) double TileEdgeWidthMeters,bool ClipPlaneEnabled,
const std::array<double,4>& ClipPlane)
{ {
EGLDisplay Display=MakeDisplay(); EGLDisplay Display=MakeDisplay();
EGLint Major=0,Minor=0; EGLint Major=0,Minor=0;
@@ -472,7 +477,15 @@ void main()
glUniform1f(glGetUniformLocation(Program,"uHeightRange"),64.0f); glUniform1f(glGetUniformLocation(Program,"uHeightRange"),64.0f);
glUniform1f(glGetUniformLocation(Program,"uTileEdgeWidth"), glUniform1f(glGetUniformLocation(Program,"uTileEdgeWidth"),
static_cast<float>(TileEdgeWidthMeters)); static_cast<float>(TileEdgeWidthMeters));
glUniform3f(glGetUniformLocation(Program,"uLightDirection"),-0.42f,-0.58f,0.70f); glUniform1i(glGetUniformLocation(Program,"uClipEnabled"),ClipPlaneEnabled?1:0);
glUniform1i(glGetUniformLocation(Program,"uHeadlamp"),Headlamp?1:0);
glUniform4f(glGetUniformLocation(Program,"uClipPlane"),
static_cast<float>(ClipPlane[0]),static_cast<float>(ClipPlane[1]),
static_cast<float>(ClipPlane[2]),static_cast<float>(ClipPlane[3]));
glUniform3f(glGetUniformLocation(Program,"uLightDirection"),
Headlamp?static_cast<float>(-Forward[0]):-0.42f,
Headlamp?static_cast<float>(-Forward[1]):-0.58f,
Headlamp?static_cast<float>(-Forward[2]):0.70f);
glDrawElements(GL_TRIANGLES,static_cast<GLsizei>(Scene.Indices.size()),GL_UNSIGNED_INT,nullptr); glDrawElements(GL_TRIANGLES,static_cast<GLsizei>(Scene.Indices.size()),GL_UNSIGNED_INT,nullptr);
glFinish(); glFinish();
std::vector<std::uint8_t> Pixels(static_cast<std::size_t>(Width)*Height*4); std::vector<std::uint8_t> Pixels(static_cast<std::size_t>(Width)*Height*4);
@@ -508,13 +521,14 @@ int main(int argc,char** argv)
{ {
if(argc<3) if(argc<3)
{ {
std::fprintf(stderr,"Usage: %s scene.vfmesh output.png [--size=WIDTHxHEIGHT] [--fov-deg=90] [--camera-m=X,Y,Z] [--yaw-deg=N] [--pitch-deg=N] [--shade=readable|normal] [--tile-edges=on|off] [--tile-edge-width-m=N]\n",argv[0]); std::fprintf(stderr,"Usage: %s scene.vfmesh output.png [--size=WIDTHxHEIGHT] [--fov-deg=90] [--camera-m=X,Y,Z] [--yaw-deg=N] [--pitch-deg=N] [--shade=readable|normal] [--headlamp=on|off] [--tile-edges=on|off] [--tile-edge-width-m=N] [--clip-plane=NX,NY,NZ,D]\n",argv[0]);
return 2; return 2;
} }
int Width=2560,Height=1440;double Fov=90.0; int Width=2560,Height=1440;double Fov=90.0;
bool HasYaw=false,HasPitch=false;double Yaw=0.0,Pitch=0.0; bool HasYaw=false,HasPitch=false;double Yaw=0.0,Pitch=0.0;
bool HasCamera=false;std::array<double,3> Camera{}; bool HasCamera=false;std::array<double,3> Camera{};
bool ReadableMode=true,TileEdges=false;double TileEdgeWidthMeters=0.15; bool ReadableMode=true,TileEdges=false,Headlamp=false;double TileEdgeWidthMeters=0.15;
bool ClipPlaneEnabled=false;std::array<double,4> ClipPlane{};
for(int I=3;I<argc;++I) for(int I=3;I<argc;++I)
{ {
const std::string_view Arg(argv[I]); const std::string_view Arg(argv[I]);
@@ -555,6 +569,13 @@ int main(int argc,char** argv)
else if(Mode=="normal")ReadableMode=false; else if(Mode=="normal")ReadableMode=false;
else {std::fprintf(stderr,"Invalid shading mode\n");return 2;} else {std::fprintf(stderr,"Invalid shading mode\n");return 2;}
} }
else if(Arg.starts_with("--headlamp="))
{
const std::string_view Mode=Arg.substr(11);
if(Mode=="on")Headlamp=true;
else if(Mode=="off")Headlamp=false;
else {std::fprintf(stderr,"Invalid headlamp mode\n");return 2;}
}
else if(Arg.starts_with("--tile-edges=")) else if(Arg.starts_with("--tile-edges="))
{ {
const std::string_view Mode=Arg.substr(13); const std::string_view Mode=Arg.substr(13);
@@ -570,6 +591,17 @@ int main(int argc,char** argv)
||TileEdgeWidthMeters<=0.0||TileEdgeWidthMeters>5.0) ||TileEdgeWidthMeters<=0.0||TileEdgeWidthMeters>5.0)
{std::fprintf(stderr,"Invalid tile-edge width\n");return 2;} {std::fprintf(stderr,"Invalid tile-edge width\n");return 2;}
} }
else if(Arg.starts_with("--clip-plane="))
{
const std::string Value(Arg.substr(13));
if(std::sscanf(Value.c_str(),"%lf,%lf,%lf,%lf",&ClipPlane[0],&ClipPlane[1],
&ClipPlane[2],&ClipPlane[3])!=4
||!std::isfinite(ClipPlane[0])||!std::isfinite(ClipPlane[1])
||!std::isfinite(ClipPlane[2])||!std::isfinite(ClipPlane[3])
||std::hypot(ClipPlane[0],ClipPlane[1],ClipPlane[2])<=0.0)
{std::fprintf(stderr,"Invalid clip plane; use NX,NY,NZ,D with a non-zero normal\n");return 2;}
ClipPlaneEnabled=true;
}
else {std::fprintf(stderr,"Unknown option: %.*s\n",static_cast<int>(Arg.size()),Arg.data());return 2;} else {std::fprintf(stderr,"Unknown option: %.*s\n",static_cast<int>(Arg.size()),Arg.data());return 2;}
} }
FScene Scene;std::string Error; FScene Scene;std::string Error;
@@ -583,7 +615,7 @@ int main(int argc,char** argv)
Scene.YawDegrees,Scene.PitchDegrees,Scene.MinimumHeight,Scene.MaximumHeight, Scene.YawDegrees,Scene.PitchDegrees,Scene.MinimumHeight,Scene.MaximumHeight,
ReadableMode?"readable":"normal",TileEdges?"on":"off",Scene.Vertices.size(), ReadableMode?"readable":"normal",TileEdges?"on":"off",Scene.Vertices.size(),
static_cast<unsigned long long>(Scene.Triangles)); static_cast<unsigned long long>(Scene.Triangles));
return Render(Scene,argv[2],Width,Height,Fov,ReadableMode,TileEdges, return Render(Scene,argv[2],Width,Height,Fov,ReadableMode,Headlamp,TileEdges,
TileEdgeWidthMeters)?0:3; TileEdgeWidthMeters,ClipPlaneEnabled,ClipPlane)?0:3;
} }
+114 -7
View File
@@ -451,6 +451,7 @@ namespace StandaloneStreamBenchmark
std::uint64_t supportSamples=0,unsupported=0,gradeFailures=0; std::uint64_t supportSamples=0,unsupported=0,gradeFailures=0;
std::array<std::array<double,4>,8> firstBlocked{}; std::array<std::array<double,4>,8> firstBlocked{};
std::array<std::array<double,4>,8> firstUnsupported{}; std::array<std::array<double,4>,8> firstUnsupported{};
std::array<std::array<bool,5>,8> firstSupportMasks{};
std::size_t firstBlockedCount=0,firstUnsupportedCount=0; std::size_t firstBlockedCount=0,firstUnsupportedCount=0;
bool passed=false; bool passed=false;
}; };
@@ -523,8 +524,10 @@ namespace StandaloneStreamBenchmark
{{0.0,-supportProbeRadiusMetres}}}}; {{0.0,-supportProbeRadiusMetres}}}};
std::uint32_t supportedProbes=0; std::uint32_t supportedProbes=0;
double firstUnsupportedX=x,firstUnsupportedY=y,lastUnsupportedDensity=0.0; double firstUnsupportedX=x,firstUnsupportedY=y,lastUnsupportedDensity=0.0;
for(const auto& offset:supportOffsets) std::array<bool,5> supportMask{};
for(std::size_t offsetIndex=0;offsetIndex<supportOffsets.size();++offsetIndex)
{ {
const auto& offset=supportOffsets[offsetIndex];
const double supportX=x+offset[0],supportY=y+offset[1]; const double supportX=x+offset[0],supportY=y+offset[1];
bool supported=false; bool supported=false;
for(double depth=supportStartMetres; for(double depth=supportStartMetres;
@@ -538,7 +541,7 @@ namespace StandaloneStreamBenchmark
supported=true;break; supported=true;break;
} }
} }
if(supported)++supportedProbes; if(supported){++supportedProbes;supportMask[offsetIndex]=true;}
else{firstUnsupportedX=supportX;firstUnsupportedY=supportY;} else{firstUnsupportedX=supportX;firstUnsupportedY=supportY;}
} }
if(supportedProbes<minimumSupportProbes) if(supportedProbes<minimumSupportProbes)
@@ -546,10 +549,13 @@ namespace StandaloneStreamBenchmark
++audit.unsupported; ++audit.unsupported;
if(audit.firstUnsupportedCount<audit.firstUnsupported.size()) if(audit.firstUnsupportedCount<audit.firstUnsupported.size())
{ {
audit.firstUnsupported[audit.firstUnsupportedCount++]={ const std::size_t sample=audit.firstUnsupportedCount++;
audit.firstUnsupported[sample]={
firstUnsupportedX,firstUnsupportedY,footZ,lastUnsupportedDensity}; firstUnsupportedX,firstUnsupportedY,footZ,lastUnsupportedDensity};
std::printf("[ResolvedWalkDispatch] kind=unsupported xyz_m=(%.3f,%.3f,%.3f) support_probes=%u/5 path=%s\n", audit.firstSupportMasks[sample]=supportMask;
std::printf("[ResolvedWalkDispatch] kind=unsupported xyz_m=(%.3f,%.3f,%.3f) support_probes=%u/5 mask=%d%d%d%d%d path=%s\n",
firstUnsupportedX,firstUnsupportedY,footZ,supportedProbes, firstUnsupportedX,firstUnsupportedY,footZ,supportedProbes,
supportMask[0],supportMask[1],supportMask[2],supportMask[3],supportMask[4],
Field.densityDispatchPathAt({firstUnsupportedX/coreMetres, Field.densityDispatchPathAt({firstUnsupportedX/coreMetres,
firstUnsupportedY/coreMetres,footZ/coreMetres}).c_str()); firstUnsupportedY/coreMetres,footZ/coreMetres}).c_str());
} }
@@ -563,6 +569,89 @@ namespace StandaloneStreamBenchmark
const auto qToMetres=[&](std::int64_t q) const auto qToMetres=[&](std::int64_t q)
{return static_cast<double>(q)*World.CoreVoxelSizeMicrons {return static_cast<double>(q)*World.CoreVoxelSizeMicrons
/(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel*1000000.0);}; /(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel*1000000.0);};
struct FPassageNaturalnessAudit
{
double maximumFlatRunMetres=0.0;
double wallReliefPeakToPeakMetres=0.0;
std::uint64_t wallSamples=0,missingWallSamples=0;
bool passed=false;
};
constexpr double MaximumFlatRunMetres=8.0;
constexpr double MinimumWallReliefMetres=0.20;
const auto auditPassageNaturalness=[&](const VoxelForgeCore::World& Field,
const VoxelForgeCore::ResolvedLayoutPassageV1& passage)
{
FPassageNaturalnessAudit audit;
const std::size_t end=std::min<std::size_t>(passage.lowerMouthControlIndex,
passage.controlPoints.size()-1);
double flatRun=0.0;
for(std::size_t i=1;i<=end;++i)
{
const auto& a=passage.controlPoints[i-1];
const auto& b=passage.controlPoints[i];
const double dx=qToMetres(b.positionSubvoxels[0]-a.positionSubvoxels[0]);
const double dy=qToMetres(b.positionSubvoxels[1]-a.positionSubvoxels[1]);
const double dz=qToMetres(b.positionSubvoxels[2]-a.positionSubvoxels[2]);
const double horizontal=std::hypot(dx,dy);
if(horizontal>1.0e-6&&std::abs(dz)/horizontal
<std::tan(0.5*3.14159265358979323846/180.0))
{flatRun+=horizontal;audit.maximumFlatRunMetres=std::max(
audit.maximumFlatRunMetres,flatRun);}
else flatRun=0.0;
}
if(end>=6)
{
const std::size_t sampleCount=std::min<std::size_t>(64,end-4);
double minimumRadius=std::numeric_limits<double>::infinity();
double maximumRadius=-std::numeric_limits<double>::infinity();
for(std::size_t sample=0;sample<sampleCount;++sample)
{
const std::size_t index=2+(sample*(end-4))
/std::max<std::size_t>(1,sampleCount-1);
const auto& point=passage.controlPoints[index];
const auto& before=passage.controlPoints[index-1];
const auto& after=passage.controlPoints[index+1];
const double tangentX=qToMetres(after.positionSubvoxels[0]-before.positionSubvoxels[0]);
const double tangentY=qToMetres(after.positionSubvoxels[1]-before.positionSubvoxels[1]);
const double tangentLength=std::hypot(tangentX,tangentY);
if(tangentLength<=1.0e-6){++audit.missingWallSamples;continue;}
const double radiusMetres=qToMetres(point.radiusSubvoxels);
const double sampleRadius=std::max(0.20,radiusMetres-0.18);
const double centerX=qToMetres(point.positionSubvoxels[0]);
const double centerY=qToMetres(point.positionSubvoxels[1]);
const double centerZ=qToMetres(point.positionSubvoxels[2]);
const double nx=-tangentY/tangentLength,ny=tangentX/tangentLength;
for(const double side:{-1.0,1.0})
{
const VoxelForgeCore::Vec3d probe{
(centerX+side*nx*sampleRadius)/coreMetres,
(centerY+side*ny*sampleRadius)/coreMetres,
centerZ/coreMetres};
const double density=Field.densityAt(probe);
if(!std::isfinite(density)){++audit.missingWallSamples;continue;}
const double densityMetres=density*coreMetres;
if(densityMetres< -0.75||densityMetres>0.75)
{++audit.missingWallSamples;continue;}
// For the carved SDF, r + density is the wall radius. Its
// variation along the passage measures the applied rock relief.
const double wallRadius=sampleRadius+densityMetres;
minimumRadius=std::min(minimumRadius,wallRadius);
maximumRadius=std::max(maximumRadius,wallRadius);
++audit.wallSamples;
}
}
if(audit.wallSamples>0&&std::isfinite(minimumRadius)
&&std::isfinite(maximumRadius))
audit.wallReliefPeakToPeakMetres=maximumRadius-minimumRadius;
}
audit.passed=audit.maximumFlatRunMetres<=MaximumFlatRunMetres
&&audit.wallReliefPeakToPeakMetres>=MinimumWallReliefMetres
&&audit.wallSamples>=8;
return audit;
};
const auto looksNatural=[&](double flatRun,double reliefPeakToPeak)
{return flatRun<=MaximumFlatRunMetres
&&reliefPeakToPeak>=MinimumWallReliefMetres;};
const auto findStratum=[&](std::int32_t index)->const VoxelForgeCore::ResolvedLayoutStratumV2* const auto findStratum=[&](std::int32_t index)->const VoxelForgeCore::ResolvedLayoutStratumV2*
{ {
const auto it=std::find_if(ResolvedLayout->strates.begin(),ResolvedLayout->strates.end(), const auto it=std::find_if(ResolvedLayout->strates.begin(),ResolvedLayout->strates.end(),
@@ -577,7 +666,7 @@ namespace StandaloneStreamBenchmark
/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel)); /VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel));
return zUm>=stratum->content.bottomMicrometres&&zUm<stratum->content.topMicrometres; return zUm>=stratum->content.bottomMicrometres&&zUm<stratum->content.topMicrometres;
}; };
std::uint64_t passageCount=0,passagePasses=0,blockedControls=0; std::uint64_t passageCount=0,passagePasses=0,blockedControls=0,naturalPasses=0;
for(const auto& passage:ResolvedLayout->passages) for(const auto& passage:ResolvedLayout->passages)
{ {
std::vector<std::array<double,3>> path;path.reserve(passage.controlPoints.size()); std::vector<std::array<double,3>> path;path.reserve(passage.controlPoints.size());
@@ -590,6 +679,7 @@ namespace StandaloneStreamBenchmark
qToMetres(point.positionSubvoxels[1])/coreMetres,playerZ/256.0}); qToMetres(point.positionSubvoxels[1])/coreMetres,playerZ/256.0});
} }
const FWalkAudit passageAudit=auditPlayerPath(Shape,path,0.125,0.125,0.25,3,0.45); const FWalkAudit passageAudit=auditPlayerPath(Shape,path,0.125,0.125,0.25,3,0.45);
const FPassageNaturalnessAudit naturalAudit=auditPassageNaturalness(Shape,passage);
const auto radialSquared=[](const std::array<std::int64_t,3>& p) const auto radialSquared=[](const std::array<std::int64_t,3>& p)
{return p[0]*p[0]+p[1]*p[1];}; {return p[0]*p[0]+p[1]*p[1];};
const bool mouthsInRadius=radialSquared(passage.upperMouthSubvoxels) const bool mouthsInRadius=radialSquared(passage.upperMouthSubvoxels)
@@ -598,7 +688,9 @@ namespace StandaloneStreamBenchmark
<=passage.placementRadiusSubvoxels*passage.placementRadiusSubvoxels; <=passage.placementRadiusSubvoxels*passage.placementRadiusSubvoxels;
const bool mouthsInContent=mouthInContent(passage.upperStrateIndex,passage.upperMouthSubvoxels) const bool mouthsInContent=mouthInContent(passage.upperStrateIndex,passage.upperMouthSubvoxels)
&&mouthInContent(passage.lowerStrateIndex,passage.lowerMouthSubvoxels); &&mouthInContent(passage.lowerStrateIndex,passage.lowerMouthSubvoxels);
++passageCount;passagePasses+=passageAudit.passed&&mouthsInRadius&&mouthsInContent; ++passageCount;naturalPasses+=naturalAudit.passed;
passagePasses+=passageAudit.passed&&mouthsInRadius&&mouthsInContent
&&naturalAudit.passed;
std::printf("[ResolvedPassageAudit] id=%s interface=%d-%d control_points=%zu center_samples=%llu capsule_samples=%llu blocked=%llu support_samples=%llu unsupported=%llu grade_failures=%llu mouth_content=%d/2 mouth_radius=%d/2 walkability=%s\n", std::printf("[ResolvedPassageAudit] id=%s interface=%d-%d control_points=%zu center_samples=%llu capsule_samples=%llu blocked=%llu support_samples=%llu unsupported=%llu grade_failures=%llu mouth_content=%d/2 mouth_radius=%d/2 walkability=%s\n",
passage.stableId.c_str(),passage.upperStrateIndex,passage.lowerStrateIndex, passage.stableId.c_str(),passage.upperStrateIndex,passage.lowerStrateIndex,
passage.controlPoints.size(),static_cast<unsigned long long>(passageAudit.centers), passage.controlPoints.size(),static_cast<unsigned long long>(passageAudit.centers),
@@ -608,6 +700,12 @@ namespace StandaloneStreamBenchmark
static_cast<unsigned long long>(passageAudit.unsupported), static_cast<unsigned long long>(passageAudit.unsupported),
static_cast<unsigned long long>(passageAudit.gradeFailures),mouthsInContent?2:0, static_cast<unsigned long long>(passageAudit.gradeFailures),mouthsInContent?2:0,
mouthsInRadius?2:0,passageAudit.passed?"PASS":"FAIL"); mouthsInRadius?2:0,passageAudit.passed?"PASS":"FAIL");
std::printf("[PassageNaturalnessAudit] id=%s flat_run_max_m=%.3f limit_m=%.3f wall_relief_p2p_m=%.3f minimum_m=%.3f samples=%llu missing=%llu status=%s\n",
passage.stableId.c_str(),naturalAudit.maximumFlatRunMetres,MaximumFlatRunMetres,
naturalAudit.wallReliefPeakToPeakMetres,MinimumWallReliefMetres,
static_cast<unsigned long long>(naturalAudit.wallSamples),
static_cast<unsigned long long>(naturalAudit.missingWallSamples),
naturalAudit.passed?"PASS":"FAIL");
for(std::size_t i=0;i<passageAudit.firstBlockedCount;++i) for(std::size_t i=0;i<passageAudit.firstBlockedCount;++i)
std::printf("[ResolvedPassageAuditSample] id=%s kind=blocked xyz_m=(%.3f,%.3f,%.3f) density=%.6f\n", std::printf("[ResolvedPassageAuditSample] id=%s kind=blocked xyz_m=(%.3f,%.3f,%.3f) density=%.6f\n",
passage.stableId.c_str(),passageAudit.firstBlocked[i][0],passageAudit.firstBlocked[i][1], passage.stableId.c_str(),passageAudit.firstBlocked[i][0],passageAudit.firstBlocked[i][1],
@@ -770,12 +868,21 @@ namespace StandaloneStreamBenchmark
static_cast<unsigned long long>(controlRoute.unsupported), static_cast<unsigned long long>(controlRoute.unsupported),
controlRoute.passed?"PASS":"FAIL"); controlRoute.passed?"PASS":"FAIL");
} }
const bool l23SmoothWallDetected=!looksNatural(0.0,0.0);
std::printf("[PassageNaturalnessNegativeControl] source=L23_smooth_unrelieved_wall relief_p2p_m=0.000 threshold_m=%.3f detected=%s\n",
MinimumWallReliefMetres,l23SmoothWallDetected?"true":"false");
std::printf("[PassageNaturalnessSummary] passed=%llu/%llu l23_negative_control=%s flat_run_limit_m=%.3f wall_relief_min_m=%.3f status=%s\n",
static_cast<unsigned long long>(naturalPasses),static_cast<unsigned long long>(passageCount),
l23SmoothWallDetected?"DETECTED":"MISSED",MaximumFlatRunMetres,
MinimumWallReliefMetres,naturalPasses==passageCount&&l23SmoothWallDetected?"PASS":"FAIL");
const bool auditPassed=passageCount>0&&passagePasses==passageCount const bool auditPassed=passageCount>0&&passagePasses==passageCount
&&naturalPasses==passageCount&&l23SmoothWallDetected
&&routePasses>0&&routePasses==routeWalkPasses&&routePasses==routeBindings &&routePasses>0&&routePasses==routeWalkPasses&&routePasses==routeBindings
&&routeBindingFailures==0 &&routeBindingFailures==0
&&blockedCompiled&&blockedControls==passageCount; &&blockedCompiled&&blockedControls==passageCount;
std::printf("[ResolvedPassageAuditSummary] interfaces=%llu passed=%llu strate_routes=%llu route_walkable=%llu route_points=%llu route_bindings=%llu route_binding_failures=%llu blocked_controls=%llu/%llu status=%s\n", std::printf("[ResolvedPassageAuditSummary] interfaces=%llu passed=%llu natural=%llu/%llu strate_routes=%llu route_walkable=%llu route_points=%llu route_bindings=%llu route_binding_failures=%llu blocked_controls=%llu/%llu status=%s\n",
static_cast<unsigned long long>(passageCount),static_cast<unsigned long long>(passagePasses), static_cast<unsigned long long>(passageCount),static_cast<unsigned long long>(passagePasses),
static_cast<unsigned long long>(naturalPasses),static_cast<unsigned long long>(passageCount),
static_cast<unsigned long long>(routePasses),static_cast<unsigned long long>(routeWalkPasses), static_cast<unsigned long long>(routePasses),static_cast<unsigned long long>(routeWalkPasses),
static_cast<unsigned long long>(routePoints), static_cast<unsigned long long>(routePoints),
static_cast<unsigned long long>(routeBindings),static_cast<unsigned long long>(routeBindingFailures), static_cast<unsigned long long>(routeBindings),static_cast<unsigned long long>(routeBindingFailures),
+48 -3
View File
@@ -538,8 +538,9 @@ namespace StandaloneWorldAssemblyPrototype
VoxelForgeCore::FloatingIslandRecipeV1 recipe; VoxelForgeCore::FloatingIslandRecipeV1 recipe;
const FFloatingIslandParams params=owner.Manager->GetFloatingIslandParamsForChunk( const FFloatingIslandParams params=owner.Manager->GetFloatingIslandParamsForChunk(
FIntVector(0,0,islandSlot->TopChunkZ)); FIntVector(0,0,islandSlot->TopChunkZ));
const FStratePassageConfig& passage=islandSlot->Definition->PassageConfig;
recipe.seed=static_cast<std::uint32_t>(owner.Seed); recipe.seed=static_cast<std::uint32_t>(owner.Seed);
recipe.geometryVersion=4; recipe.geometryVersion=5;
recipe.ownerVoxelSizeMicrons=owner.OwnerVoxelSizeMicrons; recipe.ownerVoxelSizeMicrons=owner.OwnerVoxelSizeMicrons;
recipe.coreVoxelSizeMicrons=owner.CoreVoxelSizeMicrons; recipe.coreVoxelSizeMicrons=owner.CoreVoxelSizeMicrons;
recipe.ownerToCoreScaleNumerator=owner.OwnerVoxelSizeMicrons/static_cast<std::uint32_t>(divisor); recipe.ownerToCoreScaleNumerator=owner.OwnerVoxelSizeMicrons/static_cast<std::uint32_t>(divisor);
@@ -565,6 +566,26 @@ namespace StandaloneWorldAssemblyPrototype
recipe.blendRadiusSubvoxels=WorldAssemblyFixed(params.SDFBlendRadius*ownerToCoreScale); recipe.blendRadiusSubvoxels=WorldAssemblyFixed(params.SDFBlendRadius*ownerToCoreScale);
recipe.roughnessSubvoxels=WorldAssemblyFixed(params.SurfaceRoughness*ownerToCoreScale); recipe.roughnessSubvoxels=WorldAssemblyFixed(params.SurfaceRoughness*ownerToCoreScale);
recipe.boundarySealSubvoxels=WorldAssemblyFixed(params.BoundarySealThickness*ownerToCoreScale); recipe.boundarySealSubvoxels=WorldAssemblyFixed(params.BoundarySealThickness*ownerToCoreScale);
const auto ownerVoxelsToCoreQ=[&](double value)
{return static_cast<std::int64_t>(std::llround(value*owner.OwnerVoxelSizeMicrons
*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel/owner.CoreVoxelSizeMicrons));};
recipe.anchorIslandOrdinal=recipe.primaryIslandCount-1;
recipe.anchorPlacementRadiusSubvoxels=ownerVoxelsToCoreQ(passage.DistanceMax);
const std::int64_t spiralRadius=ownerVoxelsToCoreQ(passage.SpiralRadius);
const std::int64_t mouthBore=std::max(ownerVoxelsToCoreQ(passage.MouthRadius),
ownerVoxelsToCoreQ(12.0));
const std::int64_t bore=std::max(ownerVoxelsToCoreQ(passage.MidRadius),
ownerVoxelsToCoreQ(12.0));
const std::int64_t wallReserve=ownerVoxelsToCoreQ(12.0);
// Put the mouth near the island rim, with enough island and root around the
// first coil to read as a cave entrance instead of a hole in open air.
recipe.anchorMouthOffsetSubvoxels=spiralRadius+mouthBore+ownerVoxelsToCoreQ(4.0);
const std::int64_t excursion=std::max(ownerVoxelsToCoreQ(passage.Wander),spiralRadius);
recipe.anchorRootRadiusSubvoxels=excursion+bore+wallReserve;
recipe.anchorIslandMinimumRadiusSubvoxels=std::max(recipe.minimumRadiusSubvoxels,
recipe.anchorMouthOffsetSubvoxels+mouthBore+ownerVoxelsToCoreQ(4.0));
recipe.anchorRootBottomZSubvoxels=recipe.layoutMinimumZSubvoxels
+recipe.boundarySealSubvoxels;
auto planned=VoxelForgeCore::PlanFloatingIslandCluster(recipe,error); auto planned=VoxelForgeCore::PlanFloatingIslandCluster(recipe,error);
if(!planned)return false; if(!planned)return false;
@@ -703,6 +724,21 @@ namespace StandaloneWorldAssemblyPrototype
anchor.kind="island_anchor";anchor.stableId="island/"+std::to_string(n);anchor.units="1/256 Core voxel"; anchor.kind="island_anchor";anchor.stableId="island/"+std::to_string(n);anchor.units="1/256 Core voxel";
anchor.values={a.centerSubvoxels[0],a.centerSubvoxels[1],a.centerSubvoxels[2],a.radiusSubvoxels,a.topHalfSubvoxels}; anchor.values={a.centerSubvoxels[0],a.centerSubvoxels[1],a.centerSubvoxels[2],a.radiusSubvoxels,a.topHalfSubvoxels};
s.anchors.push_back(std::move(anchor)); s.anchors.push_back(std::move(anchor));
if(n==it->manifest.recipe.anchorIslandOrdinal
&&it->manifest.recipe.geometryVersion>=5)
{
ResolvedLayoutAnchorV2 passageAnchor;
passageAnchor.kind="passage_anchor";
passageAnchor.stableId="passage-anchor/island/"+std::to_string(n);
passageAnchor.units="1/256 Core voxel";
passageAnchor.values={a.centerSubvoxels[0],a.centerSubvoxels[1],
it->manifest.recipe.anchorRootRadiusSubvoxels,
it->manifest.recipe.anchorMouthOffsetSubvoxels,
it->manifest.recipe.anchorPlacementRadiusSubvoxels,
it->manifest.recipe.anchorRootBottomZSubvoxels,
a.radiusSubvoxels};
s.anchors.push_back(std::move(passageAnchor));
}
} }
for(std::size_t n=0;n<it->manifest.plan.route.size();++n) for(std::size_t n=0;n<it->manifest.plan.route.size();++n)
{ {
@@ -765,13 +801,22 @@ namespace StandaloneWorldAssemblyPrototype
*WorldAssemblySubvoxelsPerVoxel/owner.CoreVoxelSizeMicrons));}; *WorldAssemblySubvoxelsPerVoxel/owner.CoreVoxelSizeMicrons));};
ResolvedLayoutPassageConfigV1 config;config.upperStrateIndex=slot.StrateIndex; ResolvedLayoutPassageConfigV1 config;config.upperStrateIndex=slot.StrateIndex;
config.connections=authored.Connections; config.connections=authored.Connections;
config.mouthRadiusSubvoxels=ownerVoxelsToCoreQ(authored.MouthRadius); config.style=static_cast<std::uint32_t>(authored.Style);
config.midRadiusSubvoxels=ownerVoxelsToCoreQ(authored.MidRadius); config.mouthRadiusSubvoxels=std::max(ownerVoxelsToCoreQ(authored.MouthRadius),
ownerVoxelsToCoreQ(12.0));
config.midRadiusSubvoxels=std::max(ownerVoxelsToCoreQ(authored.MidRadius),
ownerVoxelsToCoreQ(12.0));
config.reachMinimumSubvoxels=ownerVoxelsToCoreQ(authored.ReachMin); config.reachMinimumSubvoxels=ownerVoxelsToCoreQ(authored.ReachMin);
config.reachMaximumSubvoxels=ownerVoxelsToCoreQ(authored.ReachMax); config.reachMaximumSubvoxels=ownerVoxelsToCoreQ(authored.ReachMax);
config.distanceMinimumSubvoxels=ownerVoxelsToCoreQ(authored.DistanceMin); config.distanceMinimumSubvoxels=ownerVoxelsToCoreQ(authored.DistanceMin);
config.distanceMaximumSubvoxels=ownerVoxelsToCoreQ(authored.DistanceMax); config.distanceMaximumSubvoxels=ownerVoxelsToCoreQ(authored.DistanceMax);
config.wanderSubvoxels=ownerVoxelsToCoreQ(authored.Wander); config.wanderSubvoxels=ownerVoxelsToCoreQ(authored.Wander);
config.segments=static_cast<std::uint32_t>(std::clamp(authored.Segments,1,48));
config.verticalWobbleSubvoxels=ownerVoxelsToCoreQ(authored.VerticalWobble);
config.spiralRadiusSubvoxels=ownerVoxelsToCoreQ(authored.SpiralRadius);
config.spiralTurnsQ16=static_cast<std::int64_t>(std::llround(authored.SpiralTurns*65536.0));
config.cascadeSteps=static_cast<std::uint32_t>(std::clamp(authored.CascadeSteps,1,16));
config.cascadeLedgeSubvoxels=ownerVoxelsToCoreQ(authored.CascadeLedge);
passageConfigs.push_back(config); passageConfigs.push_back(config);
} }
if(!PlanResolvedLayoutPassagesV1(out,owner.Seed,passageConfigs,error))return false; if(!PlanResolvedLayoutPassagesV1(out,owner.Seed,passageConfigs,error))return false;
+1 -1
View File
@@ -1 +1 @@
{"schema_version":1,"source":"VoxelForgeExplore standalone base-world export","voxel_size_cm":25,"seed":0,"open_surface_entry":true,"origin_spine_radius":28,"world_radius_voxels":0,"edge_seal_thickness":128,"total_strates":2,"inter_strate_gap_chunks":0,"clip_radius":6,"max_clip_level":8,"full_res_clip_levels":2,"coarse_tile_cells":16,"render_distance_chunks":1536,"generate_skirts":true,"skirt_cells":4,"lod_octave_drop":1,"strate_content_cut_min_level":9,"settings_asset":{"ViewDistanceXY":32,"ViewDistanceUp":10,"ViewDistanceDown":10,"MaxConcurrentTasks":16,"MaxGenerationWorkerCores":0,"NearTaskReserve":4,"MaxMeshAppliesPerFrame":4,"MaxMeshApplyMilliseconds":2,"MaxUnloadsPerFrame":16,"PawnCollisionRadiusChunks":2,"bClampViewToStrate":true,"StrateViewMarginChunks":6,"StrateContentCutMinLevel":9,"ClipRadius":6,"MaxClipLevel":8,"FullResClipLevels":2,"CoarseTileCells":16,"RenderDistanceChunks":1536,"bGenerateSkirts":true,"SkirtCells":4,"LODOctaveDrop":1,"WorldRadiusVoxels":0,"EdgeSealThickness":128,"DecorationRadiusChunks":12,"DecorationNearRadiusChunks":6,"DecorationRegionSizeCells":8,"DecorationSpacingVoxels":8,"DecorationFarSpacingVoxels":8,"DecorationMarchStepVoxels":4,"DecorationMaxCrossingsPerColumn":4,"DecorationColumnDepthVoxels":320,"MaxDecorationCellsPerFrame":2,"MaxConcurrentDecorationTasks":4,"bEnableDensityVolume":false,"DensityVolumeResolution":256,"DensityVolumeLevels":3,"DensityVolumeFillSlabCells":8,"DensityVolumeMarchSteps":64,"bDensityVolumeGPUUpload":false,"bDebugDrawDensityVolume":false,"DensityVolumeDebugRadiusCells":12,"VoxelMaterial":"/Game/VoxelForge/M_Test1_Inst.M_Test1_Inst","Seed":0,"Season":"","CurrentSeason":1,"StratePool":["/Game/VoxelForge/DA_Strate1.DA_Strate1","/Game/VoxelForge/DA_Strate3.DA_Strate3"],"FixedStrates":[],"TotalStrates":2,"InterStrateGapChunks":0,"OriginSpineRadius":28,"bOpenSurfaceEntry":true,"MaxModifications":0,"MaxBrushRadius":30,"MaxTotalVolume":0,"AssetBundleData":{"Bundles":[]},"NativeClass":"/Script/VoxelForge.VoxelSettings"},"strates":[{"index":0,"top_chunk_z":0,"bottom_chunk_z":-15,"height_chunks":16,"asset":{"StrateName":"","StrateDescription":"","StrateHeightInChunks":16,"TransitionType":0,"TransitionBlendChunks":2,"GeneratorType":0,"bEnableWorms":false,"GenerationParams":{"BaseDensity":8,"VerticalScale":0.80000001192092896,"WormFrequency":0.014999999664723873,"WormHorizontalBias":3,"WormThreshold":0.059999998658895493,"WormStrength":10,"WormNetworkRange":64,"RoomSpacing":128,"RoomDensity":0.34999999403953552,"MinRoomRadius":65,"MaxRoomRadius":120,"RoomHeightRatio":0.5,"RoomFloorCutMin":0,"RoomFloorCutMax":0,"FloorReliefStrength":0,"FloorReliefFrequency":1,"RoomMouthRiseStrength":0,"RoomMouthRiseBlendVoxels":12,"RoomShapeVariety":0.5,"OriginRoomRadius":65,"OriginRoomMaxConnections":4,"TunnelMinRadius":8,"TunnelMaxRadius":12,"TunnelDensity":0.40000000596046448,"MaxTunnelLength":360,"TunnelWarpStrength":24,"TunnelHorizontalBias":0.5,"bTunnelsFlowTowardOrigin":true,"TunnelEndpointZOffset":0.25,"bTunnelFloorEnabled":true,"bTunnelFloorTerracingEnabled":true,"TunnelFloorTerraceStepHeight":5,"TunnelFloorMaxLedgeHeight":24,"TunnelFloorGentleSlopeThreshold":88.090850830078125,"TunnelFloorLedgeCountPreference":0,"TunnelFloorMaxLedges":4096,"SDFBlendRadius":25,"CaveWarpStrength":16,"CaveWarpFrequency":0.014999999664723873,"SurfaceRoughness":1,"RoughnessFrequency":0.05000000074505806,"RoughnessNoiseType":2,"DomainWarpStrength":0,"DomainWarpFrequency":0.029999999329447746,"FloorBias":4,"BoundarySealThickness":8,"WaterLevelRelative":0},"SlabParams":{"FloorRelativeHeight":0.25,"CeilingRelativeHeight":0.60000002384185791,"FloorRoughness":4,"FloorRoughnessFrequency":0.039999999105930328,"CeilingRoughness":6,"CeilingRoughnessFrequency":0.039999999105930328,"ColumnDensity":0.079999998211860657,"ColumnMinRadius":8,"ColumnMaxRadius":16,"ColumnSpacing":96,"BoundarySealThickness":4,"BaseDensity":8},"MazeParams":{"CellSize":160,"CorridorRadius":24,"BranchProbability":1,"Verticality":0.20000000298023224,"SurfaceRoughness":2,"BoundarySealThickness":4,"BaseDensity":8},"SurfaceParams":{"BaseGroundRelative":0.25,"ElevationRange":80,"ContinentFrequency":0.0060000000521540642,"MountainStrength":0.5,"MountainFrequency":0.012000000104308128,"DetailFrequency":0.039999999105930328,"SurfaceRoughness":2,"HeightWarpStrength":48,"HeightWarpFrequency":0.0080000003799796104,"ReliefFrequency":0.001500000013038516,"ReliefStrength":0.69999998807907104,"ReliefContrast":1.6000000238418579,"TerraceStrength":0,"TerraceHeight":8,"TerraceHardness":0.5,"LayerLineDepth":0,"LayerLineSpacing":8,"CliffStrength":0,"CliffSlopeThreshold":0.5,"CliffSharpness":2,"CliffSampleDist":2,"OverhangStrength":0,"OverhangReach":16,"OverhangHeight":24,"OverhangFrequency":0.019999999552965164,"OverhangZScale":0.5,"OverhangSlopeThreshold":0.30000001192092896,"WaterLevelRelative":0.27000001072883606,"BeachWidth":12,"CeilingRelative":0.94999998807907104,"CeilingRoughness":6,"CeilingRoughnessFrequency":0.039999999105930328,"CeiLine truncated {"schema_version":1,"source":"VoxelForgeExplore standalone base-world export","voxel_size_cm":25,"seed":0,"open_surface_entry":true,"origin_spine_radius":28,"world_radius_voxels":0,"edge_seal_thickness":128,"total_strates":2,"inter_strate_gap_chunks":0,"clip_radius":6,"max_clip_level":8,"full_res_clip_levels":2,"coarse_tile_cells":16,"render_distance_chunks":1536,"generate_skirts":true,"skirt_cells":4,"lod_octave_drop":1,"strate_content_cut_min_level":9,"settings_asset":{"ViewDistanceXY":32,"ViewDistanceUp":10,"ViewDistanceDown":10,"MaxConcurrentTasks":16,"MaxGenerationWorkerCores":0,"NearTaskReserve":4,"MaxMeshAppliesPerFrame":4,"MaxMeshApplyMilliseconds":2,"MaxUnloadsPerFrame":16,"PawnCollisionRadiusChunks":2,"bClampViewToStrate":true,"StrateViewMarginChunks":6,"StrateContentCutMinLevel":9,"ClipRadius":6,"MaxClipLevel":8,"FullResClipLevels":2,"CoarseTileCells":16,"RenderDistanceChunks":1536,"bGenerateSkirts":true,"SkirtCells":4,"LODOctaveDrop":1,"WorldRadiusVoxels":0,"EdgeSealThickness":128,"DecorationRadiusChunks":12,"DecorationNearRadiusChunks":6,"DecorationRegionSizeCells":8,"DecorationSpacingVoxels":8,"DecorationFarSpacingVoxels":8,"DecorationMarchStepVoxels":4,"DecorationMaxCrossingsPerColumn":4,"DecorationColumnDepthVoxels":320,"MaxDecorationCellsPerFrame":2,"MaxConcurrentDecorationTasks":4,"bEnableDensityVolume":false,"DensityVolumeResolution":256,"DensityVolumeLevels":3,"DensityVolumeFillSlabCells":8,"DensityVolumeMarchSteps":64,"bDensityVolumeGPUUpload":false,"bDebugDrawDensityVolume":false,"DensityVolumeDebugRadiusCells":12,"VoxelMaterial":"/Game/VoxelForge/M_Test1_Inst.M_Test1_Inst","Seed":0,"Season":"","CurrentSeason":1,"StratePool":["/Game/VoxelForge/DA_Strate1.DA_Strate1","/Game/VoxelForge/DA_Strate3.DA_Strate3"],"FixedStrates":[],"TotalStrates":2,"InterStrateGapChunks":0,"OriginSpineRadius":28,"bOpenSurfaceEntry":true,"MaxModifications":0,"MaxBrushRadius":30,"MaxTotalVolume":0,"AssetBundleData":{"Bundles":[]},"NativeClass":"/Script/VoxelForge.VoxelSettings"},"strates":[{"index":0,"top_chunk_z":0,"bottom_chunk_z":-15,"height_chunks":16,"asset":{"StrateName":"","StrateDescription":"","StrateHeightInChunks":16,"TransitionType":0,"TransitionBlendChunks":2,"GeneratorType":0,"bEnableWorms":false,"GenerationParams":{"BaseDensity":8,"VerticalScale":0.800000011920929,"WormFrequency":0.014999999664723873,"WormHorizontalBias":3,"WormThreshold":0.05999999865889549,"WormStrength":10,"WormNetworkRange":64,"RoomSpacing":128,"RoomDensity":0.3499999940395355,"MinRoomRadius":65,"MaxRoomRadius":120,"RoomHeightRatio":0.5,"RoomFloorCutMin":0,"RoomFloorCutMax":0,"FloorReliefStrength":0,"FloorReliefFrequency":1,"RoomMouthRiseStrength":0,"RoomMouthRiseBlendVoxels":12,"RoomShapeVariety":0.5,"OriginRoomRadius":65,"OriginRoomMaxConnections":4,"TunnelMinRadius":8,"TunnelMaxRadius":12,"TunnelDensity":0.4000000059604645,"MaxTunnelLength":360,"TunnelWarpStrength":24,"TunnelHorizontalBias":0.5,"bTunnelsFlowTowardOrigin":true,"TunnelEndpointZOffset":0.25,"bTunnelFloorEnabled":true,"bTunnelFloorTerracingEnabled":true,"TunnelFloorTerraceStepHeight":5,"TunnelFloorMaxLedgeHeight":24,"TunnelFloorGentleSlopeThreshold":88.09085083007812,"TunnelFloorLedgeCountPreference":0,"TunnelFloorMaxLedges":4096,"SDFBlendRadius":25,"CaveWarpStrength":16,"CaveWarpFrequency":0.014999999664723873,"SurfaceRoughness":1,"RoughnessFrequency":0.05000000074505806,"RoughnessNoiseType":2,"DomainWarpStrength":0,"DomainWarpFrequency":0.029999999329447746,"FloorBias":4,"BoundarySealThickness":8,"WaterLevelRelative":0},"SlabParams":{"FloorRelativeHeight":0.25,"CeilingRelativeHeight":0.6000000238418579,"FloorRoughness":4,"FloorRoughnessFrequency":0.03999999910593033,"CeilingRoughness":6,"CeilingRoughnessFrequency":0.03999999910593033,"ColumnDensity":0.07999999821186066,"ColumnMinRadius":8,"ColumnMaxRadius":16,"ColumnSpacing":96,"BoundarySealThickness":4,"BaseDensity":8},"MazeParams":{"CellSize":160,"CorridorRadius":24,"BranchProbability":1,"Verticality":0.20000000298023224,"SurfaceRoughness":2,"BoundarySealThickness":4,"BaseDensity":8},"SurfaceParams":{"BaseGroundRelative":0.25,"ElevationRange":80,"ContinentFrequency":0.006000000052154064,"MountainStrength":0.5,"MountainFrequency":0.012000000104308128,"DetailFrequency":0.03999999910593033,"SurfaceRoughness":2,"HeightWarpStrength":48,"HeightWarpFrequency":0.00800000037997961,"ReliefFrequency":0.001500000013038516,"ReliefStrength":0.699999988079071,"ReliefContrast":1.600000023841858,"TerraceStrength":0,"TerraceHeight":8,"TerraceHardness":0.5,"LayerLineDepth":0,"LayerLineSpacing":8,"CliffStrength":0,"CliffSlopeThreshold":0.5,"CliffSharpness":2,"CliffSampleDist":2,"OverhangStrength":0,"OverhangReach":16,"OverhangHeight":24,"OverhangFrequency":0.019999999552965164,"OverhangZScale":0.5,"OverhangSlopeThreshold":0.30000001192092896,"WaterLevelRelative":0.27000001072883606,"BeachWidth":12,"CeilingRelative":0.949999988079071,"CeilingRoughness":6,"CeilingRoughnessFrequency":0.03999999910593033,"CeilingUndulation":0,"CLine truncated