core: Vertical Shafts on the shared Core cave planner (L139, v237; 15/16 seeds)

Shaft mode reuses PlanResolvedCavernNetworkV1 with a capped full-height cylinder primitive (shapeKind=1), ledges,
access bores and cross links; 11 VerticalShaftParams fields mapped. Basal law: arrival 2-3 to a walkable terminal
landing; drops/climbs stay reporting tiers. Seed 2 fails shell/seams at the 2-3 arrival mouth (3 variants tried,
stopped per AGENTS.md). Whole view faster than assembler/world in 8/8 cells; near-ready slower in 7/8.
Source/: VoxelForgeCoreAdapter.cpp.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-11-01 05:34:00 +01:00
1 parent fa4417aad0
commit 318c5cf4f2
31 files changed
+1719 -106

No files matched your search

+43 -4
View File
@@ -2299,16 +2299,55 @@ results; the code path is not promoted to legacy Maze/VerticalShafts generation.
rooms, or tunnels. The Unreal adapter preserves the established passage planner, then resolves
authored stamps and cavern graph records, then finalizes the layout. Stable names are required and
are scoped by strate in Core; landmarks may target a stamp, room, or tunnel by that stable name.
Core profile heightfields are restricted to SurfaceWorld. Native caves require the TunnelNetwork or
Underwater archetype; native lattice requires Maze or VerticalShafts. Core recipe/version fields
enter the resolved layout identity when selected. An imported manifest cannot be mixed with a
per-strate profiled heightfield.
Core profile heightfields are restricted to SurfaceWorld. Native caves require the TunnelNetwork,
Underwater, or VerticalShafts archetype; native lattice requires Maze or VerticalShafts. Core
recipe/version fields enter the resolved layout identity when selected. An imported manifest cannot
be mixed with a per-strate profiled heightfield.
The checked-out Core API contains stamp vocabularies V1, V2, and V3. Unreal uses V1 for the original
stamp kinds and V3 for monolith/platform motifs, then combines and strictly validates the result.
Research17 names V2, but the source API and motif vocabulary require V3; source was followed.
Research27 water remains out of scope for U1.
### 8.56 Native Vertical Shafts graph field — World Assembler M3 L139
Vertical Shafts uses the same `PlanResolvedCavernNetworkV1` and `Core.Caves.PrimitiveField` as
Tunnel Network. There is no separate shaft planner. The existing room/tunnel shapes do not express
a capped bore spanning the strate height, so the shared cave field gains a shaft-cylinder record
kind; resolved records then add access tunnels, shaft cross-connects, and internal ledges.
`Core.Lattice.PrimitiveField` is not used: its L58 VerticalShafts route read as a corkscrew, so it
does not satisfy this archetype's open-shaft identity.
The basal owner strate has an arrival mouth from passage 2–3 and deliberately has no departure
mouth. Here the primordial law's route is the arrival mouth to a walkable terminal landing above the
bedrock seal. The shared planner extends its walk-only spine to that terminal with the same grade
limit used for a two-mouth cave; it does not invent a second passage. Route landings and connectors
are built as part of the graph. Shaft bores and their ledges are a separate exploration tier: an
open vertical drop or a climb between ledges is not walk-only traversal. Climb and mine remain
reporting tiers; they never relax the walk-only guarantee.
`FVerticalShaftParams` mapping:
| Field | Core behavior |
|---|---|
| `ShaftSpacing` | Planner room spacing, converting owner voxels to integer micrometres. |
| `ShaftDensity` | `roomDensityQ16`; seeds shaft placement, with the route's initial shaft guaranteed when nonzero. |
| `ShaftMinRadius`, `ShaftMaxRadius` | Minimum and maximum shaft radii. |
| `CrossConnectChance` | `tunnelDensityQ16` for cross-shaft links; generic cave branches are disabled in shaft mode. |
| `ConnectorRadius` | Fixed minimum/maximum radius for access and connector bores. |
| `LedgeSpacing`, `LedgeDepth` | Shaft shelf spacing and intrusion depth; zero spacing disables ledges, while zero depth leaves them nonintrusive. |
| `SurfaceRoughness` | Shaft-wall roughness amplitude. Frequency is derived as `max(0.5 m, 4 owner voxels)` because the legacy params have no frequency field. |
| `BoundarySealThickness`, `BaseDensity` | Continue to feed the resolved top and basal boundary bands through `sealAndDensity`, not the planner settings. |
L139 increments `WorldAssemblyEvaluatorVersion` to 237 and extends the versioned cavern graph
vocabulary for shaft cylinders. The fast-tier dispatch report binds S3 to
`Core.Caves.PrimitiveField` / `Caves-native`. The 16-seed composed acceptance gate is not green:
seed 2 fails the passage-shell audit at the incoming mouth. Three geometry variants failed that
same gate, so the remaining issue is recorded for a new design premise rather than another local
patch. L139 does not claim promoted shaft generation; its L80 run is recorded as characterization
of the current worktree. See
[`Standalone/Research/WorldAssemblerM3/Linux139/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux139/REPORT.md).
The shared VoxelForge custom asset version is 2: version 1 records the tunnel-floor slope migration;
version 2 adds load-time conversion for inter-strate gap, strate height, and transition width. The
saved strate-layout width is used, currently 8 m. Old packages are converted in memory and marked
+15 -1
View File
@@ -174,7 +174,7 @@ remains `0`, and lateral regions remain gated off.
| `../../VoxelForgeCore/Private/ModuleEntry/` | Unreal module entry and an isolated `CoreMinimal.h` shim for the actual engine header. |
| `../../Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` | Standard-library-only public Core API. `VOXELFORGECORE_API` exports the API from the Unreal module and is empty for standalone builds. |
| `../../Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` / `../../Standalone/Core/src/ResolvedLayoutInternal.h` | `ResolvedLayoutSnapshotV3`: physical layout-chunk width, versioned micrometre intervals, owned ceiling/content/bedrock bands, honored transition settings, deterministic per-column transition bands, recipe anchors, director-planned passages, canonical JSON/FNV identity, compatibility/strict validators, and negative-control audit. Built once from the director-resolved plan and consumed by bounds planning, Core dispatch, composition, and validation. §8.40–8.42, §8.46. |
| `ResolvedLayoutStratePassageCapabilitiesV1::{hasNativeCaveField,hasNativeLatticeField}`, `IsNativeLatticeFieldStratum`, `HasNativeGraphField` | `Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` / `Standalone/Core/src/ResolvedLayoutInternal.h` | Explicit per-strate native graph capability; `Core.Lattice.PrimitiveField` is restricted to Maze/VerticalShafts recipe version 1. Lattice seeds include world seed, strate, recipe version and stable feature ID; explicit polyline records keep their endpoints and receive conservative bounds. §8.54. |
| `ResolvedLayoutStratePassageCapabilitiesV1::{hasNativeCaveField,hasNativeLatticeField}`, `IsNativeLatticeFieldStratum`, `HasNativeGraphField` | `Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` / `Standalone/Core/src/ResolvedLayoutInternal.h` | Explicit per-strate native graph capability; `Core.Lattice.PrimitiveField` remains Maze/VerticalShafts recipe version 1, and `Core.Caves.PrimitiveField` also accepts VerticalShafts in L139. Lattice seeds include world seed, strate, recipe version and stable feature ID; explicit polyline records keep their endpoints and receive conservative bounds. §8.54, §8.56. |
| `FCavernRuntimeFeature::FSegmentNode`, `ApplyResolvedNativeCavesField`, `ClassifyNativeCaveFieldBox` | `Standalone/Core/src/ShapeWorldInternal.h` | A deterministic nested segment BVH accelerates native graph distance and conservative interval evaluation; the outer graph BVH still supplies tile candidates and protects `isTileShapeFree`. The 0.4 m feature-union width is separate from the floor fillet width. §8.54. |
| `../../Standalone/Core/src/FloatingIslandsInternal.h` | Canonical standalone/Core floating-island planner, geometry-versioned field, manifest serializer, influence bounds, capsule-route checks, and thickness audit. |
| `ApplyOwnerFourStratePlan` | `Source/VoxelForgeEditor/Private/VoxelForgeExploreCommandlet.cpp` | BuildHost-only transient fixed-slot plan from the four staged owner strate assets; leaves the authored settings and strate packages untouched. |
@@ -1352,6 +1352,20 @@ tunnel-slope version in `VoxelStrateDefinition.cpp`, plus the standalone `VoxelF
in `VoxelForgeExploreCommandlet.cpp`. `GetStrateForChunk` still receives layout-chunk Z, not a Core
tile index. See `Standalone/Research/WorldAssemblerM3/Unreal1/REPORT.md` for BuildHost evidence.
### 3.38 Native Vertical Shafts graph field — World Assembler M3 L139
| Symbol | Canonical file | Role |
|---|---|---|
| `ResolvedCavernPlannerSettingsV1::verticalShaftMode`, basal terminal route planning | `Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h`, `Standalone/Core/src/ResolvedLayoutInternal.h` | Extends shared `PlanResolvedCavernNetworkV1` for an arrival-only basal strate; its walk-only spine terminates at a landing above bedrock. Shaft mode disables generic branches and uses dedicated shaft nodes. |
| `ResolvedCavernRoomV1::shapeKind=1`, `ApplyResolvedNativeCavesField` | `Standalone/Core/src/ShapeWorldInternal.h` | Evaluates capped vertical shaft cylinders with shelves alongside shared access and cross-connect tunnels. Uses `Core.Caves.PrimitiveField`, not the L58 lattice spiral. |
| VerticalShafts settings mapping | `Standalone/Source/WorldAssemblyPrototype.h` | Maps the 11 legacy params into shared spacing, room density/radii, bore radius, cross-connect density, ledges, roughness, and existing boundary seal/density bands. |
| `IsNativeCaveFieldStratum`, S3 binding and `FCavesFieldAdapter` | `Standalone/Core/src/ShapeWorldInternal.h`, `Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp` | Reports `VerticalShafts` as `Core.Caves.PrimitiveField` v1 / `Caves-native`; leaves the legacy generator archetype identity intact. |
| `RunL139SeedSweep.py`, `RunL139Determinism.py`, `RunL139Captures.py`, `RunL139WholeLayerCapture.py`, `RunL139FinalTiming.py` | `Standalone/Research/WorldAssemblerM3/Linux139/` | Reproducible composed 16-seed audit, Clang/GCC W1/W8/W12 identity check, five before/after eye-height views plus density-cutaway and plan-projection outside views, and L80 characterization. The seed-2 passage-shell failure blocks promotion. |
See ARCHITECTURE §8.56 and
[`Standalone/Research/WorldAssemblerM3/Linux139/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux139/REPORT.md)
for the planner semantics, field mapping, audit result, captures, and outstanding shell failure.
## 4. The density pipeline (most-edited hot path)
### 4.1 `GetDensityWithParams` (TunnelNetwork) — VoxelGenerator.cpp:3749
+11 -10
View File
@@ -1,18 +1,18 @@
# VoxelForge current state
As of 2026-10-31. L131 is on `assembler/world` as `8107196`; this L132 worktree is on `wip/l132-dispatch-truth`, starting at `ebae71b`. Current-state claims below point to their code or measured report.
As of 2026-11-01. L139 is being developed on `wip/l134-cave-planner`. Current-state claims below point to their code or measured report; L139's Vertical Shafts implementation has not passed composed acceptance and is not promoted.
## What ships today
- **Core:** evaluator version **227**. Unreal's `AVoxelWorld::bUseVoxelForgeCore` is opt-in and defaults to `false`; the legacy generator remains the default runtime path. [Core version](Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h), [runtime switch](Source/VoxelForge/Public/VoxelWorld.h), [L130 report](Standalone/Research/WorldAssemblerM3/Linux130/REPORT.md).
- **Verified owner4 generator dispatch (L132):** SW binds `SurfaceWorld-native` because the owner export contains a matching `SurfaceWorld.Heightfield2D` v2 plan. FI binds `compiled-analytic/FloatingIslands` because its `FloatingIslands.ClusterField` v0 layout has no matching native island plan. TN and VS bind `compiled-analytic/TunnelNetwork` and `compiled-analytic/VerticalShafts`; their legacy recipe IDs provide neither a `NativeCaves`/`NativeLattice` graph recipe nor native field capability. The L132 fast-tier report prints the binding and generated tile-center share at each owner strate. [Dispatch](Standalone/Core/src/ShapeWorldInternal.h), [adapter recipe checks](Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h), [L132 report](Standalone/Research/WorldAssemblerM3/Linux132/REPORT.md).
- **Core:** evaluator version **237** in the L139 worktree. Unreal's `AVoxelWorld::bUseVoxelForgeCore` remains opt-in and defaults to `false`; the legacy generator remains the default runtime path. [Core version](Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h), [runtime switch](Source/VoxelForge/Public/VoxelWorld.h), [L139 report](Standalone/Research/WorldAssemblerM3/Linux139/REPORT.md).
- **Verified owner4 generator dispatch:** S0 SurfaceWorld binds `SurfaceWorld-native`; S1 FloatingIslands remains `compiled-analytic/FloatingIslands`; S2 TunnelNetwork binds `Core.Caves.PrimitiveField`; and the L139 fast-tier report binds S3 VerticalShafts to `Core.Caves.PrimitiveField` / `Caves-native`. This proves dispatch, not the full shaft gate: the composed 16-seed sweep fails the passage-shell audit at seed 2. [Dispatch](Standalone/Core/src/ShapeWorldInternal.h), [adapter recipe checks](Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h), [L139 report](Standalone/Research/WorldAssemblerM3/Linux139/REPORT.md).
- **Passages:** continuous smooth swept tubes with a continuous floor profile. L125 stamps conservative route slabs and outside sentinels into the tile cache; exact route geometry is still evaluated. All 21,425 route samples passed the open-air walk check; the nine-station floor probe's maximum root offset was 7.884 mm. L130 sets the shared influence reach to **1.0533 m** (0.75 m shell + 0.21 m relief + 0.50 m smooth-max support / 6 + 0.01 m margin), converted once from metres to Core voxels for the proof, feature, fallback, and off-grid bounds. [L125](Standalone/Research/WorldAssemblerM3/Linux125/REPORT.md), [L130](Standalone/Research/WorldAssemblerM3/Linux130/REPORT.md).
- **Streaming:** arrival uses a bounded startup apply burst: `voxel.StreamingStartupApplyBoost=1` by default, up to 24 visible meshes or 14 ms per frame for at most 30 seconds, until the initial desired set and walkable ring are ready. `voxel.StreamingFarApplySupplement=1` is also default-on: after the normal visible apply cap, at most one far result applies when under half the apply-time budget; it is inactive during the startup burst. Dynamic near reserve defaults on; coarse-first remains off; the legacy pending-result cap defaults off. The far-apply lane is Unreal-only. [CVar source](Source/VoxelForge/Private/VoxelWorld.cpp), [L121 report](Standalone/Research/WorldAssemblerM3/Linux121/REPORT.md), [architecture](ARCHITECTURE.md).
- **Determinism:** proven on Linux with Clang and GCC at W1/W8/W12. L131's unchanged-output comparison covers owner4's SW/FI/TN/VS views in 24 compiler/worker cases; the canonical owner4/owner2 recipe matrix also passes. Windows determinism is unverified. [L131 checks](Standalone/Research/WorldAssemblerM3/Linux131/REPORT.md), [v227 canonical hashes](Standalone/Tools/verify/baselines/owner-hash/canonical/).
- **Determinism:** the L139 Vertical Shafts plan and field match across Clang/GCC at W1/W8/W12 (six cases); L131's unchanged-output comparison still covers the earlier owner4 SW/FI/TN/VS views and canonical owner4/owner2 recipe matrix. Windows determinism is unverified. [L139 checks](Standalone/Research/WorldAssemblerM3/Linux139/REPORT.md), [L131 checks](Standalone/Research/WorldAssemblerM3/Linux131/REPORT.md), [canonical hashes](Standalone/Tools/verify/baselines/owner-hash/canonical/).
## Current performance references
Headless medians in seconds, five interleaved L80-style measurements per version in each cell. Near-ready means the complete LOD0–2 set; whole-view is the full requested view. Values below are the edited L131 binary; the paired pre-cleanup comparison is in the [L131 report](Standalone/Research/WorldAssemblerM3/Linux131/REPORT.md) and [timing summary](Saved/Linux/l131/timing/summary.json).
Headless medians in seconds, five interleaved L80-style measurements per version in each cell. Near-ready means the complete LOD0–2 set; whole-view is the full requested view. Values below are the latest complete L131 measurements; L139 characterization is in the [L139 report](Standalone/Research/WorldAssemblerM3/Linux139/REPORT.md) and [timing summary](Saved/Linux/l139/timing/summary.json). The paired pre-cleanup comparison is in the [L131 report](Standalone/Research/WorldAssemblerM3/Linux131/REPORT.md) and [timing summary](Saved/Linux/l131/timing/summary.json).
| Stratum | Near-ready W8 / W12 | Whole-view W8 / W12 |
|---|---:|---:|
@@ -28,17 +28,18 @@ The latest recorded Linux Unreal start-to-LOD0–2 values are L127's one run per
## Open gaps
- Multiplayer world lifecycle is unfinished: seed/layout replication, authoritative modification RPCs, and serialized diff replay for late join are still open. See [ARCHITECTURE §9.5 and §9.7](ARCHITECTURE.md).
- L139 Vertical Shafts is not accepted: the composed audit's seed 2 fails the incoming passage shell after three geometry variants failed that same gate. L80 characterization and the Linux host build passed, but neither changes the composed acceptance result. See [L139 report](Standalone/Research/WorldAssemblerM3/Linux139/REPORT.md).
- Determinism has not been verified on Windows. Linux Clang/GCC agreement does not establish MSVC agreement.
- NullRHI measures residency and apply timing, not the first presented pixel or rendered enclosure. L127 also found four low-contrast views among 23 otherwise verified captures. [L121](Standalone/Research/WorldAssemblerM3/Linux121/REPORT.md), [L127](Standalone/Research/WorldAssemblerM3/Linux127/REPORT.md).
- UE startup/movement numbers above predate L130's v227 reach change; there is no L130/L131 UE movement comparison.
## How to verify
- Build standalone Clang or GCC with [`Standalone/BuildLinux.sh`](Standalone/BuildLinux.sh), for example `bash Standalone/BuildLinux.sh --compiler=clang++-19 --variant=l132-clang --fp-contract=off --jobs=4`. The script pins CPUs 0–11 and applies `prlimit --as=6442450944` itself.
- Fast tier: `python3 Standalone/Tools/verify/verify.py fast --out Saved/Linux/l132 --overall-timeout 1740`. It includes the L132 four-pose owner density-dispatch report; per-run logs and the summary stay under `Saved/Linux/l132`.
- Canonical output baselines are the v227 files in `Standalone/Tools/verify/baselines/owner-hash/canonical/`. L131 also provides reproducible pre-cleanup identity matrices for owner4 tile hashes and owner4/owner2 canonical hashes under `Standalone/Research/WorldAssemblerM3/Linux131/`.
- Timing follows L80: quiet machine, no overlapping build, one warm-up per version, five interleaved runs per stratum and worker count, W8/W12. Each headless run uses `taskset -c 0-11`, `prlimit --as=6442450944`, and a 1,740 s timeout. See [`RunL131Timing.py`](Standalone/Research/WorldAssemblerM3/Linux131/RunL131Timing.py).
- Linux host build: `/home/jahni/UnrealEngine/Engine/Build/BatchFiles/Linux/Build.sh VFHostEditor Linux Development -Project=/home/jahni/VFHost/VFHost.uproject -NoUBA -MaxParallelActions=4 -Log=/home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l131/ubt.log`. Run Unreal without `prlimit`.
- Build standalone Clang or GCC with [`Standalone/BuildLinux.sh`](Standalone/BuildLinux.sh), for example `bash Standalone/BuildLinux.sh --compiler=clang++-19 --variant=l139-clang --fp-contract=off --jobs=4`. The script pins CPUs 0–11 and applies `prlimit --as=6442450944` itself.
- Fast tier: `python3 Standalone/Tools/verify/verify.py fast --out Saved/Linux/l139 --overall-timeout 1740`. It includes the four-strate owner density-dispatch report; per-run logs and the summary stay under `Saved/Linux/l139`.
- The L139 v237 canonical Surface World and Floating Islands owner-hash baselines are in `Standalone/Tools/verify/baselines/owner-hash/canonical/`; both values match v236. L131 also provides reproducible pre-cleanup identity matrices for owner4 tile hashes and owner4/owner2 canonical hashes under `Standalone/Research/WorldAssemblerM3/Linux131/`.
- Timing follows L80: quiet machine, no overlapping build, one warm-up per version, five interleaved runs per stratum and worker count, W8/W12. The L139 run recorded the L137 A/A noise floor of ±1%. Each headless run uses `taskset -c 0-11`, `prlimit --as=6442450944`, and a 1,740 s timeout. See [`RunL139FinalTiming.py`](Standalone/Research/WorldAssemblerM3/Linux139/RunL139FinalTiming.py).
- Linux host build: `/home/jahni/UnrealEngine/Engine/Build/BatchFiles/Linux/Build.sh VFHostEditor Linux Development -Project=/home/jahni/VFHost/VFHost.uproject -NoUBA -MaxParallelActions=4 -Log=/home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l139/ubt.log`. Run Unreal without `prlimit`.
## Closed experiments
@@ -144,13 +144,15 @@ bool ConfigureCoreGeometryStratum(const FStrateSlot& Slot, ECaveGeneratorType Ge
return true;
case EVoxelForgeCoreGeneratorRecipe::NativeCaves:
if (GeneratorType != ECaveGeneratorType::TunnelNetwork
&& GeneratorType != ECaveGeneratorType::Underwater)
&& GeneratorType != ECaveGeneratorType::Underwater
&& GeneratorType != ECaveGeneratorType::VerticalShafts)
{
OutError = FString::Printf(TEXT("Strate %d selects NativeCaves but its generator archetype is not TunnelNetwork/Underwater."),
OutError = FString::Printf(TEXT("Strate %d selects NativeCaves but its generator archetype is not TunnelNetwork/Underwater/VerticalShafts."),
Slot.StrateIndex);
return false;
}
Item.generatorKind = "Caves";
Item.generatorKind = GeneratorType == ECaveGeneratorType::VerticalShafts
? "VerticalShafts" : "Caves";
Item.recipeId = "Core.Caves.PrimitiveField";
Item.recipeVersion = 1;
Item.boundDispatchPath = "Core native cave graph uses resolved room and tunnel records";
@@ -510,7 +510,8 @@ namespace VoxelForgeCore
// preserves open cave lanes through passage shell overlaps; 236 binds passage mouths to
// the planned graph route, preserves its walk corridor through shell overlaps, and keeps
// boundary-blended host rock from widening the authored passage wall.
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 236;
// v237 adds the shared Core cave-graph shaft primitive and basal terminal route.
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 237;
enum class FieldChannel : std::uint32_t
{
@@ -906,6 +907,7 @@ namespace VoxelForgeCore
inline constexpr std::uint32_t ResolvedLayoutStampVocabularyVersionV3 = 3;
inline constexpr std::uint32_t ResolvedLayoutCavernGraphVocabularyVersionV1 = 1;
inline constexpr std::uint32_t ResolvedLayoutCavernGraphVocabularyVersionV2 = 2;
inline constexpr std::uint32_t ResolvedLayoutCavernGraphVocabularyVersionV3 = 3;
inline constexpr std::uint32_t ResolvedLayoutOreVeinVocabularyVersionV1 = 1;
enum class ResolvedLayoutValidationModeV3 : std::uint8_t { compatibility, strict };
@@ -967,6 +969,10 @@ namespace VoxelForgeCore
{
std::string stableId;
std::int32_t strateIndex = -1;
// 0 = floor-backed room; 1 = a true vertical cylindrical shaft. The
// shaft form is part of the shared cave graph so other recipes can reuse
// the same graph, indexing, interval and density machinery.
std::uint8_t shapeKind = 0;
std::array<std::int64_t, 3> centerMicrometres{};
std::array<std::int64_t, 3> halfExtentsMicrometres{};
std::int64_t floorMicrometres = 0;
@@ -982,6 +988,11 @@ namespace VoxelForgeCore
std::int64_t irregularityAmplitudeMicrometres = 0;
std::int64_t irregularityWavelengthMicrometres = 0;
std::uint64_t irregularitySeed = 0;
// Used only by shapeKind=1. floorMicrometres is the shaft top; the
// bottom is center.z - halfExtents.z. Ledges face one cardinal side.
std::int64_t ledgeSpacingMicrometres = 0;
std::int64_t ledgeDepthMicrometres = 0;
std::uint8_t ledgeSide = 0;
std::array<std::int64_t, 3> boundsMinimumMicrometres{};
std::array<std::int64_t, 3> boundsMaximumMicrometres{};
};
@@ -1044,6 +1055,12 @@ namespace VoxelForgeCore
std::int32_t tunnelHorizontalBiasQ16 = 32768;
std::int32_t originRoomMaximumConnections = 4;
bool tunnelsFlowTowardOrigin = true;
// Vertical Shafts uses this same planner and graph field. It binds its
// incoming passage and resolves a bedrock terminal when no departure
// mouth exists, then adds shaft records beside the walk-only backbone.
bool verticalShaftMode = false;
std::int64_t ledgeSpacingMicrometres = 8000000;
std::int64_t ledgeDepthMicrometres = 1000000;
};
// Immutable material-only deposits. The capsule and its conservative bounds are in
@@ -1150,7 +1167,8 @@ namespace VoxelForgeCore
inline bool IsNativeCaveFieldStratum(const ResolvedLayoutStratumV2& stratum) noexcept
{
return (stratum.generatorKind=="Caves"||stratum.generatorKind=="TunnelNetwork")
return (stratum.generatorKind=="Caves"||stratum.generatorKind=="TunnelNetwork"
||stratum.generatorKind=="VerticalShafts")
&&stratum.recipeId=="Core.Caves.PrimitiveField"
&&stratum.recipeVersion==1;
}
+263 -31
View File
@@ -626,8 +626,12 @@ namespace
<<",\"floor_profile_distance_um\":"<<room.floorProfileDistanceMicrometres
<<",\"floor_gradient_q16\":["<<room.floorGradientQ16[0]<<','
<<room.floorGradientQ16[1]<<']'
<<",\"smooth_width_um\":"<<room.smoothWidthMicrometres
;
<<",\"smooth_width_um\":"<<room.smoothWidthMicrometres;
if(room.shapeKind!=0)
out<<",\"shape_kind\":"<<static_cast<unsigned>(room.shapeKind)
<<",\"ledge_spacing_um\":"<<room.ledgeSpacingMicrometres
<<",\"ledge_depth_um\":"<<room.ledgeDepthMicrometres
<<",\"ledge_side\":"<<static_cast<unsigned>(room.ledgeSide);
if(room.unionWidthMicrometres>0)
out<<",\"union_width_um\":"<<room.unionWidthMicrometres;
out<<",\"irregularity_amplitude_um\":"<<room.irregularityAmplitudeMicrometres
@@ -1294,7 +1298,8 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
"cavern graph vocabulary version is set without graph records");
}
else if(cavernGraphVocabularyVersion!=ResolvedLayoutCavernGraphVocabularyVersionV1
&&cavernGraphVocabularyVersion!=ResolvedLayoutCavernGraphVocabularyVersionV2)
&&cavernGraphVocabularyVersion!=ResolvedLayoutCavernGraphVocabularyVersionV2
&&cavernGraphVocabularyVersion!=ResolvedLayoutCavernGraphVocabularyVersionV3)
add("cavern_graph_vocabulary_version",
"cavern graph records require a supported vocabulary version");
if(cavernGraphVocabularyVersion!=0)
@@ -1327,6 +1332,33 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
||(room.irregularityAmplitudeMicrometres>0
&&room.irregularityWavelengthMicrometres<=0))
add("cavern_room_parameters",room.stableId+" has invalid or unbounded parameters");
const auto* roomStratum=findStratum(room.strateIndex);
if(room.shapeKind>1)
add("cavern_room_shape",room.stableId+" has an unknown shape kind");
if(room.shapeKind==1)
{
const bool verticalShaftStratum=roomStratum
&&roomStratum->generatorKind=="VerticalShafts"
&&IsNativeCaveFieldStratum(*roomStratum);
if(cavernGraphVocabularyVersion<ResolvedLayoutCavernGraphVocabularyVersionV3
||!verticalShaftStratum
||room.halfExtentsMicrometres[0]!=room.halfExtentsMicrometres[1]
||room.ledgeSpacingMicrometres<0
||room.ledgeSpacingMicrometres>100000000
||room.ledgeDepthMicrometres<0
||room.ledgeDepthMicrometres>=room.halfExtentsMicrometres[0]
||room.ledgeSide>3
||room.floorMicrometres
!=room.centerMicrometres[2]+room.halfExtentsMicrometres[2]
||!room.floorProfileStableId.empty()
||room.floorGradientQ16[0]!=0||room.floorGradientQ16[1]!=0)
add("cavern_shaft_parameters",room.stableId+
" has invalid native shaft bounds, ledges, or recipe binding");
}
else if(room.ledgeSpacingMicrometres!=0||room.ledgeDepthMicrometres!=0
||room.ledgeSide!=0)
add("cavern_room_shape",room.stableId+
" carries shaft parameters on an ordinary room");
const std::int64_t gradientSquared=static_cast<std::int64_t>(room.floorGradientQ16[0])
*room.floorGradientQ16[0]+static_cast<std::int64_t>(room.floorGradientQ16[1])
*room.floorGradientQ16[1];
@@ -1415,11 +1447,37 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
if(room!=cavernRooms.end())
{
const auto& endpoint=edge.controlPointsMicrometres.back();
bool validRoomEndpoint=false;
if(room->shapeKind==1)
{
const std::int64_t shaftBottom=room->centerMicrometres[2]
-room->halfExtentsMicrometres[2];
const std::int64_t shaftTop=room->centerMicrometres[2]
+room->halfExtentsMicrometres[2];
const bool insideShaft=endpoint[2]>=shaftBottom
&&endpoint[2]<=shaftTop;
bool onLanding=room->ledgeSpacingMicrometres==0;
if(room->ledgeSpacingMicrometres>0)
{
const std::int64_t ledgeIndex=(shaftTop-endpoint[2]
+room->ledgeSpacingMicrometres/2)
/room->ledgeSpacingMicrometres;
const std::int64_t ledgeZ=shaftTop
-ledgeIndex*room->ledgeSpacingMicrometres;
const std::int64_t maximumLedge=(shaftTop-shaftBottom-1000000)
/room->ledgeSpacingMicrometres;
onLanding=ledgeIndex>=1&&ledgeIndex<=maximumLedge
&&std::abs(endpoint[2]-ledgeZ)<=500000;
}
validRoomEndpoint=insideShaft&&onLanding;
}
else validRoomEndpoint=
std::abs(endpoint[2]-room->floorMicrometres)<=500000;
if(endpoint[0]<room->centerMicrometres[0]-room->halfExtentsMicrometres[0]
||endpoint[0]>room->centerMicrometres[0]+room->halfExtentsMicrometres[0]
||endpoint[1]<room->centerMicrometres[1]-room->halfExtentsMicrometres[1]
||endpoint[1]>room->centerMicrometres[1]+room->halfExtentsMicrometres[1]
||std::abs(endpoint[2]-room->floorMicrometres)>500000)
||!validRoomEndpoint)
{
add("cavern_edge_portal",edge.stableId+" endpoint_um=("
+std::to_string(endpoint[0])+","+std::to_string(endpoint[1])+","+
@@ -1733,6 +1791,10 @@ std::optional<ResolvedLayoutSnapshotV3> ResolvedLayoutSnapshotV3::FromJson(
room.irregularityAmplitudeMicrometres=LayoutInt(v,"irregularity_amplitude_um");
room.irregularityWavelengthMicrometres=LayoutInt(v,"irregularity_wavelength_um");
room.irregularitySeed=static_cast<std::uint64_t>(LayoutInt(v,"irregularity_seed"));
room.shapeKind=static_cast<std::uint8_t>(LayoutInt(v,"shape_kind"));
room.ledgeSpacingMicrometres=LayoutInt(v,"ledge_spacing_um");
room.ledgeDepthMicrometres=LayoutInt(v,"ledge_depth_um");
room.ledgeSide=static_cast<std::uint8_t>(LayoutInt(v,"ledge_side"));
s.cavernRooms.push_back(std::move(room));
}
for(const auto& v:root.Get("cavern_edges").AsArray())
@@ -2088,7 +2150,11 @@ bool ResolveResolvedCavernGraphV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
if(!candidate.finalize(error))return false;
layout=std::move(candidate);return true;
}
candidate.cavernGraphVocabularyVersion=ResolvedLayoutCavernGraphVocabularyVersionV2;
const bool hasVerticalShaft=std::any_of(rooms.begin(),rooms.end(),
[](const auto& room){return room.shapeKind==1;});
candidate.cavernGraphVocabularyVersion=hasVerticalShaft
?ResolvedLayoutCavernGraphVocabularyVersionV3
:ResolvedLayoutCavernGraphVocabularyVersionV2;
candidate.cavernRooms.assign(rooms.begin(),rooms.end());
candidate.cavernEdges.assign(edges.begin(),edges.end());
const auto isNativeCavesStrate=[&candidate](std::int32_t strateIndex)
@@ -2268,7 +2334,11 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
||settings.irregularityAmplitudeMicrometres<0
||settings.irregularityAmplitudeMicrometres>10000000
||(settings.irregularityAmplitudeMicrometres>0
&&settings.irregularityWavelengthMicrometres<=0))
&&settings.irregularityWavelengthMicrometres<=0)
||(settings.ledgeSpacingMicrometres<0
||settings.ledgeSpacingMicrometres>100000000)
||settings.ledgeDepthMicrometres<0
||settings.ledgeDepthMicrometres>5000000)
{error="cavern planner settings are outside the bounded room, tunnel or relief ranges";return false;}
const ResolvedLayoutPassageV1* arrivalPassage=nullptr;
@@ -2282,12 +2352,17 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
&&(!departurePassage||passage.stableId<departurePassage->stableId))
departurePassage=&passage;
}
if(!arrivalPassage||!departurePassage)
{error="cavern planner needs an incoming lower mouth and an outgoing upper mouth in the target strate";return false;}
const bool terminalShaftRoute=settings.verticalShaftMode
&&stratum->generatorKind=="VerticalShafts"&&!departurePassage;
if(!arrivalPassage||(!departurePassage&&!terminalShaftRoute)
||(settings.verticalShaftMode&&!terminalShaftRoute))
{error="cavern planner needs an incoming mouth and either an outgoing mouth or a Vertical Shafts basal terminal";return false;}
if(layout.coreVoxelSizeMicrometres==0)
{error="cavern planner requires a nonzero Core voxel size";return false;}
using FPoint=std::array<std::int64_t,3>;
const std::string routePrefix=(terminalShaftRoute?"vertical-shafts/strate-"
:"tunnel-network/strate-")+std::to_string(strateIndex)+"/";
constexpr std::int64_t MicrometresPerMetre=1000000;
constexpr std::int64_t MinimumBoreRadius=1750000;
constexpr std::int64_t MinimumBoreHeight=2200000;
@@ -2490,19 +2565,47 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
std::array<std::int64_t,2> arrivalTangentQ16{},departureTangentQ16{};
std::int64_t arrivalFloorSlopeQ16=0,departureFloorSlopeQ16=0;
if(!mouthInnerPoint(*arrivalPassage,true,routeStart,arrivalTangentQ16,
arrivalFloorSlopeQ16)
||!mouthInnerPoint(*departurePassage,false,routeEnd,departureTangentQ16,
arrivalFloorSlopeQ16))
{error="cavern passage mouth is missing a usable horizontal route tangent";return false;}
if(!terminalShaftRoute
&&!mouthInnerPoint(*departurePassage,false,routeEnd,departureTangentQ16,
departureFloorSlopeQ16))
{error="cavern passage mouth is missing a usable horizontal route tangent";return false;}
if(terminalShaftRoute)
{
departureTangentQ16=arrivalTangentQ16;
routeEnd[2]=stratum->content.bottomMicrometres+3000000;
departureFloorSlopeQ16=0;
}
const std::int64_t dx=routeEnd[0]-routeStart[0];
const std::int64_t dy=routeEnd[1]-routeStart[1];
std::int64_t dx=routeEnd[0]-routeStart[0];
std::int64_t dy=routeEnd[1]-routeStart[1];
const std::int64_t dz=routeEnd[2]-routeStart[2];
const std::int64_t horizontalBias=std::clamp<std::int64_t>(
settings.tunnelHorizontalBiasQ16,0,65536);
const std::int64_t planGradeDenominator=MaximumWalkGradeDenominator
+horizontalBias/32768;
const std::int64_t requiredLength=(std::abs(dz)*planGradeDenominator*130)/100+1000;
if(terminalShaftRoute)
{
// The basal stratum has no next mouth by design. The walk-only spine starts
// at its incoming passage and ends on a supported landing above bedrock.
// Optional open shafts are side features with separate climb/fall reporting.
// Normalize by the truncated Q16 tangent magnitude: dividing by 65536
// shortens non-cardinal vectors enough to miss the strict grade-run bound.
const std::uint64_t tangentSquared=static_cast<std::uint64_t>(
departureTangentQ16[0]*departureTangentQ16[0]
+departureTangentQ16[1]*departureTangentQ16[1]);
const std::int64_t tangentMagnitudeQ16=static_cast<std::int64_t>(
integerSqrt(tangentSquared));
if(tangentMagnitudeQ16<=0)
{error="cavern planner terminal tangent has zero horizontal length";return false;}
const std::int64_t terminalProjectionLength=requiredLength+1000;
for(std::size_t axis=0;axis<2;++axis)
routeEnd[axis]=routeStart[axis]
+departureTangentQ16[axis]*terminalProjectionLength/tangentMagnitudeQ16;
dx=routeEnd[0]-routeStart[0];dy=routeEnd[1]-routeStart[1];
}
std::int64_t pathAxisX=dx,pathAxisY=dy;
FPoint routeLeadStart=routeStart,routeLeadEnd=routeEnd;
for(std::size_t axis=0;axis<2;++axis)
@@ -2648,7 +2751,7 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
}
};
appendPassageClearanceSpans(*arrivalPassage,true);
appendPassageClearanceSpans(*departurePassage,false);
if(departurePassage)appendPassageClearanceSpans(*departurePassage,false);
const auto segmentDistanceMicrometres=[](const std::array<double,3>& p0,
const std::array<double,3>& p1,const std::array<double,3>& q0,
const std::array<double,3>& q1)
@@ -2804,8 +2907,7 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
cumulative[i]=cumulative[i-1]+distance2d(routePoints[i-1],routePoints[i]);
if(cumulative.back()!=totalRouteLength)
{error="cavern planner route length accounting failed";return false;}
const std::string backboneFloorProfileId="tunnel-network/strate-"
+std::to_string(strateIndex)+"/backbone-floor";
const std::string backboneFloorProfileId=routePrefix+"backbone-floor";
constexpr std::int64_t ProfileOne=std::int64_t{1}<<30;
const auto roundedDivide=[](std::int64_t numerator,std::int64_t denominator)
{
@@ -3008,7 +3110,7 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
std::int64_t radiusMicrometres=0;
bool destination=false;
};
std::vector<FRoomNode> nodes;nodes.reserve(nodeDistances.size());
std::vector<FRoomNode> nodes;nodes.reserve(nodeDistances.size()+1);
const std::int64_t floorCutMinimum=clampQ16(settings.roomFloorCutMinimumQ16);
const std::int64_t floorCutMaximum=std::max(floorCutMinimum,
clampQ16(settings.roomFloorCutMaximumQ16));
@@ -3025,15 +3127,23 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
node.floorProfileStableId=backboneFloorProfileId;
node.floorProfileDistanceMicrometres=distance;
node.floorGradientQ16=routeFloorGradientAt(distance);
node.destination=distance==mainRouteMidpoint;
node.id="tunnel-network/strate-"+std::to_string(strateIndex)+"/room-"
+std::to_string(nodeIndex);
node.destination=!terminalShaftRoute&&distance==mainRouteMidpoint;
node.id=routePrefix+"room-"+std::to_string(nodeIndex);
const std::uint64_t hash=LayoutStampSeed(seed,strateIndex,node.id);
node.radiusMicrometres=node.destination?settings.destinationRadiusMicrometres
:settings.minimumRoomRadiusMicrometres
+static_cast<std::int64_t>(hash%static_cast<std::uint64_t>(baseRoomSpan+1));
nodes.push_back(std::move(node));
}
if(terminalShaftRoute)
{
FRoomNode terminal;terminal.id=routePrefix+"basal-landing";
terminal.point=routeEnd;terminal.floorProfileStableId=backboneFloorProfileId;
terminal.floorProfileDistanceMicrometres=totalRouteLength;
terminal.floorGradientQ16=routeFloorGradientAt(totalRouteLength);
terminal.radiusMicrometres=settings.destinationRadiusMicrometres;
terminal.destination=true;nodes.push_back(std::move(terminal));
}
const auto makeCompound=[&](const FRoomNode& node,bool destination,
std::vector<ResolvedCavernRoomV1>& outRooms)
{
@@ -3154,6 +3264,130 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
}
return edge;
};
struct FPlannedShaftLanding
{
FRoomNode node;
FPoint point{};
};
std::vector<FPlannedShaftLanding> shaftLandings;
std::size_t plannedShaftCount=0;
if(terminalShaftRoute&&settings.roomDensityQ16>0)
{
const std::int64_t ledgeSpacing=std::max<std::int64_t>(0,
settings.ledgeSpacingMicrometres);
const std::int64_t ledgeDepth=std::clamp<std::int64_t>(
settings.ledgeDepthMicrometres,0,5000000);
bool emittedShaft=false;
const std::int64_t routeLengthXY=std::max<std::int64_t>(1,
static_cast<std::int64_t>(integerSqrt(static_cast<std::uint64_t>(
pathAxisX*pathAxisX+pathAxisY*pathAxisY))));
const std::int64_t sideX=-pathAxisY*65536/routeLengthXY;
const std::int64_t sideY=pathAxisX*65536/routeLengthXY;
const auto selectedRadius=[&](const std::string& stableId)
{
const std::int64_t span=settings.maximumRoomRadiusMicrometres
-settings.minimumRoomRadiusMicrometres;
return settings.minimumRoomRadiusMicrometres+(span==0?0:
static_cast<std::int64_t>(LayoutStampSeed(seed,strateIndex,stableId)
%static_cast<std::uint64_t>(span+1)));
};
for(std::size_t nodeIndex=1;nodeIndex+1<nodes.size()&&rooms.size()<2048;
++nodeIndex)
{
const auto& parent=nodes[nodeIndex];
const std::uint64_t hash=LayoutStampSeed(seed,strateIndex,
parent.id+"/shaft");
if(emittedShaft&&static_cast<std::int64_t>(hash&0xffffu)
>=settings.roomDensityQ16)continue;
const std::int64_t radius=selectedRadius(parent.id+"/shaft-radius");
const std::int64_t top=parent.point[2]
+(ledgeSpacing>0?ledgeSpacing:1000000);
const std::int64_t minimumTop=stratum->content.bottomMicrometres+4000000;
if(top>stratum->content.topMicrometres-1000000
||top<minimumTop)continue;
// Shaft length has no independent owner parameter: use as much content
// as available, with the graph's 200 m half-extent bound as a hard cap.
const std::int64_t bottom=std::max(
stratum->content.bottomMicrometres+1000000,top-200000000);
if(ledgeSpacing>0&&top-bottom<ledgeSpacing+2000000)continue;
const bool positiveSide=(hash&0x10000u)!=0;
const std::int64_t sideXSign=(sideX>=0?1:-1)*(positiveSide?1:-1);
const std::int64_t sideYSign=(sideY>=0?1:-1)*(positiveSide?1:-1);
const bool useX=std::abs(sideX)>=std::abs(sideY);
const std::uint8_t outwardSide=useX
?static_cast<std::uint8_t>(sideXSign>0?0:2)
:static_cast<std::uint8_t>(sideYSign>0?1:3);
const std::array<std::array<std::int64_t,2>,4> cardinal={{{1,0},{0,1},
{-1,0},{0,-1}}};
const auto outward=cardinal[outwardSide];
const std::int64_t offset=std::max<std::int64_t>(
parent.radiusMicrometres+radius+3000000,
settings.roomSpacingMicrometres/2);
FPlannedShaftLanding landing;landing.node.id=routePrefix+"shaft-"
+std::to_string(shaftLandings.size());
landing.node.point={parent.point[0]+outward[0]*offset,
parent.point[1]+outward[1]*offset,parent.point[2]};
landing.node.radiusMicrometres=radius;
const std::int64_t landingZ=top-(ledgeSpacing>0?ledgeSpacing:1000000);
const std::array<std::int64_t,2> toParent={-outward[0],-outward[1]};
const std::uint8_t ledgeSide=static_cast<std::uint8_t>(
(outwardSide+2)%4);
ResolvedCavernRoomV1 shaft;shaft.stableId=landing.node.id+"/primitive-0";
shaft.strateIndex=strateIndex;shaft.shapeKind=1;
shaft.centerMicrometres={landing.node.point[0],landing.node.point[1],
(top+bottom)/2};
shaft.halfExtentsMicrometres={radius,radius,(top-bottom)/2};
shaft.floorMicrometres=top;shaft.smoothWidthMicrometres=smooth;
shaft.ledgeSpacingMicrometres=ledgeSpacing;
shaft.ledgeDepthMicrometres=ledgeDepth;shaft.ledgeSide=ledgeSide;
shaft.irregularityAmplitudeMicrometres=irregularity;
shaft.irregularityWavelengthMicrometres=
settings.irregularityWavelengthMicrometres;
shaft.irregularitySeed=LayoutStampSeed(seed,strateIndex,shaft.stableId);
rooms.push_back(std::move(shaft));
landing.point={landing.node.point[0]
+toParent[0]*(radius-ledgeDepth/2),
landing.node.point[1]+toParent[1]*(radius-ledgeDepth/2),landingZ};
landing.node.point=landing.point;
const std::string accessId=routePrefix+"shaft-access-"
+std::to_string(shaftLandings.size());
auto access=makeEdge(accessId,componentRoomId(parent),parent.point,
landing.node,routeBoreRadius,-1,-1);
// The connector's own floor SDF retains its tread. It must not enter
// the descending backbone's finite floor-cap arbitration, where its
// nearby but independent floor could suppress backbone support.
access.floorSupportDepthMicrometres=0;
const std::int64_t run=distance2d(parent.point,landing.point);
const std::int64_t rise=std::abs(parent.point[2]-landing.point[2]);
if(run<=0||rise*MaximumWalkGradeDenominator>run)
{
rooms.pop_back();
continue;
}
edges.push_back(std::move(access));
shaftLandings.push_back(std::move(landing));
emittedShaft=true;
}
plannedShaftCount=shaftLandings.size();
for(std::size_t index=1;index<shaftLandings.size();++index)
{
const std::uint64_t hash=LayoutStampSeed(seed,strateIndex,
shaftLandings[index-1].node.id+"/cross-"+std::to_string(index));
if(static_cast<std::int64_t>(hash&0xffffu)>=settings.tunnelDensityQ16)
continue;
const auto& from=shaftLandings[index-1];
const auto& to=shaftLandings[index];
const std::int64_t run=distance2d(from.point,to.point);
const std::int64_t rise=std::abs(from.point[2]-to.point[2]);
if(run<=0||run>settings.maximumTunnelLengthMicrometres
||rise*MaximumWalkGradeDenominator>run)continue;
auto link=makeEdge(routePrefix+"shaft-cross-"+std::to_string(index-1),
componentRoomId(from.node),from.point,to.node,routeBoreRadius,-1,-1);
link.floorSupportDepthMicrometres=0;
edges.push_back(std::move(link));
}
}
const auto arrivalPoint=routeStart;
FPoint previousPoint=arrivalPoint;
std::int64_t previousProfileDistance=0;
@@ -3161,8 +3395,7 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
for(std::size_t nodeIndex=0;nodeIndex<nodes.size();++nodeIndex)
{
const auto& node=nodes[nodeIndex];
const std::string edgeId="tunnel-network/strate-"+std::to_string(strateIndex)
+"/route-edge-"+std::to_string(nodeIndex);
const std::string edgeId=routePrefix+"route-edge-"+std::to_string(nodeIndex);
auto edge=makeEdge(edgeId,previousAnchor,previousPoint,node,
selectedBoreRadius(edgeId),
previousProfileDistance,node.floorProfileDistanceMicrometres);
@@ -3171,10 +3404,9 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
previousProfileDistance=node.floorProfileDistanceMicrometres;
previousAnchor=componentRoomId(node);
}
if(!nodes.empty())
if(!nodes.empty()&&departurePassage)
{
const std::string departureEdgeId="tunnel-network/strate-"
+std::to_string(strateIndex)+"/departure-edge";
const std::string departureEdgeId=routePrefix+"departure-edge";
auto edge=makeEdge(departureEdgeId,departurePassage->stableId+"/upper-mouth",
routeEnd,nodes.back(),selectedBoreRadius(departureEdgeId),
totalRouteLength,nodes.back().floorProfileDistanceMicrometres);
@@ -3187,7 +3419,7 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
const std::size_t branchLimit=settings.originRoomMaximumConnections<=0
?32u:static_cast<std::size_t>(std::clamp(settings.originRoomMaximumConnections,0,32));
std::size_t branchCount=0;
for(std::size_t nodeIndex=1;nodeIndex+1<nodes.size()
for(std::size_t nodeIndex=1;!terminalShaftRoute&&nodeIndex+1<nodes.size()
&&branchCount<branchLimit&&rooms.size()<2048;++nodeIndex)
{
const auto& parent=nodes[nodeIndex];
@@ -3210,8 +3442,7 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
// A side branch leaves the backbone laterally, so it has its own local flat
// floor and must not reference the parent's one-dimensional h(s) profile.
makeCompound(branch,false,rooms);
const std::string branchEdgeId="tunnel-network/strate-"
+std::to_string(strateIndex)+"/side-edge-"+std::to_string(branchCount);
const std::string branchEdgeId=routePrefix+"side-edge-"+std::to_string(branchCount);
ResolvedCavernEdgeV1 branchEdge;
if(settings.tunnelsFlowTowardOrigin)
branchEdge=makeEdge(branchEdgeId,componentRoomId(branch),
@@ -3226,7 +3457,7 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
++branchCount;
}
if(nodes.empty()||rooms.empty()||edges.empty())
{error="cavern planner emitted an empty arrival-to-departure graph";return false;}
{error="cavern planner emitted an empty mouth-to-terminal graph";return false;}
double maximumGradeDegrees=0.0;
for(const auto& edge:edges)
@@ -3255,9 +3486,10 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
}
const double plannerMilliseconds=std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-plannerStarted).count();
std::printf("[CavernPlannerV1] strate=%d seed=%d mouths=%s->%s route_nodes=%zu room_primitives=%zu edges=%zu branches=%zu route_horizontal_m=%.3f floor_delta_m=%.3f max_grade_deg=%.3f mouth_tangent_min_dot=%.3f grade_limit_deg=11.310 clearance_radius_m=%.3f clearance_height_m=%.3f destination_radius_m=%.3f plan_ms=%.3f status=PASS\n",
strateIndex,seed,arrivalPassage->stableId.c_str(),departurePassage->stableId.c_str(),
nodes.size(),rooms.size(),edges.size(),branchCount,totalRouteLength/1.0e6,
std::printf("[CavernPlannerV1] strate=%d seed=%d route=%s->%s route_nodes=%zu room_primitives=%zu shafts=%zu edges=%zu branches=%zu route_horizontal_m=%.3f floor_delta_m=%.3f max_grade_deg=%.3f mouth_tangent_min_dot=%.3f grade_limit_deg=11.310 clearance_radius_m=%.3f clearance_height_m=%.3f destination_radius_m=%.3f plan_ms=%.3f status=PASS\n",
strateIndex,seed,arrivalPassage->stableId.c_str(),
departurePassage?departurePassage->stableId.c_str():"basal-terminal",
nodes.size(),rooms.size(),plannedShaftCount,edges.size(),branchCount,totalRouteLength/1.0e6,
std::abs(dz)/1.0e6,maximumGradeDegrees,plannedMouthTangentAlignment,
routeBoreRadius/1.0e6,PlannedBoreHeight/1.0e6,
settings.destinationRadiusMicrometres/1.0e6,plannerMilliseconds);
+89 -6
View File
@@ -1284,7 +1284,8 @@ public:
const std::uint32_t reliefSeed=static_cast<std::uint32_t>(Owner?Owner->Seed:0);
const auto* queryStratum=FindBoundStratumForManagerZ(Z);
const bool plannedTunnelStratum=queryStratum&&queryStratum->Resolved
&&queryStratum->Resolved->generatorKind=="TunnelNetwork"
&&(queryStratum->Resolved->generatorKind=="TunnelNetwork"
||queryStratum->Resolved->generatorKind=="VerticalShafts")
&&queryStratum->Resolved->recipeId=="Core.Caves.PrimitiveField"
&&queryStratum->Resolved->recipeVersion==1;
const auto* routePassageIndices=passageTileScopeMatches
@@ -1617,9 +1618,12 @@ public:
const auto plannedPortalWalkCorridorContains=[&]()
{
if(!queryStratum||!queryStratum->Resolved
||queryStratum->Resolved->generatorKind!="TunnelNetwork"
||(queryStratum->Resolved->generatorKind!="TunnelNetwork"
&&queryStratum->Resolved->generatorKind!="VerticalShafts")
||queryStratum->Resolved->recipeId!="Core.Caves.PrimitiveField"
||queryStratum->Resolved->recipeVersion!=1)return false;
const bool terminalShaftRoute=
queryStratum->Resolved->generatorKind=="VerticalShafts";
const VoxelForgeCore::ResolvedLayoutAnchorV2* first=nullptr;
const VoxelForgeCore::ResolvedLayoutAnchorV2* second=nullptr;
const VoxelForgeCore::ResolvedLayoutAnchorV2* third=nullptr;
@@ -1638,8 +1642,12 @@ public:
}
if(routeCount<6||!first||!second||!third||!lastThree||!lastTwo||!last
||!first->stableId.ends_with("000-arrival")
||!second->stableId.ends_with("000-edge-start")
||!lastTwo->stableId.ends_with("998-departure-inner")
||!second->stableId.ends_with("000-edge-start"))return false;
if(terminalShaftRoute)
{
if(!last->stableId.ends_with("999-basal-terminal"))return false;
}
else if(!lastTwo->stableId.ends_with("998-departure-inner")
||!last->stableId.ends_with("999-departure"))return false;
const double metresPerSubvoxel=1.0/qPerMetre;
const auto position=[&](const VoxelForgeCore::ResolvedLayoutAnchorV2& anchor)
@@ -1677,6 +1685,7 @@ public:
if(nearRouteSegment(*first,*second,PortalWalkLeadMetres,false,0.0)
||nearRouteSegment(*second,*third,PortalWalkLeadMetres,false,
arrivalInnerDistance))return true;
if(terminalShaftRoute)return false;
const auto departureInner=position(*lastTwo),departure=position(*last);
const double departureTailDistance=std::hypot(
departure[0]-departureInner[0],departure[1]-departureInner[1]);
@@ -3835,8 +3844,11 @@ private:
const VoxelForgeCore::ResolvedCavernRoomV1* room=nullptr;
const VoxelForgeCore::ResolvedCavernEdgeV1* edge=nullptr;
bool nativeGraph=false;
std::uint8_t roomShapeKind=0;
std::uint8_t ledgeSide=0;
std::array<std::int64_t,3> boundsMinimumMicrometres{},boundsMaximumMicrometres{};
std::array<double,3> centerMetres{},halfExtentsMetres{};
double ledgeSpacingMetres=0.0,ledgeDepthMetres=0.0;
struct FSegment
{
std::array<double,3> start{},end{},minimum{},maximum{};
@@ -4285,7 +4297,9 @@ private:
||(ResolvedLayout->cavernGraphVocabularyVersion
!=VoxelForgeCore::ResolvedLayoutCavernGraphVocabularyVersionV1
&&ResolvedLayout->cavernGraphVocabularyVersion
!=VoxelForgeCore::ResolvedLayoutCavernGraphVocabularyVersionV2))return;
!=VoxelForgeCore::ResolvedLayoutCavernGraphVocabularyVersionV2
&&ResolvedLayout->cavernGraphVocabularyVersion
!=VoxelForgeCore::ResolvedLayoutCavernGraphVocabularyVersionV3))return;
const auto isNativeGraphStrate=[this](std::int32_t strateIndex)
{
const auto found=std::find_if(ResolvedLayout->strates.begin(),
@@ -4302,6 +4316,10 @@ private:
{
FCavernRuntimeFeature runtime;runtime.room=&room;
runtime.nativeGraph=isNativeGraphStrate(room.strateIndex);
runtime.roomShapeKind=room.shapeKind;
runtime.ledgeSide=room.ledgeSide;
runtime.ledgeSpacingMetres=room.ledgeSpacingMicrometres/1000000.0;
runtime.ledgeDepthMetres=room.ledgeDepthMicrometres/1000000.0;
runtime.boundsMinimumMicrometres=room.boundsMinimumMicrometres;
runtime.boundsMaximumMicrometres=room.boundsMaximumMicrometres;
for(std::size_t axis=0;axis<3;++axis)
@@ -4956,7 +4974,8 @@ private:
ResolvedLayout->strates.end(),[&](const auto& stratum)
{return stratum.strateIndex==passage.lowerStrateIndex;});
if(lowerStratum!=ResolvedLayout->strates.end()
&&lowerStratum->generatorKind=="TunnelNetwork"
&&(lowerStratum->generatorKind=="TunnelNetwork"
||lowerStratum->generatorKind=="VerticalShafts")
&&routeEnd<controls.size())
{
double routeHeight=0.0,distanceSquared=0.0;
@@ -5667,6 +5686,11 @@ public:
FMetricInterval featureVoid;
if(feature.room)
{
// Shaft wall and ledge SDFs are evaluated exactly per sample.
// Until a conservative interval bound for their carved shelves is
// available, keep intersecting boxes Mixed instead of proving a
// potentially false uniform sign.
if(feature.roomShapeKind==1)return {0.0,0.0,false};
double normalizedMinimumSquared=0.0,normalizedMaximumSquared=0.0;
double meanRadius=0.0;
for(std::size_t axis=0;axis<3;++axis)
@@ -7884,6 +7908,64 @@ private:
double edgeFloor=0.0;
if(feature.room)
{
if(feature.roomShapeKind==1)
{
const double radius=feature.halfExtentsMetres[0];
const double bottom=feature.centerMetres[2]-feature.halfExtentsMetres[2];
const double top=feature.centerMetres[2]+feature.halfExtentsMetres[2];
const double radial=std::hypot(point[0]-feature.centerMetres[0],
point[1]-feature.centerMetres[1]);
const double radialQ=radial-radius;
const double verticalQ=std::max(bottom-point[2],point[2]-top);
double shaftDistance=std::min(std::max(radialQ,verticalQ),0.0)
+std::hypot(std::max(radialQ,0.0),std::max(verticalQ,0.0));
if(feature.irregularityAmplitudeVoxels>0.0f)
shaftDistance+=StampValueNoise(
X-feature.centerVoxels[0],Y-feature.centerVoxels[1],
Z-feature.centerVoxels[2],feature.irregularityWavelengthVoxels,
feature.irregularitySeed)
*feature.irregularityAmplitudeVoxels*metresPerVoxel;
// A cardinal ledge faces the walkable backbone. Shelves are
// cut out of the otherwise open cylinder, leaving most of the
// shaft cross-section clear at every interval.
if(feature.ledgeSpacingMetres>0.0&&feature.ledgeDepthMetres>0.0)
{
const long long nearest=static_cast<long long>(std::llround(
(top-point[2])/feature.ledgeSpacingMetres));
const long long maximum=static_cast<long long>(std::floor(
(top-bottom-1000000.0)/feature.ledgeSpacingMetres));
if(nearest>=1&&nearest<=maximum)
{
const double ledgeZ=top-nearest*feature.ledgeSpacingMetres;
constexpr double LedgeThicknessMetres=0.6;
constexpr double LedgeFilletMetres=0.1;
const double dx=point[0]-feature.centerMetres[0];
const double dy=point[1]-feature.centerMetres[1];
const std::array<std::array<double,2>,4> sides={{{1.0,0.0},
{0.0,1.0},{-1.0,0.0},{0.0,-1.0}}};
const auto side=sides[feature.ledgeSide%4];
const double radialLocal=dx*side[0]+dy*side[1];
const double tangentLocal=-dx*side[1]+dy*side[0];
const double halfDepth=feature.ledgeDepthMetres*0.5;
const double halfWidth=radius*0.45;
const double halfThickness=LedgeThicknessMetres*0.5;
const double qx=std::abs(radialLocal-(radius-halfDepth))-halfDepth;
const double qy=std::abs(tangentLocal)-halfWidth;
const double qz=std::abs(point[2]-(ledgeZ-halfThickness))
-halfThickness;
const double outside=std::hypot(std::max(qx,0.0),
std::max(qy,0.0),std::max(qz,0.0));
const double platformDistance=outside
+std::min(std::max(qx,std::max(qy,qz)),0.0)
-LedgeFilletMetres;
shaftDistance=std::max(shaftDistance,-platformDistance);
}
}
featureVoid=shaftDistance;
}
else
{
double normalizedSquared=0.0,meanRadius=0.0;
for(std::size_t axis=0;axis<3;++axis)
{
@@ -7907,6 +7989,7 @@ private:
+feature.floorGradient[1]*(point[1]-feature.centerMetres[1]);
const double width=feature.room->smoothWidthMicrometres/1000000.0;
featureVoid=SmoothMaxMetres(ellipsoid,floor,width);
}
}
else if(feature.edge&&!feature.segments.empty())
{
@@ -0,0 +1,92 @@
# Linux139 — Vertical Shafts on the Core planner
## Result
S3 now reports `Core.Caves.PrimitiveField` / `Caves-native`, using the shared Tunnel Network
planner. The composed acceptance gate is not green: **15/16 seeds pass; seed 2 fails the passage
shell and tile seam checks at the arrival from passage 2–3.** This worktree is not promoted as an
accepted Vertical Shafts generator.
The basal primordial route law means arrival passage 2–3 to a walkable terminal landing above the
bedrock seal. There is no departure mouth at the bottom of the owner world. The shared planner
builds that walk-only spine, its access tunnels, and landings within the existing grade contract.
The full-height open shafts and their ledges are an exploration tier: a drop or climb is not
walk-only traversal, and climb or mine access stays in the reporting tiers. `Core.Lattice` was not
used because L58's VerticalShafts lattice route looked like a corkscrew, not an open shaft.
## Plan and parameter mapping
`PlanResolvedCavernNetworkV1` is reused without a planner fork. `Core.Caves.PrimitiveField` is the
shared evaluator, extended with `shapeKind=1`: the existing room/tunnel shapes do not represent a
capped, full-height vertical bore, and the L58 lattice route was a corkscrew. Shaft records use
that cylinder primitive with ledges, short access bores, and cross-shaft links. Generic cave
branches are disabled in shaft mode.
| `FVerticalShaftParams` field(s) | Core setting |
|---|---|
| `ShaftSpacing` | Shared planner room spacing; owner voxels convert to integer micrometres. |
| `ShaftDensity` | `roomDensityQ16`, controlling shaft placement; a nonzero setting guarantees the route's initial shaft. |
| `ShaftMinRadius`, `ShaftMaxRadius` | Minimum and maximum shaft radii. |
| `CrossConnectChance` | `tunnelDensityQ16` for links between shafts. |
| `ConnectorRadius` | Equal minimum/maximum radius for access and connector bores. |
| `LedgeSpacing`, `LedgeDepth` | Vertical shelf interval and intrusion; zero spacing disables shelves and zero depth makes them nonintrusive. |
| `SurfaceRoughness` | Roughness amplitude; frequency derives as `max(0.5 m, 4 owner voxels)` because no frequency field exists. |
| `BoundarySealThickness`, `BaseDensity` | Existing resolved top/basal boundary bands via `sealAndDensity`, outside planner settings. |
## Measurements
The final source stamp was `b8603b6cb3975ca0f9669759e4e2464561c557fdb7aa6836acef123ecf0b99d1`.
The composed sweep covered seeds 0–15 with one run per seed (reported process time 320.485 s in
total; individual runs 10.296–34.015 s). Seeds 0, 1, and 3–15 passed passages, route, seams, shell,
and blocked-route controls. Seed 2's route grades/support, naturalness, mouth profile, ripple,
dispatch, and negative controls passed; its composed passage, seam, and shell summaries failed.
The shell audit reported eight `wall_missing` samples on `passage/2-3/0`, at route stations
489.627–490.321 m. The final log records positive native density at those samples and negative
wall-shell margins. See `Saved/Linux/l139/seeds/seed2-final.log` and
`Saved/Linux/l139/seeds/summary.json`.
Three geometry variants were tried against this same shell gate: starting passage clearance at
12 m, adding a guarded cubic lateral bow, and starting clearance at 8 m. The first and third still
failed wall checks; the bow failed floor checks. Per the three-failure stop rule, these were
discarded. The final binary retains the successful Q16 endpoint-normalization correction needed
for seed 1 and no seed-2 geometry patch.
L80 ran after the seed sweep. The host gate was **load 1.31, CPU idle 99.4%**. It used one warm-up
per build and stratum/worker cell, then five interleaved measured runs for each of three builds at
W8 and W12 across all four strata: 120 measured runs total. Comparisons use `fa4417a`,
`assembler/world`, and the L139 binary; the established L137 A/A noise floor is ±1% ([A/A report](../Linux137/REPORT.md)).
Times are medians in seconds; deltas are L139 relative to each comparison build. Paired values in
each cell are W8 / W12.
| Stratum | LOD0–2 L139 | Δ vs `fa4417a` | Δ vs `assembler/world` | Whole-view L139 | Δ vs `fa4417a` | Δ vs `assembler/world` |
|---|---:|---:|---:|---:|---:|---:|
| S0 Surface World | 0.838 / 0.886 | +1.71% / +3.97% | +2.92% / +5.06% | 1.862 / 1.804 | +2.42% / +1.63% | −0.11% / −0.33% |
| S1 Floating Islands | 1.036 / 1.038 | +4.72% / +4.61% | +2.09% / +5.35% | 1.842 / 1.830 | +0.11% / +1.16% | −6.73% / −1.24% |
| S2 Tunnel Network | 1.105 / 1.078 | +2.44% / +3.07% | +3.57% / +7.57% | 1.876 / 1.808 | −1.11% / +0.00% | −4.33% / −1.15% |
| S3 Vertical Shafts | 0.899 / 0.884 | −3.81% / −0.94% | −0.95% / +2.00% | 1.706 / 1.657 | −6.57% / −2.53% | −6.21% / −4.77% |
Whole-view medians are numerically lower than `assembler/world` in all eight cells; the S0 W8 and
W12 differences are within the ±1% noise floor. Seven of eight LOD0–2 cells are slower than
`assembler/world`; the remaining cell is within the noise floor. This is characterization of an
unaccepted build, not a promotion claim. Raw runs and medians are in
`Saved/Linux/l139/timing/summary.json`.
## Checks and captures
- Fast tier: **PASS**, 284.0 s; the S3 dispatch line is `VerticalShafts` → `Core.Caves.PrimitiveField` v1 / `Caves-native` / `FCavesFieldAdapter`.
- Clang and GCC determinism: **PASS**, W1/W8/W12 (six cases), 13 hash lines and resolved-layout identity matched. Both compilers built the final source stamp above.
- Canonical v237 baselines added for Surface World and Floating Islands; both match their v236 values.
- Five eye-height views were captured before and after: arrival mouth, shaft access landing, looking down, looking up from a ledge, and basal terminal landing. All five L139 views pass the existing quality threshold. Two L138 baseline views fail it: the old layer has no shaft at the selected site, so the down-shaft view is flat, and the upward view is low contrast. The threshold was not relaxed. Images and camera details are in `captures/`.
- The outside views are `captures/after-whole-layer-density-cutaway.jpg` (coarse LOD density-mesh slice at y=−5 m) and `captures/after-whole-layer-side-elevation.svg` (resolved-plan projection that labels the walk route and all 53 shaft primitives). The cutaway passes the unchanged image-quality threshold; its plan companion makes the full shaft layout clearer.
- Linux `VFHostEditor` Development build: **PASS**, 27 actions, 72.58 s, run after timing with `-NoUBA -MaxParallelActions=4` and no `prlimit`. It emitted three pre-existing deprecated RealtimeMesh API warnings from `VoxelWorld.cpp`; that file was not changed.
- `git diff --check` and Python bytecode compilation of the L139 runners passed. Windows build and Windows determinism were not run.
Logs and source-stamped binaries are under `Saved/Linux/l139/`. The only changed Unreal `Source/`
file is `Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp`.
## Remaining risk
The passage shell and seam break at the incoming mouth on seed 2 remain unresolved. The local
clearance/heading premise failed three geometry attempts; a new route-to-mouth design premise is
needed before another geometry change. Walk-only connectivity stays guaranteed by construction;
vertical drops, climbs, and mine access remain reporting tiers.
@@ -0,0 +1,278 @@
#!/usr/bin/env python3
"""Capture comparable player-height views of the L138 and L139 owner shaft layer."""
from __future__ import annotations
import json
import math
import os
from pathlib import Path
import re
import subprocess
import sys
import time
ROOT = Path(__file__).resolve().parents[4]
DATA = ROOT / "Standalone/data/owner_world.json"
LAYOUT = ROOT / "Saved/Linux/l139/seeds/seed0.json"
OUT = ROOT / "Saved/Linux/l139/captures"
ARTIFACTS = ROOT / "Standalone/Research/WorldAssemblerM3/Linux139/captures"
LIMIT = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--"]
VERSIONS = {
"before-l138": {
"headless": ROOT / "Saved/Linux/l138/bin/VoxelForgeCoreHeadless-l138-clang",
"renderer": ROOT / "Saved/Linux/l138/bin/VoxelForgeOffscreenCapture-l138-clang",
"stamp": "9aade4e6cb33e86c36b89440644d9ef5d440288fb6ff0af03af128291c95f93f",
},
"after-l139": {
"headless": ROOT / "Saved/Linux/l139/bin/VoxelForgeCoreHeadless-l139-clang",
"renderer": ROOT / "Saved/Linux/l139/bin/VoxelForgeOffscreenCapture-l139-clang",
"stamp": "b8603b6cb3975ca0f9669759e4e2464561c557fdb7aa6836acef123ecf0b99d1",
},
}
STAMP_RE = re.compile(r"^\[VoxelForgeSourceStamp\] sha256=([0-9a-f]{64})$", re.M)
def fmt(values: tuple[float, ...]) -> str:
return ",".join(f"{value:.6f}" for value in values)
def angles(camera: tuple[float, float, float],
target: tuple[float, float, float]) -> tuple[float, float]:
dx, dy, dz = (target[i] - camera[i] for i in range(3))
return math.degrees(math.atan2(dx, dy)), math.degrees(
math.atan2(dz, math.hypot(dx, dy)))
def stamped_run(name: str, command: list[str], log: Path, expected: str,
env: dict[str, str]) -> str:
binary = Path(command[0])
probe = subprocess.run([*LIMIT, "timeout", "15s", str(binary), "--print-source-stamp"],
cwd=ROOT, env=env, text=True, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, check=False, timeout=20)
match = STAMP_RE.search(probe.stdout or "")
if probe.returncode != 0 or not match or match.group(1) != expected:
raise RuntimeError(f"{name}: executable stamp mismatch for {binary}")
log.parent.mkdir(parents=True, exist_ok=True)
started = time.monotonic()
with log.open("w", encoding="utf-8") as stream:
result = subprocess.run([*LIMIT, "timeout", "1740s", *map(str, command)],
cwd=ROOT, env=env, stdout=stream, stderr=subprocess.STDOUT,
timeout=1745, check=False)
output = log.read_text(encoding="utf-8", errors="replace")
emitted = STAMP_RE.search(output)
if (result.returncode != 0 or not emitted or emitted.group(1) != expected):
raise RuntimeError(f"{name}: process failed or source changed; see {log}")
print(f"[L139CaptureRun] step={name} elapsed_s={time.monotonic()-started:.1f} "
f"log={log.relative_to(ROOT)}", flush=True)
return output
def point_um(values: list[int]) -> tuple[float, float, float]:
return tuple(value / 1_000_000.0 for value in values)
def make_views(layout: dict) -> list[dict]:
edges = {row["stable_id"]: row for row in layout["cavern_edges"]}
rooms = {row["stable_id"]: row for row in layout["cavern_rooms"]}
shaft = rooms["vertical-shafts/strate-3/shaft-0/primitive-0"]
access = edges["vertical-shafts/strate-3/shaft-access-0"]["polyline_um"]
arrival = edges["vertical-shafts/strate-3/route-edge-0"]["polyline_um"]
basal = rooms["vertical-shafts/strate-3/basal-landing/primitive-0"]
center = point_um(shaft["center_um"])
half = point_um(shaft["half_extents_um"])
shaft_top = center[2] + half[2]
shaft_bottom = center[2] - half[2]
route_a, route_b = point_um(arrival[0]), point_um(arrival[1])
tx, ty = route_b[0] - route_a[0], route_b[1] - route_a[1]
tlen = max(1.0e-9, math.hypot(tx, ty))
tangent = (tx / tlen, ty / tlen)
mouth_camera = (route_a[0] - tangent[0] * 2.0,
route_a[1] - tangent[1] * 2.0, route_a[2] + 1.45)
mouth_target = (route_a[0] + tangent[0] * 8.0,
route_a[1] + tangent[1] * 8.0, route_a[2] + 1.45)
access_a, access_b = point_um(access[0]), point_um(access[-1])
access_camera = (access_a[0], access_a[1], access_a[2] + 1.45)
access_target = (access_b[0], access_b[1], access_b[2] + 1.45)
shaft_lip_camera = (access_b[0], access_b[1], access_b[2] + 1.45)
basal_center = point_um(basal["center_um"])
basal_camera = (basal_center[0], basal_center[1], basal["floor_um"] / 1.0e6 + 1.45)
basal_target = (basal_center[0] + tangent[0] * 8.0,
basal_center[1] + tangent[1] * 8.0, basal_camera[2])
ledge_z = shaft_top - 12 * (shaft["ledge_spacing_um"] / 1.0e6)
return [
{"name": "arrival-mouth", "camera": mouth_camera, "target": mouth_target,
"focus": (route_a[0], route_a[1], route_a[2] + 1.4, 12.0),
"note": "player eye at the passage arrival, looking into the connected layer"},
{"name": "landing-at-shaft-access", "camera": access_camera,
"target": access_target,
"focus": (center[0], center[1], access_a[2], 14.0),
"note": "walkable route landing and short shaft access tunnel"},
{"name": "looking-down-shaft", "camera": shaft_lip_camera,
"target": (center[0], center[1], shaft_top - 30.0),
"focus": (center[0], center[1], shaft_top - 25.0, 14.0),
"fov": 35.0,
"note": "player eye at the upper access landing, looking down the open shaft"},
{"name": "looking-up-from-ledge", "camera": (center[0], center[1], ledge_z + 1.45),
"target": (center[0], center[1], shaft_top - 2.0),
"focus": (center[0], center[1], shaft_top - 22.0, 20.0),
"note": "player eye at an internal ledge, looking up; reaching this ledge is a climb tier"},
{"name": "basal-terminal-landing", "camera": basal_camera,
"target": basal_target,
"focus": (basal_center[0], basal_center[1], basal_center[2], 16.0),
"note": "walkable terminal landing above the basal seal"},
]
def write_overview(layout: dict, destination: Path) -> None:
shafts = [room for room in layout["cavern_rooms"] if room.get("shape_kind") == 1]
route_edges = sorted((edge for edge in layout["cavern_edges"]
if edge["stable_id"].startswith("vertical-shafts/strate-3/route-edge-")),
key=lambda edge: int(edge["stable_id"].rsplit("-", 1)[1]))
if not shafts or not route_edges:
raise ValueError("overview needs the resolved shaft primitives and walk route")
points = [point_um(row) for edge in route_edges for row in edge["polyline_um"]]
shaft_x_bounds = [(point_um(room["center_um"])[0]
-room["half_extents_um"][0]/1e6,
point_um(room["center_um"])[0]+room["half_extents_um"][0]/1e6)
for room in shafts]
x_min = min(min(bounds[0] for bounds in shaft_x_bounds),
min(row[0] for row in points))
x_max = max(x + room["half_extents_um"][0] / 1e6
for room in shafts for x in [point_um(room["center_um"])[0]])
x_max = max(x_max, max(row[0] for row in points))
z_min = min(point_um(room["center_um"])[2] - room["half_extents_um"][2] / 1e6
for room in shafts)
z_max = max(point_um(room["center_um"])[2] + room["half_extents_um"][2] / 1e6
for room in shafts)
z_min = min(z_min, min(row[2] for row in points))
z_max = max(z_max, max(row[2] for row in points))
width, height, margin = 1800.0, 760.0, 72.0
sx = (width - 2 * margin) / max(1.0, x_max - x_min)
sz = (height - 2 * margin) / max(1.0, z_max - z_min)
scale = min(sx, sz)
x_offset = margin + ((width - 2 * margin) - (x_max - x_min) * scale) * 0.5
z_offset = margin + ((height - 2 * margin) - (z_max - z_min) * scale) * 0.5
def xy(x: float, z: float) -> tuple[float, float]:
return x_offset + (x - x_min) * scale, height - (z_offset + (z - z_min) * scale)
route_xy = [xy(p[0], p[2]) for p in points]
route = " ".join(("M" if i == 0 else "L") + f"{x:.2f},{y:.2f}"
for i, (x, y) in enumerate(route_xy))
cylinders = []
for room in shafts:
x, _y, z = point_um(room["center_um"])
radius = room["half_extents_um"][0] / 1e6
half_z = room["half_extents_um"][2] / 1e6
left, top = xy(x - radius, z + half_z)
right, bottom = xy(x + radius, z - half_z)
cylinders.append(f'<rect x="{left:.2f}" y="{top:.2f}" width="{max(1.2,right-left):.2f}" '
f'height="{bottom-top:.2f}" rx="{min(7.0,(right-left)/2):.2f}" '
'fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/>')
rx0, ry0 = xy(x_min, 0.0)
rx1, ry1 = xy(x_max, 0.0)
svg = f'''<svg xmlns="http://www.w3.org/2000/svg" width="1800" height="760" viewBox="0 0 1800 760">
<rect width="1800" height="760" fill="#07111a"/>
<text x="72" y="42" fill="#edf6fb" font-family="sans-serif" font-size="24">Vertical Shafts — whole-layer side elevation</text>
<text x="72" y="64" fill="#9bb5c5" font-family="sans-serif" font-size="13">Resolved plan • seed 0 • strate 3 • vertical axis and route shown in metres • Y projected out</text>
<line x1="{rx0:.1f}" y1="{ry0:.1f}" x2="{rx1:.1f}" y2="{ry1:.1f}" stroke="#315060" stroke-dasharray="5 8"/>
{''.join(cylinders)}
<path d="{route}" fill="none" stroke="#f4bf68" stroke-width="3.0" stroke-linecap="round" stroke-linejoin="round"/>
<text x="82" y="716" fill="#f4bf68" font-family="sans-serif" font-size="15">walk-only graded spine and landings</text>
<rect x="416" y="704" width="15" height="15" fill="#93dded" fill-opacity="0.65" stroke="#42b8d1"/>
<text x="440" y="716" fill="#b8dce5" font-family="sans-serif" font-size="15">open vertical shaft primitives (53)</text>
<text x="1718" y="716" fill="#9bb5c5" text-anchor="end" font-family="sans-serif" font-size="12">Plan projection, not a density mesh</text>
</svg>'''
destination.parent.mkdir(parents=True, exist_ok=True)
destination.write_text(svg, encoding="utf-8")
def main() -> int:
if not DATA.is_file() or not LAYOUT.is_file():
raise SystemExit("missing owner fixture or passing seed-0 resolved layout")
layout = json.loads(LAYOUT.read_text(encoding="utf-8"))
views = make_views(layout)
OUT.mkdir(parents=True, exist_ok=True)
ARTIFACTS.mkdir(parents=True, exist_ok=True)
env = os.environ.copy()
env["LP_NUM_THREADS"] = "12"
env["XDG_CACHE_HOME"] = str(OUT / "xdg-cache")
quality = ROOT / "Standalone/Tools/verify/capture_quality.py"
manifest = []
quality_failures = []
for version, config in VERSIONS.items():
phase = "before" if version.startswith("before") else "after"
for view in views:
name = view["name"]
camera, target = view["camera"], view["target"]
yaw, pitch = angles(camera, target)
mesh_dir = OUT / "meshes" / phase
mesh_dir.mkdir(parents=True, exist_ok=True)
mesh = mesh_dir / f"{name}.vfmesh"
capture_layout = mesh_dir / f"{name}-layout.json"
export_log = OUT / f"{phase}-{name}-export.log"
export = [config["headless"], DATA, "--world-assembler-prototype",
"--stream-benchmark", "--stream-pipeline", "--workers=12",
"--clip-radius=6", "--max-clip-level=0", "--max-level=0",
"--force-generate", "--seed=0", "--recipe=surface-world",
"--render-distance-chunks=0", f"--camera={fmt((*camera,yaw,pitch))}",
f"--resolved-layout-output={capture_layout}", f"--mesh-export={mesh}"]
stamped_run(f"{phase}-{name}-export", list(map(str, export)), export_log,
config["stamp"], env)
if not mesh.is_file() or mesh.stat().st_size == 0:
raise RuntimeError(f"{phase}-{name}: empty mesh export")
png = OUT / f"{phase}-{name}.png"
jpg = ARTIFACTS / f"{phase}-{name}.jpg"
fov = float(view.get("fov", 78.0))
render = [config["renderer"], mesh, png, "--size=1280x720",
f"--fov-deg={fov:.1f}", f"--camera-m={fmt(camera)}", f"--yaw-deg={yaw:.6f}",
f"--pitch-deg={pitch:.6f}", "--tile-skirts=off", "--shade=readable",
"--headlamp=on", "--height-tint=on", "--height-center-m=-950",
"--height-range-m=60", "--tile-edges=off",
f"--focus-region={fmt(view['focus'])}"]
stamped_run(f"{phase}-{name}-render", list(map(str, render)),
OUT / f"{phase}-{name}-render.log", config["stamp"], env)
jpeg = ["ffmpeg", "-nostdin", "-hide_banner", "-loglevel", "error", "-y",
"-i", png, "-frames:v", "1", "-q:v", "4", jpg]
subprocess.run([*LIMIT, "timeout", "120s", *map(str, jpeg)], cwd=ROOT,
env=env, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT,
timeout=125)
check = subprocess.run([*LIMIT, "timeout", "120s", sys.executable,
quality, jpg], cwd=ROOT, env=env, text=True, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, check=False, timeout=125)
quality_ok = check.returncode == 0 and "status=PASS" in check.stdout
quality_result = (check.stdout.strip().splitlines()[-1]
if check.stdout.strip() else f"quality command exit={check.returncode}")
if not quality_ok:
quality_failures.append(f"{phase}-{name}")
manifest.append({"phase": phase, "view": name, "file": str(jpg.relative_to(ROOT)),
"bytes": jpg.stat().st_size, "camera_m": camera, "target_m": target,
"yaw_deg": yaw, "pitch_deg": pitch, "fov_deg": fov,
"note": view["note"],
"quality_status": "PASS" if quality_ok else "FAIL",
"quality": quality_result})
print(f"[L139Capture] phase={phase} view={name} file={jpg.relative_to(ROOT)} "
f"bytes={jpg.stat().st_size} quality={'PASS' if quality_ok else 'FAIL'}",
flush=True)
overview = ARTIFACTS / "after-whole-layer-side-elevation.svg"
write_overview(layout, overview)
manifest.append({"phase": "after", "view": "whole-layer-side-elevation",
"file": str(overview.relative_to(ROOT)), "shafts": sum(
room.get("shape_kind") == 1 for room in layout["cavern_rooms"]),
"walk_route_edges": len([edge for edge in layout["cavern_edges"]
if edge["stable_id"].startswith("vertical-shafts/strate-3/route-edge-")]),
"note": "resolved-plan side projection; not a density-mesh capture"})
(ARTIFACTS / "manifest.json").write_text(json.dumps(manifest, indent=2) + "\n",
encoding="utf-8")
status = "PASS" if not quality_failures else "FAIL"
print(f"[L139CaptureSummary] spot_views={len(views)} comparisons={len(views)*2} "
f"whole_layer_projection=1 quality_failures={len(quality_failures)} "
f"status={status}", flush=True)
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except Exception as error:
print(f"[L139CaptureFailure] {error}", file=sys.stderr, flush=True)
raise SystemExit(1)
@@ -0,0 +1,86 @@
#!/usr/bin/env python3
"""Compare composed owner hashes and resolved graph identity at W1/W8/W12."""
from __future__ import annotations
import json
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l139/determinism"
DATA = ROOT / "Standalone/data/owner_world.json"
STAMP = "b8603b6cb3975ca0f9669759e4e2464561c557fdb7aa6836acef123ecf0b99d1"
BINARIES = {
"clang": ROOT / "Saved/Linux/l139/bin/VoxelForgeCoreHeadless-l139-clang",
"gcc": ROOT / "Saved/Linux/l139/bin/VoxelForgeCoreHeadless-l139-gcc",
}
CAMERA = "-20.062500,-5.068359,-900.353516,0,0"
HASH_RE = re.compile(
r"^\[Pipeline(?:FullDetailHash|WholeViewHash|MaterialHash|LODHash|ExactQueryHash)\].*$",
re.M)
STAMP_RE = re.compile(r"^\[VoxelForgeSourceStamp\] sha256=([0-9a-f]+)$", re.M)
def main() -> int:
if any(not path.is_file() for path in (DATA, *BINARIES.values())):
raise SystemExit("missing L139 determinism binary or composed owner fixture")
OUT.mkdir(parents=True, exist_ok=True)
rows = []
reference_hashes: tuple[str, ...] | None = None
reference_layout_identity: str | None = None
for compiler, binary in BINARIES.items():
for workers in (1, 8, 12):
name = f"determinism-{compiler}-w{workers}"
log, layout = OUT / f"{name}.log", OUT / f"{name}.json"
command = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1740s", str(binary), str(DATA), "--world-assembler-prototype",
"--stream-benchmark", "--stream-pipeline", f"--workers={workers}",
"--clip-radius=3", "--max-clip-level=8", "--max-level=0",
f"--camera={CAMERA}", "--stream-hash", "--force-generate", "--seed=0",
"--recipe=surface-world", "--render-distance-chunks=0",
"--density-dispatch-report", f"--resolved-layout-output={layout}"]
started = time.monotonic()
with log.open("w", encoding="utf-8") as output:
proc = subprocess.run(command, cwd=ROOT, stdout=output,
stderr=subprocess.STDOUT, check=False, timeout=1740)
content = log.read_text(encoding="utf-8", errors="replace")
stamp = STAMP_RE.search(content)
hashes = tuple(HASH_RE.findall(content))
current_layout = json.loads(layout.read_text(encoding="utf-8"))
stable_identity = current_layout.get("stable_identity")
dispatch_ok = bool(re.search(
r"^\[DensityDispatch\] strate=3 generator=VerticalShafts "
r"recipe=Core\.Caves\.PrimitiveField version=1 "
r"bound_path=Caves-native adapter=FCavesFieldAdapter$", content, re.M))
passed = (proc.returncode == 0 and stamp is not None and stamp.group(1) == STAMP
and len(hashes) >= 4 and dispatch_ok and bool(stable_identity))
if reference_hashes is None and passed:
reference_hashes = hashes
reference_layout_identity = stable_identity
else:
passed = (passed and hashes == reference_hashes
and stable_identity == reference_layout_identity)
row = {"compiler": compiler, "workers": workers, "exit": proc.returncode,
"stamp": stamp.group(1) if stamp else None, "hash_lines": len(hashes),
"hashes_match_first": hashes == reference_hashes,
"layout_identity": stable_identity,
"layout_matches_first": stable_identity == reference_layout_identity,
"shaft_dispatch": dispatch_ok, "passed": bool(passed),
"elapsed_s": round(time.monotonic() - started, 3),
"log": str(log.relative_to(ROOT)), "layout": str(layout.relative_to(ROOT))}
rows.append(row)
print("[L139DeterminismRun] " + json.dumps(row, sort_keys=True), flush=True)
(OUT / "runs-partial.json").write_text(json.dumps(rows, indent=2) + "\n")
if not passed:
raise SystemExit(f"composed owner determinism mismatch; see {log}")
(OUT / "summary.json").write_text(json.dumps({"status": "PASS", "stamp": STAMP,
"layout_identity": reference_layout_identity, "runs": rows}, indent=2) + "\n")
print(f"[L139DeterminismSummary] runs={len(rows)} source_stamp={STAMP} status=PASS")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,167 @@
#!/usr/bin/env python3
"""Run interleaved L80 timings for L139, fa4417a, and assembler/world."""
from __future__ import annotations
import json
import os
import re
import statistics
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l139/timing"
DATA = ROOT / "Standalone/data/owner_world.json"
BINARIES = {
"assembler-world": (
ROOT / "Saved/Linux/l137/bin/VoxelForgeCoreHeadless-l137-aa-a",
"4499fb2a8a4ede7d6cbc68d1b794f104658cdaae93ed9cacbc91fe7ff3fc3621"),
"fa4417a": (
ROOT / "Saved/Linux/l138/bin/VoxelForgeCoreHeadless-l138-clang",
"9aade4e6cb33e86c36b89440644d9ef5d440288fb6ff0af03af128291c95f93f"),
"l139": (
ROOT / "Saved/Linux/l139/bin/VoxelForgeCoreHeadless-l139-clang",
"b8603b6cb3975ca0f9669759e4e2464561c557fdb7aa6836acef123ecf0b99d1"),
}
CAMERAS = {
0: (8.0, 16.0, -372.2626953125),
1: (25.8369140625, 3.19921875, -737.0),
2: (19.166015625, -14.2001953125, -753.3740234375),
3: (-20.0625, -5.068359375, -900.353515625),
}
PIPELINE = re.compile(r"^\[StreamPipeline\] (.*)$", re.M)
FIELDS = re.compile(r"([a-zA-Z0-9_]+)=([^ ]+)")
NEAR = re.compile(
r"^\[WorldAssemblerNearLODReady\] lods=0-2 tiles=(\d+)/(\d+) cold_ready_s=([0-9.]+)$",
re.M)
STAMP = re.compile(r"^\[VoxelForgeSourceStamp\] sha256=([0-9a-f]+)$", re.M)
def run(strate: int, workers: int, version: str, phase: str, replicate: int) -> dict:
OUT.mkdir(parents=True, exist_ok=True)
binary, expected_stamp = BINARIES[version]
camera = ",".join(f"{value:.12f}" for value in CAMERAS[strate]) + ",0,0"
label = f"s{strate}-w{workers}-{version}-{phase}{replicate}"
log = OUT / f"{label}.log"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1740s", str(binary), str(DATA), "--world-assembler-prototype",
"--stream-benchmark", "--stream-pipeline", f"--workers={workers}",
"--clip-radius=3", "--max-clip-level=8", "--max-level=8",
f"--camera={camera}", "--profile-lod-work", "--stream-scheduler-policy=3",
]
started = time.monotonic()
with log.open("w", encoding="utf-8") as output:
proc = subprocess.run(command, cwd=ROOT, stdout=output,
stderr=subprocess.STDOUT, check=False, timeout=1740)
content = log.read_text(encoding="utf-8", errors="replace")
pipeline, near, stamp = PIPELINE.search(content), NEAR.search(content), STAMP.search(content)
if (proc.returncode or pipeline is None or near is None or stamp is None
or stamp.group(1) != expected_stamp):
raise SystemExit(f"L80 timing failed or stamp mismatch: {log}; exit={proc.returncode}")
fields = dict(FIELDS.findall(pipeline.group(1)))
row = {
"strate": strate, "workers": workers, "version": version,
"phase": phase, "replicate": replicate, "source_stamp": stamp.group(1),
"lod02_ready_s": float(near.group(3)),
"whole_view_ready_s": float(fields["whole_view_ready"].removesuffix("s")),
"whole_tiles": int(fields["whole_tiles"].split("/")[0]),
"wall_s": round(time.monotonic() - started, 3),
"log": str(log.relative_to(ROOT)),
}
print("[L139FinalTimingRun] " + json.dumps(row, sort_keys=True), flush=True)
return row
def quiet_machine() -> tuple[float, float]:
load_1m = os.getloadavg()[0]
def totals() -> tuple[int, int]:
fields = Path("/proc/stat").read_text().splitlines()[0].split()
values = [int(value) for value in fields[1:]]
return sum(values), values[3] + values[4]
before = totals()
time.sleep(2.0)
after = totals()
idle = 100.0 * (after[1] - before[1]) / max(1, after[0] - before[0])
return load_1m, idle
def main() -> int:
if any(not path.is_file() for path in [DATA, *(entry[0] for entry in BINARIES.values())]):
raise SystemExit("missing L139 timing input")
OUT.mkdir(parents=True, exist_ok=True)
load_1m, idle = quiet_machine()
(OUT / "machine-load-before.txt").write_text(
f"1-minute load: {load_1m:.2f}\n2-second CPU idle: {idle:.1f}%\n")
print(f"[L139FinalTimingGate] load_1m={load_1m:.2f} cpu_idle_2s={idle:.1f}%", flush=True)
if load_1m >= 1.5 or idle < 97.0:
raise SystemExit("timing refused: machine is above the L80 quiet-load gate")
rows = []
for strate in range(4):
for workers in (8, 12):
for version in BINARIES:
run(strate, workers, version, "warmup", 0)
for replicate in range(1, 6):
order = tuple(BINARIES)
order = order[replicate % 3:] + order[:replicate % 3]
if replicate % 2 == 0:
order = tuple(reversed(order))
for version in order:
rows.append(run(strate, workers, version, "measured", replicate))
(OUT / "runs-partial.json").write_text(
json.dumps(rows, indent=2) + "\n", encoding="utf-8")
measured = [row for row in rows if row["phase"] == "measured"]
summary = []
for strate in range(4):
for workers in (8, 12):
group = [row for row in measured
if row["strate"] == strate and row["workers"] == workers]
medians = {
version: {
key: statistics.median(row[key] for row in group
if row["version"] == version)
for key in ("lod02_ready_s", "whole_view_ready_s")
}
for version in BINARIES
}
entry = {"strate": strate, "workers": workers,
"replicates_per_version": 5, "medians": medians}
for key in ("lod02_ready_s", "whole_view_ready_s"):
for baseline in ("fa4417a", "assembler-world"):
entry[f"l139_vs_{baseline}_{key}_delta_pct"] = round(
100.0 * (medians["l139"][key] / medians[baseline][key] - 1.0), 2)
entry[f"l139_vs_fa4417a_{key}_paired_deltas_pct"] = [
round(100.0 * (
next(row[key] for row in group
if row["version"] == "l139" and row["replicate"] == replicate)
/ next(row[key] for row in group
if row["version"] == "fa4417a" and row["replicate"] == replicate)
- 1.0), 3) for replicate in range(1, 6)]
entry[f"l139_vs_assembler-world_{key}_paired_deltas_pct"] = [
round(100.0 * (
next(row[key] for row in group
if row["version"] == "l139" and row["replicate"] == replicate)
/ next(row[key] for row in group
if row["version"] == "assembler-world"
and row["replicate"] == replicate) - 1.0), 3)
for replicate in range(1, 6)]
summary.append(entry)
(OUT / "summary.json").write_text(json.dumps({
"a_a_noise_floor_pct": 1.0,
"fa4417a_commit": "fa4417a",
"timing_protocol": (
"one warmup per binary, five interleaved runs, W8/W12, all four owner layers"),
"runs": rows,
"summary": summary,
}, indent=2) + "\n", encoding="utf-8")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,107 @@
#!/usr/bin/env python3
"""Audit the composed owner world, including the basal Vertical Shafts route."""
from __future__ import annotations
import json
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l139/seeds"
BIN = ROOT / "Saved/Linux/l139/bin/VoxelForgeCoreHeadless-l139-clang"
DATA = ROOT / "Standalone/data/owner_world.json"
STAMP = "b8603b6cb3975ca0f9669759e4e2464561c557fdb7aa6836acef123ecf0b99d1"
STAMP_RE = re.compile(r"^\[VoxelForgeSourceStamp\] sha256=([0-9a-f]+)$", re.M)
REQUIRED = {
"passages": re.compile(r"^\[ResolvedPassageAuditSummary\].*status=PASS$", re.M),
"route": re.compile(
r"^\[ResolvedStrateRouteAudit\] strate=3 .*unsupported=0 grade_failures=0 .*status=PASS$",
re.M),
"seams": re.compile(r"^\[PassageTileSeamAudit\].*status=PASS$", re.M),
"shell": re.compile(r"^\[PassageShellAuditSummary\].*status=PASS$", re.M),
"naturalness": re.compile(r"^\[PassageNaturalnessSummary\].*status=PASS$", re.M),
"mouth_profile": re.compile(r"^\[PassageMouthProfileSummary\].*status=PASS$", re.M),
"ripple": re.compile(r"^\[PassageRippleAuditSummary\].*status=PASS$", re.M),
"shaft_dispatch": re.compile(
r"^\[DensityDispatch\] strate=3 generator=VerticalShafts "
r"recipe=Core\.Caves\.PrimitiveField version=1 "
r"bound_path=Caves-native adapter=FCavesFieldAdapter$", re.M),
"shaft_blocked_route": re.compile(
r"^\[VerticalShaftsBlockedRouteControl\].*expected=FAIL status=PASS$", re.M),
}
def main() -> int:
if not BIN.is_file() or not DATA.is_file():
raise SystemExit("missing L139 Clang binary or composed owner fixture")
OUT.mkdir(parents=True, exist_ok=True)
results = []
partial = OUT / "runs-partial.json"
if partial.is_file():
for prior in json.loads(partial.read_text(encoding="utf-8")):
seed = len(results)
if prior.get("seed") != seed:
break
log = ROOT / prior.get("log", "")
layout = ROOT / prior.get("layout", "")
if not log.is_file() or not layout.is_file():
break
content = log.read_text(encoding="utf-8", errors="replace")
stamp = STAMP_RE.search(content)
checks = {name: bool(pattern.search(content))
for name, pattern in REQUIRED.items()}
checks["source_stamp"] = stamp is not None and stamp.group(1) == STAMP
checks["blocked_passage_controls"] = len(re.findall(
r"^\[ResolvedPassageBlockedControl\].*detected=true$", content, re.M)) == 3
checks["passage_count"] = len(re.findall(
r"^\[ResolvedPassageAudit\].*walkability=PASS$", content, re.M)) == 3
passed = prior.get("exit") == 0 and all(checks.values())
if checks != prior.get("checks") or passed != prior.get("passed"):
break
results.append(prior)
print(f"[L139SeedSweepResume] reused={len(results)} source_stamp={STAMP}",
flush=True)
for seed in range(len(results), 16):
log = OUT / f"seed{seed}.log"
layout = OUT / f"seed{seed}.json"
command = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1740s", str(BIN), str(DATA), "--world-assembler-prototype",
"--stream-benchmark", "--stream-pipeline", "--workers=8", "--clip-radius=3",
"--max-clip-level=8", "--max-level=0", "--force-generate", f"--seed={seed}",
"--recipe=surface-world", "--density-dispatch-report",
"--audit-resolved-layout-passages", "--audit-passage-tile-seams",
f"--resolved-layout-output={layout}"]
started = time.monotonic()
with log.open("w", encoding="utf-8") as output:
proc = subprocess.run(command, cwd=ROOT, stdout=output,
stderr=subprocess.STDOUT, check=False, timeout=1740)
content = log.read_text(encoding="utf-8", errors="replace")
stamp = STAMP_RE.search(content)
checks = {name: bool(pattern.search(content)) for name, pattern in REQUIRED.items()}
checks["source_stamp"] = stamp is not None and stamp.group(1) == STAMP
checks["blocked_passage_controls"] = len(re.findall(
r"^\[ResolvedPassageBlockedControl\].*detected=true$", content, re.M)) == 3
checks["passage_count"] = len(re.findall(
r"^\[ResolvedPassageAudit\].*walkability=PASS$", content, re.M)) == 3
passed = proc.returncode == 0 and all(checks.values())
row = {"seed": seed, "exit": proc.returncode, "passed": passed,
"checks": checks, "elapsed_s": round(time.monotonic() - started, 3),
"log": str(log.relative_to(ROOT)), "layout": str(layout.relative_to(ROOT))}
results.append(row)
print("[L139SeedSweep] " + json.dumps(row, sort_keys=True), flush=True)
partial.write_text(json.dumps(results, indent=2) + "\n")
passed_count = sum(row["passed"] for row in results)
failed_count = len(results) - passed_count
summary = {"source_stamp": STAMP, "seeds": list(range(len(results))),
"passed": passed_count, "failed": failed_count, "results": results}
(OUT / "summary.json").write_text(json.dumps(summary, indent=2) + "\n")
print(f"[L139SeedSweepSummary] passed={passed_count}/16 failed={failed_count} "
f"status={'PASS' if len(results)==16 and failed_count==0 else 'FAIL'}")
return 0 if len(results) == 16 and failed_count == 0 else 1
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,110 @@
#!/usr/bin/env python3
"""Render a coarse whole-strate cutaway from the L139 density mesh."""
from __future__ import annotations
import json
import os
from pathlib import Path
import re
import subprocess
import sys
import time
ROOT = Path(__file__).resolve().parents[4]
DATA = ROOT / "Standalone/data/owner_world.json"
BIN = ROOT / "Saved/Linux/l139/bin/VoxelForgeCoreHeadless-l139-clang"
RENDERER = ROOT / "Saved/Linux/l139/bin/VoxelForgeOffscreenCapture-l139-clang"
OUT = ROOT / "Saved/Linux/l139/captures"
ARTIFACTS = ROOT / "Standalone/Research/WorldAssemblerM3/Linux139/captures"
STAMP = "b8603b6cb3975ca0f9669759e4e2464561c557fdb7aa6836acef123ecf0b99d1"
STAMP_RE = re.compile(r"^\[VoxelForgeSourceStamp\] sha256=([0-9a-f]{64})$", re.M)
LIMIT = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--"]
SLICE_Y = -5.0
CAMERA = (-450.0, SLICE_Y - 100.0, -950.0)
YAW, PITCH, FOV = 0.0, 0.0, 70.0
def stamped_run(label: str, command: list[str], log: Path, env: dict[str, str]) -> str:
probe = subprocess.run([*LIMIT, "timeout", "15s", str(command[0]), "--print-source-stamp"],
cwd=ROOT, env=env, text=True, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, check=False, timeout=20)
match = STAMP_RE.search(probe.stdout or "")
if probe.returncode != 0 or not match or match.group(1) != STAMP:
raise RuntimeError(f"{label}: source stamp mismatch")
log.parent.mkdir(parents=True, exist_ok=True)
started = time.monotonic()
with log.open("w", encoding="utf-8") as stream:
result = subprocess.run([*LIMIT, "timeout", "1740s", *map(str, command)],
cwd=ROOT, env=env, stdout=stream, stderr=subprocess.STDOUT,
timeout=1745, check=False)
content = log.read_text(encoding="utf-8", errors="replace")
emitted = STAMP_RE.search(content)
if result.returncode != 0 or not emitted or emitted.group(1) != STAMP:
raise RuntimeError(f"{label}: process failed or source changed; see {log}")
print(f"[L139WholeLayerCapture] step={label} elapsed_s={time.monotonic()-started:.1f} "
f"log={log.relative_to(ROOT)}", flush=True)
return content
def main() -> int:
if not DATA.is_file() or not BIN.is_file() or not RENDERER.is_file():
raise SystemExit("missing owner fixture or final L139 capture binaries")
OUT.mkdir(parents=True, exist_ok=True)
ARTIFACTS.mkdir(parents=True, exist_ok=True)
env = os.environ.copy()
env["LP_NUM_THREADS"] = "12"
env["XDG_CACHE_HOME"] = str(OUT / "xdg-cache")
stem = "whole-layer-density-cutaway"
mesh = OUT / f"{stem}.vfmesh"
layout = OUT / f"{stem}-layout.json"
camera_text = ",".join(f"{value:.6f}" for value in (*CAMERA, YAW, PITCH))
export = [BIN, DATA, "--world-assembler-prototype", "--stream-benchmark",
"--stream-pipeline", "--workers=12", "--clip-radius=3", "--max-clip-level=8",
"--max-level=8", "--force-generate", "--seed=0", "--recipe=surface-world",
"--render-distance-chunks=0", "--capture-skip-walkability", "--density-dispatch-report",
f"--camera={camera_text}", f"--resolved-layout-output={layout}", f"--mesh-export={mesh}"]
content = stamped_run("export", export, OUT / f"{stem}-export.log", env)
dispatch = re.search(
r"^\[DensityDispatch\] strate=3 generator=VerticalShafts "
r"recipe=Core\.Caves\.PrimitiveField version=1 "
r"bound_path=Caves-native adapter=FCavesFieldAdapter$", content, re.M)
if not dispatch or not mesh.is_file() or mesh.stat().st_size == 0:
raise RuntimeError("coarse density export is empty or lacks native shaft dispatch")
png = OUT / f"{stem}.png"
jpg = ARTIFACTS / "after-whole-layer-density-cutaway.jpg"
render = [RENDERER, mesh, png, "--size=2200x320", f"--fov-deg={FOV:.1f}",
"--camera-m=" + ",".join(f"{value:.6f}" for value in CAMERA),
f"--yaw-deg={YAW:.6f}", f"--pitch-deg={PITCH:.6f}",
f"--clip-plane=0,-1,0,{SLICE_Y:.6f}", "--tile-skirts=off", "--shade=readable",
"--headlamp=on", "--height-tint=off", "--height-center-m=-950",
"--height-range-m=80", "--tile-edges=off"]
stamped_run("render", render, OUT / f"{stem}-render.log", env)
subprocess.run([*LIMIT, "timeout", "120s", "ffmpeg", "-nostdin", "-hide_banner",
"-loglevel", "error", "-y", "-i", png, "-frames:v", "1", "-q:v", "3", jpg],
cwd=ROOT, env=env, check=True, stdout=subprocess.DEVNULL,
stderr=subprocess.STDOUT, timeout=125)
quality = subprocess.run([*LIMIT, "timeout", "120s", sys.executable,
ROOT / "Standalone/Tools/verify/capture_quality.py", jpg], cwd=ROOT, env=env,
text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
check=False, timeout=125)
quality_line = quality.stdout.strip().splitlines()[-1] if quality.stdout.strip() else ""
ok = quality.returncode == 0 and "status=PASS" in quality_line
manifest_path = ARTIFACTS / "manifest.json"
manifest = json.loads(manifest_path.read_text(encoding="utf-8")) if manifest_path.is_file() else []
manifest = [row for row in manifest if row.get("view") != "whole-layer-density-cutaway"]
manifest.append({"phase": "after", "view": "whole-layer-density-cutaway",
"file": str(jpg.relative_to(ROOT)), "bytes": jpg.stat().st_size,
"camera_m": CAMERA, "target_m": (-450.0, SLICE_Y, -950.0),
"yaw_deg": YAW, "pitch_deg": PITCH, "fov_deg": FOV,
"clip_plane": [0.0, -1.0, 0.0, SLICE_Y],
"note": "outside side cutaway through the coarse LOD density mesh across the whole Vertical Shafts strate at y=-5 m",
"quality_status": "PASS" if ok else "FAIL", "quality": quality_line})
manifest_path.write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8")
print(f"[L139WholeLayerCaptureSummary] file={jpg.relative_to(ROOT)} "
f"quality={'PASS' if ok else 'FAIL'} {quality_line}", flush=True)
return 0 if ok else 1
if __name__ == "__main__":
raise SystemExit(main())
Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

@@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1800" height="760" viewBox="0 0 1800 760">
<rect width="1800" height="760" fill="#07111a"/>
<text x="72" y="42" fill="#edf6fb" font-family="sans-serif" font-size="24">Vertical Shafts — whole-layer side elevation</text>
<text x="72" y="64" fill="#9bb5c5" font-family="sans-serif" font-size="13">Resolved plan • seed 0 • strate 3 • vertical axis and route shown in metres • Y projected out</text>
<line x1="72.0" y1="-1440.6" x2="1728.0" y2="-1440.6" stroke="#315060" stroke-dasharray="5 8"/>
<rect x="1707.84" y="284.18" width="4.65" height="191.65" rx="2.33" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="1667.56" y="288.45" width="5.72" height="187.37" rx="2.86" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="1245.04" y="344.01" width="6.24" height="131.81" rx="3.12" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="1206.94" y="349.61" width="5.68" height="126.22" rx="2.84" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="1188.40" y="352.41" width="4.37" height="123.41" rx="2.18" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="1168.07" y="355.22" width="6.64" height="120.60" rx="3.32" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="1130.81" y="360.85" width="4.40" height="114.97" rx="2.20" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="1092.31" y="366.48" width="4.62" height="109.35" rx="2.31" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="1072.90" y="369.29" width="5.04" height="106.54" rx="2.52" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="1053.43" y="372.09" width="5.61" height="103.74" rx="2.80" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="1034.94" y="374.88" width="4.20" height="100.94" rx="2.10" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="1015.20" y="377.67" width="5.28" height="98.16" rx="2.64" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="1648.28" y="290.59" width="5.89" height="185.23" rx="2.95" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="995.75" y="380.44" width="5.81" height="95.38" rx="2.90" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="976.77" y="383.20" width="5.37" height="92.62" rx="2.68" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="938.19" y="388.67" width="5.75" height="87.15" rx="2.88" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="918.75" y="391.38" width="6.25" height="84.44" rx="3.13" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="896.89" y="394.44" width="6.22" height="81.38" rx="3.11" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="880.62" y="396.74" width="5.74" height="79.09" rx="2.87" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="862.36" y="399.38" width="3.87" height="76.45" rx="1.94" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="842.60" y="401.99" width="5.01" height="73.83" rx="2.50" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="804.64" y="407.14" width="4.16" height="68.69" rx="2.08" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="785.14" y="409.66" width="4.77" height="66.16" rx="2.39" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="1629.77" y="292.79" width="4.53" height="183.04" rx="2.26" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="765.24" y="412.16" width="6.18" height="63.67" rx="3.09" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="746.73" y="414.61" width="4.82" height="61.21" rx="2.41" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="727.40" y="417.04" width="5.08" height="58.79" rx="2.54" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="707.91" y="419.42" width="5.68" height="56.41" rx="2.84" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="669.16" y="424.06" width="6.41" height="51.77" rx="3.20" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="650.97" y="426.31" width="4.40" height="49.51" rx="2.20" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="593.65" y="432.79" width="3.89" height="43.04" rx="1.94" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="573.84" y="434.85" width="5.11" height="40.98" rx="2.56" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="478.04" y="444.31" width="4.78" height="31.52" rx="2.39" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="440.09" y="447.66" width="3.90" height="28.16" rx="1.95" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="1590.94" y="297.31" width="5.40" height="178.52" rx="2.70" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="420.88" y="449.24" width="3.93" height="26.58" rx="1.97" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rLine truncated
<path d="M1728.00,290.01 L1716.48,291.19 L1716.48,291.19 L1710.17,291.85 L1710.17,291.85 L1689.61,294.04 L1689.61,294.04 L1670.42,296.13 L1670.42,296.13 L1651.23,298.27 L1651.23,298.27 L1632.03,300.46 L1632.03,300.46 L1612.84,302.70 L1612.84,302.70 L1593.65,304.98 L1593.65,304.98 L1574.45,307.31 L1574.45,307.31 L1555.26,309.68 L1555.26,309.68 L1536.07,312.09 L1536.07,312.09 L1516.87,314.53 L1516.87,314.53 L1497.68,317.01 L1497.68,317.01 L1478.49,319.53 L1478.49,319.53 L1459.29,322.07 L1459.29,322.07 L1440.10,324.65 L1440.10,324.65 L1420.91,327.26 L1420.91,327.26 L1401.71,329.89 L1401.71,329.89 L1382.52,332.55 L1382.52,332.55 L1363.33,335.23 L1363.33,335.23 L1344.13,337.93 L1344.13,337.93 L1324.94,340.65 L1324.94,340.65 L1305.74,343.39 L1305.74,343.39 L1286.55,346.14 L1286.55,346.14 L1267.36,348.91 L1267.36,348.91 L1248.16,351.69 L1248.16,351.69 L1228.97,354.48 L1228.97,354.48 L1209.78,357.28 L1209.78,357.28 L1190.58,360.09 L1190.58,360.09 L1171.39,362.90 L1171.39,362.90 L1152.20,365.71 L1152.20,365.71 L1133.00,368.53 L1133.00,368.53 L1113.81,371.34 L1113.81,371.34 L1094.62,374.16 L1094.62,374.16 L1075.42,376.97 L1075.42,376.97 L1056.23,379.77 L1056.23,379.77 L1037.04,382.56 L1037.04,382.56 L1017.84,385.35 L1017.84,385.35 L998.65,388.12 L998.65,388.12 L979.46,390.88 L979.46,390.88 L960.26,393.62 L960.26,393.62 L941.07,396.35 L941.07,396.35 L921.88,399.06 L921.88,399.06 L902.68,401.75 L902.68,401.75 L900.00,402.12 L900.00,402.12 L883.49,404.41 L883.49,404.41 L864.30,407.05 L864.30,407.05 L845.10,409.67 L845.10,409.67 L825.91,412.26 L825.91,412.26 L806.72,414.82 L806.72,414.82 L787.52,417.34 L787.52,417.34 L768.33,419.83 L768.33,419.83 L749.14,422.29 L749.14,422.29 L729.94,424.71 L729.94,424.71 L710.75,427.09 L710.75,427.09 L691.56,429.44 L691.56,429.44 L672.36,431.73 L672.36,431.73 L653.17,433.99 L653.17,433.99 L633.98,436.20 L633.98,436.20 L614.78,438.36 L614.78,438.36 L595.59,440.47 L595.59,440.47 L576.39,442.53 L576.39,442.53 L557.20,444.53 L557.20,444.53 L538.01,446.48 L538.01,446.48 L518.81,448.37 L518.81,448.37 L499.62,450.21 L499.62,450.21 L480.43,451.98 L480.43,451.98 L461.23,453.69 L461.23,453.69 L442.04,455.34 L442.04,455.34 L422.85,456.92 L422.85,456.92 L403.65,458.43 L403.65,458.43 L384.46,459.88 L384.46,459.88 L365.27,461.25 L365.27,461.25 L346.07,462.55 L346.07,462.55 L326.88,463.77 L326.88,463.77 L307.69,464.91 L307.69,464.91 L288.49,465.98 L288.49,465.98 L269.30,466.97 L269.30,466.97 L250.11,467.87 L250.11,467.87 L230.91,468.69 L230.91,468.69 L211.72,469.42 L211.72,469.42 L192.53,470.06 L192.53,470.06 L173.33,470.62 L173.33,470.62 L154.14,471.08 L154.14,471.08 L134.95,471.45 L134.95,471.45 L115.75,471.73 L115.75,471.73 L96.56,471.90 L96.56,471.90 L89.83,471.94 L89.83,471.94 L83.52,471.97 L83.52,471.97 L77.37,471.98 L77.37,471.98 L72.00,471.99" fill="none" stroke="#f4bf68" stroke-width="3.0" stroke-linecap="round" stroke-linejoin="round"/>
<text x="82" y="716" fill="#f4bf68" font-family="sans-serif" font-size="15">walk-only graded spine and landings</text>
<rect x="416" y="704" width="15" height="15" fill="#93dded" fill-opacity="0.65" stroke="#42b8d1"/>
<text x="440" y="716" fill="#b8dce5" font-family="sans-serif" font-size="15">open vertical shaft primitives (53)</text>
<text x="1718" y="716" fill="#9bb5c5" text-anchor="end" font-family="sans-serif" font-size="12">Plan projection, not a density mesh</text>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

@@ -0,0 +1,258 @@
[
{
"phase": "before",
"view": "arrival-mouth",
"file": "Standalone/Research/WorldAssemblerM3/Linux139/captures/before-arrival-mouth.jpg",
"bytes": 21788,
"camera_m": [
-20.0625,
-5.068359,
-900.237499
],
"target_m": [
-30.0625,
-5.068359,
-900.237499
],
"yaw_deg": -90.0,
"pitch_deg": 0.0,
"fov_deg": 78.0,
"note": "player eye at the passage arrival, looking into the connected layer",
"quality_status": "PASS",
"quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux139/captures/before-arrival-mouth.jpg pixels=921600 mean_luma=110.102 stddev_luma=3.380 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS"
},
{
"phase": "before",
"view": "landing-at-shaft-access",
"file": "Standalone/Research/WorldAssemblerM3/Linux139/captures/before-landing-at-shaft-access.jpg",
"bytes": 15114,
"camera_m": [
-31.354577,
-5.068359,
-901.1982009999999
],
"target_m": [
-31.354577,
-9.889916,
-901.1982009999999
],
"yaw_deg": 180.0,
"pitch_deg": 0.0,
"fov_deg": 78.0,
"note": "walkable route landing and short shaft access tunnel",
"quality_status": "PASS",
"quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux139/captures/before-landing-at-shaft-access.jpg pixels=921600 mean_luma=110.563 stddev_luma=3.056 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS"
},
{
"phase": "before",
"view": "looking-down-shaft",
"file": "Standalone/Research/WorldAssemblerM3/Linux139/captures/before-looking-down-shaft.jpg",
"bytes": 11005,
"camera_m": [
-31.354577,
-9.889916,
-901.1982009999999
],
"target_m": [
-31.354577,
-10.852462,
-928.6482010000001
],
"yaw_deg": 180.0,
"pitch_deg": -87.99172181188044,
"fov_deg": 35.0,
"note": "player eye at the upper access landing, looking down the open shaft",
"quality_status": "FAIL",
"quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux139/captures/before-looking-down-shaft.jpg pixels=921600 mean_luma=112.000 stddev_luma=0.000 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=FAIL"
},
{
"phase": "before",
"view": "looking-up-from-ledge",
"file": "Standalone/Research/WorldAssemblerM3/Linux139/captures/before-looking-up-from-ledge.jpg",
"bytes": 15507,
"camera_m": [
-31.354577,
-10.852462,
-945.198201
],
"target_m": [
-31.354577,
-10.852462,
-900.6482010000001
],
"yaw_deg": 0.0,
"pitch_deg": 90.0,
"fov_deg": 78.0,
"note": "player eye at an internal ledge, looking up; reaching this ledge is a climb tier",
"quality_status": "FAIL",
"quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux139/captures/before-looking-up-from-ledge.jpg pixels=921600 mean_luma=111.693 stddev_luma=1.515 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=FAIL"
},
{
"phase": "before",
"view": "basal-terminal-landing",
"file": "Standalone/Research/WorldAssemblerM3/Linux139/captures/before-basal-terminal-landing.jpg",
"bytes": 13933,
"camera_m": [
-884.858259,
-5.068359,
-995.05
],
"target_m": [
-892.858259,
-5.068359,
-995.05
],
"yaw_deg": -90.0,
"pitch_deg": 0.0,
"fov_deg": 78.0,
"note": "walkable terminal landing above the basal seal",
"quality_status": "PASS",
"quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux139/captures/before-basal-terminal-landing.jpg pixels=921600 mean_luma=108.996 stddev_luma=6.932 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS"
},
{
"phase": "after",
"view": "arrival-mouth",
"file": "Standalone/Research/WorldAssemblerM3/Linux139/captures/after-arrival-mouth.jpg",
"bytes": 19044,
"camera_m": [
-20.0625,
-5.068359,
-900.237499
],
"target_m": [
-30.0625,
-5.068359,
-900.237499
],
"yaw_deg": -90.0,
"pitch_deg": 0.0,
"fov_deg": 78.0,
"note": "player eye at the passage arrival, looking into the connected layer",
"quality_status": "PASS",
"quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux139/captures/after-arrival-mouth.jpg pixels=921600 mean_luma=125.004 stddev_luma=16.734 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS"
},
{
"phase": "after",
"view": "landing-at-shaft-access",
"file": "Standalone/Research/WorldAssemblerM3/Linux139/captures/after-landing-at-shaft-access.jpg",
"bytes": 15595,
"camera_m": [
-31.354577,
-5.068359,
-901.1982009999999
],
"target_m": [
-31.354577,
-9.889916,
-901.1982009999999
],
"yaw_deg": 180.0,
"pitch_deg": 0.0,
"fov_deg": 78.0,
"note": "walkable route landing and short shaft access tunnel",
"quality_status": "PASS",
"quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux139/captures/after-landing-at-shaft-access.jpg pixels=921600 mean_luma=117.622 stddev_luma=14.149 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS"
},
{
"phase": "after",
"view": "looking-down-shaft",
"file": "Standalone/Research/WorldAssemblerM3/Linux139/captures/after-looking-down-shaft.jpg",
"bytes": 24396,
"camera_m": [
-31.354577,
-9.889916,
-901.1982009999999
],
"target_m": [
-31.354577,
-10.852462,
-928.6482010000001
],
"yaw_deg": 180.0,
"pitch_deg": -87.99172181188044,
"fov_deg": 35.0,
"note": "player eye at the upper access landing, looking down the open shaft",
"quality_status": "PASS",
"quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux139/captures/after-looking-down-shaft.jpg pixels=921600 mean_luma=112.365 stddev_luma=3.113 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS"
},
{
"phase": "after",
"view": "looking-up-from-ledge",
"file": "Standalone/Research/WorldAssemblerM3/Linux139/captures/after-looking-up-from-ledge.jpg",
"bytes": 23172,
"camera_m": [
-31.354577,
-10.852462,
-945.198201
],
"target_m": [
-31.354577,
-10.852462,
-900.6482010000001
],
"yaw_deg": 0.0,
"pitch_deg": 90.0,
"fov_deg": 78.0,
"note": "player eye at an internal ledge, looking up; reaching this ledge is a climb tier",
"quality_status": "PASS",
"quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux139/captures/after-looking-up-from-ledge.jpg pixels=921600 mean_luma=111.549 stddev_luma=2.538 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS"
},
{
"phase": "after",
"view": "basal-terminal-landing",
"file": "Standalone/Research/WorldAssemblerM3/Linux139/captures/after-basal-terminal-landing.jpg",
"bytes": 12527,
"camera_m": [
-884.858259,
-5.068359,
-995.05
],
"target_m": [
-892.858259,
-5.068359,
-995.05
],
"yaw_deg": -90.0,
"pitch_deg": 0.0,
"fov_deg": 78.0,
"note": "walkable terminal landing above the basal seal",
"quality_status": "PASS",
"quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux139/captures/after-basal-terminal-landing.jpg pixels=921600 mean_luma=125.238 stddev_luma=3.555 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS"
},
{
"phase": "after",
"view": "whole-layer-side-elevation",
"file": "Standalone/Research/WorldAssemblerM3/Linux139/captures/after-whole-layer-side-elevation.svg",
"shafts": 53,
"walk_route_edges": 91,
"note": "resolved-plan side projection; not a density-mesh capture"
},
{
"phase": "after",
"view": "whole-layer-density-cutaway",
"file": "Standalone/Research/WorldAssemblerM3/Linux139/captures/after-whole-layer-density-cutaway.jpg",
"bytes": 20280,
"camera_m": [
-450.0,
-105.0,
-950.0
],
"target_m": [
-450.0,
-5.0,
-950.0
],
"yaw_deg": 0.0,
"pitch_deg": 0.0,
"fov_deg": 70.0,
"clip_plane": [
0.0,
-1.0,
0.0,
-5.0
],
"note": "outside side cutaway through the coarse LOD density mesh across the whole Vertical Shafts strate at the denser shaft row",
"quality_status": "PASS",
"quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux139/captures/after-whole-layer-density-cutaway.jpg pixels=704000 mean_luma=113.286 stddev_luma=3.364 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS"
}
]
+28 -9
View File
@@ -5147,6 +5147,7 @@ namespace StandaloneStreamBenchmark
}
std::uint64_t routePoints=0,routePasses=0,routeWalkPasses=0,routeBindings=0,routeBindingFailures=0;
std::uint64_t tunnelNetworkRoutes=0,tunnelNetworkBlockedControls=0;
std::uint64_t verticalShaftRoutes=0,verticalShaftBlockedControls=0;
for(const auto& stratum:ResolvedLayout->strates)
{
std::vector<const VoxelForgeCore::ResolvedLayoutAnchorV2*> anchors;
@@ -5206,7 +5207,8 @@ namespace StandaloneStreamBenchmark
supportMaximumMetres=routeConstraints->supportSearchDepthSubvoxels*qMetres;
}
}
if(stratum.generatorKind=="TunnelNetwork")
if(stratum.generatorKind=="TunnelNetwork"
||stratum.generatorKind=="VerticalShafts")
{
std::int64_t floorSupportDepth=0;
for(const auto& edge:ResolvedLayout->cavernEdges)
@@ -5297,9 +5299,12 @@ namespace StandaloneStreamBenchmark
stratum.strateIndex,routeAudit.firstUnsupported[i][0],
routeAudit.firstUnsupported[i][1],routeAudit.firstUnsupported[i][2],
routeAudit.firstUnsupported[i][3]);
if(stratum.generatorKind=="TunnelNetwork")
if(stratum.generatorKind=="TunnelNetwork"
||stratum.generatorKind=="VerticalShafts")
{
++tunnelNetworkRoutes;
const bool verticalShafts=stratum.generatorKind=="VerticalShafts";
if(verticalShafts)++verticalShaftRoutes;
else ++tunnelNetworkRoutes;
auto controlRoute=route;
constexpr double OffBoreControlOffsetMetres=12.0;
const std::size_t controlIndex=controlRoute.size()/2;
@@ -5319,11 +5324,22 @@ namespace StandaloneStreamBenchmark
supportStartMetres,supportStepMetres,supportMaximumMetres,
minimumSupportProbes,supportProbeRadiusMetres);
const bool detected=control.blocked>0&&!control.passed;
tunnelNetworkBlockedControls+=detected;
std::printf("[TunnelNetworkBlockedRouteControl] strate=%d mode=off_bore_lateral_deviation_m=%.1f blocked_capsule_samples=%llu result=%s expected=FAIL status=%s\n",
stratum.strateIndex,OffBoreControlOffsetMetres,
static_cast<unsigned long long>(control.blocked),
control.passed?"PASS":"FAIL",detected?"PASS":"FAIL");
if(verticalShafts)
{
verticalShaftBlockedControls+=detected;
std::printf("[VerticalShaftsBlockedRouteControl] strate=%d mode=off_bore_lateral_deviation_m=%.1f blocked_capsule_samples=%llu result=%s expected=FAIL status=%s\n",
stratum.strateIndex,OffBoreControlOffsetMetres,
static_cast<unsigned long long>(control.blocked),
control.passed?"PASS":"FAIL",detected?"PASS":"FAIL");
}
else
{
tunnelNetworkBlockedControls+=detected;
std::printf("[TunnelNetworkBlockedRouteControl] strate=%d mode=off_bore_lateral_deviation_m=%.1f blocked_capsule_samples=%llu result=%s expected=FAIL status=%s\n",
stratum.strateIndex,OffBoreControlOffsetMetres,
static_cast<unsigned long long>(control.blocked),
control.passed?"PASS":"FAIL",detected?"PASS":"FAIL");
}
}
}
for(std::size_t passageIndex=0;
@@ -5392,8 +5408,9 @@ namespace StandaloneStreamBenchmark
&&routePasses>0&&routePasses==routeWalkPasses&&routePasses==routeBindings
&&routeBindingFailures==0
&&tunnelNetworkRoutes==tunnelNetworkBlockedControls
&&verticalShaftRoutes==verticalShaftBlockedControls
&&blockedCompiled&&blockedControls==passageCount;
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 tunnel_network_block_controls=%llu/%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 tunnel_network_block_controls=%llu/%llu vertical_shaft_block_controls=%llu/%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>(naturalPasses),static_cast<unsigned long long>(passageCount),
static_cast<unsigned long long>(routePasses),static_cast<unsigned long long>(routeWalkPasses),
@@ -5401,6 +5418,8 @@ namespace StandaloneStreamBenchmark
static_cast<unsigned long long>(routeBindings),static_cast<unsigned long long>(routeBindingFailures),
static_cast<unsigned long long>(tunnelNetworkBlockedControls),
static_cast<unsigned long long>(tunnelNetworkRoutes),
static_cast<unsigned long long>(verticalShaftBlockedControls),
static_cast<unsigned long long>(verticalShaftRoutes),
static_cast<unsigned long long>(blockedControls),static_cast<unsigned long long>(passageCount),
auditPassed?"PASS":"FAIL");
if(!auditPassed)
+107 -36
View File
@@ -1517,13 +1517,16 @@ namespace StandaloneWorldAssemblyPrototype
{
using namespace VoxelForgeCore;
if(!owner.Manager)
{error="Tunnel Network cave planner requires the initialized owner director";return false;}
std::vector<const FStrateSlot*> tunnelSlots;
{error="native cave planner requires the initialized owner director";return false;}
std::vector<std::pair<const FStrateSlot*,bool>> plannerSlots;
for(const FStrateSlot& slot:owner.Manager->GetLayout())
{
if(!slot.Definition
||owner.Manager->GetGeneratorTypeForChunk(FIntVector(0,0,slot.TopChunkZ))
!=ECaveGeneratorType::TunnelNetwork)continue;
if(!slot.Definition)continue;
const ECaveGeneratorType generatorType=
owner.Manager->GetGeneratorTypeForChunk(FIntVector(0,0,slot.TopChunkZ));
const bool tunnelNetwork=generatorType==ECaveGeneratorType::TunnelNetwork;
const bool verticalShafts=generatorType==ECaveGeneratorType::VerticalShafts;
if(!tunnelNetwork&&!verticalShafts)continue;
const bool hasIncomingMouth=std::any_of(layout.passages.begin(),
layout.passages.end(),[&](const auto& passage)
{return passage.lowerStrateIndex==slot.StrateIndex
@@ -1532,24 +1535,27 @@ namespace StandaloneWorldAssemblyPrototype
layout.passages.end(),[&](const auto& passage)
{return passage.upperStrateIndex==slot.StrateIndex
&&passage.lowerStrateIndex>slot.StrateIndex;});
// A native planner route requires two mouths. Boundary Tunnel Network
// strates keep their legacy field until they have both connections.
if(!hasIncomingMouth||!hasOutgoingMouth)continue;
// Tunnel Network connects two passages. The basal Vertical Shafts
// stratum is terminal: it inherits its arrival from the preceding
// strate and intentionally has no departure passage.
if((tunnelNetwork&&(!hasIncomingMouth||!hasOutgoingMouth))
||(verticalShafts&&(!hasIncomingMouth||hasOutgoingMouth)))continue;
auto stratum=std::find_if(layout.strates.begin(),layout.strates.end(),
[&](const auto& item){return item.strateIndex==slot.StrateIndex;});
if(stratum==layout.strates.end())
{error="Tunnel Network strate is missing from the resolved layout";return false;}
stratum->generatorKind="TunnelNetwork";
stratum->generatorKind=verticalShafts?"VerticalShafts":"TunnelNetwork";
stratum->recipeId="Core.Caves.PrimitiveField";
stratum->recipeVersion=1;
stratum->stableId=layout.worldIdentity+"/strate/"
+std::to_string(slot.StrateIndex)+"/generator/TunnelNetwork";
+std::to_string(slot.StrateIndex)+"/generator/"
+(verticalShafts?"VerticalShafts":"TunnelNetwork");
const bool alreadyPlanned=std::any_of(layout.cavernRooms.begin(),
layout.cavernRooms.end(),[&](const auto& room)
{return room.strateIndex==slot.StrateIndex;})
&&std::any_of(layout.cavernEdges.begin(),layout.cavernEdges.end(),
[&](const auto& edge){return edge.strateIndex==slot.StrateIndex;});
if(!alreadyPlanned)tunnelSlots.push_back(&slot);
if(!alreadyPlanned)plannerSlots.emplace_back(&slot,verticalShafts);
}
if(!layout.finalize(error))return false;
const auto toMicrometres=[&](double ownerVoxels)
@@ -1562,14 +1568,55 @@ namespace StandaloneWorldAssemblyPrototype
return static_cast<std::int32_t>(std::llround(std::clamp(
static_cast<double>(value),0.0,1.0)*65536.0));
};
if(!tunnelSlots.empty())
if(!plannerSlots.empty())
{
std::vector<ResolvedCavernRoomV1> plannedRooms=layout.cavernRooms;
std::vector<ResolvedCavernEdgeV1> plannedEdges=layout.cavernEdges;
for(const FStrateSlot* slot:tunnelSlots)
for(const auto& [slot,verticalShafts]:plannerSlots)
{
const FStrateGenerationParams& params=slot->Definition->GenerationParams;
ResolvedCavernPlannerSettingsV1 settings;
if(verticalShafts)
{
const FVerticalShaftParams& shaft=slot->Definition->VerticalShaftParams;
settings.verticalShaftMode=true;
settings.roomSpacingMicrometres=std::clamp<std::int64_t>(
toMicrometres(shaft.ShaftSpacing),4000000,100000000);
settings.minimumRoomRadiusMicrometres=std::max<std::int64_t>(
500000,toMicrometres(shaft.ShaftMinRadius));
settings.maximumRoomRadiusMicrometres=std::max(
settings.minimumRoomRadiusMicrometres,
toMicrometres(shaft.ShaftMaxRadius));
settings.destinationRadiusMicrometres=std::max(
settings.maximumRoomRadiusMicrometres,
settings.maximumRoomRadiusMicrometres*135/100);
settings.minimumTunnelRadiusMicrometres=std::max<std::int64_t>(0,
toMicrometres(shaft.ConnectorRadius));
settings.maximumTunnelRadiusMicrometres=
settings.minimumTunnelRadiusMicrometres;
settings.maximumTunnelLengthMicrometres=std::clamp<std::int64_t>(
settings.roomSpacingMicrometres*2,4000000,100000000);
settings.smoothWidthMicrometres=1500000;
settings.floorSupportDepthMicrometres=2000000;
settings.irregularityAmplitudeMicrometres=std::max<std::int64_t>(0,
toMicrometres(shaft.SurfaceRoughness));
settings.irregularityWavelengthMicrometres=std::max<std::int64_t>(
500000,static_cast<std::int64_t>(owner.OwnerVoxelSizeMicrons)*4);
settings.roomDensityQ16=toQ16(shaft.ShaftDensity);
settings.tunnelDensityQ16=toQ16(shaft.CrossConnectChance);
settings.roomShapeVarietyQ16=0;
settings.roomHeightRatioQ16=65536;
settings.roomFloorCutMinimumQ16=0;
settings.roomFloorCutMaximumQ16=0;
settings.tunnelHorizontalBiasQ16=65536;
settings.originRoomMaximumConnections=32;
settings.ledgeSpacingMicrometres=std::max<std::int64_t>(0,
toMicrometres(shaft.LedgeSpacing));
settings.ledgeDepthMicrometres=std::max<std::int64_t>(0,
toMicrometres(shaft.LedgeDepth));
}
else
{
settings.roomSpacingMicrometres=std::max<std::int64_t>(4000000,
toMicrometres(params.RoomSpacing));
settings.minimumRoomRadiusMicrometres=std::max<std::int64_t>(500000,
@@ -1607,6 +1654,7 @@ namespace StandaloneWorldAssemblyPrototype
settings.tunnelHorizontalBiasQ16=toQ16(params.TunnelHorizontalBias);
settings.originRoomMaximumConnections=params.OriginRoomMaxConnections;
settings.tunnelsFlowTowardOrigin=params.bTunnelsFlowTowardOrigin;
}
std::vector<ResolvedCavernRoomV1> rooms;
std::vector<ResolvedCavernEdgeV1> edges;
if(!PlanResolvedCavernNetworkV1(layout,owner.Seed,slot->StrateIndex,
@@ -1629,24 +1677,27 @@ namespace StandaloneWorldAssemblyPrototype
constexpr std::int64_t PlayerCenterAboveFloorMicrometres=1130000;
for(const FStrateSlot& slot:owner.Manager->GetLayout())
{
if(!slot.Definition
||owner.Manager->GetGeneratorTypeForChunk(FIntVector(0,0,slot.TopChunkZ))
!=ECaveGeneratorType::TunnelNetwork)continue;
if(!slot.Definition)continue;
const ECaveGeneratorType generatorType=
owner.Manager->GetGeneratorTypeForChunk(FIntVector(0,0,slot.TopChunkZ));
const bool verticalShafts=generatorType==ECaveGeneratorType::VerticalShafts;
if(generatorType!=ECaveGeneratorType::TunnelNetwork&&!verticalShafts)continue;
auto stratum=std::find_if(layout.strates.begin(),layout.strates.end(),
[&](const auto& item){return item.strateIndex==slot.StrateIndex;});
if(stratum==layout.strates.end()
||!IsNativeCaveFieldStratum(*stratum))continue;
const std::string routePrefix="tunnel-network/strate-"
const std::string routePrefix=(verticalShafts?"vertical-shafts/strate-"
:"tunnel-network/strate-")
+std::to_string(slot.StrateIndex)+"/";
const bool hasPlannedArrival=std::any_of(layout.cavernEdges.begin(),
layout.cavernEdges.end(),[&](const auto& edge)
{return edge.strateIndex==slot.StrateIndex
&&edge.stableId==routePrefix+"route-edge-0";});
const bool hasPlannedDeparture=std::any_of(layout.cavernEdges.begin(),
layout.cavernEdges.end(),[&](const auto& edge)
{return edge.strateIndex==slot.StrateIndex
const bool hasPlannedDeparture=!verticalShafts
&&std::any_of(layout.cavernEdges.begin(),layout.cavernEdges.end(),
[&](const auto& edge){return edge.strateIndex==slot.StrateIndex
&&edge.stableId==routePrefix+"departure-edge";});
if(!hasPlannedArrival||!hasPlannedDeparture)continue;
if(!hasPlannedArrival||(!verticalShafts&&!hasPlannedDeparture))continue;
// Route-point anchors also drive the passage-mouth floor blend. The
// native cave planner replaces the legacy finite-strate route, so
// retaining its old anchors would join the passage floor to a route
@@ -1663,13 +1714,14 @@ namespace StandaloneWorldAssemblyPrototype
if(passage.upperStrateIndex==slot.StrateIndex
&&passage.lowerStrateIndex>slot.StrateIndex)departure=&passage;
}
if(!arrival||!departure)
{error="Tunnel Network route anchors cannot bind both passage mouths";return false;}
if(!arrival||(!verticalShafts&&!departure))
{error="native cave route anchors cannot bind their required passage mouths";return false;}
const auto appendRoutePoint=[&](std::string id,
const std::array<std::int64_t,3>& point)
{
ResolvedLayoutAnchorV2 anchor;anchor.kind="route_point";
anchor.stableId="tunnel-network/route/"+std::move(id);
anchor.stableId=(verticalShafts?"vertical-shafts/route/"
:"tunnel-network/route/")+std::move(id);
anchor.units="1/256 Core voxel";
anchor.values={point[0],point[1],point[2]};
stratum->anchors.push_back(std::move(anchor));
@@ -1684,14 +1736,23 @@ namespace StandaloneWorldAssemblyPrototype
appendRoutePoint("000-arrival",start);
for(std::size_t edgeIndex=0;edgeIndex<256;++edgeIndex)
{
const std::string stableId="tunnel-network/strate-"
+std::to_string(slot.StrateIndex)+"/route-edge-"
const std::string stableId=routePrefix+"route-edge-"
+std::to_string(edgeIndex);
const auto edge=std::find_if(layout.cavernEdges.begin(),
layout.cavernEdges.end(),[&](const auto& value)
{return value.strateIndex==slot.StrateIndex
&&value.stableId==stableId;});
if(edge==layout.cavernEdges.end())break;
bool terminalRouteEdge=false;
if(verticalShafts)
{
const std::string nextRouteEdge=routePrefix+"route-edge-"
+std::to_string(edgeIndex+1);
terminalRouteEdge=std::none_of(layout.cavernEdges.begin(),
layout.cavernEdges.end(),[&](const auto& candidate)
{return candidate.strateIndex==slot.StrateIndex
&&candidate.stableId==nextRouteEdge;});
}
if(edgeIndex==0)
{
auto inner=edge->controlPointsMicrometres.front();
@@ -1708,18 +1769,20 @@ namespace StandaloneWorldAssemblyPrototype
}
auto point=edge->controlPointsMicrometres.back();
point[2]+=PlayerCenterAboveFloorMicrometres;
appendRoutePoint(std::to_string(edgeIndex+1),
appendRoutePoint(terminalRouteEdge?"999-basal-terminal"
:std::to_string(edgeIndex+1),
{toCoreSubvoxels(point[0]),toCoreSubvoxels(point[1]),
toCoreSubvoxels(point[2])});
}
const std::string departureEdgeId="tunnel-network/strate-"
+std::to_string(slot.StrateIndex)+"/departure-edge";
const auto departureEdge=std::find_if(layout.cavernEdges.begin(),
layout.cavernEdges.end(),[&](const auto& value)
{return value.strateIndex==slot.StrateIndex
&&value.stableId==departureEdgeId;});
if(departureEdge!=layout.cavernEdges.end())
if(departure)
{
const std::string departureEdgeId=routePrefix+"departure-edge";
const auto departureEdge=std::find_if(layout.cavernEdges.begin(),
layout.cavernEdges.end(),[&](const auto& value)
{return value.strateIndex==slot.StrateIndex
&&value.stableId==departureEdgeId;});
if(departureEdge==layout.cavernEdges.end())
{error="native cave route lost its planned departure edge";return false;}
auto inner=departureEdge->controlPointsMicrometres.front();
inner[2]+=PlayerCenterAboveFloorMicrometres;
const std::array<std::int64_t,3> innerPoint={
@@ -1728,9 +1791,17 @@ namespace StandaloneWorldAssemblyPrototype
auto end=departure->upperMouthSubvoxels;
if(!sameRoutePoint(innerPoint,end))
appendRoutePoint("998-departure-inner",innerPoint);
appendRoutePoint("999-departure",departure->upperMouthSubvoxels);
}
else
{
const auto terminal=std::find_if(layout.cavernRooms.begin(),
layout.cavernRooms.end(),[&](const auto& room)
{return room.strateIndex==slot.StrateIndex
&&room.stableId==routePrefix+"basal-landing/primitive-0";});
if(terminal==layout.cavernRooms.end())
{error="Vertical Shafts basal route is missing its terminal landing";return false;}
}
auto end=departure->upperMouthSubvoxels;
appendRoutePoint("999-departure",end);
}
return layout.finalize(error);
}
@@ -0,0 +1,12 @@
[PipelineFullDetailHash] tiles=960 density=0x91931a57a067fd92 mesh=0xa818654be89790ac payload=0x370ff80451105a39 topology=0x06e507557aa70173
[PipelineWholeViewHash] tiles=2370 density=0x7111b4b743e03c95 mesh=0x8c7240939db21b88 payload=0x3981ff5ca7076219 topology=0x07947d2e691b2e55
[PipelineLODHash] lod=0 tiles=512 density=0x2fd4c78685a43cfe mesh=0x50478cff1c1f8ca0 payload=0x54e48e57e0af26de topology=0x860c011cfdb9833a
[PipelineLODHash] lod=1 tiles=448 density=0x4b8d9a6dcd2942ad mesh=0x17e89b7087bfe711 payload=0x2389c109eb780db6 topology=0x7eccd4cf560d0630
[PipelineLODHash] lod=2 tiles=320 density=0x48d4c21a852e6e21 mesh=0x5516a17618d142d5 payload=0x59cda38ab9ca6b7a topology=0x15ae353bd835e6a1
[PipelineLODHash] lod=3 tiles=208 density=0x09b4811098a48c8d mesh=0xbac3fb6d54bca8e9 payload=0xca779555d55be411 topology=0xc63e8ee46890a03a
[PipelineLODHash] lod=4 tiles=240 density=0xfb11591772a3d18e mesh=0xebf2baee8764591b payload=0xe491d7ac9c0061ad topology=0xd618ac33b95ce494
[PipelineLODHash] lod=5 tiles=144 density=0x2f8b7b7fb1e67258 mesh=0x4cd508cbe97344fd payload=0x2b6e96a6050458c9 topology=0x4ff6d0eda8988573
[PipelineLODHash] lod=6 tiles=96 density=0x0114a34c8b8d6113 mesh=0xb1fec4d01ae1fa44 payload=0x3e50cfd429b8e950 topology=0x08090c96d15c1618
[PipelineLODHash] lod=7 tiles=96 density=0x3836ed8cfce73418 mesh=0x40bc6589c5fc1aac payload=0x677349bcae16790f topology=0x36e3aa53192e6a6b
[PipelineLODHash] lod=8 tiles=306 density=0x144f3c1fd722708b mesh=0xef77b28bf7fd9453 payload=0xe3f47b37fcd61489 topology=0x5a3c73e295494e04
[PipelineExactQueryHash] probes=9 density=0x7b7c50ac24ccadcc unclamped=0x7b7c50ac24ccadcc detail=0xefcba20c0c70c415 collision=0xe604823a249029bf path=direct-Core-queries
@@ -0,0 +1,12 @@
[PipelineFullDetailHash] tiles=960 density=0xace56c8f205238ad mesh=0xa9c603a1248de6b8 payload=0xea55f1f4fcc6a894 topology=0x5ea7bc78a1b678ae
[PipelineWholeViewHash] tiles=2370 density=0x826704417f556bb8 mesh=0x3dc31324d910ad2b payload=0x32cd1847a8e05d0a topology=0xb800610aa85336d7
[PipelineLODHash] lod=0 tiles=512 density=0xcd6ab28cfa504aca mesh=0xf03dc80a669fca31 payload=0x397aded1c3d2d297 topology=0xb4a1dc84fabc1fe3
[PipelineLODHash] lod=1 tiles=448 density=0xce6e713828b2a20a mesh=0x1cf408c25d67d18c payload=0x8a2b5d20571c997e topology=0x51cd985fe8e7d100
[PipelineLODHash] lod=2 tiles=320 density=0xd7f003d75d73e5b1 mesh=0x4e9c56918da506ae payload=0x3867c5915b409d6d topology=0x02a2a7c0ee9ac809
[PipelineLODHash] lod=3 tiles=208 density=0xce02d09814028b15 mesh=0xe4c2addc4c54c439 payload=0x7e25d9eee6469c5a topology=0xe603e61c9b9bb733
[PipelineLODHash] lod=4 tiles=240 density=0x0be8798407724eaf mesh=0x131c303cbbe51434 payload=0x2a90ecf0a2411ca0 topology=0xe3e054a371152e08
[PipelineLODHash] lod=5 tiles=144 density=0xf20897ad272103a9 mesh=0x13d00b6253ae1f72 payload=0x0db3880693a962fa topology=0x462bd87a80f15e89
[PipelineLODHash] lod=6 tiles=96 density=0xc040fcac457d6313 mesh=0x1ed132eeb718fb62 payload=0xcd0214e35577c6e3 topology=0x2c56c69e6fdcfd64
[PipelineLODHash] lod=7 tiles=96 density=0xc2d419446355fdb8 mesh=0x4ea44452574fc84b payload=0xfd9779bedfc9ae0f topology=0x8948546e770c9b72
[PipelineLODHash] lod=8 tiles=306 density=0x62db31696f2da831 mesh=0xd373f9e9481532fe payload=0x540a60526e0e3e46 topology=0xc725cb901e6dc335
[PipelineExactQueryHash] probes=9 density=0xc949c4df2ce6f9d9 unclamped=0xc949c4df2ce6f9d9 detail=0x114c501e2d830d94 collision=0xe6011d3a248d4849 path=direct-Core-queries
+4 -4
View File
@@ -125,11 +125,11 @@ RECIPE_RE = re.compile(
def owner_hash_baseline(recipe: str, style: int) -> tuple[Path, str]:
if recipe == "surface-world" and style == 1:
return (BASELINES / "owner-hash/canonical/owner-hash-surface-world-style1-v236.baseline",
"L135-v236-cave-mouth-joints")
return (BASELINES / "owner-hash/canonical/owner-hash-surface-world-style1-v237.baseline",
"L139-v237-vertical-shafts-core-planner")
if recipe == "floating-islands" and style == 2:
return (BASELINES / "owner-hash/canonical/owner-hash-floating-islands-style2-v236.baseline",
"L135-v236-cave-mouth-joints")
return (BASELINES / "owner-hash/canonical/owner-hash-floating-islands-style2-v237.baseline",
"L139-v237-vertical-shafts-core-planner")
raise VerificationFailure(f"no pinned owner hash baseline for {recipe} style {style}")