From 318c5cf4f29e55f9f609196f3f36ec3ab55ba8a8 Mon Sep 17 00:00:00 2001 From: Fr0zka Date: Sun, 1 Nov 2026 05:34:00 +0100 Subject: [PATCH] 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 Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1 --- ARCHITECTURE.md | 47 ++- CODEMAP.md | 16 +- CURRENT-STATE.md | 21 +- .../Private/VoxelForgeCoreAdapter.cpp | 8 +- .../include/VoxelForgeCore/VoxelForgeCore.h | 22 +- Standalone/Core/src/ResolvedLayoutInternal.h | 294 ++++++++++++++++-- Standalone/Core/src/ShapeWorldInternal.h | 95 +++++- .../WorldAssemblerM3/Linux139/REPORT.md | 92 ++++++ .../Linux139/RunL139Captures.py | 278 +++++++++++++++++ .../Linux139/RunL139Determinism.py | 86 +++++ .../Linux139/RunL139FinalTiming.py | 167 ++++++++++ .../Linux139/RunL139SeedSweep.py | 107 +++++++ .../Linux139/RunL139WholeLayerCapture.py | 110 +++++++ .../Linux139/captures/after-arrival-mouth.jpg | Bin 0 -> 19044 bytes .../captures/after-basal-terminal-landing.jpg | Bin 0 -> 12527 bytes .../after-landing-at-shaft-access.jpg | Bin 0 -> 15595 bytes .../captures/after-looking-down-shaft.jpg | Bin 0 -> 24396 bytes .../captures/after-looking-up-from-ledge.jpg | Bin 0 -> 23172 bytes .../after-whole-layer-density-cutaway.jpg | Bin 0 -> 20280 bytes .../after-whole-layer-side-elevation.svg | 12 + .../captures/before-arrival-mouth.jpg | Bin 0 -> 21788 bytes .../before-basal-terminal-landing.jpg | Bin 0 -> 13933 bytes .../before-landing-at-shaft-access.jpg | Bin 0 -> 15114 bytes .../captures/before-looking-down-shaft.jpg | Bin 0 -> 11005 bytes .../captures/before-looking-up-from-ledge.jpg | Bin 0 -> 15507 bytes .../Linux139/captures/manifest.json | 258 +++++++++++++++ Standalone/Source/StreamBenchmarkPipeline.h | 37 ++- Standalone/Source/WorldAssemblyPrototype.h | 143 ++++++--- ...hash-floating-islands-style2-v237.baseline | 12 + ...er-hash-surface-world-style1-v237.baseline | 12 + Standalone/Tools/verify/verify.py | 8 +- 31 files changed, 1719 insertions(+), 106 deletions(-) create mode 100644 Standalone/Research/WorldAssemblerM3/Linux139/REPORT.md create mode 100644 Standalone/Research/WorldAssemblerM3/Linux139/RunL139Captures.py create mode 100644 Standalone/Research/WorldAssemblerM3/Linux139/RunL139Determinism.py create mode 100644 Standalone/Research/WorldAssemblerM3/Linux139/RunL139FinalTiming.py create mode 100644 Standalone/Research/WorldAssemblerM3/Linux139/RunL139SeedSweep.py create mode 100644 Standalone/Research/WorldAssemblerM3/Linux139/RunL139WholeLayerCapture.py create mode 100644 Standalone/Research/WorldAssemblerM3/Linux139/captures/after-arrival-mouth.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Linux139/captures/after-basal-terminal-landing.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Linux139/captures/after-landing-at-shaft-access.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Linux139/captures/after-looking-down-shaft.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Linux139/captures/after-looking-up-from-ledge.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Linux139/captures/after-whole-layer-density-cutaway.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Linux139/captures/after-whole-layer-side-elevation.svg create mode 100644 Standalone/Research/WorldAssemblerM3/Linux139/captures/before-arrival-mouth.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Linux139/captures/before-basal-terminal-landing.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Linux139/captures/before-landing-at-shaft-access.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Linux139/captures/before-looking-down-shaft.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Linux139/captures/before-looking-up-from-ledge.jpg create mode 100644 Standalone/Research/WorldAssemblerM3/Linux139/captures/manifest.json create mode 100644 Standalone/Tools/verify/baselines/owner-hash/canonical/owner-hash-floating-islands-style2-v237.baseline create mode 100644 Standalone/Tools/verify/baselines/owner-hash/canonical/owner-hash-surface-world-style1-v237.baseline diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index a8762dc..521428e 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -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 diff --git a/CODEMAP.md b/CODEMAP.md index 21f31f1..a302ea0 100644 --- a/CODEMAP.md +++ b/CODEMAP.md @@ -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 diff --git a/CURRENT-STATE.md b/CURRENT-STATE.md index 30685fc..12794df 100644 --- a/CURRENT-STATE.md +++ b/CURRENT-STATE.md @@ -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 diff --git a/Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp b/Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp index d275a7b..5e66412 100644 --- a/Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp +++ b/Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp @@ -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"; diff --git a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h index 97523cd..8c44461 100644 --- a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h +++ b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h @@ -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 centerMicrometres{}; std::array 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 boundsMinimumMicrometres{}; std::array 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; } diff --git a/Standalone/Core/src/ResolvedLayoutInternal.h b/Standalone/Core/src/ResolvedLayoutInternal.h index 5344ff0..7b7520a 100644 --- a/Standalone/Core/src/ResolvedLayoutInternal.h +++ b/Standalone/Core/src/ResolvedLayoutInternal.h @@ -626,8 +626,12 @@ namespace <<",\"floor_profile_distance_um\":"<(room.shapeKind) + <<",\"ledge_spacing_um\":"<(room.ledgeSide); if(room.unionWidthMicrometres>0) out<<",\"union_width_um\":"<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(cavernGraphVocabularyVersion100000000 + ||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(room.floorGradientQ16[0]) *room.floorGradientQ16[0]+static_cast(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]centerMicrometres[0]-room->halfExtentsMicrometres[0] ||endpoint[0]>room->centerMicrometres[0]+room->halfExtentsMicrometres[0] ||endpoint[1]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::FromJson( room.irregularityAmplitudeMicrometres=LayoutInt(v,"irregularity_amplitude_um"); room.irregularityWavelengthMicrometres=LayoutInt(v,"irregularity_wavelength_um"); room.irregularitySeed=static_cast(LayoutInt(v,"irregularity_seed")); + room.shapeKind=static_cast(LayoutInt(v,"shape_kind")); + room.ledgeSpacingMicrometres=LayoutInt(v,"ledge_spacing_um"); + room.ledgeDepthMicrometres=LayoutInt(v,"ledge_depth_um"); + room.ledgeSide=static_cast(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.stableIdstableId)) 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; + 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 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( 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( + departureTangentQ16[0]*departureTangentQ16[0] + +departureTangentQ16[1]*departureTangentQ16[1]); + const std::int64_t tangentMagnitudeQ16=static_cast( + 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& p0, const std::array& p1,const std::array& q0, const std::array& 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 nodes;nodes.reserve(nodeDistances.size()); + std::vector 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(hash%static_cast(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& outRooms) { @@ -3154,6 +3264,130 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout, } return edge; }; + struct FPlannedShaftLanding + { + FRoomNode node; + FPoint point{}; + }; + std::vector shaftLandings; + std::size_t plannedShaftCount=0; + if(terminalShaftRoute&&settings.roomDensityQ16>0) + { + const std::int64_t ledgeSpacing=std::max(0, + settings.ledgeSpacingMicrometres); + const std::int64_t ledgeDepth=std::clamp( + settings.ledgeDepthMicrometres,0,5000000); + bool emittedShaft=false; + const std::int64_t routeLengthXY=std::max(1, + static_cast(integerSqrt(static_cast( + 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(LayoutStampSeed(seed,strateIndex,stableId) + %static_cast(span+1))); + }; + for(std::size_t nodeIndex=1;nodeIndex+1(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 + ||topcontent.bottomMicrometres+1000000,top-200000000); + if(ledgeSpacing>0&&top-bottom=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(sideXSign>0?0:2) + :static_cast(sideYSign>0?1:3); + const std::array,4> cardinal={{{1,0},{0,1}, + {-1,0},{0,-1}}}; + const auto outward=cardinal[outwardSide]; + const std::int64_t offset=std::max( + 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 toParent={-outward[0],-outward[1]}; + const std::uint8_t ledgeSide=static_cast( + (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(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;nodeIndexstableId+"/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::clamp(settings.originRoomMaximumConnections,0,32)); std::size_t branchCount=0; - for(std::size_t nodeIndex=1;nodeIndex+1( 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); diff --git a/Standalone/Core/src/ShapeWorldInternal.h b/Standalone/Core/src/ShapeWorldInternal.h index 4fa4862..1a57f85 100644 --- a/Standalone/Core/src/ShapeWorldInternal.h +++ b/Standalone/Core/src/ShapeWorldInternal.h @@ -1284,7 +1284,8 @@ public: const std::uint32_t reliefSeed=static_cast(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 boundsMinimumMicrometres{},boundsMaximumMicrometres{}; std::array centerMetres{},halfExtentsMetres{}; + double ledgeSpacingMetres=0.0,ledgeDepthMetres=0.0; struct FSegment { std::array 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") &&routeEnd0.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(std::llround( + (top-point[2])/feature.ledgeSpacingMetres)); + const long long maximum=static_cast(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,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()) { diff --git a/Standalone/Research/WorldAssemblerM3/Linux139/REPORT.md b/Standalone/Research/WorldAssemblerM3/Linux139/REPORT.md new file mode 100644 index 0000000..c25e7f5 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux139/REPORT.md @@ -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. diff --git a/Standalone/Research/WorldAssemblerM3/Linux139/RunL139Captures.py b/Standalone/Research/WorldAssemblerM3/Linux139/RunL139Captures.py new file mode 100644 index 0000000..3697632 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux139/RunL139Captures.py @@ -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'') + rx0, ry0 = xy(x_min, 0.0) + rx1, ry1 = xy(x_max, 0.0) + svg = f''' + +Vertical Shafts — whole-layer side elevation +Resolved plan • seed 0 • strate 3 • vertical axis and route shown in metres • Y projected out + +{''.join(cylinders)} + +walk-only graded spine and landings + +open vertical shaft primitives (53) +Plan projection, not a density mesh +''' + 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) diff --git a/Standalone/Research/WorldAssemblerM3/Linux139/RunL139Determinism.py b/Standalone/Research/WorldAssemblerM3/Linux139/RunL139Determinism.py new file mode 100644 index 0000000..b7b88e9 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux139/RunL139Determinism.py @@ -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()) diff --git a/Standalone/Research/WorldAssemblerM3/Linux139/RunL139FinalTiming.py b/Standalone/Research/WorldAssemblerM3/Linux139/RunL139FinalTiming.py new file mode 100644 index 0000000..e727f37 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux139/RunL139FinalTiming.py @@ -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()) diff --git a/Standalone/Research/WorldAssemblerM3/Linux139/RunL139SeedSweep.py b/Standalone/Research/WorldAssemblerM3/Linux139/RunL139SeedSweep.py new file mode 100644 index 0000000..25a7bbf --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux139/RunL139SeedSweep.py @@ -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()) diff --git a/Standalone/Research/WorldAssemblerM3/Linux139/RunL139WholeLayerCapture.py b/Standalone/Research/WorldAssemblerM3/Linux139/RunL139WholeLayerCapture.py new file mode 100644 index 0000000..a4a8eda --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Linux139/RunL139WholeLayerCapture.py @@ -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()) diff --git a/Standalone/Research/WorldAssemblerM3/Linux139/captures/after-arrival-mouth.jpg b/Standalone/Research/WorldAssemblerM3/Linux139/captures/after-arrival-mouth.jpg new file mode 100644 index 0000000000000000000000000000000000000000..54fdd4e8abcd10f885f3ce17ae64aeb619076a3f GIT binary patch literal 19044 zcmc(H3tUr2`sgGONU36?Nh=7NjjuFlT?9mVnY001e300HK&|eAfN0es4+Rmj;I<(O zNGmGHBLM`=_3o?N()yZVO*H$JiY_H^%dCIwL`6n&bJ zzJVTYNW8Bn{FA=r--0ERX$$}UL*dcnDcl?1#-9G{lLxHT*1p?kKC}8UWb}K)q*Lo? zNRKiZQ70q4$!PR0ngCyi>&_>QP@ z;)W2$5rHUQBFL}PFWKwN%vdvDB3ik|JV@#imTT|K z)U^UY2xtN*Q4kyd_T!@NXRx0lh95kZzI`9(4A*wna5avpCzmwv>mARZ^v<_fHsDsC zpSP^XVxclSKffwAJ8xlZqC7TRz*AD?RHb(O?{q`g&J{qQBpz^O9E4(JL>@t2iE|t} zQRdM(#NCLvYM&u0Q6ewD2Rf>ZipUmk;q`Bz;~*ot48TX_Q;)7zOlaCjzwXAH9Uz;X z@1vTq>3o-Zrs+u^joNZEpEK@ruozI18U3VGE8eL_$Vjei;3?}l6FI+~_{4zIXdvLc z?`aJ{Eh0Z*W`o7#(*-k&0bq%zi}*Zym)Sx|lND1Mx-2$(qe3PB&v4^vsO0QmC;&|& z(X}(C>JTZGtXJZj4jS(uoeCz86OZVNqr@F4;SD9O9K$$xsKOO*6CKu#gJgOf4p)}K zqhEF7S$V7C78PmemYQ~rCN4AYpjvGOP*am#;g;|vDzd<6`nI2Ua|8}s!^Dkbt0o5tR)I$x7roG)U9ryK^1`!g#vo+16xS#tEnSwiPC-bYMb_w35K%`#0$f262+4XeQ6it=da@Q@B?7WD7SWC&#vO?T@P{8#MED8` zY9(Y&chyG>F#~PTXY!OOM+7cg0&WQB``!>OUpwGz5Vp3&S>)$&%|q(43BOOm2-X(K zwCk+v1ua0#ooMg(`s)y9tPW??iiupL1hgY}90qqeinLl=B=MMDeaoSVeJnEDBtP%d zivgYzRLN@q&r}4;&E$|B z5Lb+zZ;N`X>F-spvfxyj?^Iiug*{YRWI;~xsB#@z(%T?2UK{o=?z!Fuh5V!gewbuF zFX(_-TH!GR=dnI05|*h5Q^T>e<9f(-q2w`K{e4`Id(QUvnV#yEk|K?qiLtZ7U+B6_ z#5QG00SADljy1y>1|SN9tkEJN;1jOIiJ^}%2hfv5I2wh-HS*iqXZSgCCh-+=+Hnzh zh=c;@2q?FR>}CuHVGv`e2`|v_2t*n^8=iW$Zyj@ffa1N)AK<^(33sn+=oNXHnpjIs zg+{%P_!x1)*r+4X=zWeX_&ggWpVwz4pGQ*?(-|hMQBwBe`})p-3BeL}<|6fk;1Ypf z0-sx0aZashuNbc9p7-j!Dl(fFS||uEtiLK+^S=2!iQ()*v2OCPd&U8oCPz=x+S6jQ zZ>3di24=IcFs-UMNtEvf*A;M8KQ&27ku-9R5|By`*iKL{-Ee^>a=b=Z zDdTTBf{<1>?)lqyRmO{KePs{)!ui~Dx9=%9Z6#xXWjWKvLDoxV{NiMUn(r8qAE2yv zoLtDC2vikl!?J$h=lB*BV{Id+DHxfx+TF}=Pe1f<;YIkaANZlenO|MITKO#CqOb~h^N$c z4M?M)FH}yjRvT~ODIWQSNerh=!|0mFX0G)qJSI$rkK-i8U?HjK7r<_<=XK=ei@5@li znMl;YnYa+1gD9|2qsJB++M8(A3li`zxpHh9P4wSysPY8ZMR=vK zC7mfXo-WB9DK%0L^GCkG$67qOM)p|czrk)JRI6*>14jMuG+w~Z|>vu z1JUcBn@q523pv*k!`z{FHnqLayW3==Yu+h`W`1_Vt&fiTNW=X=kOFyAh4{*aya`Qz zH?%7eY%dUFycd}r{QX67+fTWRAz$I|Y`C*#GdHmp3ZHPYk8x2q-N<$_C;P8hh||u7 zoRdyWrM@Gu5>yuiS0ZU4!6OPBvW~ctHAsdFR0V8OoG`to^QkJC5|{9PCtp(m_t)VM z3;}O`x>81!DrefHDVGFg@Zu8lObh6E9W?TTMFNxj8=?)k3rGyu7Gw_5WD{LfoOzq5 z3C^;-=iU9~>4rV^n_JHhDb5R<)|hpo$hD804Mjr9F<{~*VxfXu9}80r=hqT}fEX?y z>o^i`pb{7Yt>Ixx#B=n{4>)a|v}&`7+B!)TXpdJY#A5!QW`Wry2Xv&dHdGaT1xI<+wb zh>&?{GZARH$-kSvL$>#$DB7SY4-~go55DaTvat`PH(IE<4sI-V-e6{3=U~*H)k>{JQEq(Rj{*d)at!49H&P^Ca8%Ipa%fH<@WpQ-i zqJ3=v+ZUk(q*ieiYI+@&hm>^fZ-QFSNUiu$9mn3JTyncn!v3-1vDAOv%J$4BcN0{` z-uW?GSgr(r@RSITB7YAG?2eE;fmxo1$m}Yf4p3kT5{vb*H~}y;jYresnZgE}!?Ca> zmVgkn1hz-Pq*dP6xJfRb@%VP-&XQuGM9c~Tqll?Uj2S&4Z;*ZA{b(51yW_y+tUaSBt0Uhh ze5S;>?8B=sBifS^!sl$SD(71x1@VI}mAY;@py=~e7DjYSPmNy2Cbgo~&qHFE715oO zz@6k2bfPwVkGa2}4b41NHjz#>(qnJb%kZI}^w6j#XiFLM4S&<8qQ^5AYkQB1m41@h_77aizpy}&yiU6i=BM2BIjI`KK(}(4OL@o-M$3hK- zE!LOPoH5k33K#)XiFSENkTX4iJHs20@G3(e@RAmapU4(U#BSHI#;K&`-w>4S6-;fg z)2J73EiC;fcX5!#Ctx@tn&OL^uAQsVKLwlSF-&DV06kjnb@@$0lVawnE4^<9Uyq_Q zk)x=gx^iBKdaj1+i~4A@p;ig$tn{2$X`DMH;i z0j_A;zH)J7k@wXAm*j3(hm?(xehW|Yx+invj$zZ25n+J=E{mwKz)?A5Q;w3nV^)p% zftF=cV@VK@x)VGj5Q&!}es+6sz6ViQjQy1F4tPuY@2SQH&=pet2cP{0JJGUMA=eYk@a-E6|Z<;aZpW`)B_ zg$8ic9Y68za^t>Ks_Vegsa9YnN|nJSRtD3D#DPV?=!fq((bdF_Toi2`UNRn04H6Og zM2U-piDt|=qvUH%fodZK`4Tp1>dc&22Kd?8fG=h$QUty^EEfxFaX^!e(7?v4(L;D` zMI%GE|3IW>YPTe4N9B}rDkJ#XY~R5a_vjI$na%x6KRmML*RtW4?q!wF-<&%7?A2!v zhudPZTBD+C9}HUs-7c^3*IbcI-6EExfXU1R6h0=8YURubsH5Jpcczup)G@aNmzv5` z56kyeKbM=N%JF$M%OGbiA`zx=v$nR`<^WyoXlYOfkL)@3kpY^O< znwIr^P0)Z zzSsl~Uh#YnWn6}j%Hr$nNCoGCW5O;E2I%grPyr2@j)Zv zkwhyR0wQyu1UGyk_dDF+sTPxfN>WMmA}bVtPE-{Z;~^Qz)MPGJhl4~lbi;uT(g2=>DF?$yJr<^GA_8bpE&Uae`QbA`^Q(#x9DoVuz}=H zTpYP$^X0&*GtKS|rzfs-3tDq2qCDoledeuOWz6V-vo1f~VGi`{k6ydLaMkoetG(;D zT(GmaGyUxC*NZdxBYpDgL8mWWTGW&E*{@@BGrUs%%r5dpN%Zok_sx#STZo2F6>m^}Mb8`HD?&|#9WiaP_rQgwq!*0Dck1s|n>UVcNaWklEjc0P? z^Fwi0f)_pLY5q(5^uwJ47u+6Mq;Bm$lX3jaz7-+gd~$uk=S}mytKC0}^bbjSk+<=2 zwVGEC>%`~Eumq5@saWnXk`Q#v+0Q1+O=Wz8YqUs)(2sSXIEu`gvT>8zyl)b zo%F8aXEz?){u5~v0Yj5oV^;;%rtDK#vA>1gAm0`-g-x@BExt!hUoluTgN-Wt-L{o4%NF@(eD~^9?y;AnXu8|TjNDhz>lNLV zJDz9WzjY$0+%kIia%Sq*i8@9fj5E^U(0rr4oYRM620~_!QfJ-3BBeG@M zl^waQKDI7hqKf-nkMC72dD=C7y*$|b_OM4r-{l7LtcK>YZLVHUEtCF{I6d|7DEhWV z+E+PTnVU0s*=a$1#;%8Re@&Vj5E*!*?uB;fx*Mf8IDK9)tJ?Kd%Jaa3F%`Sb7nBS< zOJC8ke#+8WFC?|*mYp3@2?2#2tu49oXZM<;d@8ylK0oWZE9%Xq3-><1{;$P}1JUf< z-BB4Y+w2`$PUfoCINUmLYNdHA=rqLwHYhDqb0q9*D?Jzy{Y@%4GhL;zOQr!!6a&L_ z4#|<|%OPM!{xlB}1(FYXx;S zMIsOjC={px*-{{7AUCDeYS})iMlbU_H_jtWkHU*`q*F`MP)ZtqLadA{BR>svELNMH zhh=k9rt@Sacg4N#C7GqCV{D5$EZbh3ylA=9zUQ^w#ng*WOi$U~PEGyR{Vz+LIvSq4 zc5svn#RdXlng^vOhi$-@2={t5vJD;AI7%c_8~sN*I#->^37S#5`^K4wO5QFf^D9Fi z^#)dzeXJM=?d!VI*LX8wzWusz*URCqnd{u;n}?o0YyJ7u(!uVIN4NGzdgQMz^tuO5 z_C}+=o}l9D+9j!}-3N37e|tCB=GNu8d1JNv;vmhso5emw@rkQ{=CApvAji(`;ITJ% zhAMt;u9;pbfA)GL%Io=w+T)kLFdI4D*Ci}pMeHa`BlNW02%H?|b%fKNmv!*I!gpdy6sg6kz zStwEYWJgN!CL?dadq`>ldz6? zBcahtc+KO`jVl{#E*At9W#pbNtlco+y1en(_31tHPb@79i_Fe^lX|D_)#3!N8x19A z9(Sfby3^hfkvTK3#`=;Pw+LD4Wzx1f0Mu*(& zDczwXKFN8xpI6)-T6Mc``L$;~Ykls9R7G#>ZoYMR0Aoq{^zR=h(_Hqj1mXZS=aHw{ z*mU!9x}s(^FD{=8g7EF!&L38nX|x-cd4<4h4bU5P}ulfx-asA$@Q(0aHM9pq07=xU(_A z3-Sc*fcGf^0bGD==_@BRfh{;d4aiY1U$UvNR1j!Zq?kEd7U$W0S7mz21_Yn#FP+f_ zF-V00ITAz1*=)o4R!2qK8~r?lW;?tu7c>Sd~u^jFGILzYO4_J7I;~d& zTtSK7A_)qT;ry5&j0=c^Aee`P*Lzq61rB*r zoZ1Y_9^73&=~JtkzLrPvmAvkR!lsvbUOB#nr9de((`U>$alfge%4JELRZV2>h5;vg zpDk8aH30#UzrOY6;o-lNCg;`k&UYHzw|>jqJ6jrhuJklEBz$4NIwo|*_8Pn38`p=1 zqjPtxxp2C-C*e*0Wk>s(jP8xEi>B0?bo4!6+qC`HCSTuRj6G8dZ_Zk=BEYu%`JtU) zUph(?pnw9jJx^+A{_z3f!c-K7sYVqDwQA6y#mX9eu zT5Abl6w#dEECgEcbX~;Cs>GE2OmCKfMCjc?Evln7&1VsTBx%=`aR=`-e`0J0{-klE z0pue-ihH!7%dF9CH#8KTkz`xjHZ+PRG!KWr+&+rlUsu*(S)24ZRLuw9MO0vK$&u%Q zt9O*F+v3sh@gf1E;&s04Dv$H%7?7u+48Ht9X-!u?r?0zlM$9)sSBK@bkK@l+ravAG zbP8;KAiZicKQmm>`r2>lYp3Z>eJkIqd~*1dL+H!kB>P``?F0HxF7XU>40%QAO8PS~ z*FU%zM7gyM_bmOUeEsSB4$&V+|8lvO)i8=`hI{gB&&-@YB>bT(YNgTOtw&k53j$`$ z{rdj3M^=%{2WTecsU5$*t z>I4WN2yY#J*tfd}?WppnP2YEHgXFH~Y-dS}l?7kj4T~QPTtq}EmSqb7D%wO)1~N~s z9$oijJRKm`U#2Ak3WyKT8e6z(T{k5E*G0`huULbE)mK`&(JkFjl?TP{I?K7+oaWr^ zh#+`Kcmy2ZlbhzV4XkTqW?%2oeHHEu%wDu*0~Jt&_k1d1LLzD-+HPjNwBKo_oS_=| zGWz}xz3um3?%LHof2ioitsM`7s;icI{Yr=|6R`bF11nyqUL2I6j;k%Lx%+j0w{6L0 zvmQ%k`#3Vt5n>KXZ2P2KliF)7FE4Lf-Wl?X$AyuR@2<5~HeaZF@x68!zki!NV$s^# zdH+Gr1*^3_cD7HmLa%jQ@*OA%Oi}qpJsTOGwqvK_Y5C`tj`n9=%O<5AO!GOD7R1I@h%DX4EYKjMaUj}AMht;NmC>n6 zy)&Qjcvufr_7D+eRn=-f4%|5gRyQ0?W+;LsCR<=b1)0Dtfit=d34~WGvtiR4epx3_ zYWkc;@T$%Kq}*7cNb@a;0ZcU1k4W zU&rV}Ua1kSPtElank)nEu9tXF!>L%!!?p(?Y2bVV-d^=qJ{liFrbg9`*Y8^Y^P!g|c|$v6iW;8z4ZfKdm|kR6)Z1Q``|#DN z>#6q_zP9+;?>l9j0Hg@e9zT*A&Nl_e&0lh)u1A8-bXtB8f4Dlp%A~@t$ll0j^Ty(> zhYug_{(4JlygYrCt-FK!*2yyPy(pcky#a;cy*8N=Hlqc<3O9pHFM?B|PFs)kB zT>__854QTHH8$OBI((%0QsUa|JJYfr2nkA}&r%cDfSN0vYiLTqT4piQv}ct{+e1HArVr zhqs9y;Vz@gs8l+zbsigmnZ(cz8=jnnY1dWCid5wUsbmZRiBIUiCFx*QR~n%Om8oUl znOsh5@_%ESw5Ib#=}2l!gUz<*?Jv_+-euo@>e2QZy>ji~#$rLz$_-t0QzrVXD7+x%&RTWkDs!|k#Ty`OtxiZu7y04x z3g3ty?6VI>+V39zNBrURiq2TuT8GfVU1yIk`*qr-`ME&_*;C9OG+Hfh8rbU@GNtN^ zzfCnYm4bvCBsKU*U=~`snz3*{rHM&16&q~At1U77lT!CpaG{U+4sqQyIjZNzfeq5g zkqP0##mnNBf!3Fe?E-)Z#{xJLWhy0IXA;!)0%G|1wbrq3c+At%c`CueF~8Q4dDyB- zc!A0XPC=l_SZCx?$8xArBw~rw2q=B4N*$MQ{D82gNGcE@m>dy^uWO+bO`vR$0))>g zt25%z`Z*Ujd^d_xM$tDh4YOWnte@dH^zxYBXTF<4wjS}kn6)h04~MkTA!Js8-Dimj zd}UwHqJMIFpElaQuKDK2vS(*U(Xlr^XCIU(o;7B59a-_Rw`1qJ*`uf=rhci-;okRG zo{LZFRFv;3>fErq^`UMwe|)DnxPNF{If}wg49Cnfiy89C`sVo+<-cOi0vW3RYH4#r zZ*)-To+!U=b0$SfNMm-zlxr>rgmQu5MiFZ(qVe=HczP)o%VNi5Rc#ar`B62hN7g^- z9be6jyK!(kVb;O!N-T{T7XtZC=JM=Ze9>)a4ebE(V+;B3>jyPB8R_^mE|9CpfgD~k zkh^t3MdMcyGkOpk=&`VZEXscI24$Qz%jAT>)sZa-t`8EK1xba#8*~XQnDBd>q-YC6 zKL%VKfvD5$f&M5bui{)Xyb8juI{D+SdyZg+7)ULz(B6I#VoZxHI zH>0%B>Q-u!)b?Rm*52h`raiQK80d5^WV*6X@OD6dlyhe%ZcBR8{n@iOUV#b4>*H@8 z?x}2MPU#9Yn>5(}YJHl|0}Go6{SS9p4XxTe|7y~K-7~?ZP7`4sEcoiiQL!&m5Wd%h zg+15`u_51sG0q4>npTJ_d>%+L%M^%h)NwHp{et7sy{a)GhHyS`13VOov5K`{XWs>9 zJ<|IA!Ds?g8OMhqa9B4X8h(TmxcJ92XJE<@SU^6~?lT6GFc<;~4XgujHb}Uifw}Pf zfE(cUS1H@G1;#9}^=m~wvf;bedJc_d*fn?7uY=#!FMg3_SGFO%J1O;Gih1dyHfL#A z_aTYLv~F`}>AJ$3Vv9wDnfRbWHEF@mroVJIJ}X$UspZJ!P3^fhccup&x_YJS)kL55 znR8>LPtv#FPjt5`KapYA9CPizI}PBc#|ZH)?lQfr$Y_GSIR&3vDt z!*6x^H>W?>YC> zm(Tmo=B|6)z3z)ndF|TEPmSx3Y>HZW;8BCsba{K5*UcpQu1|yiD%0SzHJ~2EeVxFI zn{qQ#IY~5E1*&ws0PhDo4}2KncM>-6i$7pcnm+=@YsKf;Rrv9#68+VJ!UyIWV0MicAvJ;l3NGBpPxp#Ct?1Qe0sog)#Jr+RDe zEzN$ub$#}NK!~pVbHlR!7nw0u^ZAZWfs?HG>i&py2M8Lt7u?6y*bUUGH3>eI5)Y$V zX9MeEM#O;myvQiS51$o0d%Er0h`_E-HT5O zcWuu8zI&7Vb@QsH_Z{B+QvUMUyrjG}o@v%cT7sERQy(WDvMM-r;?$QdA&HXHc4bf2 zFFKeqP{7U)*qv-%RM$})YTZARwJ#iOccbXstk7heux$kqm)fl!%O59x^RU#%|EZ9*nO{t?43m~$H{DG|(J9KcQuFfBXy98^Yj)VXKfeI!O zCk^HSAalf-9wM#?$QULm(8Kr(L3EfRIJN;13n1)+8jeN$srj8kv;LX`%&MkM>iA8F z3uZm&`Hz!Y?~F~4SlhwliO-~HsGZPK<07b9%*2ixXc zKG}CWq;>1J4xfH~I4R3~%I8~$L|4*7`o8aFI=MvdYP|n6z0}ZlmEEZ}>!oYlpPp*0 zI-m9Tf|1CqEteBhuQw$AP>R7qhGW%znU8my-CNxC%c(xoQIvOLsJW}{3)}B+ymnlArpy&Sk!$g)qO7ZH_u$C+ zLo1(I$GE%qoVo#@I8il^C(=*#+5OHZ*4gF$X|qq?krj7$1xCGGdrec|Tc{`(g^PU6GIn+!_vHrc z5N%7%NOZg=T2V7Is+JeFYSqQC&&oSQD_(u!c<*5Rpy*N6dG&*V&R%SB})w+`LA7jnwr@{ChkgM9{Sm!7>ils?=wxZq(?+4deaviss zKl<#-%pJQvJ83cfL~6g|nxz)mLyZq@!Y*F84&Jm~+04vC|9Vy2Z(SUvC=cz+ifRk4 zd+pu2NtP}K9*hOb{ft$z*#V6ZgicAY@(}+CnIAP_Z2|WCZ|wQE*l3`X*_t36O|oO( zBfOWAU3U`v+23OtR=jx}KH?ied=U8Uj1)k!wbYeLcBCAON8)HBh&ZG*k|5MTuVrK? zlU8;nX*7A25j{}B3Qpz9fYQ@Z3QuLZT45Zyl4@*cT^E-ErX3l!jgz1H{VO#Vq7lJ% zyW1aa%a_?U*(r=6W=n$ux8Q=~xK6DdQK&m_IVsiMw?2-A*scyblw#pwhhFa55d;O9 zUD!3193I0Nj<8BF5rtJ&8^M07rsA9;`|R4NpOihfW-R2{FMg4%!~#Hy;ns66l7-DD zoi<3!OIvSxnEz6$Fh054+0fNV6$kyoI6}I3c44(@<$Wkk9H>=J^r;lQ?^P`Mpb5er zP4^z4)HA_J&LV#2OT5H6rvK=DXW zNputr0t;~sgdE}<1ey|eT|+iStPOz0Psb&JSOl^dgd>VPB_8bklw=_Uh1KMUF^v<} z{zWQ0Fiuq+*Y0f=BsR7{K$O|&L$%`lo6ZKMRXm#}iSQ(xbqG5k-kh(B`@)}P+Ranm z@`f-w7>PmZe||~VL&Hfp908GgW5C8M8Ji8D=V3IM7`6xZ9;*a{ioU_a@4@lMMi96G z#_@3ux3^=}<#-UIZYPo%-&vsq9TMzDTq96E`0$4yo}RQ8s^Db^m5G&u$q{@ypj+^< zeGjKt{6SJ`q2g_W!|G&EgDrrBg@@Qmpv4deVPS7+Gx*FbQAJnatssZk8ZP1*>2VCb zDMr^>Q%^VxYjV)IPzvbt|3QKS5keHi6dsRMsPkp){o3P7fA}R>vCusNb3aR(dA21B z?hxY(qCIiO8N_}Fb|)-u!0-WAAk3ODkWpyFLPHgy3`6HQ3L4JyXg^~zW~L`Zc>=-5 zMnJwwL`@@o0Ri{trHc zx9_rQP#A#H;*ifUI3flD3mY~vSfePCbK@a}6)WNgf(iv66?h99mu4^K)-@_ z1O%LZGf8+3q6zYsIzw!w*uXLIOuG*#Mz1Q01=R)-uSWVV~A*>gTo--{gH11 zIiz^Q6Qs{(dV4VZ1tuN>U$8Ug=$nDlXnT%$D!AEzFL9LPd;ag_sn|tXFED`9Z}kic zxTem5O@+bQ%0!zOlwvIM9wTjGzXJ<`@axbCwj6;iW3bWgL98r8B2f!N22N8*^qr*u z9tyU{gP#ZjPC~&|1R)tx4d;6v=f4Aq-+mRy=`WyK!nw4sWXAg;JP#jsH~NK(Zo(M^ z4+ta)BEvVd?S5l^Lt1#z1jKXVgv>jjz|}+>u=OxR9&7-gkN_1lgrE-AWF;NKallu) z1RO1)U{G*Va)_?}>lfDV!J)z{AZ&;D1aCL>&fe;0M3-L&j}2d!BHkz_D0#FpD<83Jg^~elv&Fra{1ZI zz{B`9(IIe6&EdfX{Co-m5?~~T98rWztO8f7;g|zm6U$ZOdKrcwbRmO3LShE11R)l1 z@PDiUKmz|@ED`)+jYJd^mF%at4lZc&UrL2=!orJFAfTL+O)PiDkuwyRJ>npIYR6PS z#85#j4V-UIW=oCZZMc@dD*RmY2nDQICNW)L(Nvn-t~fnO#_4{_2N`oaVU>4Qj4 zf&XHJI4>pun?R`-81)`D_nCr#ZQ0u#gD#7AO1W2c^b#@yW@BN(BaMNH(Pl7_U+^2Bk`LzZ{NOq z_xSk!v-cD{e)ietpMU=S?|<)mKls5P9S(o|;os6D71M-Lz7N z$B#-r{3<<;!+-oidU*9d4e!&#`}E-)Unq%_67Y5X%xf*MPt3KE1qt$9N2xO|;=2Qzkq^X_`vm`NlO`RHuRxg+F zS})2&&2wEu3ff-UhvJnOW4!}}A#R~~9t6Y@isQ_%^LT{}xeUx6fOi>VMdVbT6&3#5 ziq(#S7<#00nN9^>Tr~JoF$~x7cqmt_7LUN^P!6YK;5D47T)c~+}pU>gw-yzJBEfsFXq|QFS{a=S=qwBK*fygq00o~ zS)O|wo;06jx-98(9gWEKl3BB3&DXRX7g1(x`e0JD7DO3|TgY27QsutjHXoqcjixco z#VlbCIlYk`(;k8c5iTr-1UBR7qHGH)--Tu@VMVQ6?qL6+ILloSn0dI36RA-vFqg}4 zJ|53ZpL+0WA2Nq0m~qA#gVL!&4S5(8mh1)JA~lzJ7NrW!31PsICeCZ>`9yJXpIX#(FK&Zx^*d{0&{atJckCEM=ub%YE`8))!`LRKkVM6RVJeA*Hbc8NlQq zSd;*flGFHfuH!PXQ_mZ1z{%bmbq_^rP-&gryLBcVc^AGE1)7pPXLvB;(}7%gi>{@P z4?jywOkZ-3!&0hbX;39rFR#Lz&<(~^*wr*RT8V)*Q4+~f&T9ggT;+Bf>UGzb^VI4w-RO`Vo2VQjvu zOq)<(x0dxpm<;~v&eJiznrP;tX+hngR2t2v3QWO}h#NX>l}B}JlRGWPdIdn1bwbD@ ztCC1b6H1*Z(M1OELWm(&LtDu*R&~OXFjDj203y%PL~`jaukLjh89oKeriTs1Sre$H zVK|iyjDoQ~2S!7n`G_3yPUoky@ert;BXa^WrqJ;NB88X1upW~n2m5H;Swb?1GKz5( zx!@ivZ>y2rXb%zwv~9t#phfNZ=+UctY3T*ki9mwQt-zj&rGUihZ(9pLG&_Zxd7;jH zxn53Z)uQB-VTQ&v@mDb1u?uLK?NOC<6bN)eQQDl(p?sOisgbC$8Vf}}6sTYDfb;iK zWDPWR5V+x8?ujzD7BO2gDi~*CZ7!UFmdSURs&=2W3(G6J z9@XH^P&NrP-+~v^s@afMN@#UWCPDHeEmG*68w2)pwlsHeC-KBb(rdNgepGCeS}D{$ zfC{94MMaQcODpw~DJ!Z_q_8GBCHjQMsMnzhK9CV8U|L{^y@ijG#jUobj=T$7WWB00 zs2xm*tt^lTXdYz5G#W!68o0+xQ3kfIIbNhC4a+SLpwMhl+C(4&Tfh~e(K2F>;WkQT zMTFuwJC3;rA#{HPk>)#5-n&UO+g7=CTOS6&!&S;M>ep!GBpTFMj+V`S zjxC2cGeKE93VBkU5kPx^-a3_?nGRufifx7}i(vUHE?neluhCT?4F};p%nyHW(~#o3 zz#RCjLG}R@jWp5r76-vwX%7y=X~iL3)$7VpiW+p8Sy}*$K-oTo)Ug$1KYNbebZcf( zsbaK#U^OaNZhXGra9Azw+D%5wPLQ@2S9o|0wm}lJWyT@pFwfs!S2f*9QSPaj`2d$( zQXM0)jo}LR7t@cZjjZc<#qDz9hW*E?m7{0#p6hVZY!~E|w!=z}8auew=u~){)Y9-R z9XN!*mZ|7$cJ7yB790MIW{(@MrqsH&@db{28d{9KMnzS(fN$G`w~E}7Qo1i|I16Jm z6huJ_&T{i%DToxSunx`rx?{%G+?h`TXW_Vw?h?7$j0C+Ha;qs@i;SIc0JHJwSj(18 zC4v)|9X4N%@hH#`IaKuPcw`AN-K{}CCRl+oh(R6vQ1I~c^QoxM8ai%!Ojgdx2~crj za0DGE(INf{0*nurSCIp?WjAr5UxhQg8Y--b$lk;`a$QyDm~?k43;Ha(4&&3=HyvG% zFRV3i;5bXbr*^XH;x+2MDw0gsMj($+xw64w*BZVSgQwPvE z`NX;!M}QzcHz?d~A2oRtA?(Ev!|zPkEro}Vs5?9?PxQ<*xo`pGC5l}OPNDE#NyRrn zkauVk<_e{Cbg(SYIG|g+;%JRjE4NVm+SsNN8w$oE<6ts!@wKvjtfs()^?H#D<&Ib- z;WliXjq(kc+jh=%V&t2;I3fTE45+FJU0R#wP`Qn@tIb^EfYIvr53_>;PLz!xK@p?y zP%af=;o0!&weiN>sPt+O1Qtez*H3F++VlQl*6omxUd8|v2ts7!0Ib(XO8^olaZ~wz zVD(_CWnzNIxO6ZHyUoHoKG}vj{)!6T*&62}6nu|sZ89=qgLxTV^v)~!UT z+_R%p)9b8ool^VgV`p!G{xO5KzzI?E;UC;m9Ld6*6c^-H z>|Cy#l+Q#Q>aVI8?w6sp)+2U4za4C)sO0OP-JUvs$x-a``bj4?-PXomvt}&Z-~KZF z8o^(Rp!c8M01q&((M0pC9U-=29sK}H1GY2Dt{)@!>Bh~X)zK+z$Sa9}1RqPQY>#Yq zITP!963vds#XGjk(U_-@**@7dJ<6Nw#kytR5x`}2QR*)MUSD0x2s*NG8Pj1zOJKxY zr(W~J5f3TO2dUP<$b zthA1@5n}0DrjhK@vRjj05Q9UoqKO!ljl499g{-7HGI6WVvBN?S6VAzVQ13zFuNtJK{vwu7V_}dqAc)(!9-PG2=ZGyL6%} z4b+Z}_1iRO-do^Q5QQ9ZQL> zZrXv_sODm#m<)*``x$ZeFg^nbcwJ}a-KZ^_s0L>zL$Fu`HQR2r;sYu^=^ps%%fCN3 zoc<0DvJnAE75(1ipo+dRz-}v*J(QMMQ6Z08Iq_x1QQue)ovtk5ijBH*PG~0U-U3Jq zo`jFz6oe64?@C!GCfsCj8p}lS9(1T|p-s7gz?cNXqQu?UDhf=Xza}E?3!4+aoj9@>T1s|SzF%8G@mjV` zuhRVvl}r(#5rtINf38Gd|7Nu1hzwN*ggOTqPV5aY#5C4VU!X=zxJqJ)iEtlUfTuI& zk~~!=vj0#Rp?MKI6*q8k$EMZx#=B>BxMs0bFP(U5RLs>}) zQAWo`9R&yN0wsO@o9754GWp|M&OQRXupO_jL{s3apcg;uXw0EotFDUsaG=!Xohs|@ zb6MR<(C5BqBss|poG4i^y zhDrmd=n{#Sj+G*8eb-jtV+o1b1xz`C(T_GJF2=fM2WdT2E8LX+EoJWbvO=RpEB3tMpr%#f{3vdXHvB?GQj(rH%#0|%+7+i!n;2cE*jqrn#6 zOWkTnp;Q0dl*h_iahID9cP+v$vr8LIMlyM k|3M{PiC0*%Dhj{h)ax9OLRtS(MoQO*pP^xW`0$_q2Vw8`%>V!Z literal 0 HcmV?d00001 diff --git a/Standalone/Research/WorldAssemblerM3/Linux139/captures/after-landing-at-shaft-access.jpg b/Standalone/Research/WorldAssemblerM3/Linux139/captures/after-landing-at-shaft-access.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3fbc6714fef66abab51870c01d818c59918079e0 GIT binary patch literal 15595 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 OzR=~IcKw`t@4o=ni|9N6 literal 0 HcmV?d00001 diff --git a/Standalone/Research/WorldAssemblerM3/Linux139/captures/after-looking-down-shaft.jpg b/Standalone/Research/WorldAssemblerM3/Linux139/captures/after-looking-down-shaft.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2726ce4b324aa4ae3bc8657e41fc12a1a5cd9392 GIT binary patch literal 24396 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 b`~`u(An+Fi{(`_?5cmrMe>wzsPbdB#YsO+; literal 0 HcmV?d00001 diff --git a/Standalone/Research/WorldAssemblerM3/Linux139/captures/after-looking-up-from-ledge.jpg b/Standalone/Research/WorldAssemblerM3/Linux139/captures/after-looking-up-from-ledge.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c54a4deb89c8fa1fb99cfa0cbe0d3a3d37baf23c GIT binary patch literal 23172 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 Fe*r + +Vertical Shafts — whole-layer side elevation +Resolved plan • seed 0 • strate 3 • vertical axis and route shown in metres • Y projected out + + + +walk-only graded spine and landings + +open vertical shaft primitives (53) +Plan projection, not a density mesh + \ No newline at end of file diff --git a/Standalone/Research/WorldAssemblerM3/Linux139/captures/before-arrival-mouth.jpg b/Standalone/Research/WorldAssemblerM3/Linux139/captures/before-arrival-mouth.jpg new file mode 100644 index 0000000000000000000000000000000000000000..37481e72d4bfe73a0e29ae22e6e3f3ce2f80afc2 GIT binary patch literal 21788 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 Of8Dyj+SPT6-v0+~Uun?* literal 0 HcmV?d00001 diff --git a/Standalone/Research/WorldAssemblerM3/Linux139/captures/before-basal-terminal-landing.jpg b/Standalone/Research/WorldAssemblerM3/Linux139/captures/before-basal-terminal-landing.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d8cf0624e98a9d1aa4ae0e635852ad214a613d80 GIT binary patch literal 13933 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 literal 0 HcmV?d00001 diff --git a/Standalone/Research/WorldAssemblerM3/Linux139/captures/before-landing-at-shaft-access.jpg b/Standalone/Research/WorldAssemblerM3/Linux139/captures/before-landing-at-shaft-access.jpg new file mode 100644 index 0000000000000000000000000000000000000000..35ce025d2b1bcf4ca04fdd027c4b4a8b00a9b992 GIT binary patch literal 15114 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 HWjfsebiNp8 literal 0 HcmV?d00001 diff --git a/Standalone/Research/WorldAssemblerM3/Linux139/captures/before-looking-up-from-ledge.jpg b/Standalone/Research/WorldAssemblerM3/Linux139/captures/before-looking-up-from-ledge.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8f7ad606cae76ee000ca735f2defb9203e631d18 GIT binary patch literal 15507 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.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" + } +] diff --git a/Standalone/Source/StreamBenchmarkPipeline.h b/Standalone/Source/StreamBenchmarkPipeline.h index 0a337d9..d2d867e 100644 --- a/Standalone/Source/StreamBenchmarkPipeline.h +++ b/Standalone/Source/StreamBenchmarkPipeline.h @@ -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 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(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(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(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(passageCount),static_cast(passagePasses), static_cast(naturalPasses),static_cast(passageCount), static_cast(routePasses),static_cast(routeWalkPasses), @@ -5401,6 +5418,8 @@ namespace StandaloneStreamBenchmark static_cast(routeBindings),static_cast(routeBindingFailures), static_cast(tunnelNetworkBlockedControls), static_cast(tunnelNetworkRoutes), + static_cast(verticalShaftBlockedControls), + static_cast(verticalShaftRoutes), static_cast(blockedControls),static_cast(passageCount), auditPassed?"PASS":"FAIL"); if(!auditPassed) diff --git a/Standalone/Source/WorldAssemblyPrototype.h b/Standalone/Source/WorldAssemblyPrototype.h index d9ae1a8..4e20068 100644 --- a/Standalone/Source/WorldAssemblyPrototype.h +++ b/Standalone/Source/WorldAssemblyPrototype.h @@ -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 tunnelSlots; + {error="native cave planner requires the initialized owner director";return false;} + std::vector> 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::llround(std::clamp( static_cast(value),0.0,1.0)*65536.0)); }; - if(!tunnelSlots.empty()) + if(!plannerSlots.empty()) { std::vector plannedRooms=layout.cavernRooms; std::vector 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( + toMicrometres(shaft.ShaftSpacing),4000000,100000000); + settings.minimumRoomRadiusMicrometres=std::max( + 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(0, + toMicrometres(shaft.ConnectorRadius)); + settings.maximumTunnelRadiusMicrometres= + settings.minimumTunnelRadiusMicrometres; + settings.maximumTunnelLengthMicrometres=std::clamp( + settings.roomSpacingMicrometres*2,4000000,100000000); + settings.smoothWidthMicrometres=1500000; + settings.floorSupportDepthMicrometres=2000000; + settings.irregularityAmplitudeMicrometres=std::max(0, + toMicrometres(shaft.SurfaceRoughness)); + settings.irregularityWavelengthMicrometres=std::max( + 500000,static_cast(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(0, + toMicrometres(shaft.LedgeSpacing)); + settings.ledgeDepthMicrometres=std::max(0, + toMicrometres(shaft.LedgeDepth)); + } + else + { settings.roomSpacingMicrometres=std::max(4000000, toMicrometres(params.RoomSpacing)); settings.minimumRoomRadiusMicrometres=std::max(500000, @@ -1607,6 +1654,7 @@ namespace StandaloneWorldAssemblyPrototype settings.tunnelHorizontalBiasQ16=toQ16(params.TunnelHorizontalBias); settings.originRoomMaximumConnections=params.OriginRoomMaxConnections; settings.tunnelsFlowTowardOrigin=params.bTunnelsFlowTowardOrigin; + } std::vector rooms; std::vector 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& 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 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); } diff --git a/Standalone/Tools/verify/baselines/owner-hash/canonical/owner-hash-floating-islands-style2-v237.baseline b/Standalone/Tools/verify/baselines/owner-hash/canonical/owner-hash-floating-islands-style2-v237.baseline new file mode 100644 index 0000000..98d4cc8 --- /dev/null +++ b/Standalone/Tools/verify/baselines/owner-hash/canonical/owner-hash-floating-islands-style2-v237.baseline @@ -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 diff --git a/Standalone/Tools/verify/baselines/owner-hash/canonical/owner-hash-surface-world-style1-v237.baseline b/Standalone/Tools/verify/baselines/owner-hash/canonical/owner-hash-surface-world-style1-v237.baseline new file mode 100644 index 0000000..e43aaf0 --- /dev/null +++ b/Standalone/Tools/verify/baselines/owner-hash/canonical/owner-hash-surface-world-style1-v237.baseline @@ -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 diff --git a/Standalone/Tools/verify/verify.py b/Standalone/Tools/verify/verify.py index 228ec8c..e97c250 100644 --- a/Standalone/Tools/verify/verify.py +++ b/Standalone/Tools/verify/verify.py @@ -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}")