core: Vertical Shafts on the shared Core cave planner (L139, v237; 15/16 seeds)
Shaft mode reuses PlanResolvedCavernNetworkV1 with a capped full-height cylinder primitive (shapeKind=1), ledges, access bores and cross links; 11 VerticalShaftParams fields mapped. Basal law: arrival 2-3 to a walkable terminal landing; drops/climbs stay reporting tiers. Seed 2 fails shell/seams at the 2-3 arrival mouth (3 variants tried, stopped per AGENTS.md). Whole view faster than assembler/world in 8/8 cells; near-ready slower in 7/8. Source/: VoxelForgeCoreAdapter.cpp. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
No files matched your search
@@ -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
|
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
|
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.
|
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
|
Core profile heightfields are restricted to SurfaceWorld. Native caves require the TunnelNetwork,
|
||||||
Underwater archetype; native lattice requires Maze or VerticalShafts. Core recipe/version fields
|
Underwater, or VerticalShafts archetype; native lattice requires Maze or VerticalShafts. Core
|
||||||
enter the resolved layout identity when selected. An imported manifest cannot be mixed with a
|
recipe/version fields enter the resolved layout identity when selected. An imported manifest cannot
|
||||||
per-strate profiled heightfield.
|
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
|
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.
|
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.
|
Research17 names V2, but the source API and motif vocabulary require V3; source was followed.
|
||||||
Research27 water remains out of scope for U1.
|
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;
|
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
|
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
|
saved strate-layout width is used, currently 8 m. Old packages are converted in memory and marked
|
||||||
|
|||||||
@@ -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. |
|
| `../../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` | 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. |
|
| `../../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. |
|
| `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. |
|
| `../../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. |
|
| `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
|
in `VoxelForgeExploreCommandlet.cpp`. `GetStrateForChunk` still receives layout-chunk Z, not a Core
|
||||||
tile index. See `Standalone/Research/WorldAssemblerM3/Unreal1/REPORT.md` for BuildHost evidence.
|
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. The density pipeline (most-edited hot path)
|
||||||
|
|
||||||
### 4.1 `GetDensityWithParams` (TunnelNetwork) — VoxelGenerator.cpp:3749
|
### 4.1 `GetDensityWithParams` (TunnelNetwork) — VoxelGenerator.cpp:3749
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
# VoxelForge current state
|
# 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
|
## 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).
|
- **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 (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).
|
- **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).
|
- **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).
|
- **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
|
## 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 |
|
| 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
|
## 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).
|
- 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.
|
- 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).
|
- 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.
|
- UE startup/movement numbers above predate L130's v227 reach change; there is no L130/L131 UE movement comparison.
|
||||||
|
|
||||||
## How to verify
|
## 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.
|
- 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/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`.
|
- 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`.
|
||||||
- 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/`.
|
- 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. 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).
|
- 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/l131/ubt.log`. Run Unreal without `prlimit`.
|
- 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
|
## Closed experiments
|
||||||
|
|
||||||
|
|||||||
@@ -144,13 +144,15 @@ bool ConfigureCoreGeometryStratum(const FStrateSlot& Slot, ECaveGeneratorType Ge
|
|||||||
return true;
|
return true;
|
||||||
case EVoxelForgeCoreGeneratorRecipe::NativeCaves:
|
case EVoxelForgeCoreGeneratorRecipe::NativeCaves:
|
||||||
if (GeneratorType != ECaveGeneratorType::TunnelNetwork
|
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);
|
Slot.StrateIndex);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Item.generatorKind = "Caves";
|
Item.generatorKind = GeneratorType == ECaveGeneratorType::VerticalShafts
|
||||||
|
? "VerticalShafts" : "Caves";
|
||||||
Item.recipeId = "Core.Caves.PrimitiveField";
|
Item.recipeId = "Core.Caves.PrimitiveField";
|
||||||
Item.recipeVersion = 1;
|
Item.recipeVersion = 1;
|
||||||
Item.boundDispatchPath = "Core native cave graph uses resolved room and tunnel records";
|
Item.boundDispatchPath = "Core native cave graph uses resolved room and tunnel records";
|
||||||
|
|||||||
@@ -510,7 +510,8 @@ namespace VoxelForgeCore
|
|||||||
// preserves open cave lanes through passage shell overlaps; 236 binds passage mouths to
|
// 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
|
// the planned graph route, preserves its walk corridor through shell overlaps, and keeps
|
||||||
// boundary-blended host rock from widening the authored passage wall.
|
// 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
|
enum class FieldChannel : std::uint32_t
|
||||||
{
|
{
|
||||||
@@ -906,6 +907,7 @@ namespace VoxelForgeCore
|
|||||||
inline constexpr std::uint32_t ResolvedLayoutStampVocabularyVersionV3 = 3;
|
inline constexpr std::uint32_t ResolvedLayoutStampVocabularyVersionV3 = 3;
|
||||||
inline constexpr std::uint32_t ResolvedLayoutCavernGraphVocabularyVersionV1 = 1;
|
inline constexpr std::uint32_t ResolvedLayoutCavernGraphVocabularyVersionV1 = 1;
|
||||||
inline constexpr std::uint32_t ResolvedLayoutCavernGraphVocabularyVersionV2 = 2;
|
inline constexpr std::uint32_t ResolvedLayoutCavernGraphVocabularyVersionV2 = 2;
|
||||||
|
inline constexpr std::uint32_t ResolvedLayoutCavernGraphVocabularyVersionV3 = 3;
|
||||||
inline constexpr std::uint32_t ResolvedLayoutOreVeinVocabularyVersionV1 = 1;
|
inline constexpr std::uint32_t ResolvedLayoutOreVeinVocabularyVersionV1 = 1;
|
||||||
enum class ResolvedLayoutValidationModeV3 : std::uint8_t { compatibility, strict };
|
enum class ResolvedLayoutValidationModeV3 : std::uint8_t { compatibility, strict };
|
||||||
|
|
||||||
@@ -967,6 +969,10 @@ namespace VoxelForgeCore
|
|||||||
{
|
{
|
||||||
std::string stableId;
|
std::string stableId;
|
||||||
std::int32_t strateIndex = -1;
|
std::int32_t strateIndex = -1;
|
||||||
|
// 0 = floor-backed room; 1 = a true vertical cylindrical shaft. The
|
||||||
|
// shaft form is part of the shared cave graph so other recipes can reuse
|
||||||
|
// the same graph, indexing, interval and density machinery.
|
||||||
|
std::uint8_t shapeKind = 0;
|
||||||
std::array<std::int64_t, 3> centerMicrometres{};
|
std::array<std::int64_t, 3> centerMicrometres{};
|
||||||
std::array<std::int64_t, 3> halfExtentsMicrometres{};
|
std::array<std::int64_t, 3> halfExtentsMicrometres{};
|
||||||
std::int64_t floorMicrometres = 0;
|
std::int64_t floorMicrometres = 0;
|
||||||
@@ -982,6 +988,11 @@ namespace VoxelForgeCore
|
|||||||
std::int64_t irregularityAmplitudeMicrometres = 0;
|
std::int64_t irregularityAmplitudeMicrometres = 0;
|
||||||
std::int64_t irregularityWavelengthMicrometres = 0;
|
std::int64_t irregularityWavelengthMicrometres = 0;
|
||||||
std::uint64_t irregularitySeed = 0;
|
std::uint64_t irregularitySeed = 0;
|
||||||
|
// Used only by shapeKind=1. floorMicrometres is the shaft top; the
|
||||||
|
// bottom is center.z - halfExtents.z. Ledges face one cardinal side.
|
||||||
|
std::int64_t ledgeSpacingMicrometres = 0;
|
||||||
|
std::int64_t ledgeDepthMicrometres = 0;
|
||||||
|
std::uint8_t ledgeSide = 0;
|
||||||
std::array<std::int64_t, 3> boundsMinimumMicrometres{};
|
std::array<std::int64_t, 3> boundsMinimumMicrometres{};
|
||||||
std::array<std::int64_t, 3> boundsMaximumMicrometres{};
|
std::array<std::int64_t, 3> boundsMaximumMicrometres{};
|
||||||
};
|
};
|
||||||
@@ -1044,6 +1055,12 @@ namespace VoxelForgeCore
|
|||||||
std::int32_t tunnelHorizontalBiasQ16 = 32768;
|
std::int32_t tunnelHorizontalBiasQ16 = 32768;
|
||||||
std::int32_t originRoomMaximumConnections = 4;
|
std::int32_t originRoomMaximumConnections = 4;
|
||||||
bool tunnelsFlowTowardOrigin = true;
|
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
|
// 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
|
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.recipeId=="Core.Caves.PrimitiveField"
|
||||||
&&stratum.recipeVersion==1;
|
&&stratum.recipeVersion==1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -626,8 +626,12 @@ namespace
|
|||||||
<<",\"floor_profile_distance_um\":"<<room.floorProfileDistanceMicrometres
|
<<",\"floor_profile_distance_um\":"<<room.floorProfileDistanceMicrometres
|
||||||
<<",\"floor_gradient_q16\":["<<room.floorGradientQ16[0]<<','
|
<<",\"floor_gradient_q16\":["<<room.floorGradientQ16[0]<<','
|
||||||
<<room.floorGradientQ16[1]<<']'
|
<<room.floorGradientQ16[1]<<']'
|
||||||
<<",\"smooth_width_um\":"<<room.smoothWidthMicrometres
|
<<",\"smooth_width_um\":"<<room.smoothWidthMicrometres;
|
||||||
;
|
if(room.shapeKind!=0)
|
||||||
|
out<<",\"shape_kind\":"<<static_cast<unsigned>(room.shapeKind)
|
||||||
|
<<",\"ledge_spacing_um\":"<<room.ledgeSpacingMicrometres
|
||||||
|
<<",\"ledge_depth_um\":"<<room.ledgeDepthMicrometres
|
||||||
|
<<",\"ledge_side\":"<<static_cast<unsigned>(room.ledgeSide);
|
||||||
if(room.unionWidthMicrometres>0)
|
if(room.unionWidthMicrometres>0)
|
||||||
out<<",\"union_width_um\":"<<room.unionWidthMicrometres;
|
out<<",\"union_width_um\":"<<room.unionWidthMicrometres;
|
||||||
out<<",\"irregularity_amplitude_um\":"<<room.irregularityAmplitudeMicrometres
|
out<<",\"irregularity_amplitude_um\":"<<room.irregularityAmplitudeMicrometres
|
||||||
@@ -1294,7 +1298,8 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
|
|||||||
"cavern graph vocabulary version is set without graph records");
|
"cavern graph vocabulary version is set without graph records");
|
||||||
}
|
}
|
||||||
else if(cavernGraphVocabularyVersion!=ResolvedLayoutCavernGraphVocabularyVersionV1
|
else if(cavernGraphVocabularyVersion!=ResolvedLayoutCavernGraphVocabularyVersionV1
|
||||||
&&cavernGraphVocabularyVersion!=ResolvedLayoutCavernGraphVocabularyVersionV2)
|
&&cavernGraphVocabularyVersion!=ResolvedLayoutCavernGraphVocabularyVersionV2
|
||||||
|
&&cavernGraphVocabularyVersion!=ResolvedLayoutCavernGraphVocabularyVersionV3)
|
||||||
add("cavern_graph_vocabulary_version",
|
add("cavern_graph_vocabulary_version",
|
||||||
"cavern graph records require a supported vocabulary version");
|
"cavern graph records require a supported vocabulary version");
|
||||||
if(cavernGraphVocabularyVersion!=0)
|
if(cavernGraphVocabularyVersion!=0)
|
||||||
@@ -1327,6 +1332,33 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
|
|||||||
||(room.irregularityAmplitudeMicrometres>0
|
||(room.irregularityAmplitudeMicrometres>0
|
||||||
&&room.irregularityWavelengthMicrometres<=0))
|
&&room.irregularityWavelengthMicrometres<=0))
|
||||||
add("cavern_room_parameters",room.stableId+" has invalid or unbounded parameters");
|
add("cavern_room_parameters",room.stableId+" has invalid or unbounded parameters");
|
||||||
|
const auto* roomStratum=findStratum(room.strateIndex);
|
||||||
|
if(room.shapeKind>1)
|
||||||
|
add("cavern_room_shape",room.stableId+" has an unknown shape kind");
|
||||||
|
if(room.shapeKind==1)
|
||||||
|
{
|
||||||
|
const bool verticalShaftStratum=roomStratum
|
||||||
|
&&roomStratum->generatorKind=="VerticalShafts"
|
||||||
|
&&IsNativeCaveFieldStratum(*roomStratum);
|
||||||
|
if(cavernGraphVocabularyVersion<ResolvedLayoutCavernGraphVocabularyVersionV3
|
||||||
|
||!verticalShaftStratum
|
||||||
|
||room.halfExtentsMicrometres[0]!=room.halfExtentsMicrometres[1]
|
||||||
|
||room.ledgeSpacingMicrometres<0
|
||||||
|
||room.ledgeSpacingMicrometres>100000000
|
||||||
|
||room.ledgeDepthMicrometres<0
|
||||||
|
||room.ledgeDepthMicrometres>=room.halfExtentsMicrometres[0]
|
||||||
|
||room.ledgeSide>3
|
||||||
|
||room.floorMicrometres
|
||||||
|
!=room.centerMicrometres[2]+room.halfExtentsMicrometres[2]
|
||||||
|
||!room.floorProfileStableId.empty()
|
||||||
|
||room.floorGradientQ16[0]!=0||room.floorGradientQ16[1]!=0)
|
||||||
|
add("cavern_shaft_parameters",room.stableId+
|
||||||
|
" has invalid native shaft bounds, ledges, or recipe binding");
|
||||||
|
}
|
||||||
|
else if(room.ledgeSpacingMicrometres!=0||room.ledgeDepthMicrometres!=0
|
||||||
|
||room.ledgeSide!=0)
|
||||||
|
add("cavern_room_shape",room.stableId+
|
||||||
|
" carries shaft parameters on an ordinary room");
|
||||||
const std::int64_t gradientSquared=static_cast<std::int64_t>(room.floorGradientQ16[0])
|
const std::int64_t gradientSquared=static_cast<std::int64_t>(room.floorGradientQ16[0])
|
||||||
*room.floorGradientQ16[0]+static_cast<std::int64_t>(room.floorGradientQ16[1])
|
*room.floorGradientQ16[0]+static_cast<std::int64_t>(room.floorGradientQ16[1])
|
||||||
*room.floorGradientQ16[1];
|
*room.floorGradientQ16[1];
|
||||||
@@ -1415,11 +1447,37 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
|
|||||||
if(room!=cavernRooms.end())
|
if(room!=cavernRooms.end())
|
||||||
{
|
{
|
||||||
const auto& endpoint=edge.controlPointsMicrometres.back();
|
const auto& endpoint=edge.controlPointsMicrometres.back();
|
||||||
|
bool validRoomEndpoint=false;
|
||||||
|
if(room->shapeKind==1)
|
||||||
|
{
|
||||||
|
const std::int64_t shaftBottom=room->centerMicrometres[2]
|
||||||
|
-room->halfExtentsMicrometres[2];
|
||||||
|
const std::int64_t shaftTop=room->centerMicrometres[2]
|
||||||
|
+room->halfExtentsMicrometres[2];
|
||||||
|
const bool insideShaft=endpoint[2]>=shaftBottom
|
||||||
|
&&endpoint[2]<=shaftTop;
|
||||||
|
bool onLanding=room->ledgeSpacingMicrometres==0;
|
||||||
|
if(room->ledgeSpacingMicrometres>0)
|
||||||
|
{
|
||||||
|
const std::int64_t ledgeIndex=(shaftTop-endpoint[2]
|
||||||
|
+room->ledgeSpacingMicrometres/2)
|
||||||
|
/room->ledgeSpacingMicrometres;
|
||||||
|
const std::int64_t ledgeZ=shaftTop
|
||||||
|
-ledgeIndex*room->ledgeSpacingMicrometres;
|
||||||
|
const std::int64_t maximumLedge=(shaftTop-shaftBottom-1000000)
|
||||||
|
/room->ledgeSpacingMicrometres;
|
||||||
|
onLanding=ledgeIndex>=1&&ledgeIndex<=maximumLedge
|
||||||
|
&&std::abs(endpoint[2]-ledgeZ)<=500000;
|
||||||
|
}
|
||||||
|
validRoomEndpoint=insideShaft&&onLanding;
|
||||||
|
}
|
||||||
|
else validRoomEndpoint=
|
||||||
|
std::abs(endpoint[2]-room->floorMicrometres)<=500000;
|
||||||
if(endpoint[0]<room->centerMicrometres[0]-room->halfExtentsMicrometres[0]
|
if(endpoint[0]<room->centerMicrometres[0]-room->halfExtentsMicrometres[0]
|
||||||
||endpoint[0]>room->centerMicrometres[0]+room->halfExtentsMicrometres[0]
|
||endpoint[0]>room->centerMicrometres[0]+room->halfExtentsMicrometres[0]
|
||||||
||endpoint[1]<room->centerMicrometres[1]-room->halfExtentsMicrometres[1]
|
||endpoint[1]<room->centerMicrometres[1]-room->halfExtentsMicrometres[1]
|
||||||
||endpoint[1]>room->centerMicrometres[1]+room->halfExtentsMicrometres[1]
|
||endpoint[1]>room->centerMicrometres[1]+room->halfExtentsMicrometres[1]
|
||||||
||std::abs(endpoint[2]-room->floorMicrometres)>500000)
|
||!validRoomEndpoint)
|
||||||
{
|
{
|
||||||
add("cavern_edge_portal",edge.stableId+" endpoint_um=("
|
add("cavern_edge_portal",edge.stableId+" endpoint_um=("
|
||||||
+std::to_string(endpoint[0])+","+std::to_string(endpoint[1])+","+
|
+std::to_string(endpoint[0])+","+std::to_string(endpoint[1])+","+
|
||||||
@@ -1733,6 +1791,10 @@ std::optional<ResolvedLayoutSnapshotV3> ResolvedLayoutSnapshotV3::FromJson(
|
|||||||
room.irregularityAmplitudeMicrometres=LayoutInt(v,"irregularity_amplitude_um");
|
room.irregularityAmplitudeMicrometres=LayoutInt(v,"irregularity_amplitude_um");
|
||||||
room.irregularityWavelengthMicrometres=LayoutInt(v,"irregularity_wavelength_um");
|
room.irregularityWavelengthMicrometres=LayoutInt(v,"irregularity_wavelength_um");
|
||||||
room.irregularitySeed=static_cast<std::uint64_t>(LayoutInt(v,"irregularity_seed"));
|
room.irregularitySeed=static_cast<std::uint64_t>(LayoutInt(v,"irregularity_seed"));
|
||||||
|
room.shapeKind=static_cast<std::uint8_t>(LayoutInt(v,"shape_kind"));
|
||||||
|
room.ledgeSpacingMicrometres=LayoutInt(v,"ledge_spacing_um");
|
||||||
|
room.ledgeDepthMicrometres=LayoutInt(v,"ledge_depth_um");
|
||||||
|
room.ledgeSide=static_cast<std::uint8_t>(LayoutInt(v,"ledge_side"));
|
||||||
s.cavernRooms.push_back(std::move(room));
|
s.cavernRooms.push_back(std::move(room));
|
||||||
}
|
}
|
||||||
for(const auto& v:root.Get("cavern_edges").AsArray())
|
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;
|
if(!candidate.finalize(error))return false;
|
||||||
layout=std::move(candidate);return true;
|
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.cavernRooms.assign(rooms.begin(),rooms.end());
|
||||||
candidate.cavernEdges.assign(edges.begin(),edges.end());
|
candidate.cavernEdges.assign(edges.begin(),edges.end());
|
||||||
const auto isNativeCavesStrate=[&candidate](std::int32_t strateIndex)
|
const auto isNativeCavesStrate=[&candidate](std::int32_t strateIndex)
|
||||||
@@ -2268,7 +2334,11 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
|||||||
||settings.irregularityAmplitudeMicrometres<0
|
||settings.irregularityAmplitudeMicrometres<0
|
||||||
||settings.irregularityAmplitudeMicrometres>10000000
|
||settings.irregularityAmplitudeMicrometres>10000000
|
||||||
||(settings.irregularityAmplitudeMicrometres>0
|
||(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;}
|
{error="cavern planner settings are outside the bounded room, tunnel or relief ranges";return false;}
|
||||||
|
|
||||||
const ResolvedLayoutPassageV1* arrivalPassage=nullptr;
|
const ResolvedLayoutPassageV1* arrivalPassage=nullptr;
|
||||||
@@ -2282,12 +2352,17 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
|||||||
&&(!departurePassage||passage.stableId<departurePassage->stableId))
|
&&(!departurePassage||passage.stableId<departurePassage->stableId))
|
||||||
departurePassage=&passage;
|
departurePassage=&passage;
|
||||||
}
|
}
|
||||||
if(!arrivalPassage||!departurePassage)
|
const bool terminalShaftRoute=settings.verticalShaftMode
|
||||||
{error="cavern planner needs an incoming lower mouth and an outgoing upper mouth in the target strate";return false;}
|
&&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)
|
if(layout.coreVoxelSizeMicrometres==0)
|
||||||
{error="cavern planner requires a nonzero Core voxel size";return false;}
|
{error="cavern planner requires a nonzero Core voxel size";return false;}
|
||||||
|
|
||||||
using FPoint=std::array<std::int64_t,3>;
|
using FPoint=std::array<std::int64_t,3>;
|
||||||
|
const std::string routePrefix=(terminalShaftRoute?"vertical-shafts/strate-"
|
||||||
|
:"tunnel-network/strate-")+std::to_string(strateIndex)+"/";
|
||||||
constexpr std::int64_t MicrometresPerMetre=1000000;
|
constexpr std::int64_t MicrometresPerMetre=1000000;
|
||||||
constexpr std::int64_t MinimumBoreRadius=1750000;
|
constexpr std::int64_t MinimumBoreRadius=1750000;
|
||||||
constexpr std::int64_t MinimumBoreHeight=2200000;
|
constexpr std::int64_t MinimumBoreHeight=2200000;
|
||||||
@@ -2490,19 +2565,47 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
|||||||
std::array<std::int64_t,2> arrivalTangentQ16{},departureTangentQ16{};
|
std::array<std::int64_t,2> arrivalTangentQ16{},departureTangentQ16{};
|
||||||
std::int64_t arrivalFloorSlopeQ16=0,departureFloorSlopeQ16=0;
|
std::int64_t arrivalFloorSlopeQ16=0,departureFloorSlopeQ16=0;
|
||||||
if(!mouthInnerPoint(*arrivalPassage,true,routeStart,arrivalTangentQ16,
|
if(!mouthInnerPoint(*arrivalPassage,true,routeStart,arrivalTangentQ16,
|
||||||
arrivalFloorSlopeQ16)
|
arrivalFloorSlopeQ16))
|
||||||
||!mouthInnerPoint(*departurePassage,false,routeEnd,departureTangentQ16,
|
{error="cavern passage mouth is missing a usable horizontal route tangent";return false;}
|
||||||
|
if(!terminalShaftRoute
|
||||||
|
&&!mouthInnerPoint(*departurePassage,false,routeEnd,departureTangentQ16,
|
||||||
departureFloorSlopeQ16))
|
departureFloorSlopeQ16))
|
||||||
{error="cavern passage mouth is missing a usable horizontal route tangent";return false;}
|
{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];
|
std::int64_t dx=routeEnd[0]-routeStart[0];
|
||||||
const std::int64_t dy=routeEnd[1]-routeStart[1];
|
std::int64_t dy=routeEnd[1]-routeStart[1];
|
||||||
const std::int64_t dz=routeEnd[2]-routeStart[2];
|
const std::int64_t dz=routeEnd[2]-routeStart[2];
|
||||||
const std::int64_t horizontalBias=std::clamp<std::int64_t>(
|
const std::int64_t horizontalBias=std::clamp<std::int64_t>(
|
||||||
settings.tunnelHorizontalBiasQ16,0,65536);
|
settings.tunnelHorizontalBiasQ16,0,65536);
|
||||||
const std::int64_t planGradeDenominator=MaximumWalkGradeDenominator
|
const std::int64_t planGradeDenominator=MaximumWalkGradeDenominator
|
||||||
+horizontalBias/32768;
|
+horizontalBias/32768;
|
||||||
const std::int64_t requiredLength=(std::abs(dz)*planGradeDenominator*130)/100+1000;
|
const std::int64_t requiredLength=(std::abs(dz)*planGradeDenominator*130)/100+1000;
|
||||||
|
if(terminalShaftRoute)
|
||||||
|
{
|
||||||
|
// The basal stratum has no next mouth by design. The walk-only spine starts
|
||||||
|
// at its incoming passage and ends on a supported landing above bedrock.
|
||||||
|
// Optional open shafts are side features with separate climb/fall reporting.
|
||||||
|
// Normalize by the truncated Q16 tangent magnitude: dividing by 65536
|
||||||
|
// shortens non-cardinal vectors enough to miss the strict grade-run bound.
|
||||||
|
const std::uint64_t tangentSquared=static_cast<std::uint64_t>(
|
||||||
|
departureTangentQ16[0]*departureTangentQ16[0]
|
||||||
|
+departureTangentQ16[1]*departureTangentQ16[1]);
|
||||||
|
const std::int64_t tangentMagnitudeQ16=static_cast<std::int64_t>(
|
||||||
|
integerSqrt(tangentSquared));
|
||||||
|
if(tangentMagnitudeQ16<=0)
|
||||||
|
{error="cavern planner terminal tangent has zero horizontal length";return false;}
|
||||||
|
const std::int64_t terminalProjectionLength=requiredLength+1000;
|
||||||
|
for(std::size_t axis=0;axis<2;++axis)
|
||||||
|
routeEnd[axis]=routeStart[axis]
|
||||||
|
+departureTangentQ16[axis]*terminalProjectionLength/tangentMagnitudeQ16;
|
||||||
|
dx=routeEnd[0]-routeStart[0];dy=routeEnd[1]-routeStart[1];
|
||||||
|
}
|
||||||
std::int64_t pathAxisX=dx,pathAxisY=dy;
|
std::int64_t pathAxisX=dx,pathAxisY=dy;
|
||||||
FPoint routeLeadStart=routeStart,routeLeadEnd=routeEnd;
|
FPoint routeLeadStart=routeStart,routeLeadEnd=routeEnd;
|
||||||
for(std::size_t axis=0;axis<2;++axis)
|
for(std::size_t axis=0;axis<2;++axis)
|
||||||
@@ -2648,7 +2751,7 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
appendPassageClearanceSpans(*arrivalPassage,true);
|
appendPassageClearanceSpans(*arrivalPassage,true);
|
||||||
appendPassageClearanceSpans(*departurePassage,false);
|
if(departurePassage)appendPassageClearanceSpans(*departurePassage,false);
|
||||||
const auto segmentDistanceMicrometres=[](const std::array<double,3>& p0,
|
const auto segmentDistanceMicrometres=[](const std::array<double,3>& p0,
|
||||||
const std::array<double,3>& p1,const std::array<double,3>& q0,
|
const std::array<double,3>& p1,const std::array<double,3>& q0,
|
||||||
const std::array<double,3>& q1)
|
const std::array<double,3>& q1)
|
||||||
@@ -2804,8 +2907,7 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
|||||||
cumulative[i]=cumulative[i-1]+distance2d(routePoints[i-1],routePoints[i]);
|
cumulative[i]=cumulative[i-1]+distance2d(routePoints[i-1],routePoints[i]);
|
||||||
if(cumulative.back()!=totalRouteLength)
|
if(cumulative.back()!=totalRouteLength)
|
||||||
{error="cavern planner route length accounting failed";return false;}
|
{error="cavern planner route length accounting failed";return false;}
|
||||||
const std::string backboneFloorProfileId="tunnel-network/strate-"
|
const std::string backboneFloorProfileId=routePrefix+"backbone-floor";
|
||||||
+std::to_string(strateIndex)+"/backbone-floor";
|
|
||||||
constexpr std::int64_t ProfileOne=std::int64_t{1}<<30;
|
constexpr std::int64_t ProfileOne=std::int64_t{1}<<30;
|
||||||
const auto roundedDivide=[](std::int64_t numerator,std::int64_t denominator)
|
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;
|
std::int64_t radiusMicrometres=0;
|
||||||
bool destination=false;
|
bool destination=false;
|
||||||
};
|
};
|
||||||
std::vector<FRoomNode> nodes;nodes.reserve(nodeDistances.size());
|
std::vector<FRoomNode> nodes;nodes.reserve(nodeDistances.size()+1);
|
||||||
const std::int64_t floorCutMinimum=clampQ16(settings.roomFloorCutMinimumQ16);
|
const std::int64_t floorCutMinimum=clampQ16(settings.roomFloorCutMinimumQ16);
|
||||||
const std::int64_t floorCutMaximum=std::max(floorCutMinimum,
|
const std::int64_t floorCutMaximum=std::max(floorCutMinimum,
|
||||||
clampQ16(settings.roomFloorCutMaximumQ16));
|
clampQ16(settings.roomFloorCutMaximumQ16));
|
||||||
@@ -3025,15 +3127,23 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
|||||||
node.floorProfileStableId=backboneFloorProfileId;
|
node.floorProfileStableId=backboneFloorProfileId;
|
||||||
node.floorProfileDistanceMicrometres=distance;
|
node.floorProfileDistanceMicrometres=distance;
|
||||||
node.floorGradientQ16=routeFloorGradientAt(distance);
|
node.floorGradientQ16=routeFloorGradientAt(distance);
|
||||||
node.destination=distance==mainRouteMidpoint;
|
node.destination=!terminalShaftRoute&&distance==mainRouteMidpoint;
|
||||||
node.id="tunnel-network/strate-"+std::to_string(strateIndex)+"/room-"
|
node.id=routePrefix+"room-"+std::to_string(nodeIndex);
|
||||||
+std::to_string(nodeIndex);
|
|
||||||
const std::uint64_t hash=LayoutStampSeed(seed,strateIndex,node.id);
|
const std::uint64_t hash=LayoutStampSeed(seed,strateIndex,node.id);
|
||||||
node.radiusMicrometres=node.destination?settings.destinationRadiusMicrometres
|
node.radiusMicrometres=node.destination?settings.destinationRadiusMicrometres
|
||||||
:settings.minimumRoomRadiusMicrometres
|
:settings.minimumRoomRadiusMicrometres
|
||||||
+static_cast<std::int64_t>(hash%static_cast<std::uint64_t>(baseRoomSpan+1));
|
+static_cast<std::int64_t>(hash%static_cast<std::uint64_t>(baseRoomSpan+1));
|
||||||
nodes.push_back(std::move(node));
|
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,
|
const auto makeCompound=[&](const FRoomNode& node,bool destination,
|
||||||
std::vector<ResolvedCavernRoomV1>& outRooms)
|
std::vector<ResolvedCavernRoomV1>& outRooms)
|
||||||
{
|
{
|
||||||
@@ -3154,6 +3264,130 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
|||||||
}
|
}
|
||||||
return edge;
|
return edge;
|
||||||
};
|
};
|
||||||
|
struct FPlannedShaftLanding
|
||||||
|
{
|
||||||
|
FRoomNode node;
|
||||||
|
FPoint point{};
|
||||||
|
};
|
||||||
|
std::vector<FPlannedShaftLanding> shaftLandings;
|
||||||
|
std::size_t plannedShaftCount=0;
|
||||||
|
if(terminalShaftRoute&&settings.roomDensityQ16>0)
|
||||||
|
{
|
||||||
|
const std::int64_t ledgeSpacing=std::max<std::int64_t>(0,
|
||||||
|
settings.ledgeSpacingMicrometres);
|
||||||
|
const std::int64_t ledgeDepth=std::clamp<std::int64_t>(
|
||||||
|
settings.ledgeDepthMicrometres,0,5000000);
|
||||||
|
bool emittedShaft=false;
|
||||||
|
const std::int64_t routeLengthXY=std::max<std::int64_t>(1,
|
||||||
|
static_cast<std::int64_t>(integerSqrt(static_cast<std::uint64_t>(
|
||||||
|
pathAxisX*pathAxisX+pathAxisY*pathAxisY))));
|
||||||
|
const std::int64_t sideX=-pathAxisY*65536/routeLengthXY;
|
||||||
|
const std::int64_t sideY=pathAxisX*65536/routeLengthXY;
|
||||||
|
const auto selectedRadius=[&](const std::string& stableId)
|
||||||
|
{
|
||||||
|
const std::int64_t span=settings.maximumRoomRadiusMicrometres
|
||||||
|
-settings.minimumRoomRadiusMicrometres;
|
||||||
|
return settings.minimumRoomRadiusMicrometres+(span==0?0:
|
||||||
|
static_cast<std::int64_t>(LayoutStampSeed(seed,strateIndex,stableId)
|
||||||
|
%static_cast<std::uint64_t>(span+1)));
|
||||||
|
};
|
||||||
|
for(std::size_t nodeIndex=1;nodeIndex+1<nodes.size()&&rooms.size()<2048;
|
||||||
|
++nodeIndex)
|
||||||
|
{
|
||||||
|
const auto& parent=nodes[nodeIndex];
|
||||||
|
const std::uint64_t hash=LayoutStampSeed(seed,strateIndex,
|
||||||
|
parent.id+"/shaft");
|
||||||
|
if(emittedShaft&&static_cast<std::int64_t>(hash&0xffffu)
|
||||||
|
>=settings.roomDensityQ16)continue;
|
||||||
|
const std::int64_t radius=selectedRadius(parent.id+"/shaft-radius");
|
||||||
|
const std::int64_t top=parent.point[2]
|
||||||
|
+(ledgeSpacing>0?ledgeSpacing:1000000);
|
||||||
|
const std::int64_t minimumTop=stratum->content.bottomMicrometres+4000000;
|
||||||
|
if(top>stratum->content.topMicrometres-1000000
|
||||||
|
||top<minimumTop)continue;
|
||||||
|
// Shaft length has no independent owner parameter: use as much content
|
||||||
|
// as available, with the graph's 200 m half-extent bound as a hard cap.
|
||||||
|
const std::int64_t bottom=std::max(
|
||||||
|
stratum->content.bottomMicrometres+1000000,top-200000000);
|
||||||
|
if(ledgeSpacing>0&&top-bottom<ledgeSpacing+2000000)continue;
|
||||||
|
const bool positiveSide=(hash&0x10000u)!=0;
|
||||||
|
const std::int64_t sideXSign=(sideX>=0?1:-1)*(positiveSide?1:-1);
|
||||||
|
const std::int64_t sideYSign=(sideY>=0?1:-1)*(positiveSide?1:-1);
|
||||||
|
const bool useX=std::abs(sideX)>=std::abs(sideY);
|
||||||
|
const std::uint8_t outwardSide=useX
|
||||||
|
?static_cast<std::uint8_t>(sideXSign>0?0:2)
|
||||||
|
:static_cast<std::uint8_t>(sideYSign>0?1:3);
|
||||||
|
const std::array<std::array<std::int64_t,2>,4> cardinal={{{1,0},{0,1},
|
||||||
|
{-1,0},{0,-1}}};
|
||||||
|
const auto outward=cardinal[outwardSide];
|
||||||
|
const std::int64_t offset=std::max<std::int64_t>(
|
||||||
|
parent.radiusMicrometres+radius+3000000,
|
||||||
|
settings.roomSpacingMicrometres/2);
|
||||||
|
FPlannedShaftLanding landing;landing.node.id=routePrefix+"shaft-"
|
||||||
|
+std::to_string(shaftLandings.size());
|
||||||
|
landing.node.point={parent.point[0]+outward[0]*offset,
|
||||||
|
parent.point[1]+outward[1]*offset,parent.point[2]};
|
||||||
|
landing.node.radiusMicrometres=radius;
|
||||||
|
const std::int64_t landingZ=top-(ledgeSpacing>0?ledgeSpacing:1000000);
|
||||||
|
const std::array<std::int64_t,2> toParent={-outward[0],-outward[1]};
|
||||||
|
const std::uint8_t ledgeSide=static_cast<std::uint8_t>(
|
||||||
|
(outwardSide+2)%4);
|
||||||
|
ResolvedCavernRoomV1 shaft;shaft.stableId=landing.node.id+"/primitive-0";
|
||||||
|
shaft.strateIndex=strateIndex;shaft.shapeKind=1;
|
||||||
|
shaft.centerMicrometres={landing.node.point[0],landing.node.point[1],
|
||||||
|
(top+bottom)/2};
|
||||||
|
shaft.halfExtentsMicrometres={radius,radius,(top-bottom)/2};
|
||||||
|
shaft.floorMicrometres=top;shaft.smoothWidthMicrometres=smooth;
|
||||||
|
shaft.ledgeSpacingMicrometres=ledgeSpacing;
|
||||||
|
shaft.ledgeDepthMicrometres=ledgeDepth;shaft.ledgeSide=ledgeSide;
|
||||||
|
shaft.irregularityAmplitudeMicrometres=irregularity;
|
||||||
|
shaft.irregularityWavelengthMicrometres=
|
||||||
|
settings.irregularityWavelengthMicrometres;
|
||||||
|
shaft.irregularitySeed=LayoutStampSeed(seed,strateIndex,shaft.stableId);
|
||||||
|
rooms.push_back(std::move(shaft));
|
||||||
|
landing.point={landing.node.point[0]
|
||||||
|
+toParent[0]*(radius-ledgeDepth/2),
|
||||||
|
landing.node.point[1]+toParent[1]*(radius-ledgeDepth/2),landingZ};
|
||||||
|
landing.node.point=landing.point;
|
||||||
|
const std::string accessId=routePrefix+"shaft-access-"
|
||||||
|
+std::to_string(shaftLandings.size());
|
||||||
|
auto access=makeEdge(accessId,componentRoomId(parent),parent.point,
|
||||||
|
landing.node,routeBoreRadius,-1,-1);
|
||||||
|
// The connector's own floor SDF retains its tread. It must not enter
|
||||||
|
// the descending backbone's finite floor-cap arbitration, where its
|
||||||
|
// nearby but independent floor could suppress backbone support.
|
||||||
|
access.floorSupportDepthMicrometres=0;
|
||||||
|
const std::int64_t run=distance2d(parent.point,landing.point);
|
||||||
|
const std::int64_t rise=std::abs(parent.point[2]-landing.point[2]);
|
||||||
|
if(run<=0||rise*MaximumWalkGradeDenominator>run)
|
||||||
|
{
|
||||||
|
rooms.pop_back();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
edges.push_back(std::move(access));
|
||||||
|
shaftLandings.push_back(std::move(landing));
|
||||||
|
emittedShaft=true;
|
||||||
|
}
|
||||||
|
plannedShaftCount=shaftLandings.size();
|
||||||
|
for(std::size_t index=1;index<shaftLandings.size();++index)
|
||||||
|
{
|
||||||
|
const std::uint64_t hash=LayoutStampSeed(seed,strateIndex,
|
||||||
|
shaftLandings[index-1].node.id+"/cross-"+std::to_string(index));
|
||||||
|
if(static_cast<std::int64_t>(hash&0xffffu)>=settings.tunnelDensityQ16)
|
||||||
|
continue;
|
||||||
|
const auto& from=shaftLandings[index-1];
|
||||||
|
const auto& to=shaftLandings[index];
|
||||||
|
const std::int64_t run=distance2d(from.point,to.point);
|
||||||
|
const std::int64_t rise=std::abs(from.point[2]-to.point[2]);
|
||||||
|
if(run<=0||run>settings.maximumTunnelLengthMicrometres
|
||||||
|
||rise*MaximumWalkGradeDenominator>run)continue;
|
||||||
|
auto link=makeEdge(routePrefix+"shaft-cross-"+std::to_string(index-1),
|
||||||
|
componentRoomId(from.node),from.point,to.node,routeBoreRadius,-1,-1);
|
||||||
|
link.floorSupportDepthMicrometres=0;
|
||||||
|
edges.push_back(std::move(link));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const auto arrivalPoint=routeStart;
|
const auto arrivalPoint=routeStart;
|
||||||
FPoint previousPoint=arrivalPoint;
|
FPoint previousPoint=arrivalPoint;
|
||||||
std::int64_t previousProfileDistance=0;
|
std::int64_t previousProfileDistance=0;
|
||||||
@@ -3161,8 +3395,7 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
|||||||
for(std::size_t nodeIndex=0;nodeIndex<nodes.size();++nodeIndex)
|
for(std::size_t nodeIndex=0;nodeIndex<nodes.size();++nodeIndex)
|
||||||
{
|
{
|
||||||
const auto& node=nodes[nodeIndex];
|
const auto& node=nodes[nodeIndex];
|
||||||
const std::string edgeId="tunnel-network/strate-"+std::to_string(strateIndex)
|
const std::string edgeId=routePrefix+"route-edge-"+std::to_string(nodeIndex);
|
||||||
+"/route-edge-"+std::to_string(nodeIndex);
|
|
||||||
auto edge=makeEdge(edgeId,previousAnchor,previousPoint,node,
|
auto edge=makeEdge(edgeId,previousAnchor,previousPoint,node,
|
||||||
selectedBoreRadius(edgeId),
|
selectedBoreRadius(edgeId),
|
||||||
previousProfileDistance,node.floorProfileDistanceMicrometres);
|
previousProfileDistance,node.floorProfileDistanceMicrometres);
|
||||||
@@ -3171,10 +3404,9 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
|||||||
previousProfileDistance=node.floorProfileDistanceMicrometres;
|
previousProfileDistance=node.floorProfileDistanceMicrometres;
|
||||||
previousAnchor=componentRoomId(node);
|
previousAnchor=componentRoomId(node);
|
||||||
}
|
}
|
||||||
if(!nodes.empty())
|
if(!nodes.empty()&&departurePassage)
|
||||||
{
|
{
|
||||||
const std::string departureEdgeId="tunnel-network/strate-"
|
const std::string departureEdgeId=routePrefix+"departure-edge";
|
||||||
+std::to_string(strateIndex)+"/departure-edge";
|
|
||||||
auto edge=makeEdge(departureEdgeId,departurePassage->stableId+"/upper-mouth",
|
auto edge=makeEdge(departureEdgeId,departurePassage->stableId+"/upper-mouth",
|
||||||
routeEnd,nodes.back(),selectedBoreRadius(departureEdgeId),
|
routeEnd,nodes.back(),selectedBoreRadius(departureEdgeId),
|
||||||
totalRouteLength,nodes.back().floorProfileDistanceMicrometres);
|
totalRouteLength,nodes.back().floorProfileDistanceMicrometres);
|
||||||
@@ -3187,7 +3419,7 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
|||||||
const std::size_t branchLimit=settings.originRoomMaximumConnections<=0
|
const std::size_t branchLimit=settings.originRoomMaximumConnections<=0
|
||||||
?32u:static_cast<std::size_t>(std::clamp(settings.originRoomMaximumConnections,0,32));
|
?32u:static_cast<std::size_t>(std::clamp(settings.originRoomMaximumConnections,0,32));
|
||||||
std::size_t branchCount=0;
|
std::size_t branchCount=0;
|
||||||
for(std::size_t nodeIndex=1;nodeIndex+1<nodes.size()
|
for(std::size_t nodeIndex=1;!terminalShaftRoute&&nodeIndex+1<nodes.size()
|
||||||
&&branchCount<branchLimit&&rooms.size()<2048;++nodeIndex)
|
&&branchCount<branchLimit&&rooms.size()<2048;++nodeIndex)
|
||||||
{
|
{
|
||||||
const auto& parent=nodes[nodeIndex];
|
const auto& parent=nodes[nodeIndex];
|
||||||
@@ -3210,8 +3442,7 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
|||||||
// A side branch leaves the backbone laterally, so it has its own local flat
|
// A side branch leaves the backbone laterally, so it has its own local flat
|
||||||
// floor and must not reference the parent's one-dimensional h(s) profile.
|
// floor and must not reference the parent's one-dimensional h(s) profile.
|
||||||
makeCompound(branch,false,rooms);
|
makeCompound(branch,false,rooms);
|
||||||
const std::string branchEdgeId="tunnel-network/strate-"
|
const std::string branchEdgeId=routePrefix+"side-edge-"+std::to_string(branchCount);
|
||||||
+std::to_string(strateIndex)+"/side-edge-"+std::to_string(branchCount);
|
|
||||||
ResolvedCavernEdgeV1 branchEdge;
|
ResolvedCavernEdgeV1 branchEdge;
|
||||||
if(settings.tunnelsFlowTowardOrigin)
|
if(settings.tunnelsFlowTowardOrigin)
|
||||||
branchEdge=makeEdge(branchEdgeId,componentRoomId(branch),
|
branchEdge=makeEdge(branchEdgeId,componentRoomId(branch),
|
||||||
@@ -3226,7 +3457,7 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
|||||||
++branchCount;
|
++branchCount;
|
||||||
}
|
}
|
||||||
if(nodes.empty()||rooms.empty()||edges.empty())
|
if(nodes.empty()||rooms.empty()||edges.empty())
|
||||||
{error="cavern planner emitted an empty arrival-to-departure graph";return false;}
|
{error="cavern planner emitted an empty mouth-to-terminal graph";return false;}
|
||||||
|
|
||||||
double maximumGradeDegrees=0.0;
|
double maximumGradeDegrees=0.0;
|
||||||
for(const auto& edge:edges)
|
for(const auto& edge:edges)
|
||||||
@@ -3255,9 +3486,10 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
|||||||
}
|
}
|
||||||
const double plannerMilliseconds=std::chrono::duration<double,std::milli>(
|
const double plannerMilliseconds=std::chrono::duration<double,std::milli>(
|
||||||
std::chrono::steady_clock::now()-plannerStarted).count();
|
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",
|
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->stableId.c_str(),
|
strateIndex,seed,arrivalPassage->stableId.c_str(),
|
||||||
nodes.size(),rooms.size(),edges.size(),branchCount,totalRouteLength/1.0e6,
|
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,
|
std::abs(dz)/1.0e6,maximumGradeDegrees,plannedMouthTangentAlignment,
|
||||||
routeBoreRadius/1.0e6,PlannedBoreHeight/1.0e6,
|
routeBoreRadius/1.0e6,PlannedBoreHeight/1.0e6,
|
||||||
settings.destinationRadiusMicrometres/1.0e6,plannerMilliseconds);
|
settings.destinationRadiusMicrometres/1.0e6,plannerMilliseconds);
|
||||||
|
|||||||
@@ -1284,7 +1284,8 @@ public:
|
|||||||
const std::uint32_t reliefSeed=static_cast<std::uint32_t>(Owner?Owner->Seed:0);
|
const std::uint32_t reliefSeed=static_cast<std::uint32_t>(Owner?Owner->Seed:0);
|
||||||
const auto* queryStratum=FindBoundStratumForManagerZ(Z);
|
const auto* queryStratum=FindBoundStratumForManagerZ(Z);
|
||||||
const bool plannedTunnelStratum=queryStratum&&queryStratum->Resolved
|
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->recipeId=="Core.Caves.PrimitiveField"
|
||||||
&&queryStratum->Resolved->recipeVersion==1;
|
&&queryStratum->Resolved->recipeVersion==1;
|
||||||
const auto* routePassageIndices=passageTileScopeMatches
|
const auto* routePassageIndices=passageTileScopeMatches
|
||||||
@@ -1617,9 +1618,12 @@ public:
|
|||||||
const auto plannedPortalWalkCorridorContains=[&]()
|
const auto plannedPortalWalkCorridorContains=[&]()
|
||||||
{
|
{
|
||||||
if(!queryStratum||!queryStratum->Resolved
|
if(!queryStratum||!queryStratum->Resolved
|
||||||
||queryStratum->Resolved->generatorKind!="TunnelNetwork"
|
||(queryStratum->Resolved->generatorKind!="TunnelNetwork"
|
||||||
|
&&queryStratum->Resolved->generatorKind!="VerticalShafts")
|
||||||
||queryStratum->Resolved->recipeId!="Core.Caves.PrimitiveField"
|
||queryStratum->Resolved->recipeId!="Core.Caves.PrimitiveField"
|
||||||
||queryStratum->Resolved->recipeVersion!=1)return false;
|
||queryStratum->Resolved->recipeVersion!=1)return false;
|
||||||
|
const bool terminalShaftRoute=
|
||||||
|
queryStratum->Resolved->generatorKind=="VerticalShafts";
|
||||||
const VoxelForgeCore::ResolvedLayoutAnchorV2* first=nullptr;
|
const VoxelForgeCore::ResolvedLayoutAnchorV2* first=nullptr;
|
||||||
const VoxelForgeCore::ResolvedLayoutAnchorV2* second=nullptr;
|
const VoxelForgeCore::ResolvedLayoutAnchorV2* second=nullptr;
|
||||||
const VoxelForgeCore::ResolvedLayoutAnchorV2* third=nullptr;
|
const VoxelForgeCore::ResolvedLayoutAnchorV2* third=nullptr;
|
||||||
@@ -1638,8 +1642,12 @@ public:
|
|||||||
}
|
}
|
||||||
if(routeCount<6||!first||!second||!third||!lastThree||!lastTwo||!last
|
if(routeCount<6||!first||!second||!third||!lastThree||!lastTwo||!last
|
||||||
||!first->stableId.ends_with("000-arrival")
|
||!first->stableId.ends_with("000-arrival")
|
||||||
||!second->stableId.ends_with("000-edge-start")
|
||!second->stableId.ends_with("000-edge-start"))return false;
|
||||||
||!lastTwo->stableId.ends_with("998-departure-inner")
|
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;
|
||!last->stableId.ends_with("999-departure"))return false;
|
||||||
const double metresPerSubvoxel=1.0/qPerMetre;
|
const double metresPerSubvoxel=1.0/qPerMetre;
|
||||||
const auto position=[&](const VoxelForgeCore::ResolvedLayoutAnchorV2& anchor)
|
const auto position=[&](const VoxelForgeCore::ResolvedLayoutAnchorV2& anchor)
|
||||||
@@ -1677,6 +1685,7 @@ public:
|
|||||||
if(nearRouteSegment(*first,*second,PortalWalkLeadMetres,false,0.0)
|
if(nearRouteSegment(*first,*second,PortalWalkLeadMetres,false,0.0)
|
||||||
||nearRouteSegment(*second,*third,PortalWalkLeadMetres,false,
|
||nearRouteSegment(*second,*third,PortalWalkLeadMetres,false,
|
||||||
arrivalInnerDistance))return true;
|
arrivalInnerDistance))return true;
|
||||||
|
if(terminalShaftRoute)return false;
|
||||||
const auto departureInner=position(*lastTwo),departure=position(*last);
|
const auto departureInner=position(*lastTwo),departure=position(*last);
|
||||||
const double departureTailDistance=std::hypot(
|
const double departureTailDistance=std::hypot(
|
||||||
departure[0]-departureInner[0],departure[1]-departureInner[1]);
|
departure[0]-departureInner[0],departure[1]-departureInner[1]);
|
||||||
@@ -3835,8 +3844,11 @@ private:
|
|||||||
const VoxelForgeCore::ResolvedCavernRoomV1* room=nullptr;
|
const VoxelForgeCore::ResolvedCavernRoomV1* room=nullptr;
|
||||||
const VoxelForgeCore::ResolvedCavernEdgeV1* edge=nullptr;
|
const VoxelForgeCore::ResolvedCavernEdgeV1* edge=nullptr;
|
||||||
bool nativeGraph=false;
|
bool nativeGraph=false;
|
||||||
|
std::uint8_t roomShapeKind=0;
|
||||||
|
std::uint8_t ledgeSide=0;
|
||||||
std::array<std::int64_t,3> boundsMinimumMicrometres{},boundsMaximumMicrometres{};
|
std::array<std::int64_t,3> boundsMinimumMicrometres{},boundsMaximumMicrometres{};
|
||||||
std::array<double,3> centerMetres{},halfExtentsMetres{};
|
std::array<double,3> centerMetres{},halfExtentsMetres{};
|
||||||
|
double ledgeSpacingMetres=0.0,ledgeDepthMetres=0.0;
|
||||||
struct FSegment
|
struct FSegment
|
||||||
{
|
{
|
||||||
std::array<double,3> start{},end{},minimum{},maximum{};
|
std::array<double,3> start{},end{},minimum{},maximum{};
|
||||||
@@ -4285,7 +4297,9 @@ private:
|
|||||||
||(ResolvedLayout->cavernGraphVocabularyVersion
|
||(ResolvedLayout->cavernGraphVocabularyVersion
|
||||||
!=VoxelForgeCore::ResolvedLayoutCavernGraphVocabularyVersionV1
|
!=VoxelForgeCore::ResolvedLayoutCavernGraphVocabularyVersionV1
|
||||||
&&ResolvedLayout->cavernGraphVocabularyVersion
|
&&ResolvedLayout->cavernGraphVocabularyVersion
|
||||||
!=VoxelForgeCore::ResolvedLayoutCavernGraphVocabularyVersionV2))return;
|
!=VoxelForgeCore::ResolvedLayoutCavernGraphVocabularyVersionV2
|
||||||
|
&&ResolvedLayout->cavernGraphVocabularyVersion
|
||||||
|
!=VoxelForgeCore::ResolvedLayoutCavernGraphVocabularyVersionV3))return;
|
||||||
const auto isNativeGraphStrate=[this](std::int32_t strateIndex)
|
const auto isNativeGraphStrate=[this](std::int32_t strateIndex)
|
||||||
{
|
{
|
||||||
const auto found=std::find_if(ResolvedLayout->strates.begin(),
|
const auto found=std::find_if(ResolvedLayout->strates.begin(),
|
||||||
@@ -4302,6 +4316,10 @@ private:
|
|||||||
{
|
{
|
||||||
FCavernRuntimeFeature runtime;runtime.room=&room;
|
FCavernRuntimeFeature runtime;runtime.room=&room;
|
||||||
runtime.nativeGraph=isNativeGraphStrate(room.strateIndex);
|
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.boundsMinimumMicrometres=room.boundsMinimumMicrometres;
|
||||||
runtime.boundsMaximumMicrometres=room.boundsMaximumMicrometres;
|
runtime.boundsMaximumMicrometres=room.boundsMaximumMicrometres;
|
||||||
for(std::size_t axis=0;axis<3;++axis)
|
for(std::size_t axis=0;axis<3;++axis)
|
||||||
@@ -4956,7 +4974,8 @@ private:
|
|||||||
ResolvedLayout->strates.end(),[&](const auto& stratum)
|
ResolvedLayout->strates.end(),[&](const auto& stratum)
|
||||||
{return stratum.strateIndex==passage.lowerStrateIndex;});
|
{return stratum.strateIndex==passage.lowerStrateIndex;});
|
||||||
if(lowerStratum!=ResolvedLayout->strates.end()
|
if(lowerStratum!=ResolvedLayout->strates.end()
|
||||||
&&lowerStratum->generatorKind=="TunnelNetwork"
|
&&(lowerStratum->generatorKind=="TunnelNetwork"
|
||||||
|
||lowerStratum->generatorKind=="VerticalShafts")
|
||||||
&&routeEnd<controls.size())
|
&&routeEnd<controls.size())
|
||||||
{
|
{
|
||||||
double routeHeight=0.0,distanceSquared=0.0;
|
double routeHeight=0.0,distanceSquared=0.0;
|
||||||
@@ -5667,6 +5686,11 @@ public:
|
|||||||
FMetricInterval featureVoid;
|
FMetricInterval featureVoid;
|
||||||
if(feature.room)
|
if(feature.room)
|
||||||
{
|
{
|
||||||
|
// Shaft wall and ledge SDFs are evaluated exactly per sample.
|
||||||
|
// Until a conservative interval bound for their carved shelves is
|
||||||
|
// available, keep intersecting boxes Mixed instead of proving a
|
||||||
|
// potentially false uniform sign.
|
||||||
|
if(feature.roomShapeKind==1)return {0.0,0.0,false};
|
||||||
double normalizedMinimumSquared=0.0,normalizedMaximumSquared=0.0;
|
double normalizedMinimumSquared=0.0,normalizedMaximumSquared=0.0;
|
||||||
double meanRadius=0.0;
|
double meanRadius=0.0;
|
||||||
for(std::size_t axis=0;axis<3;++axis)
|
for(std::size_t axis=0;axis<3;++axis)
|
||||||
@@ -7884,6 +7908,64 @@ private:
|
|||||||
double edgeFloor=0.0;
|
double edgeFloor=0.0;
|
||||||
if(feature.room)
|
if(feature.room)
|
||||||
{
|
{
|
||||||
|
if(feature.roomShapeKind==1)
|
||||||
|
{
|
||||||
|
const double radius=feature.halfExtentsMetres[0];
|
||||||
|
const double bottom=feature.centerMetres[2]-feature.halfExtentsMetres[2];
|
||||||
|
const double top=feature.centerMetres[2]+feature.halfExtentsMetres[2];
|
||||||
|
const double radial=std::hypot(point[0]-feature.centerMetres[0],
|
||||||
|
point[1]-feature.centerMetres[1]);
|
||||||
|
const double radialQ=radial-radius;
|
||||||
|
const double verticalQ=std::max(bottom-point[2],point[2]-top);
|
||||||
|
double shaftDistance=std::min(std::max(radialQ,verticalQ),0.0)
|
||||||
|
+std::hypot(std::max(radialQ,0.0),std::max(verticalQ,0.0));
|
||||||
|
if(feature.irregularityAmplitudeVoxels>0.0f)
|
||||||
|
shaftDistance+=StampValueNoise(
|
||||||
|
X-feature.centerVoxels[0],Y-feature.centerVoxels[1],
|
||||||
|
Z-feature.centerVoxels[2],feature.irregularityWavelengthVoxels,
|
||||||
|
feature.irregularitySeed)
|
||||||
|
*feature.irregularityAmplitudeVoxels*metresPerVoxel;
|
||||||
|
|
||||||
|
// A cardinal ledge faces the walkable backbone. Shelves are
|
||||||
|
// cut out of the otherwise open cylinder, leaving most of the
|
||||||
|
// shaft cross-section clear at every interval.
|
||||||
|
if(feature.ledgeSpacingMetres>0.0&&feature.ledgeDepthMetres>0.0)
|
||||||
|
{
|
||||||
|
const long long nearest=static_cast<long long>(std::llround(
|
||||||
|
(top-point[2])/feature.ledgeSpacingMetres));
|
||||||
|
const long long maximum=static_cast<long long>(std::floor(
|
||||||
|
(top-bottom-1000000.0)/feature.ledgeSpacingMetres));
|
||||||
|
if(nearest>=1&&nearest<=maximum)
|
||||||
|
{
|
||||||
|
const double ledgeZ=top-nearest*feature.ledgeSpacingMetres;
|
||||||
|
constexpr double LedgeThicknessMetres=0.6;
|
||||||
|
constexpr double LedgeFilletMetres=0.1;
|
||||||
|
const double dx=point[0]-feature.centerMetres[0];
|
||||||
|
const double dy=point[1]-feature.centerMetres[1];
|
||||||
|
const std::array<std::array<double,2>,4> sides={{{1.0,0.0},
|
||||||
|
{0.0,1.0},{-1.0,0.0},{0.0,-1.0}}};
|
||||||
|
const auto side=sides[feature.ledgeSide%4];
|
||||||
|
const double radialLocal=dx*side[0]+dy*side[1];
|
||||||
|
const double tangentLocal=-dx*side[1]+dy*side[0];
|
||||||
|
const double halfDepth=feature.ledgeDepthMetres*0.5;
|
||||||
|
const double halfWidth=radius*0.45;
|
||||||
|
const double halfThickness=LedgeThicknessMetres*0.5;
|
||||||
|
const double qx=std::abs(radialLocal-(radius-halfDepth))-halfDepth;
|
||||||
|
const double qy=std::abs(tangentLocal)-halfWidth;
|
||||||
|
const double qz=std::abs(point[2]-(ledgeZ-halfThickness))
|
||||||
|
-halfThickness;
|
||||||
|
const double outside=std::hypot(std::max(qx,0.0),
|
||||||
|
std::max(qy,0.0),std::max(qz,0.0));
|
||||||
|
const double platformDistance=outside
|
||||||
|
+std::min(std::max(qx,std::max(qy,qz)),0.0)
|
||||||
|
-LedgeFilletMetres;
|
||||||
|
shaftDistance=std::max(shaftDistance,-platformDistance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
featureVoid=shaftDistance;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
double normalizedSquared=0.0,meanRadius=0.0;
|
double normalizedSquared=0.0,meanRadius=0.0;
|
||||||
for(std::size_t axis=0;axis<3;++axis)
|
for(std::size_t axis=0;axis<3;++axis)
|
||||||
{
|
{
|
||||||
@@ -7907,6 +7989,7 @@ private:
|
|||||||
+feature.floorGradient[1]*(point[1]-feature.centerMetres[1]);
|
+feature.floorGradient[1]*(point[1]-feature.centerMetres[1]);
|
||||||
const double width=feature.room->smoothWidthMicrometres/1000000.0;
|
const double width=feature.room->smoothWidthMicrometres/1000000.0;
|
||||||
featureVoid=SmoothMaxMetres(ellipsoid,floor,width);
|
featureVoid=SmoothMaxMetres(ellipsoid,floor,width);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if(feature.edge&&!feature.segments.empty())
|
else if(feature.edge&&!feature.segments.empty())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
# Linux139 — Vertical Shafts on the Core planner
|
||||||
|
|
||||||
|
## Result
|
||||||
|
|
||||||
|
S3 now reports `Core.Caves.PrimitiveField` / `Caves-native`, using the shared Tunnel Network
|
||||||
|
planner. The composed acceptance gate is not green: **15/16 seeds pass; seed 2 fails the passage
|
||||||
|
shell and tile seam checks at the arrival from passage 2–3.** This worktree is not promoted as an
|
||||||
|
accepted Vertical Shafts generator.
|
||||||
|
|
||||||
|
The basal primordial route law means arrival passage 2–3 to a walkable terminal landing above the
|
||||||
|
bedrock seal. There is no departure mouth at the bottom of the owner world. The shared planner
|
||||||
|
builds that walk-only spine, its access tunnels, and landings within the existing grade contract.
|
||||||
|
The full-height open shafts and their ledges are an exploration tier: a drop or climb is not
|
||||||
|
walk-only traversal, and climb or mine access stays in the reporting tiers. `Core.Lattice` was not
|
||||||
|
used because L58's VerticalShafts lattice route looked like a corkscrew, not an open shaft.
|
||||||
|
|
||||||
|
## Plan and parameter mapping
|
||||||
|
|
||||||
|
`PlanResolvedCavernNetworkV1` is reused without a planner fork. `Core.Caves.PrimitiveField` is the
|
||||||
|
shared evaluator, extended with `shapeKind=1`: the existing room/tunnel shapes do not represent a
|
||||||
|
capped, full-height vertical bore, and the L58 lattice route was a corkscrew. Shaft records use
|
||||||
|
that cylinder primitive with ledges, short access bores, and cross-shaft links. Generic cave
|
||||||
|
branches are disabled in shaft mode.
|
||||||
|
|
||||||
|
| `FVerticalShaftParams` field(s) | Core setting |
|
||||||
|
|---|---|
|
||||||
|
| `ShaftSpacing` | Shared planner room spacing; owner voxels convert to integer micrometres. |
|
||||||
|
| `ShaftDensity` | `roomDensityQ16`, controlling shaft placement; a nonzero setting guarantees the route's initial shaft. |
|
||||||
|
| `ShaftMinRadius`, `ShaftMaxRadius` | Minimum and maximum shaft radii. |
|
||||||
|
| `CrossConnectChance` | `tunnelDensityQ16` for links between shafts. |
|
||||||
|
| `ConnectorRadius` | Equal minimum/maximum radius for access and connector bores. |
|
||||||
|
| `LedgeSpacing`, `LedgeDepth` | Vertical shelf interval and intrusion; zero spacing disables shelves and zero depth makes them nonintrusive. |
|
||||||
|
| `SurfaceRoughness` | Roughness amplitude; frequency derives as `max(0.5 m, 4 owner voxels)` because no frequency field exists. |
|
||||||
|
| `BoundarySealThickness`, `BaseDensity` | Existing resolved top/basal boundary bands via `sealAndDensity`, outside planner settings. |
|
||||||
|
|
||||||
|
## Measurements
|
||||||
|
|
||||||
|
The final source stamp was `b8603b6cb3975ca0f9669759e4e2464561c557fdb7aa6836acef123ecf0b99d1`.
|
||||||
|
The composed sweep covered seeds 0–15 with one run per seed (reported process time 320.485 s in
|
||||||
|
total; individual runs 10.296–34.015 s). Seeds 0, 1, and 3–15 passed passages, route, seams, shell,
|
||||||
|
and blocked-route controls. Seed 2's route grades/support, naturalness, mouth profile, ripple,
|
||||||
|
dispatch, and negative controls passed; its composed passage, seam, and shell summaries failed.
|
||||||
|
The shell audit reported eight `wall_missing` samples on `passage/2-3/0`, at route stations
|
||||||
|
489.627–490.321 m. The final log records positive native density at those samples and negative
|
||||||
|
wall-shell margins. See `Saved/Linux/l139/seeds/seed2-final.log` and
|
||||||
|
`Saved/Linux/l139/seeds/summary.json`.
|
||||||
|
|
||||||
|
Three geometry variants were tried against this same shell gate: starting passage clearance at
|
||||||
|
12 m, adding a guarded cubic lateral bow, and starting clearance at 8 m. The first and third still
|
||||||
|
failed wall checks; the bow failed floor checks. Per the three-failure stop rule, these were
|
||||||
|
discarded. The final binary retains the successful Q16 endpoint-normalization correction needed
|
||||||
|
for seed 1 and no seed-2 geometry patch.
|
||||||
|
|
||||||
|
L80 ran after the seed sweep. The host gate was **load 1.31, CPU idle 99.4%**. It used one warm-up
|
||||||
|
per build and stratum/worker cell, then five interleaved measured runs for each of three builds at
|
||||||
|
W8 and W12 across all four strata: 120 measured runs total. Comparisons use `fa4417a`,
|
||||||
|
`assembler/world`, and the L139 binary; the established L137 A/A noise floor is ±1% ([A/A report](../Linux137/REPORT.md)).
|
||||||
|
Times are medians in seconds; deltas are L139 relative to each comparison build. Paired values in
|
||||||
|
each cell are W8 / W12.
|
||||||
|
|
||||||
|
| Stratum | LOD0–2 L139 | Δ vs `fa4417a` | Δ vs `assembler/world` | Whole-view L139 | Δ vs `fa4417a` | Δ vs `assembler/world` |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
| S0 Surface World | 0.838 / 0.886 | +1.71% / +3.97% | +2.92% / +5.06% | 1.862 / 1.804 | +2.42% / +1.63% | −0.11% / −0.33% |
|
||||||
|
| S1 Floating Islands | 1.036 / 1.038 | +4.72% / +4.61% | +2.09% / +5.35% | 1.842 / 1.830 | +0.11% / +1.16% | −6.73% / −1.24% |
|
||||||
|
| S2 Tunnel Network | 1.105 / 1.078 | +2.44% / +3.07% | +3.57% / +7.57% | 1.876 / 1.808 | −1.11% / +0.00% | −4.33% / −1.15% |
|
||||||
|
| S3 Vertical Shafts | 0.899 / 0.884 | −3.81% / −0.94% | −0.95% / +2.00% | 1.706 / 1.657 | −6.57% / −2.53% | −6.21% / −4.77% |
|
||||||
|
|
||||||
|
Whole-view medians are numerically lower than `assembler/world` in all eight cells; the S0 W8 and
|
||||||
|
W12 differences are within the ±1% noise floor. Seven of eight LOD0–2 cells are slower than
|
||||||
|
`assembler/world`; the remaining cell is within the noise floor. This is characterization of an
|
||||||
|
unaccepted build, not a promotion claim. Raw runs and medians are in
|
||||||
|
`Saved/Linux/l139/timing/summary.json`.
|
||||||
|
|
||||||
|
## Checks and captures
|
||||||
|
|
||||||
|
- Fast tier: **PASS**, 284.0 s; the S3 dispatch line is `VerticalShafts` → `Core.Caves.PrimitiveField` v1 / `Caves-native` / `FCavesFieldAdapter`.
|
||||||
|
- Clang and GCC determinism: **PASS**, W1/W8/W12 (six cases), 13 hash lines and resolved-layout identity matched. Both compilers built the final source stamp above.
|
||||||
|
- Canonical v237 baselines added for Surface World and Floating Islands; both match their v236 values.
|
||||||
|
- Five eye-height views were captured before and after: arrival mouth, shaft access landing, looking down, looking up from a ledge, and basal terminal landing. All five L139 views pass the existing quality threshold. Two L138 baseline views fail it: the old layer has no shaft at the selected site, so the down-shaft view is flat, and the upward view is low contrast. The threshold was not relaxed. Images and camera details are in `captures/`.
|
||||||
|
- The outside views are `captures/after-whole-layer-density-cutaway.jpg` (coarse LOD density-mesh slice at y=−5 m) and `captures/after-whole-layer-side-elevation.svg` (resolved-plan projection that labels the walk route and all 53 shaft primitives). The cutaway passes the unchanged image-quality threshold; its plan companion makes the full shaft layout clearer.
|
||||||
|
- Linux `VFHostEditor` Development build: **PASS**, 27 actions, 72.58 s, run after timing with `-NoUBA -MaxParallelActions=4` and no `prlimit`. It emitted three pre-existing deprecated RealtimeMesh API warnings from `VoxelWorld.cpp`; that file was not changed.
|
||||||
|
- `git diff --check` and Python bytecode compilation of the L139 runners passed. Windows build and Windows determinism were not run.
|
||||||
|
|
||||||
|
Logs and source-stamped binaries are under `Saved/Linux/l139/`. The only changed Unreal `Source/`
|
||||||
|
file is `Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp`.
|
||||||
|
|
||||||
|
## Remaining risk
|
||||||
|
|
||||||
|
The passage shell and seam break at the incoming mouth on seed 2 remain unresolved. The local
|
||||||
|
clearance/heading premise failed three geometry attempts; a new route-to-mouth design premise is
|
||||||
|
needed before another geometry change. Walk-only connectivity stays guaranteed by construction;
|
||||||
|
vertical drops, climbs, and mine access remain reporting tiers.
|
||||||
@@ -0,0 +1,278 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Capture comparable player-height views of the L138 and L139 owner shaft layer."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[4]
|
||||||
|
DATA = ROOT / "Standalone/data/owner_world.json"
|
||||||
|
LAYOUT = ROOT / "Saved/Linux/l139/seeds/seed0.json"
|
||||||
|
OUT = ROOT / "Saved/Linux/l139/captures"
|
||||||
|
ARTIFACTS = ROOT / "Standalone/Research/WorldAssemblerM3/Linux139/captures"
|
||||||
|
LIMIT = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--"]
|
||||||
|
VERSIONS = {
|
||||||
|
"before-l138": {
|
||||||
|
"headless": ROOT / "Saved/Linux/l138/bin/VoxelForgeCoreHeadless-l138-clang",
|
||||||
|
"renderer": ROOT / "Saved/Linux/l138/bin/VoxelForgeOffscreenCapture-l138-clang",
|
||||||
|
"stamp": "9aade4e6cb33e86c36b89440644d9ef5d440288fb6ff0af03af128291c95f93f",
|
||||||
|
},
|
||||||
|
"after-l139": {
|
||||||
|
"headless": ROOT / "Saved/Linux/l139/bin/VoxelForgeCoreHeadless-l139-clang",
|
||||||
|
"renderer": ROOT / "Saved/Linux/l139/bin/VoxelForgeOffscreenCapture-l139-clang",
|
||||||
|
"stamp": "b8603b6cb3975ca0f9669759e4e2464561c557fdb7aa6836acef123ecf0b99d1",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
STAMP_RE = re.compile(r"^\[VoxelForgeSourceStamp\] sha256=([0-9a-f]{64})$", re.M)
|
||||||
|
|
||||||
|
|
||||||
|
def fmt(values: tuple[float, ...]) -> str:
|
||||||
|
return ",".join(f"{value:.6f}" for value in values)
|
||||||
|
|
||||||
|
|
||||||
|
def angles(camera: tuple[float, float, float],
|
||||||
|
target: tuple[float, float, float]) -> tuple[float, float]:
|
||||||
|
dx, dy, dz = (target[i] - camera[i] for i in range(3))
|
||||||
|
return math.degrees(math.atan2(dx, dy)), math.degrees(
|
||||||
|
math.atan2(dz, math.hypot(dx, dy)))
|
||||||
|
|
||||||
|
|
||||||
|
def stamped_run(name: str, command: list[str], log: Path, expected: str,
|
||||||
|
env: dict[str, str]) -> str:
|
||||||
|
binary = Path(command[0])
|
||||||
|
probe = subprocess.run([*LIMIT, "timeout", "15s", str(binary), "--print-source-stamp"],
|
||||||
|
cwd=ROOT, env=env, text=True, stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT, check=False, timeout=20)
|
||||||
|
match = STAMP_RE.search(probe.stdout or "")
|
||||||
|
if probe.returncode != 0 or not match or match.group(1) != expected:
|
||||||
|
raise RuntimeError(f"{name}: executable stamp mismatch for {binary}")
|
||||||
|
log.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
started = time.monotonic()
|
||||||
|
with log.open("w", encoding="utf-8") as stream:
|
||||||
|
result = subprocess.run([*LIMIT, "timeout", "1740s", *map(str, command)],
|
||||||
|
cwd=ROOT, env=env, stdout=stream, stderr=subprocess.STDOUT,
|
||||||
|
timeout=1745, check=False)
|
||||||
|
output = log.read_text(encoding="utf-8", errors="replace")
|
||||||
|
emitted = STAMP_RE.search(output)
|
||||||
|
if (result.returncode != 0 or not emitted or emitted.group(1) != expected):
|
||||||
|
raise RuntimeError(f"{name}: process failed or source changed; see {log}")
|
||||||
|
print(f"[L139CaptureRun] step={name} elapsed_s={time.monotonic()-started:.1f} "
|
||||||
|
f"log={log.relative_to(ROOT)}", flush=True)
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
def point_um(values: list[int]) -> tuple[float, float, float]:
|
||||||
|
return tuple(value / 1_000_000.0 for value in values)
|
||||||
|
|
||||||
|
|
||||||
|
def make_views(layout: dict) -> list[dict]:
|
||||||
|
edges = {row["stable_id"]: row for row in layout["cavern_edges"]}
|
||||||
|
rooms = {row["stable_id"]: row for row in layout["cavern_rooms"]}
|
||||||
|
shaft = rooms["vertical-shafts/strate-3/shaft-0/primitive-0"]
|
||||||
|
access = edges["vertical-shafts/strate-3/shaft-access-0"]["polyline_um"]
|
||||||
|
arrival = edges["vertical-shafts/strate-3/route-edge-0"]["polyline_um"]
|
||||||
|
basal = rooms["vertical-shafts/strate-3/basal-landing/primitive-0"]
|
||||||
|
center = point_um(shaft["center_um"])
|
||||||
|
half = point_um(shaft["half_extents_um"])
|
||||||
|
shaft_top = center[2] + half[2]
|
||||||
|
shaft_bottom = center[2] - half[2]
|
||||||
|
route_a, route_b = point_um(arrival[0]), point_um(arrival[1])
|
||||||
|
tx, ty = route_b[0] - route_a[0], route_b[1] - route_a[1]
|
||||||
|
tlen = max(1.0e-9, math.hypot(tx, ty))
|
||||||
|
tangent = (tx / tlen, ty / tlen)
|
||||||
|
mouth_camera = (route_a[0] - tangent[0] * 2.0,
|
||||||
|
route_a[1] - tangent[1] * 2.0, route_a[2] + 1.45)
|
||||||
|
mouth_target = (route_a[0] + tangent[0] * 8.0,
|
||||||
|
route_a[1] + tangent[1] * 8.0, route_a[2] + 1.45)
|
||||||
|
access_a, access_b = point_um(access[0]), point_um(access[-1])
|
||||||
|
access_camera = (access_a[0], access_a[1], access_a[2] + 1.45)
|
||||||
|
access_target = (access_b[0], access_b[1], access_b[2] + 1.45)
|
||||||
|
shaft_lip_camera = (access_b[0], access_b[1], access_b[2] + 1.45)
|
||||||
|
basal_center = point_um(basal["center_um"])
|
||||||
|
basal_camera = (basal_center[0], basal_center[1], basal["floor_um"] / 1.0e6 + 1.45)
|
||||||
|
basal_target = (basal_center[0] + tangent[0] * 8.0,
|
||||||
|
basal_center[1] + tangent[1] * 8.0, basal_camera[2])
|
||||||
|
ledge_z = shaft_top - 12 * (shaft["ledge_spacing_um"] / 1.0e6)
|
||||||
|
return [
|
||||||
|
{"name": "arrival-mouth", "camera": mouth_camera, "target": mouth_target,
|
||||||
|
"focus": (route_a[0], route_a[1], route_a[2] + 1.4, 12.0),
|
||||||
|
"note": "player eye at the passage arrival, looking into the connected layer"},
|
||||||
|
{"name": "landing-at-shaft-access", "camera": access_camera,
|
||||||
|
"target": access_target,
|
||||||
|
"focus": (center[0], center[1], access_a[2], 14.0),
|
||||||
|
"note": "walkable route landing and short shaft access tunnel"},
|
||||||
|
{"name": "looking-down-shaft", "camera": shaft_lip_camera,
|
||||||
|
"target": (center[0], center[1], shaft_top - 30.0),
|
||||||
|
"focus": (center[0], center[1], shaft_top - 25.0, 14.0),
|
||||||
|
"fov": 35.0,
|
||||||
|
"note": "player eye at the upper access landing, looking down the open shaft"},
|
||||||
|
{"name": "looking-up-from-ledge", "camera": (center[0], center[1], ledge_z + 1.45),
|
||||||
|
"target": (center[0], center[1], shaft_top - 2.0),
|
||||||
|
"focus": (center[0], center[1], shaft_top - 22.0, 20.0),
|
||||||
|
"note": "player eye at an internal ledge, looking up; reaching this ledge is a climb tier"},
|
||||||
|
{"name": "basal-terminal-landing", "camera": basal_camera,
|
||||||
|
"target": basal_target,
|
||||||
|
"focus": (basal_center[0], basal_center[1], basal_center[2], 16.0),
|
||||||
|
"note": "walkable terminal landing above the basal seal"},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def write_overview(layout: dict, destination: Path) -> None:
|
||||||
|
shafts = [room for room in layout["cavern_rooms"] if room.get("shape_kind") == 1]
|
||||||
|
route_edges = sorted((edge for edge in layout["cavern_edges"]
|
||||||
|
if edge["stable_id"].startswith("vertical-shafts/strate-3/route-edge-")),
|
||||||
|
key=lambda edge: int(edge["stable_id"].rsplit("-", 1)[1]))
|
||||||
|
if not shafts or not route_edges:
|
||||||
|
raise ValueError("overview needs the resolved shaft primitives and walk route")
|
||||||
|
points = [point_um(row) for edge in route_edges for row in edge["polyline_um"]]
|
||||||
|
shaft_x_bounds = [(point_um(room["center_um"])[0]
|
||||||
|
-room["half_extents_um"][0]/1e6,
|
||||||
|
point_um(room["center_um"])[0]+room["half_extents_um"][0]/1e6)
|
||||||
|
for room in shafts]
|
||||||
|
x_min = min(min(bounds[0] for bounds in shaft_x_bounds),
|
||||||
|
min(row[0] for row in points))
|
||||||
|
x_max = max(x + room["half_extents_um"][0] / 1e6
|
||||||
|
for room in shafts for x in [point_um(room["center_um"])[0]])
|
||||||
|
x_max = max(x_max, max(row[0] for row in points))
|
||||||
|
z_min = min(point_um(room["center_um"])[2] - room["half_extents_um"][2] / 1e6
|
||||||
|
for room in shafts)
|
||||||
|
z_max = max(point_um(room["center_um"])[2] + room["half_extents_um"][2] / 1e6
|
||||||
|
for room in shafts)
|
||||||
|
z_min = min(z_min, min(row[2] for row in points))
|
||||||
|
z_max = max(z_max, max(row[2] for row in points))
|
||||||
|
width, height, margin = 1800.0, 760.0, 72.0
|
||||||
|
sx = (width - 2 * margin) / max(1.0, x_max - x_min)
|
||||||
|
sz = (height - 2 * margin) / max(1.0, z_max - z_min)
|
||||||
|
scale = min(sx, sz)
|
||||||
|
x_offset = margin + ((width - 2 * margin) - (x_max - x_min) * scale) * 0.5
|
||||||
|
z_offset = margin + ((height - 2 * margin) - (z_max - z_min) * scale) * 0.5
|
||||||
|
def xy(x: float, z: float) -> tuple[float, float]:
|
||||||
|
return x_offset + (x - x_min) * scale, height - (z_offset + (z - z_min) * scale)
|
||||||
|
route_xy = [xy(p[0], p[2]) for p in points]
|
||||||
|
route = " ".join(("M" if i == 0 else "L") + f"{x:.2f},{y:.2f}"
|
||||||
|
for i, (x, y) in enumerate(route_xy))
|
||||||
|
cylinders = []
|
||||||
|
for room in shafts:
|
||||||
|
x, _y, z = point_um(room["center_um"])
|
||||||
|
radius = room["half_extents_um"][0] / 1e6
|
||||||
|
half_z = room["half_extents_um"][2] / 1e6
|
||||||
|
left, top = xy(x - radius, z + half_z)
|
||||||
|
right, bottom = xy(x + radius, z - half_z)
|
||||||
|
cylinders.append(f'<rect x="{left:.2f}" y="{top:.2f}" width="{max(1.2,right-left):.2f}" '
|
||||||
|
f'height="{bottom-top:.2f}" rx="{min(7.0,(right-left)/2):.2f}" '
|
||||||
|
'fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/>')
|
||||||
|
rx0, ry0 = xy(x_min, 0.0)
|
||||||
|
rx1, ry1 = xy(x_max, 0.0)
|
||||||
|
svg = f'''<svg xmlns="http://www.w3.org/2000/svg" width="1800" height="760" viewBox="0 0 1800 760">
|
||||||
|
<rect width="1800" height="760" fill="#07111a"/>
|
||||||
|
<text x="72" y="42" fill="#edf6fb" font-family="sans-serif" font-size="24">Vertical Shafts — whole-layer side elevation</text>
|
||||||
|
<text x="72" y="64" fill="#9bb5c5" font-family="sans-serif" font-size="13">Resolved plan • seed 0 • strate 3 • vertical axis and route shown in metres • Y projected out</text>
|
||||||
|
<line x1="{rx0:.1f}" y1="{ry0:.1f}" x2="{rx1:.1f}" y2="{ry1:.1f}" stroke="#315060" stroke-dasharray="5 8"/>
|
||||||
|
{''.join(cylinders)}
|
||||||
|
<path d="{route}" fill="none" stroke="#f4bf68" stroke-width="3.0" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<text x="82" y="716" fill="#f4bf68" font-family="sans-serif" font-size="15">walk-only graded spine and landings</text>
|
||||||
|
<rect x="416" y="704" width="15" height="15" fill="#93dded" fill-opacity="0.65" stroke="#42b8d1"/>
|
||||||
|
<text x="440" y="716" fill="#b8dce5" font-family="sans-serif" font-size="15">open vertical shaft primitives (53)</text>
|
||||||
|
<text x="1718" y="716" fill="#9bb5c5" text-anchor="end" font-family="sans-serif" font-size="12">Plan projection, not a density mesh</text>
|
||||||
|
</svg>'''
|
||||||
|
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
destination.write_text(svg, encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
if not DATA.is_file() or not LAYOUT.is_file():
|
||||||
|
raise SystemExit("missing owner fixture or passing seed-0 resolved layout")
|
||||||
|
layout = json.loads(LAYOUT.read_text(encoding="utf-8"))
|
||||||
|
views = make_views(layout)
|
||||||
|
OUT.mkdir(parents=True, exist_ok=True)
|
||||||
|
ARTIFACTS.mkdir(parents=True, exist_ok=True)
|
||||||
|
env = os.environ.copy()
|
||||||
|
env["LP_NUM_THREADS"] = "12"
|
||||||
|
env["XDG_CACHE_HOME"] = str(OUT / "xdg-cache")
|
||||||
|
quality = ROOT / "Standalone/Tools/verify/capture_quality.py"
|
||||||
|
manifest = []
|
||||||
|
quality_failures = []
|
||||||
|
for version, config in VERSIONS.items():
|
||||||
|
phase = "before" if version.startswith("before") else "after"
|
||||||
|
for view in views:
|
||||||
|
name = view["name"]
|
||||||
|
camera, target = view["camera"], view["target"]
|
||||||
|
yaw, pitch = angles(camera, target)
|
||||||
|
mesh_dir = OUT / "meshes" / phase
|
||||||
|
mesh_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
mesh = mesh_dir / f"{name}.vfmesh"
|
||||||
|
capture_layout = mesh_dir / f"{name}-layout.json"
|
||||||
|
export_log = OUT / f"{phase}-{name}-export.log"
|
||||||
|
export = [config["headless"], DATA, "--world-assembler-prototype",
|
||||||
|
"--stream-benchmark", "--stream-pipeline", "--workers=12",
|
||||||
|
"--clip-radius=6", "--max-clip-level=0", "--max-level=0",
|
||||||
|
"--force-generate", "--seed=0", "--recipe=surface-world",
|
||||||
|
"--render-distance-chunks=0", f"--camera={fmt((*camera,yaw,pitch))}",
|
||||||
|
f"--resolved-layout-output={capture_layout}", f"--mesh-export={mesh}"]
|
||||||
|
stamped_run(f"{phase}-{name}-export", list(map(str, export)), export_log,
|
||||||
|
config["stamp"], env)
|
||||||
|
if not mesh.is_file() or mesh.stat().st_size == 0:
|
||||||
|
raise RuntimeError(f"{phase}-{name}: empty mesh export")
|
||||||
|
png = OUT / f"{phase}-{name}.png"
|
||||||
|
jpg = ARTIFACTS / f"{phase}-{name}.jpg"
|
||||||
|
fov = float(view.get("fov", 78.0))
|
||||||
|
render = [config["renderer"], mesh, png, "--size=1280x720",
|
||||||
|
f"--fov-deg={fov:.1f}", f"--camera-m={fmt(camera)}", f"--yaw-deg={yaw:.6f}",
|
||||||
|
f"--pitch-deg={pitch:.6f}", "--tile-skirts=off", "--shade=readable",
|
||||||
|
"--headlamp=on", "--height-tint=on", "--height-center-m=-950",
|
||||||
|
"--height-range-m=60", "--tile-edges=off",
|
||||||
|
f"--focus-region={fmt(view['focus'])}"]
|
||||||
|
stamped_run(f"{phase}-{name}-render", list(map(str, render)),
|
||||||
|
OUT / f"{phase}-{name}-render.log", config["stamp"], env)
|
||||||
|
jpeg = ["ffmpeg", "-nostdin", "-hide_banner", "-loglevel", "error", "-y",
|
||||||
|
"-i", png, "-frames:v", "1", "-q:v", "4", jpg]
|
||||||
|
subprocess.run([*LIMIT, "timeout", "120s", *map(str, jpeg)], cwd=ROOT,
|
||||||
|
env=env, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT,
|
||||||
|
timeout=125)
|
||||||
|
check = subprocess.run([*LIMIT, "timeout", "120s", sys.executable,
|
||||||
|
quality, jpg], cwd=ROOT, env=env, text=True, stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT, check=False, timeout=125)
|
||||||
|
quality_ok = check.returncode == 0 and "status=PASS" in check.stdout
|
||||||
|
quality_result = (check.stdout.strip().splitlines()[-1]
|
||||||
|
if check.stdout.strip() else f"quality command exit={check.returncode}")
|
||||||
|
if not quality_ok:
|
||||||
|
quality_failures.append(f"{phase}-{name}")
|
||||||
|
manifest.append({"phase": phase, "view": name, "file": str(jpg.relative_to(ROOT)),
|
||||||
|
"bytes": jpg.stat().st_size, "camera_m": camera, "target_m": target,
|
||||||
|
"yaw_deg": yaw, "pitch_deg": pitch, "fov_deg": fov,
|
||||||
|
"note": view["note"],
|
||||||
|
"quality_status": "PASS" if quality_ok else "FAIL",
|
||||||
|
"quality": quality_result})
|
||||||
|
print(f"[L139Capture] phase={phase} view={name} file={jpg.relative_to(ROOT)} "
|
||||||
|
f"bytes={jpg.stat().st_size} quality={'PASS' if quality_ok else 'FAIL'}",
|
||||||
|
flush=True)
|
||||||
|
overview = ARTIFACTS / "after-whole-layer-side-elevation.svg"
|
||||||
|
write_overview(layout, overview)
|
||||||
|
manifest.append({"phase": "after", "view": "whole-layer-side-elevation",
|
||||||
|
"file": str(overview.relative_to(ROOT)), "shafts": sum(
|
||||||
|
room.get("shape_kind") == 1 for room in layout["cavern_rooms"]),
|
||||||
|
"walk_route_edges": len([edge for edge in layout["cavern_edges"]
|
||||||
|
if edge["stable_id"].startswith("vertical-shafts/strate-3/route-edge-")]),
|
||||||
|
"note": "resolved-plan side projection; not a density-mesh capture"})
|
||||||
|
(ARTIFACTS / "manifest.json").write_text(json.dumps(manifest, indent=2) + "\n",
|
||||||
|
encoding="utf-8")
|
||||||
|
status = "PASS" if not quality_failures else "FAIL"
|
||||||
|
print(f"[L139CaptureSummary] spot_views={len(views)} comparisons={len(views)*2} "
|
||||||
|
f"whole_layer_projection=1 quality_failures={len(quality_failures)} "
|
||||||
|
f"status={status}", flush=True)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
raise SystemExit(main())
|
||||||
|
except Exception as error:
|
||||||
|
print(f"[L139CaptureFailure] {error}", file=sys.stderr, flush=True)
|
||||||
|
raise SystemExit(1)
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Compare composed owner hashes and resolved graph identity at W1/W8/W12."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[4]
|
||||||
|
OUT = ROOT / "Saved/Linux/l139/determinism"
|
||||||
|
DATA = ROOT / "Standalone/data/owner_world.json"
|
||||||
|
STAMP = "b8603b6cb3975ca0f9669759e4e2464561c557fdb7aa6836acef123ecf0b99d1"
|
||||||
|
BINARIES = {
|
||||||
|
"clang": ROOT / "Saved/Linux/l139/bin/VoxelForgeCoreHeadless-l139-clang",
|
||||||
|
"gcc": ROOT / "Saved/Linux/l139/bin/VoxelForgeCoreHeadless-l139-gcc",
|
||||||
|
}
|
||||||
|
CAMERA = "-20.062500,-5.068359,-900.353516,0,0"
|
||||||
|
HASH_RE = re.compile(
|
||||||
|
r"^\[Pipeline(?:FullDetailHash|WholeViewHash|MaterialHash|LODHash|ExactQueryHash)\].*$",
|
||||||
|
re.M)
|
||||||
|
STAMP_RE = re.compile(r"^\[VoxelForgeSourceStamp\] sha256=([0-9a-f]+)$", re.M)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
if any(not path.is_file() for path in (DATA, *BINARIES.values())):
|
||||||
|
raise SystemExit("missing L139 determinism binary or composed owner fixture")
|
||||||
|
OUT.mkdir(parents=True, exist_ok=True)
|
||||||
|
rows = []
|
||||||
|
reference_hashes: tuple[str, ...] | None = None
|
||||||
|
reference_layout_identity: str | None = None
|
||||||
|
for compiler, binary in BINARIES.items():
|
||||||
|
for workers in (1, 8, 12):
|
||||||
|
name = f"determinism-{compiler}-w{workers}"
|
||||||
|
log, layout = OUT / f"{name}.log", OUT / f"{name}.json"
|
||||||
|
command = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
|
||||||
|
"timeout", "1740s", str(binary), str(DATA), "--world-assembler-prototype",
|
||||||
|
"--stream-benchmark", "--stream-pipeline", f"--workers={workers}",
|
||||||
|
"--clip-radius=3", "--max-clip-level=8", "--max-level=0",
|
||||||
|
f"--camera={CAMERA}", "--stream-hash", "--force-generate", "--seed=0",
|
||||||
|
"--recipe=surface-world", "--render-distance-chunks=0",
|
||||||
|
"--density-dispatch-report", f"--resolved-layout-output={layout}"]
|
||||||
|
started = time.monotonic()
|
||||||
|
with log.open("w", encoding="utf-8") as output:
|
||||||
|
proc = subprocess.run(command, cwd=ROOT, stdout=output,
|
||||||
|
stderr=subprocess.STDOUT, check=False, timeout=1740)
|
||||||
|
content = log.read_text(encoding="utf-8", errors="replace")
|
||||||
|
stamp = STAMP_RE.search(content)
|
||||||
|
hashes = tuple(HASH_RE.findall(content))
|
||||||
|
current_layout = json.loads(layout.read_text(encoding="utf-8"))
|
||||||
|
stable_identity = current_layout.get("stable_identity")
|
||||||
|
dispatch_ok = bool(re.search(
|
||||||
|
r"^\[DensityDispatch\] strate=3 generator=VerticalShafts "
|
||||||
|
r"recipe=Core\.Caves\.PrimitiveField version=1 "
|
||||||
|
r"bound_path=Caves-native adapter=FCavesFieldAdapter$", content, re.M))
|
||||||
|
passed = (proc.returncode == 0 and stamp is not None and stamp.group(1) == STAMP
|
||||||
|
and len(hashes) >= 4 and dispatch_ok and bool(stable_identity))
|
||||||
|
if reference_hashes is None and passed:
|
||||||
|
reference_hashes = hashes
|
||||||
|
reference_layout_identity = stable_identity
|
||||||
|
else:
|
||||||
|
passed = (passed and hashes == reference_hashes
|
||||||
|
and stable_identity == reference_layout_identity)
|
||||||
|
row = {"compiler": compiler, "workers": workers, "exit": proc.returncode,
|
||||||
|
"stamp": stamp.group(1) if stamp else None, "hash_lines": len(hashes),
|
||||||
|
"hashes_match_first": hashes == reference_hashes,
|
||||||
|
"layout_identity": stable_identity,
|
||||||
|
"layout_matches_first": stable_identity == reference_layout_identity,
|
||||||
|
"shaft_dispatch": dispatch_ok, "passed": bool(passed),
|
||||||
|
"elapsed_s": round(time.monotonic() - started, 3),
|
||||||
|
"log": str(log.relative_to(ROOT)), "layout": str(layout.relative_to(ROOT))}
|
||||||
|
rows.append(row)
|
||||||
|
print("[L139DeterminismRun] " + json.dumps(row, sort_keys=True), flush=True)
|
||||||
|
(OUT / "runs-partial.json").write_text(json.dumps(rows, indent=2) + "\n")
|
||||||
|
if not passed:
|
||||||
|
raise SystemExit(f"composed owner determinism mismatch; see {log}")
|
||||||
|
(OUT / "summary.json").write_text(json.dumps({"status": "PASS", "stamp": STAMP,
|
||||||
|
"layout_identity": reference_layout_identity, "runs": rows}, indent=2) + "\n")
|
||||||
|
print(f"[L139DeterminismSummary] runs={len(rows)} source_stamp={STAMP} status=PASS")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Run interleaved L80 timings for L139, fa4417a, and assembler/world."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import statistics
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[4]
|
||||||
|
OUT = ROOT / "Saved/Linux/l139/timing"
|
||||||
|
DATA = ROOT / "Standalone/data/owner_world.json"
|
||||||
|
BINARIES = {
|
||||||
|
"assembler-world": (
|
||||||
|
ROOT / "Saved/Linux/l137/bin/VoxelForgeCoreHeadless-l137-aa-a",
|
||||||
|
"4499fb2a8a4ede7d6cbc68d1b794f104658cdaae93ed9cacbc91fe7ff3fc3621"),
|
||||||
|
"fa4417a": (
|
||||||
|
ROOT / "Saved/Linux/l138/bin/VoxelForgeCoreHeadless-l138-clang",
|
||||||
|
"9aade4e6cb33e86c36b89440644d9ef5d440288fb6ff0af03af128291c95f93f"),
|
||||||
|
"l139": (
|
||||||
|
ROOT / "Saved/Linux/l139/bin/VoxelForgeCoreHeadless-l139-clang",
|
||||||
|
"b8603b6cb3975ca0f9669759e4e2464561c557fdb7aa6836acef123ecf0b99d1"),
|
||||||
|
}
|
||||||
|
CAMERAS = {
|
||||||
|
0: (8.0, 16.0, -372.2626953125),
|
||||||
|
1: (25.8369140625, 3.19921875, -737.0),
|
||||||
|
2: (19.166015625, -14.2001953125, -753.3740234375),
|
||||||
|
3: (-20.0625, -5.068359375, -900.353515625),
|
||||||
|
}
|
||||||
|
PIPELINE = re.compile(r"^\[StreamPipeline\] (.*)$", re.M)
|
||||||
|
FIELDS = re.compile(r"([a-zA-Z0-9_]+)=([^ ]+)")
|
||||||
|
NEAR = re.compile(
|
||||||
|
r"^\[WorldAssemblerNearLODReady\] lods=0-2 tiles=(\d+)/(\d+) cold_ready_s=([0-9.]+)$",
|
||||||
|
re.M)
|
||||||
|
STAMP = re.compile(r"^\[VoxelForgeSourceStamp\] sha256=([0-9a-f]+)$", re.M)
|
||||||
|
|
||||||
|
|
||||||
|
def run(strate: int, workers: int, version: str, phase: str, replicate: int) -> dict:
|
||||||
|
OUT.mkdir(parents=True, exist_ok=True)
|
||||||
|
binary, expected_stamp = BINARIES[version]
|
||||||
|
camera = ",".join(f"{value:.12f}" for value in CAMERAS[strate]) + ",0,0"
|
||||||
|
label = f"s{strate}-w{workers}-{version}-{phase}{replicate}"
|
||||||
|
log = OUT / f"{label}.log"
|
||||||
|
command = [
|
||||||
|
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
|
||||||
|
"timeout", "1740s", str(binary), str(DATA), "--world-assembler-prototype",
|
||||||
|
"--stream-benchmark", "--stream-pipeline", f"--workers={workers}",
|
||||||
|
"--clip-radius=3", "--max-clip-level=8", "--max-level=8",
|
||||||
|
f"--camera={camera}", "--profile-lod-work", "--stream-scheduler-policy=3",
|
||||||
|
]
|
||||||
|
started = time.monotonic()
|
||||||
|
with log.open("w", encoding="utf-8") as output:
|
||||||
|
proc = subprocess.run(command, cwd=ROOT, stdout=output,
|
||||||
|
stderr=subprocess.STDOUT, check=False, timeout=1740)
|
||||||
|
content = log.read_text(encoding="utf-8", errors="replace")
|
||||||
|
pipeline, near, stamp = PIPELINE.search(content), NEAR.search(content), STAMP.search(content)
|
||||||
|
if (proc.returncode or pipeline is None or near is None or stamp is None
|
||||||
|
or stamp.group(1) != expected_stamp):
|
||||||
|
raise SystemExit(f"L80 timing failed or stamp mismatch: {log}; exit={proc.returncode}")
|
||||||
|
fields = dict(FIELDS.findall(pipeline.group(1)))
|
||||||
|
row = {
|
||||||
|
"strate": strate, "workers": workers, "version": version,
|
||||||
|
"phase": phase, "replicate": replicate, "source_stamp": stamp.group(1),
|
||||||
|
"lod02_ready_s": float(near.group(3)),
|
||||||
|
"whole_view_ready_s": float(fields["whole_view_ready"].removesuffix("s")),
|
||||||
|
"whole_tiles": int(fields["whole_tiles"].split("/")[0]),
|
||||||
|
"wall_s": round(time.monotonic() - started, 3),
|
||||||
|
"log": str(log.relative_to(ROOT)),
|
||||||
|
}
|
||||||
|
print("[L139FinalTimingRun] " + json.dumps(row, sort_keys=True), flush=True)
|
||||||
|
return row
|
||||||
|
|
||||||
|
|
||||||
|
def quiet_machine() -> tuple[float, float]:
|
||||||
|
load_1m = os.getloadavg()[0]
|
||||||
|
|
||||||
|
def totals() -> tuple[int, int]:
|
||||||
|
fields = Path("/proc/stat").read_text().splitlines()[0].split()
|
||||||
|
values = [int(value) for value in fields[1:]]
|
||||||
|
return sum(values), values[3] + values[4]
|
||||||
|
|
||||||
|
before = totals()
|
||||||
|
time.sleep(2.0)
|
||||||
|
after = totals()
|
||||||
|
idle = 100.0 * (after[1] - before[1]) / max(1, after[0] - before[0])
|
||||||
|
return load_1m, idle
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
if any(not path.is_file() for path in [DATA, *(entry[0] for entry in BINARIES.values())]):
|
||||||
|
raise SystemExit("missing L139 timing input")
|
||||||
|
OUT.mkdir(parents=True, exist_ok=True)
|
||||||
|
load_1m, idle = quiet_machine()
|
||||||
|
(OUT / "machine-load-before.txt").write_text(
|
||||||
|
f"1-minute load: {load_1m:.2f}\n2-second CPU idle: {idle:.1f}%\n")
|
||||||
|
print(f"[L139FinalTimingGate] load_1m={load_1m:.2f} cpu_idle_2s={idle:.1f}%", flush=True)
|
||||||
|
if load_1m >= 1.5 or idle < 97.0:
|
||||||
|
raise SystemExit("timing refused: machine is above the L80 quiet-load gate")
|
||||||
|
|
||||||
|
rows = []
|
||||||
|
for strate in range(4):
|
||||||
|
for workers in (8, 12):
|
||||||
|
for version in BINARIES:
|
||||||
|
run(strate, workers, version, "warmup", 0)
|
||||||
|
for replicate in range(1, 6):
|
||||||
|
order = tuple(BINARIES)
|
||||||
|
order = order[replicate % 3:] + order[:replicate % 3]
|
||||||
|
if replicate % 2 == 0:
|
||||||
|
order = tuple(reversed(order))
|
||||||
|
for version in order:
|
||||||
|
rows.append(run(strate, workers, version, "measured", replicate))
|
||||||
|
(OUT / "runs-partial.json").write_text(
|
||||||
|
json.dumps(rows, indent=2) + "\n", encoding="utf-8")
|
||||||
|
|
||||||
|
measured = [row for row in rows if row["phase"] == "measured"]
|
||||||
|
summary = []
|
||||||
|
for strate in range(4):
|
||||||
|
for workers in (8, 12):
|
||||||
|
group = [row for row in measured
|
||||||
|
if row["strate"] == strate and row["workers"] == workers]
|
||||||
|
medians = {
|
||||||
|
version: {
|
||||||
|
key: statistics.median(row[key] for row in group
|
||||||
|
if row["version"] == version)
|
||||||
|
for key in ("lod02_ready_s", "whole_view_ready_s")
|
||||||
|
}
|
||||||
|
for version in BINARIES
|
||||||
|
}
|
||||||
|
entry = {"strate": strate, "workers": workers,
|
||||||
|
"replicates_per_version": 5, "medians": medians}
|
||||||
|
for key in ("lod02_ready_s", "whole_view_ready_s"):
|
||||||
|
for baseline in ("fa4417a", "assembler-world"):
|
||||||
|
entry[f"l139_vs_{baseline}_{key}_delta_pct"] = round(
|
||||||
|
100.0 * (medians["l139"][key] / medians[baseline][key] - 1.0), 2)
|
||||||
|
entry[f"l139_vs_fa4417a_{key}_paired_deltas_pct"] = [
|
||||||
|
round(100.0 * (
|
||||||
|
next(row[key] for row in group
|
||||||
|
if row["version"] == "l139" and row["replicate"] == replicate)
|
||||||
|
/ next(row[key] for row in group
|
||||||
|
if row["version"] == "fa4417a" and row["replicate"] == replicate)
|
||||||
|
- 1.0), 3) for replicate in range(1, 6)]
|
||||||
|
entry[f"l139_vs_assembler-world_{key}_paired_deltas_pct"] = [
|
||||||
|
round(100.0 * (
|
||||||
|
next(row[key] for row in group
|
||||||
|
if row["version"] == "l139" and row["replicate"] == replicate)
|
||||||
|
/ next(row[key] for row in group
|
||||||
|
if row["version"] == "assembler-world"
|
||||||
|
and row["replicate"] == replicate) - 1.0), 3)
|
||||||
|
for replicate in range(1, 6)]
|
||||||
|
summary.append(entry)
|
||||||
|
(OUT / "summary.json").write_text(json.dumps({
|
||||||
|
"a_a_noise_floor_pct": 1.0,
|
||||||
|
"fa4417a_commit": "fa4417a",
|
||||||
|
"timing_protocol": (
|
||||||
|
"one warmup per binary, five interleaved runs, W8/W12, all four owner layers"),
|
||||||
|
"runs": rows,
|
||||||
|
"summary": summary,
|
||||||
|
}, indent=2) + "\n", encoding="utf-8")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Audit the composed owner world, including the basal Vertical Shafts route."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[4]
|
||||||
|
OUT = ROOT / "Saved/Linux/l139/seeds"
|
||||||
|
BIN = ROOT / "Saved/Linux/l139/bin/VoxelForgeCoreHeadless-l139-clang"
|
||||||
|
DATA = ROOT / "Standalone/data/owner_world.json"
|
||||||
|
STAMP = "b8603b6cb3975ca0f9669759e4e2464561c557fdb7aa6836acef123ecf0b99d1"
|
||||||
|
STAMP_RE = re.compile(r"^\[VoxelForgeSourceStamp\] sha256=([0-9a-f]+)$", re.M)
|
||||||
|
REQUIRED = {
|
||||||
|
"passages": re.compile(r"^\[ResolvedPassageAuditSummary\].*status=PASS$", re.M),
|
||||||
|
"route": re.compile(
|
||||||
|
r"^\[ResolvedStrateRouteAudit\] strate=3 .*unsupported=0 grade_failures=0 .*status=PASS$",
|
||||||
|
re.M),
|
||||||
|
"seams": re.compile(r"^\[PassageTileSeamAudit\].*status=PASS$", re.M),
|
||||||
|
"shell": re.compile(r"^\[PassageShellAuditSummary\].*status=PASS$", re.M),
|
||||||
|
"naturalness": re.compile(r"^\[PassageNaturalnessSummary\].*status=PASS$", re.M),
|
||||||
|
"mouth_profile": re.compile(r"^\[PassageMouthProfileSummary\].*status=PASS$", re.M),
|
||||||
|
"ripple": re.compile(r"^\[PassageRippleAuditSummary\].*status=PASS$", re.M),
|
||||||
|
"shaft_dispatch": re.compile(
|
||||||
|
r"^\[DensityDispatch\] strate=3 generator=VerticalShafts "
|
||||||
|
r"recipe=Core\.Caves\.PrimitiveField version=1 "
|
||||||
|
r"bound_path=Caves-native adapter=FCavesFieldAdapter$", re.M),
|
||||||
|
"shaft_blocked_route": re.compile(
|
||||||
|
r"^\[VerticalShaftsBlockedRouteControl\].*expected=FAIL status=PASS$", re.M),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
if not BIN.is_file() or not DATA.is_file():
|
||||||
|
raise SystemExit("missing L139 Clang binary or composed owner fixture")
|
||||||
|
OUT.mkdir(parents=True, exist_ok=True)
|
||||||
|
results = []
|
||||||
|
partial = OUT / "runs-partial.json"
|
||||||
|
if partial.is_file():
|
||||||
|
for prior in json.loads(partial.read_text(encoding="utf-8")):
|
||||||
|
seed = len(results)
|
||||||
|
if prior.get("seed") != seed:
|
||||||
|
break
|
||||||
|
log = ROOT / prior.get("log", "")
|
||||||
|
layout = ROOT / prior.get("layout", "")
|
||||||
|
if not log.is_file() or not layout.is_file():
|
||||||
|
break
|
||||||
|
content = log.read_text(encoding="utf-8", errors="replace")
|
||||||
|
stamp = STAMP_RE.search(content)
|
||||||
|
checks = {name: bool(pattern.search(content))
|
||||||
|
for name, pattern in REQUIRED.items()}
|
||||||
|
checks["source_stamp"] = stamp is not None and stamp.group(1) == STAMP
|
||||||
|
checks["blocked_passage_controls"] = len(re.findall(
|
||||||
|
r"^\[ResolvedPassageBlockedControl\].*detected=true$", content, re.M)) == 3
|
||||||
|
checks["passage_count"] = len(re.findall(
|
||||||
|
r"^\[ResolvedPassageAudit\].*walkability=PASS$", content, re.M)) == 3
|
||||||
|
passed = prior.get("exit") == 0 and all(checks.values())
|
||||||
|
if checks != prior.get("checks") or passed != prior.get("passed"):
|
||||||
|
break
|
||||||
|
results.append(prior)
|
||||||
|
print(f"[L139SeedSweepResume] reused={len(results)} source_stamp={STAMP}",
|
||||||
|
flush=True)
|
||||||
|
for seed in range(len(results), 16):
|
||||||
|
log = OUT / f"seed{seed}.log"
|
||||||
|
layout = OUT / f"seed{seed}.json"
|
||||||
|
command = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
|
||||||
|
"timeout", "1740s", str(BIN), str(DATA), "--world-assembler-prototype",
|
||||||
|
"--stream-benchmark", "--stream-pipeline", "--workers=8", "--clip-radius=3",
|
||||||
|
"--max-clip-level=8", "--max-level=0", "--force-generate", f"--seed={seed}",
|
||||||
|
"--recipe=surface-world", "--density-dispatch-report",
|
||||||
|
"--audit-resolved-layout-passages", "--audit-passage-tile-seams",
|
||||||
|
f"--resolved-layout-output={layout}"]
|
||||||
|
started = time.monotonic()
|
||||||
|
with log.open("w", encoding="utf-8") as output:
|
||||||
|
proc = subprocess.run(command, cwd=ROOT, stdout=output,
|
||||||
|
stderr=subprocess.STDOUT, check=False, timeout=1740)
|
||||||
|
content = log.read_text(encoding="utf-8", errors="replace")
|
||||||
|
stamp = STAMP_RE.search(content)
|
||||||
|
checks = {name: bool(pattern.search(content)) for name, pattern in REQUIRED.items()}
|
||||||
|
checks["source_stamp"] = stamp is not None and stamp.group(1) == STAMP
|
||||||
|
checks["blocked_passage_controls"] = len(re.findall(
|
||||||
|
r"^\[ResolvedPassageBlockedControl\].*detected=true$", content, re.M)) == 3
|
||||||
|
checks["passage_count"] = len(re.findall(
|
||||||
|
r"^\[ResolvedPassageAudit\].*walkability=PASS$", content, re.M)) == 3
|
||||||
|
passed = proc.returncode == 0 and all(checks.values())
|
||||||
|
row = {"seed": seed, "exit": proc.returncode, "passed": passed,
|
||||||
|
"checks": checks, "elapsed_s": round(time.monotonic() - started, 3),
|
||||||
|
"log": str(log.relative_to(ROOT)), "layout": str(layout.relative_to(ROOT))}
|
||||||
|
results.append(row)
|
||||||
|
print("[L139SeedSweep] " + json.dumps(row, sort_keys=True), flush=True)
|
||||||
|
partial.write_text(json.dumps(results, indent=2) + "\n")
|
||||||
|
passed_count = sum(row["passed"] for row in results)
|
||||||
|
failed_count = len(results) - passed_count
|
||||||
|
summary = {"source_stamp": STAMP, "seeds": list(range(len(results))),
|
||||||
|
"passed": passed_count, "failed": failed_count, "results": results}
|
||||||
|
(OUT / "summary.json").write_text(json.dumps(summary, indent=2) + "\n")
|
||||||
|
print(f"[L139SeedSweepSummary] passed={passed_count}/16 failed={failed_count} "
|
||||||
|
f"status={'PASS' if len(results)==16 and failed_count==0 else 'FAIL'}")
|
||||||
|
return 0 if len(results) == 16 and failed_count == 0 else 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Render a coarse whole-strate cutaway from the L139 density mesh."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[4]
|
||||||
|
DATA = ROOT / "Standalone/data/owner_world.json"
|
||||||
|
BIN = ROOT / "Saved/Linux/l139/bin/VoxelForgeCoreHeadless-l139-clang"
|
||||||
|
RENDERER = ROOT / "Saved/Linux/l139/bin/VoxelForgeOffscreenCapture-l139-clang"
|
||||||
|
OUT = ROOT / "Saved/Linux/l139/captures"
|
||||||
|
ARTIFACTS = ROOT / "Standalone/Research/WorldAssemblerM3/Linux139/captures"
|
||||||
|
STAMP = "b8603b6cb3975ca0f9669759e4e2464561c557fdb7aa6836acef123ecf0b99d1"
|
||||||
|
STAMP_RE = re.compile(r"^\[VoxelForgeSourceStamp\] sha256=([0-9a-f]{64})$", re.M)
|
||||||
|
LIMIT = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--"]
|
||||||
|
SLICE_Y = -5.0
|
||||||
|
CAMERA = (-450.0, SLICE_Y - 100.0, -950.0)
|
||||||
|
YAW, PITCH, FOV = 0.0, 0.0, 70.0
|
||||||
|
|
||||||
|
|
||||||
|
def stamped_run(label: str, command: list[str], log: Path, env: dict[str, str]) -> str:
|
||||||
|
probe = subprocess.run([*LIMIT, "timeout", "15s", str(command[0]), "--print-source-stamp"],
|
||||||
|
cwd=ROOT, env=env, text=True, stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT, check=False, timeout=20)
|
||||||
|
match = STAMP_RE.search(probe.stdout or "")
|
||||||
|
if probe.returncode != 0 or not match or match.group(1) != STAMP:
|
||||||
|
raise RuntimeError(f"{label}: source stamp mismatch")
|
||||||
|
log.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
started = time.monotonic()
|
||||||
|
with log.open("w", encoding="utf-8") as stream:
|
||||||
|
result = subprocess.run([*LIMIT, "timeout", "1740s", *map(str, command)],
|
||||||
|
cwd=ROOT, env=env, stdout=stream, stderr=subprocess.STDOUT,
|
||||||
|
timeout=1745, check=False)
|
||||||
|
content = log.read_text(encoding="utf-8", errors="replace")
|
||||||
|
emitted = STAMP_RE.search(content)
|
||||||
|
if result.returncode != 0 or not emitted or emitted.group(1) != STAMP:
|
||||||
|
raise RuntimeError(f"{label}: process failed or source changed; see {log}")
|
||||||
|
print(f"[L139WholeLayerCapture] step={label} elapsed_s={time.monotonic()-started:.1f} "
|
||||||
|
f"log={log.relative_to(ROOT)}", flush=True)
|
||||||
|
return content
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
if not DATA.is_file() or not BIN.is_file() or not RENDERER.is_file():
|
||||||
|
raise SystemExit("missing owner fixture or final L139 capture binaries")
|
||||||
|
OUT.mkdir(parents=True, exist_ok=True)
|
||||||
|
ARTIFACTS.mkdir(parents=True, exist_ok=True)
|
||||||
|
env = os.environ.copy()
|
||||||
|
env["LP_NUM_THREADS"] = "12"
|
||||||
|
env["XDG_CACHE_HOME"] = str(OUT / "xdg-cache")
|
||||||
|
stem = "whole-layer-density-cutaway"
|
||||||
|
mesh = OUT / f"{stem}.vfmesh"
|
||||||
|
layout = OUT / f"{stem}-layout.json"
|
||||||
|
camera_text = ",".join(f"{value:.6f}" for value in (*CAMERA, YAW, PITCH))
|
||||||
|
export = [BIN, DATA, "--world-assembler-prototype", "--stream-benchmark",
|
||||||
|
"--stream-pipeline", "--workers=12", "--clip-radius=3", "--max-clip-level=8",
|
||||||
|
"--max-level=8", "--force-generate", "--seed=0", "--recipe=surface-world",
|
||||||
|
"--render-distance-chunks=0", "--capture-skip-walkability", "--density-dispatch-report",
|
||||||
|
f"--camera={camera_text}", f"--resolved-layout-output={layout}", f"--mesh-export={mesh}"]
|
||||||
|
content = stamped_run("export", export, OUT / f"{stem}-export.log", env)
|
||||||
|
dispatch = re.search(
|
||||||
|
r"^\[DensityDispatch\] strate=3 generator=VerticalShafts "
|
||||||
|
r"recipe=Core\.Caves\.PrimitiveField version=1 "
|
||||||
|
r"bound_path=Caves-native adapter=FCavesFieldAdapter$", content, re.M)
|
||||||
|
if not dispatch or not mesh.is_file() or mesh.stat().st_size == 0:
|
||||||
|
raise RuntimeError("coarse density export is empty or lacks native shaft dispatch")
|
||||||
|
png = OUT / f"{stem}.png"
|
||||||
|
jpg = ARTIFACTS / "after-whole-layer-density-cutaway.jpg"
|
||||||
|
render = [RENDERER, mesh, png, "--size=2200x320", f"--fov-deg={FOV:.1f}",
|
||||||
|
"--camera-m=" + ",".join(f"{value:.6f}" for value in CAMERA),
|
||||||
|
f"--yaw-deg={YAW:.6f}", f"--pitch-deg={PITCH:.6f}",
|
||||||
|
f"--clip-plane=0,-1,0,{SLICE_Y:.6f}", "--tile-skirts=off", "--shade=readable",
|
||||||
|
"--headlamp=on", "--height-tint=off", "--height-center-m=-950",
|
||||||
|
"--height-range-m=80", "--tile-edges=off"]
|
||||||
|
stamped_run("render", render, OUT / f"{stem}-render.log", env)
|
||||||
|
subprocess.run([*LIMIT, "timeout", "120s", "ffmpeg", "-nostdin", "-hide_banner",
|
||||||
|
"-loglevel", "error", "-y", "-i", png, "-frames:v", "1", "-q:v", "3", jpg],
|
||||||
|
cwd=ROOT, env=env, check=True, stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.STDOUT, timeout=125)
|
||||||
|
quality = subprocess.run([*LIMIT, "timeout", "120s", sys.executable,
|
||||||
|
ROOT / "Standalone/Tools/verify/capture_quality.py", jpg], cwd=ROOT, env=env,
|
||||||
|
text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
||||||
|
check=False, timeout=125)
|
||||||
|
quality_line = quality.stdout.strip().splitlines()[-1] if quality.stdout.strip() else ""
|
||||||
|
ok = quality.returncode == 0 and "status=PASS" in quality_line
|
||||||
|
manifest_path = ARTIFACTS / "manifest.json"
|
||||||
|
manifest = json.loads(manifest_path.read_text(encoding="utf-8")) if manifest_path.is_file() else []
|
||||||
|
manifest = [row for row in manifest if row.get("view") != "whole-layer-density-cutaway"]
|
||||||
|
manifest.append({"phase": "after", "view": "whole-layer-density-cutaway",
|
||||||
|
"file": str(jpg.relative_to(ROOT)), "bytes": jpg.stat().st_size,
|
||||||
|
"camera_m": CAMERA, "target_m": (-450.0, SLICE_Y, -950.0),
|
||||||
|
"yaw_deg": YAW, "pitch_deg": PITCH, "fov_deg": FOV,
|
||||||
|
"clip_plane": [0.0, -1.0, 0.0, SLICE_Y],
|
||||||
|
"note": "outside side cutaway through the coarse LOD density mesh across the whole Vertical Shafts strate at y=-5 m",
|
||||||
|
"quality_status": "PASS" if ok else "FAIL", "quality": quality_line})
|
||||||
|
manifest_path.write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8")
|
||||||
|
print(f"[L139WholeLayerCaptureSummary] file={jpg.relative_to(ROOT)} "
|
||||||
|
f"quality={'PASS' if ok else 'FAIL'} {quality_line}", flush=True)
|
||||||
|
return 0 if ok else 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 20 KiB |
@@ -0,0 +1,12 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1800" height="760" viewBox="0 0 1800 760">
|
||||||
|
<rect width="1800" height="760" fill="#07111a"/>
|
||||||
|
<text x="72" y="42" fill="#edf6fb" font-family="sans-serif" font-size="24">Vertical Shafts — whole-layer side elevation</text>
|
||||||
|
<text x="72" y="64" fill="#9bb5c5" font-family="sans-serif" font-size="13">Resolved plan • seed 0 • strate 3 • vertical axis and route shown in metres • Y projected out</text>
|
||||||
|
<line x1="72.0" y1="-1440.6" x2="1728.0" y2="-1440.6" stroke="#315060" stroke-dasharray="5 8"/>
|
||||||
|
<rect x="1707.84" y="284.18" width="4.65" height="191.65" rx="2.33" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="1667.56" y="288.45" width="5.72" height="187.37" rx="2.86" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="1245.04" y="344.01" width="6.24" height="131.81" rx="3.12" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="1206.94" y="349.61" width="5.68" height="126.22" rx="2.84" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="1188.40" y="352.41" width="4.37" height="123.41" rx="2.18" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="1168.07" y="355.22" width="6.64" height="120.60" rx="3.32" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="1130.81" y="360.85" width="4.40" height="114.97" rx="2.20" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="1092.31" y="366.48" width="4.62" height="109.35" rx="2.31" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="1072.90" y="369.29" width="5.04" height="106.54" rx="2.52" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="1053.43" y="372.09" width="5.61" height="103.74" rx="2.80" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="1034.94" y="374.88" width="4.20" height="100.94" rx="2.10" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="1015.20" y="377.67" width="5.28" height="98.16" rx="2.64" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="1648.28" y="290.59" width="5.89" height="185.23" rx="2.95" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="995.75" y="380.44" width="5.81" height="95.38" rx="2.90" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="976.77" y="383.20" width="5.37" height="92.62" rx="2.68" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="938.19" y="388.67" width="5.75" height="87.15" rx="2.88" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="918.75" y="391.38" width="6.25" height="84.44" rx="3.13" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="896.89" y="394.44" width="6.22" height="81.38" rx="3.11" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="880.62" y="396.74" width="5.74" height="79.09" rx="2.87" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="862.36" y="399.38" width="3.87" height="76.45" rx="1.94" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="842.60" y="401.99" width="5.01" height="73.83" rx="2.50" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="804.64" y="407.14" width="4.16" height="68.69" rx="2.08" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="785.14" y="409.66" width="4.77" height="66.16" rx="2.39" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="1629.77" y="292.79" width="4.53" height="183.04" rx="2.26" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="765.24" y="412.16" width="6.18" height="63.67" rx="3.09" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="746.73" y="414.61" width="4.82" height="61.21" rx="2.41" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="727.40" y="417.04" width="5.08" height="58.79" rx="2.54" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="707.91" y="419.42" width="5.68" height="56.41" rx="2.84" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="669.16" y="424.06" width="6.41" height="51.77" rx="3.20" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="650.97" y="426.31" width="4.40" height="49.51" rx="2.20" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="593.65" y="432.79" width="3.89" height="43.04" rx="1.94" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="573.84" y="434.85" width="5.11" height="40.98" rx="2.56" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="478.04" y="444.31" width="4.78" height="31.52" rx="2.39" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="440.09" y="447.66" width="3.90" height="28.16" rx="1.95" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="1590.94" y="297.31" width="5.40" height="178.52" rx="2.70" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rect x="420.88" y="449.24" width="3.93" height="26.58" rx="1.97" fill="#93dded" fill-opacity="0.45" stroke="#42b8d1" stroke-width="1.2"/><rLine truncated
|
||||||
|
<path d="M1728.00,290.01 L1716.48,291.19 L1716.48,291.19 L1710.17,291.85 L1710.17,291.85 L1689.61,294.04 L1689.61,294.04 L1670.42,296.13 L1670.42,296.13 L1651.23,298.27 L1651.23,298.27 L1632.03,300.46 L1632.03,300.46 L1612.84,302.70 L1612.84,302.70 L1593.65,304.98 L1593.65,304.98 L1574.45,307.31 L1574.45,307.31 L1555.26,309.68 L1555.26,309.68 L1536.07,312.09 L1536.07,312.09 L1516.87,314.53 L1516.87,314.53 L1497.68,317.01 L1497.68,317.01 L1478.49,319.53 L1478.49,319.53 L1459.29,322.07 L1459.29,322.07 L1440.10,324.65 L1440.10,324.65 L1420.91,327.26 L1420.91,327.26 L1401.71,329.89 L1401.71,329.89 L1382.52,332.55 L1382.52,332.55 L1363.33,335.23 L1363.33,335.23 L1344.13,337.93 L1344.13,337.93 L1324.94,340.65 L1324.94,340.65 L1305.74,343.39 L1305.74,343.39 L1286.55,346.14 L1286.55,346.14 L1267.36,348.91 L1267.36,348.91 L1248.16,351.69 L1248.16,351.69 L1228.97,354.48 L1228.97,354.48 L1209.78,357.28 L1209.78,357.28 L1190.58,360.09 L1190.58,360.09 L1171.39,362.90 L1171.39,362.90 L1152.20,365.71 L1152.20,365.71 L1133.00,368.53 L1133.00,368.53 L1113.81,371.34 L1113.81,371.34 L1094.62,374.16 L1094.62,374.16 L1075.42,376.97 L1075.42,376.97 L1056.23,379.77 L1056.23,379.77 L1037.04,382.56 L1037.04,382.56 L1017.84,385.35 L1017.84,385.35 L998.65,388.12 L998.65,388.12 L979.46,390.88 L979.46,390.88 L960.26,393.62 L960.26,393.62 L941.07,396.35 L941.07,396.35 L921.88,399.06 L921.88,399.06 L902.68,401.75 L902.68,401.75 L900.00,402.12 L900.00,402.12 L883.49,404.41 L883.49,404.41 L864.30,407.05 L864.30,407.05 L845.10,409.67 L845.10,409.67 L825.91,412.26 L825.91,412.26 L806.72,414.82 L806.72,414.82 L787.52,417.34 L787.52,417.34 L768.33,419.83 L768.33,419.83 L749.14,422.29 L749.14,422.29 L729.94,424.71 L729.94,424.71 L710.75,427.09 L710.75,427.09 L691.56,429.44 L691.56,429.44 L672.36,431.73 L672.36,431.73 L653.17,433.99 L653.17,433.99 L633.98,436.20 L633.98,436.20 L614.78,438.36 L614.78,438.36 L595.59,440.47 L595.59,440.47 L576.39,442.53 L576.39,442.53 L557.20,444.53 L557.20,444.53 L538.01,446.48 L538.01,446.48 L518.81,448.37 L518.81,448.37 L499.62,450.21 L499.62,450.21 L480.43,451.98 L480.43,451.98 L461.23,453.69 L461.23,453.69 L442.04,455.34 L442.04,455.34 L422.85,456.92 L422.85,456.92 L403.65,458.43 L403.65,458.43 L384.46,459.88 L384.46,459.88 L365.27,461.25 L365.27,461.25 L346.07,462.55 L346.07,462.55 L326.88,463.77 L326.88,463.77 L307.69,464.91 L307.69,464.91 L288.49,465.98 L288.49,465.98 L269.30,466.97 L269.30,466.97 L250.11,467.87 L250.11,467.87 L230.91,468.69 L230.91,468.69 L211.72,469.42 L211.72,469.42 L192.53,470.06 L192.53,470.06 L173.33,470.62 L173.33,470.62 L154.14,471.08 L154.14,471.08 L134.95,471.45 L134.95,471.45 L115.75,471.73 L115.75,471.73 L96.56,471.90 L96.56,471.90 L89.83,471.94 L89.83,471.94 L83.52,471.97 L83.52,471.97 L77.37,471.98 L77.37,471.98 L72.00,471.99" fill="none" stroke="#f4bf68" stroke-width="3.0" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<text x="82" y="716" fill="#f4bf68" font-family="sans-serif" font-size="15">walk-only graded spine and landings</text>
|
||||||
|
<rect x="416" y="704" width="15" height="15" fill="#93dded" fill-opacity="0.65" stroke="#42b8d1"/>
|
||||||
|
<text x="440" y="716" fill="#b8dce5" font-family="sans-serif" font-size="15">open vertical shaft primitives (53)</text>
|
||||||
|
<text x="1718" y="716" fill="#9bb5c5" text-anchor="end" font-family="sans-serif" font-size="12">Plan projection, not a density mesh</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,258 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"phase": "before",
|
||||||
|
"view": "arrival-mouth",
|
||||||
|
"file": "Standalone/Research/WorldAssemblerM3/Linux139/captures/before-arrival-mouth.jpg",
|
||||||
|
"bytes": 21788,
|
||||||
|
"camera_m": [
|
||||||
|
-20.0625,
|
||||||
|
-5.068359,
|
||||||
|
-900.237499
|
||||||
|
],
|
||||||
|
"target_m": [
|
||||||
|
-30.0625,
|
||||||
|
-5.068359,
|
||||||
|
-900.237499
|
||||||
|
],
|
||||||
|
"yaw_deg": -90.0,
|
||||||
|
"pitch_deg": 0.0,
|
||||||
|
"fov_deg": 78.0,
|
||||||
|
"note": "player eye at the passage arrival, looking into the connected layer",
|
||||||
|
"quality_status": "PASS",
|
||||||
|
"quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux139/captures/before-arrival-mouth.jpg pixels=921600 mean_luma=110.102 stddev_luma=3.380 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"phase": "before",
|
||||||
|
"view": "landing-at-shaft-access",
|
||||||
|
"file": "Standalone/Research/WorldAssemblerM3/Linux139/captures/before-landing-at-shaft-access.jpg",
|
||||||
|
"bytes": 15114,
|
||||||
|
"camera_m": [
|
||||||
|
-31.354577,
|
||||||
|
-5.068359,
|
||||||
|
-901.1982009999999
|
||||||
|
],
|
||||||
|
"target_m": [
|
||||||
|
-31.354577,
|
||||||
|
-9.889916,
|
||||||
|
-901.1982009999999
|
||||||
|
],
|
||||||
|
"yaw_deg": 180.0,
|
||||||
|
"pitch_deg": 0.0,
|
||||||
|
"fov_deg": 78.0,
|
||||||
|
"note": "walkable route landing and short shaft access tunnel",
|
||||||
|
"quality_status": "PASS",
|
||||||
|
"quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux139/captures/before-landing-at-shaft-access.jpg pixels=921600 mean_luma=110.563 stddev_luma=3.056 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"phase": "before",
|
||||||
|
"view": "looking-down-shaft",
|
||||||
|
"file": "Standalone/Research/WorldAssemblerM3/Linux139/captures/before-looking-down-shaft.jpg",
|
||||||
|
"bytes": 11005,
|
||||||
|
"camera_m": [
|
||||||
|
-31.354577,
|
||||||
|
-9.889916,
|
||||||
|
-901.1982009999999
|
||||||
|
],
|
||||||
|
"target_m": [
|
||||||
|
-31.354577,
|
||||||
|
-10.852462,
|
||||||
|
-928.6482010000001
|
||||||
|
],
|
||||||
|
"yaw_deg": 180.0,
|
||||||
|
"pitch_deg": -87.99172181188044,
|
||||||
|
"fov_deg": 35.0,
|
||||||
|
"note": "player eye at the upper access landing, looking down the open shaft",
|
||||||
|
"quality_status": "FAIL",
|
||||||
|
"quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux139/captures/before-looking-down-shaft.jpg pixels=921600 mean_luma=112.000 stddev_luma=0.000 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=FAIL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"phase": "before",
|
||||||
|
"view": "looking-up-from-ledge",
|
||||||
|
"file": "Standalone/Research/WorldAssemblerM3/Linux139/captures/before-looking-up-from-ledge.jpg",
|
||||||
|
"bytes": 15507,
|
||||||
|
"camera_m": [
|
||||||
|
-31.354577,
|
||||||
|
-10.852462,
|
||||||
|
-945.198201
|
||||||
|
],
|
||||||
|
"target_m": [
|
||||||
|
-31.354577,
|
||||||
|
-10.852462,
|
||||||
|
-900.6482010000001
|
||||||
|
],
|
||||||
|
"yaw_deg": 0.0,
|
||||||
|
"pitch_deg": 90.0,
|
||||||
|
"fov_deg": 78.0,
|
||||||
|
"note": "player eye at an internal ledge, looking up; reaching this ledge is a climb tier",
|
||||||
|
"quality_status": "FAIL",
|
||||||
|
"quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux139/captures/before-looking-up-from-ledge.jpg pixels=921600 mean_luma=111.693 stddev_luma=1.515 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=FAIL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"phase": "before",
|
||||||
|
"view": "basal-terminal-landing",
|
||||||
|
"file": "Standalone/Research/WorldAssemblerM3/Linux139/captures/before-basal-terminal-landing.jpg",
|
||||||
|
"bytes": 13933,
|
||||||
|
"camera_m": [
|
||||||
|
-884.858259,
|
||||||
|
-5.068359,
|
||||||
|
-995.05
|
||||||
|
],
|
||||||
|
"target_m": [
|
||||||
|
-892.858259,
|
||||||
|
-5.068359,
|
||||||
|
-995.05
|
||||||
|
],
|
||||||
|
"yaw_deg": -90.0,
|
||||||
|
"pitch_deg": 0.0,
|
||||||
|
"fov_deg": 78.0,
|
||||||
|
"note": "walkable terminal landing above the basal seal",
|
||||||
|
"quality_status": "PASS",
|
||||||
|
"quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux139/captures/before-basal-terminal-landing.jpg pixels=921600 mean_luma=108.996 stddev_luma=6.932 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"phase": "after",
|
||||||
|
"view": "arrival-mouth",
|
||||||
|
"file": "Standalone/Research/WorldAssemblerM3/Linux139/captures/after-arrival-mouth.jpg",
|
||||||
|
"bytes": 19044,
|
||||||
|
"camera_m": [
|
||||||
|
-20.0625,
|
||||||
|
-5.068359,
|
||||||
|
-900.237499
|
||||||
|
],
|
||||||
|
"target_m": [
|
||||||
|
-30.0625,
|
||||||
|
-5.068359,
|
||||||
|
-900.237499
|
||||||
|
],
|
||||||
|
"yaw_deg": -90.0,
|
||||||
|
"pitch_deg": 0.0,
|
||||||
|
"fov_deg": 78.0,
|
||||||
|
"note": "player eye at the passage arrival, looking into the connected layer",
|
||||||
|
"quality_status": "PASS",
|
||||||
|
"quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux139/captures/after-arrival-mouth.jpg pixels=921600 mean_luma=125.004 stddev_luma=16.734 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"phase": "after",
|
||||||
|
"view": "landing-at-shaft-access",
|
||||||
|
"file": "Standalone/Research/WorldAssemblerM3/Linux139/captures/after-landing-at-shaft-access.jpg",
|
||||||
|
"bytes": 15595,
|
||||||
|
"camera_m": [
|
||||||
|
-31.354577,
|
||||||
|
-5.068359,
|
||||||
|
-901.1982009999999
|
||||||
|
],
|
||||||
|
"target_m": [
|
||||||
|
-31.354577,
|
||||||
|
-9.889916,
|
||||||
|
-901.1982009999999
|
||||||
|
],
|
||||||
|
"yaw_deg": 180.0,
|
||||||
|
"pitch_deg": 0.0,
|
||||||
|
"fov_deg": 78.0,
|
||||||
|
"note": "walkable route landing and short shaft access tunnel",
|
||||||
|
"quality_status": "PASS",
|
||||||
|
"quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux139/captures/after-landing-at-shaft-access.jpg pixels=921600 mean_luma=117.622 stddev_luma=14.149 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"phase": "after",
|
||||||
|
"view": "looking-down-shaft",
|
||||||
|
"file": "Standalone/Research/WorldAssemblerM3/Linux139/captures/after-looking-down-shaft.jpg",
|
||||||
|
"bytes": 24396,
|
||||||
|
"camera_m": [
|
||||||
|
-31.354577,
|
||||||
|
-9.889916,
|
||||||
|
-901.1982009999999
|
||||||
|
],
|
||||||
|
"target_m": [
|
||||||
|
-31.354577,
|
||||||
|
-10.852462,
|
||||||
|
-928.6482010000001
|
||||||
|
],
|
||||||
|
"yaw_deg": 180.0,
|
||||||
|
"pitch_deg": -87.99172181188044,
|
||||||
|
"fov_deg": 35.0,
|
||||||
|
"note": "player eye at the upper access landing, looking down the open shaft",
|
||||||
|
"quality_status": "PASS",
|
||||||
|
"quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux139/captures/after-looking-down-shaft.jpg pixels=921600 mean_luma=112.365 stddev_luma=3.113 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"phase": "after",
|
||||||
|
"view": "looking-up-from-ledge",
|
||||||
|
"file": "Standalone/Research/WorldAssemblerM3/Linux139/captures/after-looking-up-from-ledge.jpg",
|
||||||
|
"bytes": 23172,
|
||||||
|
"camera_m": [
|
||||||
|
-31.354577,
|
||||||
|
-10.852462,
|
||||||
|
-945.198201
|
||||||
|
],
|
||||||
|
"target_m": [
|
||||||
|
-31.354577,
|
||||||
|
-10.852462,
|
||||||
|
-900.6482010000001
|
||||||
|
],
|
||||||
|
"yaw_deg": 0.0,
|
||||||
|
"pitch_deg": 90.0,
|
||||||
|
"fov_deg": 78.0,
|
||||||
|
"note": "player eye at an internal ledge, looking up; reaching this ledge is a climb tier",
|
||||||
|
"quality_status": "PASS",
|
||||||
|
"quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux139/captures/after-looking-up-from-ledge.jpg pixels=921600 mean_luma=111.549 stddev_luma=2.538 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"phase": "after",
|
||||||
|
"view": "basal-terminal-landing",
|
||||||
|
"file": "Standalone/Research/WorldAssemblerM3/Linux139/captures/after-basal-terminal-landing.jpg",
|
||||||
|
"bytes": 12527,
|
||||||
|
"camera_m": [
|
||||||
|
-884.858259,
|
||||||
|
-5.068359,
|
||||||
|
-995.05
|
||||||
|
],
|
||||||
|
"target_m": [
|
||||||
|
-892.858259,
|
||||||
|
-5.068359,
|
||||||
|
-995.05
|
||||||
|
],
|
||||||
|
"yaw_deg": -90.0,
|
||||||
|
"pitch_deg": 0.0,
|
||||||
|
"fov_deg": 78.0,
|
||||||
|
"note": "walkable terminal landing above the basal seal",
|
||||||
|
"quality_status": "PASS",
|
||||||
|
"quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux139/captures/after-basal-terminal-landing.jpg pixels=921600 mean_luma=125.238 stddev_luma=3.555 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"phase": "after",
|
||||||
|
"view": "whole-layer-side-elevation",
|
||||||
|
"file": "Standalone/Research/WorldAssemblerM3/Linux139/captures/after-whole-layer-side-elevation.svg",
|
||||||
|
"shafts": 53,
|
||||||
|
"walk_route_edges": 91,
|
||||||
|
"note": "resolved-plan side projection; not a density-mesh capture"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"phase": "after",
|
||||||
|
"view": "whole-layer-density-cutaway",
|
||||||
|
"file": "Standalone/Research/WorldAssemblerM3/Linux139/captures/after-whole-layer-density-cutaway.jpg",
|
||||||
|
"bytes": 20280,
|
||||||
|
"camera_m": [
|
||||||
|
-450.0,
|
||||||
|
-105.0,
|
||||||
|
-950.0
|
||||||
|
],
|
||||||
|
"target_m": [
|
||||||
|
-450.0,
|
||||||
|
-5.0,
|
||||||
|
-950.0
|
||||||
|
],
|
||||||
|
"yaw_deg": 0.0,
|
||||||
|
"pitch_deg": 0.0,
|
||||||
|
"fov_deg": 70.0,
|
||||||
|
"clip_plane": [
|
||||||
|
0.0,
|
||||||
|
-1.0,
|
||||||
|
0.0,
|
||||||
|
-5.0
|
||||||
|
],
|
||||||
|
"note": "outside side cutaway through the coarse LOD density mesh across the whole Vertical Shafts strate at the denser shaft row",
|
||||||
|
"quality_status": "PASS",
|
||||||
|
"quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux139/captures/after-whole-layer-density-cutaway.jpg pixels=704000 mean_luma=113.286 stddev_luma=3.364 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -5147,6 +5147,7 @@ namespace StandaloneStreamBenchmark
|
|||||||
}
|
}
|
||||||
std::uint64_t routePoints=0,routePasses=0,routeWalkPasses=0,routeBindings=0,routeBindingFailures=0;
|
std::uint64_t routePoints=0,routePasses=0,routeWalkPasses=0,routeBindings=0,routeBindingFailures=0;
|
||||||
std::uint64_t tunnelNetworkRoutes=0,tunnelNetworkBlockedControls=0;
|
std::uint64_t tunnelNetworkRoutes=0,tunnelNetworkBlockedControls=0;
|
||||||
|
std::uint64_t verticalShaftRoutes=0,verticalShaftBlockedControls=0;
|
||||||
for(const auto& stratum:ResolvedLayout->strates)
|
for(const auto& stratum:ResolvedLayout->strates)
|
||||||
{
|
{
|
||||||
std::vector<const VoxelForgeCore::ResolvedLayoutAnchorV2*> anchors;
|
std::vector<const VoxelForgeCore::ResolvedLayoutAnchorV2*> anchors;
|
||||||
@@ -5206,7 +5207,8 @@ namespace StandaloneStreamBenchmark
|
|||||||
supportMaximumMetres=routeConstraints->supportSearchDepthSubvoxels*qMetres;
|
supportMaximumMetres=routeConstraints->supportSearchDepthSubvoxels*qMetres;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(stratum.generatorKind=="TunnelNetwork")
|
if(stratum.generatorKind=="TunnelNetwork"
|
||||||
|
||stratum.generatorKind=="VerticalShafts")
|
||||||
{
|
{
|
||||||
std::int64_t floorSupportDepth=0;
|
std::int64_t floorSupportDepth=0;
|
||||||
for(const auto& edge:ResolvedLayout->cavernEdges)
|
for(const auto& edge:ResolvedLayout->cavernEdges)
|
||||||
@@ -5297,9 +5299,12 @@ namespace StandaloneStreamBenchmark
|
|||||||
stratum.strateIndex,routeAudit.firstUnsupported[i][0],
|
stratum.strateIndex,routeAudit.firstUnsupported[i][0],
|
||||||
routeAudit.firstUnsupported[i][1],routeAudit.firstUnsupported[i][2],
|
routeAudit.firstUnsupported[i][1],routeAudit.firstUnsupported[i][2],
|
||||||
routeAudit.firstUnsupported[i][3]);
|
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;
|
auto controlRoute=route;
|
||||||
constexpr double OffBoreControlOffsetMetres=12.0;
|
constexpr double OffBoreControlOffsetMetres=12.0;
|
||||||
const std::size_t controlIndex=controlRoute.size()/2;
|
const std::size_t controlIndex=controlRoute.size()/2;
|
||||||
@@ -5319,11 +5324,22 @@ namespace StandaloneStreamBenchmark
|
|||||||
supportStartMetres,supportStepMetres,supportMaximumMetres,
|
supportStartMetres,supportStepMetres,supportMaximumMetres,
|
||||||
minimumSupportProbes,supportProbeRadiusMetres);
|
minimumSupportProbes,supportProbeRadiusMetres);
|
||||||
const bool detected=control.blocked>0&&!control.passed;
|
const bool detected=control.blocked>0&&!control.passed;
|
||||||
tunnelNetworkBlockedControls+=detected;
|
if(verticalShafts)
|
||||||
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,
|
verticalShaftBlockedControls+=detected;
|
||||||
static_cast<unsigned long long>(control.blocked),
|
std::printf("[VerticalShaftsBlockedRouteControl] strate=%d mode=off_bore_lateral_deviation_m=%.1f blocked_capsule_samples=%llu result=%s expected=FAIL status=%s\n",
|
||||||
control.passed?"PASS":"FAIL",detected?"PASS":"FAIL");
|
stratum.strateIndex,OffBoreControlOffsetMetres,
|
||||||
|
static_cast<unsigned long long>(control.blocked),
|
||||||
|
control.passed?"PASS":"FAIL",detected?"PASS":"FAIL");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tunnelNetworkBlockedControls+=detected;
|
||||||
|
std::printf("[TunnelNetworkBlockedRouteControl] strate=%d mode=off_bore_lateral_deviation_m=%.1f blocked_capsule_samples=%llu result=%s expected=FAIL status=%s\n",
|
||||||
|
stratum.strateIndex,OffBoreControlOffsetMetres,
|
||||||
|
static_cast<unsigned long long>(control.blocked),
|
||||||
|
control.passed?"PASS":"FAIL",detected?"PASS":"FAIL");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for(std::size_t passageIndex=0;
|
for(std::size_t passageIndex=0;
|
||||||
@@ -5392,8 +5408,9 @@ namespace StandaloneStreamBenchmark
|
|||||||
&&routePasses>0&&routePasses==routeWalkPasses&&routePasses==routeBindings
|
&&routePasses>0&&routePasses==routeWalkPasses&&routePasses==routeBindings
|
||||||
&&routeBindingFailures==0
|
&&routeBindingFailures==0
|
||||||
&&tunnelNetworkRoutes==tunnelNetworkBlockedControls
|
&&tunnelNetworkRoutes==tunnelNetworkBlockedControls
|
||||||
|
&&verticalShaftRoutes==verticalShaftBlockedControls
|
||||||
&&blockedCompiled&&blockedControls==passageCount;
|
&&blockedCompiled&&blockedControls==passageCount;
|
||||||
std::printf("[ResolvedPassageAuditSummary] interfaces=%llu passed=%llu natural=%llu/%llu strate_routes=%llu route_walkable=%llu route_points=%llu route_bindings=%llu route_binding_failures=%llu tunnel_network_block_controls=%llu/%llu blocked_controls=%llu/%llu status=%s\n",
|
std::printf("[ResolvedPassageAuditSummary] interfaces=%llu passed=%llu natural=%llu/%llu strate_routes=%llu route_walkable=%llu route_points=%llu route_bindings=%llu route_binding_failures=%llu tunnel_network_block_controls=%llu/%llu vertical_shaft_block_controls=%llu/%llu blocked_controls=%llu/%llu status=%s\n",
|
||||||
static_cast<unsigned long long>(passageCount),static_cast<unsigned long long>(passagePasses),
|
static_cast<unsigned long long>(passageCount),static_cast<unsigned long long>(passagePasses),
|
||||||
static_cast<unsigned long long>(naturalPasses),static_cast<unsigned long long>(passageCount),
|
static_cast<unsigned long long>(naturalPasses),static_cast<unsigned long long>(passageCount),
|
||||||
static_cast<unsigned long long>(routePasses),static_cast<unsigned long long>(routeWalkPasses),
|
static_cast<unsigned long long>(routePasses),static_cast<unsigned long long>(routeWalkPasses),
|
||||||
@@ -5401,6 +5418,8 @@ namespace StandaloneStreamBenchmark
|
|||||||
static_cast<unsigned long long>(routeBindings),static_cast<unsigned long long>(routeBindingFailures),
|
static_cast<unsigned long long>(routeBindings),static_cast<unsigned long long>(routeBindingFailures),
|
||||||
static_cast<unsigned long long>(tunnelNetworkBlockedControls),
|
static_cast<unsigned long long>(tunnelNetworkBlockedControls),
|
||||||
static_cast<unsigned long long>(tunnelNetworkRoutes),
|
static_cast<unsigned long long>(tunnelNetworkRoutes),
|
||||||
|
static_cast<unsigned long long>(verticalShaftBlockedControls),
|
||||||
|
static_cast<unsigned long long>(verticalShaftRoutes),
|
||||||
static_cast<unsigned long long>(blockedControls),static_cast<unsigned long long>(passageCount),
|
static_cast<unsigned long long>(blockedControls),static_cast<unsigned long long>(passageCount),
|
||||||
auditPassed?"PASS":"FAIL");
|
auditPassed?"PASS":"FAIL");
|
||||||
if(!auditPassed)
|
if(!auditPassed)
|
||||||
|
|||||||
@@ -1517,13 +1517,16 @@ namespace StandaloneWorldAssemblyPrototype
|
|||||||
{
|
{
|
||||||
using namespace VoxelForgeCore;
|
using namespace VoxelForgeCore;
|
||||||
if(!owner.Manager)
|
if(!owner.Manager)
|
||||||
{error="Tunnel Network cave planner requires the initialized owner director";return false;}
|
{error="native cave planner requires the initialized owner director";return false;}
|
||||||
std::vector<const FStrateSlot*> tunnelSlots;
|
std::vector<std::pair<const FStrateSlot*,bool>> plannerSlots;
|
||||||
for(const FStrateSlot& slot:owner.Manager->GetLayout())
|
for(const FStrateSlot& slot:owner.Manager->GetLayout())
|
||||||
{
|
{
|
||||||
if(!slot.Definition
|
if(!slot.Definition)continue;
|
||||||
||owner.Manager->GetGeneratorTypeForChunk(FIntVector(0,0,slot.TopChunkZ))
|
const ECaveGeneratorType generatorType=
|
||||||
!=ECaveGeneratorType::TunnelNetwork)continue;
|
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(),
|
const bool hasIncomingMouth=std::any_of(layout.passages.begin(),
|
||||||
layout.passages.end(),[&](const auto& passage)
|
layout.passages.end(),[&](const auto& passage)
|
||||||
{return passage.lowerStrateIndex==slot.StrateIndex
|
{return passage.lowerStrateIndex==slot.StrateIndex
|
||||||
@@ -1532,24 +1535,27 @@ namespace StandaloneWorldAssemblyPrototype
|
|||||||
layout.passages.end(),[&](const auto& passage)
|
layout.passages.end(),[&](const auto& passage)
|
||||||
{return passage.upperStrateIndex==slot.StrateIndex
|
{return passage.upperStrateIndex==slot.StrateIndex
|
||||||
&&passage.lowerStrateIndex>slot.StrateIndex;});
|
&&passage.lowerStrateIndex>slot.StrateIndex;});
|
||||||
// A native planner route requires two mouths. Boundary Tunnel Network
|
// Tunnel Network connects two passages. The basal Vertical Shafts
|
||||||
// strates keep their legacy field until they have both connections.
|
// stratum is terminal: it inherits its arrival from the preceding
|
||||||
if(!hasIncomingMouth||!hasOutgoingMouth)continue;
|
// 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(),
|
auto stratum=std::find_if(layout.strates.begin(),layout.strates.end(),
|
||||||
[&](const auto& item){return item.strateIndex==slot.StrateIndex;});
|
[&](const auto& item){return item.strateIndex==slot.StrateIndex;});
|
||||||
if(stratum==layout.strates.end())
|
if(stratum==layout.strates.end())
|
||||||
{error="Tunnel Network strate is missing from the resolved layout";return false;}
|
{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->recipeId="Core.Caves.PrimitiveField";
|
||||||
stratum->recipeVersion=1;
|
stratum->recipeVersion=1;
|
||||||
stratum->stableId=layout.worldIdentity+"/strate/"
|
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(),
|
const bool alreadyPlanned=std::any_of(layout.cavernRooms.begin(),
|
||||||
layout.cavernRooms.end(),[&](const auto& room)
|
layout.cavernRooms.end(),[&](const auto& room)
|
||||||
{return room.strateIndex==slot.StrateIndex;})
|
{return room.strateIndex==slot.StrateIndex;})
|
||||||
&&std::any_of(layout.cavernEdges.begin(),layout.cavernEdges.end(),
|
&&std::any_of(layout.cavernEdges.begin(),layout.cavernEdges.end(),
|
||||||
[&](const auto& edge){return edge.strateIndex==slot.StrateIndex;});
|
[&](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;
|
if(!layout.finalize(error))return false;
|
||||||
const auto toMicrometres=[&](double ownerVoxels)
|
const auto toMicrometres=[&](double ownerVoxels)
|
||||||
@@ -1562,14 +1568,55 @@ namespace StandaloneWorldAssemblyPrototype
|
|||||||
return static_cast<std::int32_t>(std::llround(std::clamp(
|
return static_cast<std::int32_t>(std::llround(std::clamp(
|
||||||
static_cast<double>(value),0.0,1.0)*65536.0));
|
static_cast<double>(value),0.0,1.0)*65536.0));
|
||||||
};
|
};
|
||||||
if(!tunnelSlots.empty())
|
if(!plannerSlots.empty())
|
||||||
{
|
{
|
||||||
std::vector<ResolvedCavernRoomV1> plannedRooms=layout.cavernRooms;
|
std::vector<ResolvedCavernRoomV1> plannedRooms=layout.cavernRooms;
|
||||||
std::vector<ResolvedCavernEdgeV1> plannedEdges=layout.cavernEdges;
|
std::vector<ResolvedCavernEdgeV1> plannedEdges=layout.cavernEdges;
|
||||||
for(const FStrateSlot* slot:tunnelSlots)
|
for(const auto& [slot,verticalShafts]:plannerSlots)
|
||||||
{
|
{
|
||||||
const FStrateGenerationParams& params=slot->Definition->GenerationParams;
|
const FStrateGenerationParams& params=slot->Definition->GenerationParams;
|
||||||
ResolvedCavernPlannerSettingsV1 settings;
|
ResolvedCavernPlannerSettingsV1 settings;
|
||||||
|
if(verticalShafts)
|
||||||
|
{
|
||||||
|
const FVerticalShaftParams& shaft=slot->Definition->VerticalShaftParams;
|
||||||
|
settings.verticalShaftMode=true;
|
||||||
|
settings.roomSpacingMicrometres=std::clamp<std::int64_t>(
|
||||||
|
toMicrometres(shaft.ShaftSpacing),4000000,100000000);
|
||||||
|
settings.minimumRoomRadiusMicrometres=std::max<std::int64_t>(
|
||||||
|
500000,toMicrometres(shaft.ShaftMinRadius));
|
||||||
|
settings.maximumRoomRadiusMicrometres=std::max(
|
||||||
|
settings.minimumRoomRadiusMicrometres,
|
||||||
|
toMicrometres(shaft.ShaftMaxRadius));
|
||||||
|
settings.destinationRadiusMicrometres=std::max(
|
||||||
|
settings.maximumRoomRadiusMicrometres,
|
||||||
|
settings.maximumRoomRadiusMicrometres*135/100);
|
||||||
|
settings.minimumTunnelRadiusMicrometres=std::max<std::int64_t>(0,
|
||||||
|
toMicrometres(shaft.ConnectorRadius));
|
||||||
|
settings.maximumTunnelRadiusMicrometres=
|
||||||
|
settings.minimumTunnelRadiusMicrometres;
|
||||||
|
settings.maximumTunnelLengthMicrometres=std::clamp<std::int64_t>(
|
||||||
|
settings.roomSpacingMicrometres*2,4000000,100000000);
|
||||||
|
settings.smoothWidthMicrometres=1500000;
|
||||||
|
settings.floorSupportDepthMicrometres=2000000;
|
||||||
|
settings.irregularityAmplitudeMicrometres=std::max<std::int64_t>(0,
|
||||||
|
toMicrometres(shaft.SurfaceRoughness));
|
||||||
|
settings.irregularityWavelengthMicrometres=std::max<std::int64_t>(
|
||||||
|
500000,static_cast<std::int64_t>(owner.OwnerVoxelSizeMicrons)*4);
|
||||||
|
settings.roomDensityQ16=toQ16(shaft.ShaftDensity);
|
||||||
|
settings.tunnelDensityQ16=toQ16(shaft.CrossConnectChance);
|
||||||
|
settings.roomShapeVarietyQ16=0;
|
||||||
|
settings.roomHeightRatioQ16=65536;
|
||||||
|
settings.roomFloorCutMinimumQ16=0;
|
||||||
|
settings.roomFloorCutMaximumQ16=0;
|
||||||
|
settings.tunnelHorizontalBiasQ16=65536;
|
||||||
|
settings.originRoomMaximumConnections=32;
|
||||||
|
settings.ledgeSpacingMicrometres=std::max<std::int64_t>(0,
|
||||||
|
toMicrometres(shaft.LedgeSpacing));
|
||||||
|
settings.ledgeDepthMicrometres=std::max<std::int64_t>(0,
|
||||||
|
toMicrometres(shaft.LedgeDepth));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
settings.roomSpacingMicrometres=std::max<std::int64_t>(4000000,
|
settings.roomSpacingMicrometres=std::max<std::int64_t>(4000000,
|
||||||
toMicrometres(params.RoomSpacing));
|
toMicrometres(params.RoomSpacing));
|
||||||
settings.minimumRoomRadiusMicrometres=std::max<std::int64_t>(500000,
|
settings.minimumRoomRadiusMicrometres=std::max<std::int64_t>(500000,
|
||||||
@@ -1607,6 +1654,7 @@ namespace StandaloneWorldAssemblyPrototype
|
|||||||
settings.tunnelHorizontalBiasQ16=toQ16(params.TunnelHorizontalBias);
|
settings.tunnelHorizontalBiasQ16=toQ16(params.TunnelHorizontalBias);
|
||||||
settings.originRoomMaximumConnections=params.OriginRoomMaxConnections;
|
settings.originRoomMaximumConnections=params.OriginRoomMaxConnections;
|
||||||
settings.tunnelsFlowTowardOrigin=params.bTunnelsFlowTowardOrigin;
|
settings.tunnelsFlowTowardOrigin=params.bTunnelsFlowTowardOrigin;
|
||||||
|
}
|
||||||
std::vector<ResolvedCavernRoomV1> rooms;
|
std::vector<ResolvedCavernRoomV1> rooms;
|
||||||
std::vector<ResolvedCavernEdgeV1> edges;
|
std::vector<ResolvedCavernEdgeV1> edges;
|
||||||
if(!PlanResolvedCavernNetworkV1(layout,owner.Seed,slot->StrateIndex,
|
if(!PlanResolvedCavernNetworkV1(layout,owner.Seed,slot->StrateIndex,
|
||||||
@@ -1629,24 +1677,27 @@ namespace StandaloneWorldAssemblyPrototype
|
|||||||
constexpr std::int64_t PlayerCenterAboveFloorMicrometres=1130000;
|
constexpr std::int64_t PlayerCenterAboveFloorMicrometres=1130000;
|
||||||
for(const FStrateSlot& slot:owner.Manager->GetLayout())
|
for(const FStrateSlot& slot:owner.Manager->GetLayout())
|
||||||
{
|
{
|
||||||
if(!slot.Definition
|
if(!slot.Definition)continue;
|
||||||
||owner.Manager->GetGeneratorTypeForChunk(FIntVector(0,0,slot.TopChunkZ))
|
const ECaveGeneratorType generatorType=
|
||||||
!=ECaveGeneratorType::TunnelNetwork)continue;
|
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(),
|
auto stratum=std::find_if(layout.strates.begin(),layout.strates.end(),
|
||||||
[&](const auto& item){return item.strateIndex==slot.StrateIndex;});
|
[&](const auto& item){return item.strateIndex==slot.StrateIndex;});
|
||||||
if(stratum==layout.strates.end()
|
if(stratum==layout.strates.end()
|
||||||
||!IsNativeCaveFieldStratum(*stratum))continue;
|
||!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)+"/";
|
+std::to_string(slot.StrateIndex)+"/";
|
||||||
const bool hasPlannedArrival=std::any_of(layout.cavernEdges.begin(),
|
const bool hasPlannedArrival=std::any_of(layout.cavernEdges.begin(),
|
||||||
layout.cavernEdges.end(),[&](const auto& edge)
|
layout.cavernEdges.end(),[&](const auto& edge)
|
||||||
{return edge.strateIndex==slot.StrateIndex
|
{return edge.strateIndex==slot.StrateIndex
|
||||||
&&edge.stableId==routePrefix+"route-edge-0";});
|
&&edge.stableId==routePrefix+"route-edge-0";});
|
||||||
const bool hasPlannedDeparture=std::any_of(layout.cavernEdges.begin(),
|
const bool hasPlannedDeparture=!verticalShafts
|
||||||
layout.cavernEdges.end(),[&](const auto& edge)
|
&&std::any_of(layout.cavernEdges.begin(),layout.cavernEdges.end(),
|
||||||
{return edge.strateIndex==slot.StrateIndex
|
[&](const auto& edge){return edge.strateIndex==slot.StrateIndex
|
||||||
&&edge.stableId==routePrefix+"departure-edge";});
|
&&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
|
// Route-point anchors also drive the passage-mouth floor blend. The
|
||||||
// native cave planner replaces the legacy finite-strate route, so
|
// native cave planner replaces the legacy finite-strate route, so
|
||||||
// retaining its old anchors would join the passage floor to a route
|
// retaining its old anchors would join the passage floor to a route
|
||||||
@@ -1663,13 +1714,14 @@ namespace StandaloneWorldAssemblyPrototype
|
|||||||
if(passage.upperStrateIndex==slot.StrateIndex
|
if(passage.upperStrateIndex==slot.StrateIndex
|
||||||
&&passage.lowerStrateIndex>slot.StrateIndex)departure=&passage;
|
&&passage.lowerStrateIndex>slot.StrateIndex)departure=&passage;
|
||||||
}
|
}
|
||||||
if(!arrival||!departure)
|
if(!arrival||(!verticalShafts&&!departure))
|
||||||
{error="Tunnel Network route anchors cannot bind both passage mouths";return false;}
|
{error="native cave route anchors cannot bind their required passage mouths";return false;}
|
||||||
const auto appendRoutePoint=[&](std::string id,
|
const auto appendRoutePoint=[&](std::string id,
|
||||||
const std::array<std::int64_t,3>& point)
|
const std::array<std::int64_t,3>& point)
|
||||||
{
|
{
|
||||||
ResolvedLayoutAnchorV2 anchor;anchor.kind="route_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.units="1/256 Core voxel";
|
||||||
anchor.values={point[0],point[1],point[2]};
|
anchor.values={point[0],point[1],point[2]};
|
||||||
stratum->anchors.push_back(std::move(anchor));
|
stratum->anchors.push_back(std::move(anchor));
|
||||||
@@ -1684,14 +1736,23 @@ namespace StandaloneWorldAssemblyPrototype
|
|||||||
appendRoutePoint("000-arrival",start);
|
appendRoutePoint("000-arrival",start);
|
||||||
for(std::size_t edgeIndex=0;edgeIndex<256;++edgeIndex)
|
for(std::size_t edgeIndex=0;edgeIndex<256;++edgeIndex)
|
||||||
{
|
{
|
||||||
const std::string stableId="tunnel-network/strate-"
|
const std::string stableId=routePrefix+"route-edge-"
|
||||||
+std::to_string(slot.StrateIndex)+"/route-edge-"
|
|
||||||
+std::to_string(edgeIndex);
|
+std::to_string(edgeIndex);
|
||||||
const auto edge=std::find_if(layout.cavernEdges.begin(),
|
const auto edge=std::find_if(layout.cavernEdges.begin(),
|
||||||
layout.cavernEdges.end(),[&](const auto& value)
|
layout.cavernEdges.end(),[&](const auto& value)
|
||||||
{return value.strateIndex==slot.StrateIndex
|
{return value.strateIndex==slot.StrateIndex
|
||||||
&&value.stableId==stableId;});
|
&&value.stableId==stableId;});
|
||||||
if(edge==layout.cavernEdges.end())break;
|
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)
|
if(edgeIndex==0)
|
||||||
{
|
{
|
||||||
auto inner=edge->controlPointsMicrometres.front();
|
auto inner=edge->controlPointsMicrometres.front();
|
||||||
@@ -1708,18 +1769,20 @@ namespace StandaloneWorldAssemblyPrototype
|
|||||||
}
|
}
|
||||||
auto point=edge->controlPointsMicrometres.back();
|
auto point=edge->controlPointsMicrometres.back();
|
||||||
point[2]+=PlayerCenterAboveFloorMicrometres;
|
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[0]),toCoreSubvoxels(point[1]),
|
||||||
toCoreSubvoxels(point[2])});
|
toCoreSubvoxels(point[2])});
|
||||||
}
|
}
|
||||||
const std::string departureEdgeId="tunnel-network/strate-"
|
if(departure)
|
||||||
+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())
|
|
||||||
{
|
{
|
||||||
|
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();
|
auto inner=departureEdge->controlPointsMicrometres.front();
|
||||||
inner[2]+=PlayerCenterAboveFloorMicrometres;
|
inner[2]+=PlayerCenterAboveFloorMicrometres;
|
||||||
const std::array<std::int64_t,3> innerPoint={
|
const std::array<std::int64_t,3> innerPoint={
|
||||||
@@ -1728,9 +1791,17 @@ namespace StandaloneWorldAssemblyPrototype
|
|||||||
auto end=departure->upperMouthSubvoxels;
|
auto end=departure->upperMouthSubvoxels;
|
||||||
if(!sameRoutePoint(innerPoint,end))
|
if(!sameRoutePoint(innerPoint,end))
|
||||||
appendRoutePoint("998-departure-inner",innerPoint);
|
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);
|
return layout.finalize(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
[PipelineFullDetailHash] tiles=960 density=0x91931a57a067fd92 mesh=0xa818654be89790ac payload=0x370ff80451105a39 topology=0x06e507557aa70173
|
||||||
|
[PipelineWholeViewHash] tiles=2370 density=0x7111b4b743e03c95 mesh=0x8c7240939db21b88 payload=0x3981ff5ca7076219 topology=0x07947d2e691b2e55
|
||||||
|
[PipelineLODHash] lod=0 tiles=512 density=0x2fd4c78685a43cfe mesh=0x50478cff1c1f8ca0 payload=0x54e48e57e0af26de topology=0x860c011cfdb9833a
|
||||||
|
[PipelineLODHash] lod=1 tiles=448 density=0x4b8d9a6dcd2942ad mesh=0x17e89b7087bfe711 payload=0x2389c109eb780db6 topology=0x7eccd4cf560d0630
|
||||||
|
[PipelineLODHash] lod=2 tiles=320 density=0x48d4c21a852e6e21 mesh=0x5516a17618d142d5 payload=0x59cda38ab9ca6b7a topology=0x15ae353bd835e6a1
|
||||||
|
[PipelineLODHash] lod=3 tiles=208 density=0x09b4811098a48c8d mesh=0xbac3fb6d54bca8e9 payload=0xca779555d55be411 topology=0xc63e8ee46890a03a
|
||||||
|
[PipelineLODHash] lod=4 tiles=240 density=0xfb11591772a3d18e mesh=0xebf2baee8764591b payload=0xe491d7ac9c0061ad topology=0xd618ac33b95ce494
|
||||||
|
[PipelineLODHash] lod=5 tiles=144 density=0x2f8b7b7fb1e67258 mesh=0x4cd508cbe97344fd payload=0x2b6e96a6050458c9 topology=0x4ff6d0eda8988573
|
||||||
|
[PipelineLODHash] lod=6 tiles=96 density=0x0114a34c8b8d6113 mesh=0xb1fec4d01ae1fa44 payload=0x3e50cfd429b8e950 topology=0x08090c96d15c1618
|
||||||
|
[PipelineLODHash] lod=7 tiles=96 density=0x3836ed8cfce73418 mesh=0x40bc6589c5fc1aac payload=0x677349bcae16790f topology=0x36e3aa53192e6a6b
|
||||||
|
[PipelineLODHash] lod=8 tiles=306 density=0x144f3c1fd722708b mesh=0xef77b28bf7fd9453 payload=0xe3f47b37fcd61489 topology=0x5a3c73e295494e04
|
||||||
|
[PipelineExactQueryHash] probes=9 density=0x7b7c50ac24ccadcc unclamped=0x7b7c50ac24ccadcc detail=0xefcba20c0c70c415 collision=0xe604823a249029bf path=direct-Core-queries
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
[PipelineFullDetailHash] tiles=960 density=0xace56c8f205238ad mesh=0xa9c603a1248de6b8 payload=0xea55f1f4fcc6a894 topology=0x5ea7bc78a1b678ae
|
||||||
|
[PipelineWholeViewHash] tiles=2370 density=0x826704417f556bb8 mesh=0x3dc31324d910ad2b payload=0x32cd1847a8e05d0a topology=0xb800610aa85336d7
|
||||||
|
[PipelineLODHash] lod=0 tiles=512 density=0xcd6ab28cfa504aca mesh=0xf03dc80a669fca31 payload=0x397aded1c3d2d297 topology=0xb4a1dc84fabc1fe3
|
||||||
|
[PipelineLODHash] lod=1 tiles=448 density=0xce6e713828b2a20a mesh=0x1cf408c25d67d18c payload=0x8a2b5d20571c997e topology=0x51cd985fe8e7d100
|
||||||
|
[PipelineLODHash] lod=2 tiles=320 density=0xd7f003d75d73e5b1 mesh=0x4e9c56918da506ae payload=0x3867c5915b409d6d topology=0x02a2a7c0ee9ac809
|
||||||
|
[PipelineLODHash] lod=3 tiles=208 density=0xce02d09814028b15 mesh=0xe4c2addc4c54c439 payload=0x7e25d9eee6469c5a topology=0xe603e61c9b9bb733
|
||||||
|
[PipelineLODHash] lod=4 tiles=240 density=0x0be8798407724eaf mesh=0x131c303cbbe51434 payload=0x2a90ecf0a2411ca0 topology=0xe3e054a371152e08
|
||||||
|
[PipelineLODHash] lod=5 tiles=144 density=0xf20897ad272103a9 mesh=0x13d00b6253ae1f72 payload=0x0db3880693a962fa topology=0x462bd87a80f15e89
|
||||||
|
[PipelineLODHash] lod=6 tiles=96 density=0xc040fcac457d6313 mesh=0x1ed132eeb718fb62 payload=0xcd0214e35577c6e3 topology=0x2c56c69e6fdcfd64
|
||||||
|
[PipelineLODHash] lod=7 tiles=96 density=0xc2d419446355fdb8 mesh=0x4ea44452574fc84b payload=0xfd9779bedfc9ae0f topology=0x8948546e770c9b72
|
||||||
|
[PipelineLODHash] lod=8 tiles=306 density=0x62db31696f2da831 mesh=0xd373f9e9481532fe payload=0x540a60526e0e3e46 topology=0xc725cb901e6dc335
|
||||||
|
[PipelineExactQueryHash] probes=9 density=0xc949c4df2ce6f9d9 unclamped=0xc949c4df2ce6f9d9 detail=0x114c501e2d830d94 collision=0xe6011d3a248d4849 path=direct-Core-queries
|
||||||
@@ -125,11 +125,11 @@ RECIPE_RE = re.compile(
|
|||||||
|
|
||||||
def owner_hash_baseline(recipe: str, style: int) -> tuple[Path, str]:
|
def owner_hash_baseline(recipe: str, style: int) -> tuple[Path, str]:
|
||||||
if recipe == "surface-world" and style == 1:
|
if recipe == "surface-world" and style == 1:
|
||||||
return (BASELINES / "owner-hash/canonical/owner-hash-surface-world-style1-v236.baseline",
|
return (BASELINES / "owner-hash/canonical/owner-hash-surface-world-style1-v237.baseline",
|
||||||
"L135-v236-cave-mouth-joints")
|
"L139-v237-vertical-shafts-core-planner")
|
||||||
if recipe == "floating-islands" and style == 2:
|
if recipe == "floating-islands" and style == 2:
|
||||||
return (BASELINES / "owner-hash/canonical/owner-hash-floating-islands-style2-v236.baseline",
|
return (BASELINES / "owner-hash/canonical/owner-hash-floating-islands-style2-v237.baseline",
|
||||||
"L135-v236-cave-mouth-joints")
|
"L139-v237-vertical-shafts-core-planner")
|
||||||
raise VerificationFailure(f"no pinned owner hash baseline for {recipe} style {style}")
|
raise VerificationFailure(f"no pinned owner hash baseline for {recipe} style {style}")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||