fix(shafts): the primordial law holds -- 0 trusted negatives across 16 seeds
Closes the VerticalShafts bug. Two remaining failures were investigated
and neither was a broken world.
PART A -- my measurement window was binding, not the world.
The window was a square centred on (0,0) with a radius, while mouths sit
up to ~200 voxels out in arbitrary directions, so a box holding both was
mostly empty space paid for at full price. Seed 6 at step 1 needed
16,515,072 cells and blew the cap; fitted to the two mouths it needs
6,553,344 and measures fine.
seed 6 step 2 NOT_CONNECTED -> CONNECTED at doubled margin
seed 6 step 1 NOT_CONNECTED -> CONNECTED at doubled margin
seed 14 step 2 CONNECTED
seed 14 step 1 CONNECTED
A fitted box can cut off a route that detours outside it, so every
negative is re-run at doubled margin. 5 of 16 were binding negatives and
all 5 flipped. TRUSTED NEGATIVES: 0. There is no evidence of a single
unescapable strate in 16 seeds.
PART B -- the drainage tree has no orphans, and a third cause existed.
0 / 10,372 unreachable shaft paths (0% orphan rate)
16 strict local minima, all caught by the direct-spine fallback
neighbour fallback fired 0 times; emergency fallback 0
91.8% of non-largest components are smaller than one shaft volume --
they are roughness bubbles, not geometry
So a mouth could land in a noise bubble that is open but unreachable --
worse than useless, exactly the trap the law forbids.
VF_SuggestLandingPoint now returns the selected shaft AXIS, which is on
the drainage tree by construction.
⚠️ Codex found a third cause neither of us listed: tree capsules could
cross ledge bands, so a guaranteed connector could be blocked by a ledge.
Connector Z is now drawn from a deterministic ledge-free interval,
mirrored in the op stack.
⚠️ And an 8th error in my spec: "connect to any neighbour shaft" as a
fallback can create CYCLES -- a loop of shafts that drains nowhere and is
disconnected from the spine. The fallback must preserve strictly
decreasing origin-distance. Implemented that way.
per-archetype law 6/6 · box-verdict violations 0 · stack equivalence
bit-identical over 20,000 samples · sweep 45.3 -> 20.4 s
cache 7.970 us rebuild, 0.073 us hot call (225-cell rolls on rebuild only)
Build clean, 21 succeeded / 0 failed, verified by me in VoxelM.log.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJiTcTSDNkKBtnNdiBtdaU
This commit is contained in:
1 parent
9d2324879d
commit
4f11ce81a4
10 files changed
+1813
-174
No files matched your search
+28
-19
@@ -49,27 +49,33 @@ what the world IS, and both paths compute it.
|
||||
`UVoxelGenerator::OriginSpineRadius` ← `VoxelSettings::OriginSpineRadius`. Called before
|
||||
every `ApplyBoundarySeal`.
|
||||
- `VerticalShafts` treats that structural column as a **connector endpoint**, not as a second
|
||||
density primitive. A thread-local rebuild collects a 7×7 cell window once, emits tree links only
|
||||
for the inner 3×3 cells, and resolves each parent from its complete 5×5 candidate window. Each
|
||||
density primitive. A thread-local rebuild collects a direct-indexed geometric halo once (with
|
||||
the stock settings: 9×9 tree-emission cells and a 15×15 roll), emits tree links only for the
|
||||
inner 3×3 cells, and resolves each parent from its complete ±2-cell candidate window. Each
|
||||
shaft chooses the nearest shaft whose jittered distance to (0,0) is strictly smaller; ties use
|
||||
the lowest cell index. A local minimum links directly to the spine. The wider collection is
|
||||
therefore fully contained for every emitted shaft and the parent is independent of chunk or
|
||||
evaluation-cell boundaries. The existing probabilistic `VoxelHash::Pair` links and
|
||||
the lowest cell index. If that window has no parent, the deterministic ±3 lower-origin neighbour
|
||||
fallback fires; a window touching the origin uses the spine directly, with a final direct-spine
|
||||
fallback for an unusually empty finite halo. The wider collection is therefore fully contained
|
||||
for every emitted shaft and the parent is independent of chunk or evaluation-cell boundaries.
|
||||
Every parent edge strictly decreases origin distance or terminates at the spine, which proves
|
||||
that no shaft is orphaned or cyclic. The existing probabilistic `VoxelHash::Pair` links and
|
||||
`Spacing*1.6` cutoff remain as texture and loops; they are additive, not the connectivity law.
|
||||
Tree links use a radius strictly above the proven `sup|FBM|=1.5` roughness envelope
|
||||
(`max(ConnectorRadius, SurfaceRoughness*VOXEL_NOISE_SCALE*1.5 + 1)`).
|
||||
`BuildVerticalShaftStack` mirrors the same tree, random links, windows, hashes, and radius;
|
||||
`VF_ApplyOriginSpine` remains the sole owner of the vertical column itself.
|
||||
(`max(ConnectorRadius, SurfaceRoughness*VOXEL_NOISE_SCALE*1.5 + 1)`) and choose a shared
|
||||
ledge-free Z interval. `BuildVerticalShaftStack` mirrors the same tree, random links, windows,
|
||||
hashes, radius, and connector-Z rule; `VF_ApplyOriginSpine` remains the sole owner of the
|
||||
vertical column itself.
|
||||
- Descent is **player-dug** through the thin seals at (0,0). The single auto-opened
|
||||
connection is the **surface entry shaft** at (0,0) through the top of strate 0
|
||||
(`GeneratePassages`, `bOpenSurfaceEntry`).
|
||||
- **Hybrid extras:** auto-carved *shortcut* passages per boundary, placed away from (0,0).
|
||||
Now fully **per-strate** — see §8.8 (the upper strate's `PassageConfig` drives count/style/shape).
|
||||
|
||||
The tree is a construction guarantee for the shaft field: every seeded shaft has one inward
|
||||
edge, and every chain strictly decreases distance to the origin until it reaches a local minimum
|
||||
that links to the spine. It does not alter passage placement. The acceptance measurement remains
|
||||
the actual arrival→departure flood-fill at the independently placed passage endpoints.
|
||||
The tree is a construction guarantee for the shaft field: every seeded shaft has one inward edge,
|
||||
and every chain strictly decreases distance to the origin until it reaches a local minimum that
|
||||
links to the spine. The fallback resolver preserves that proof, so no shaft is an orphan or part
|
||||
of a cycle. It does not alter passage placement. The acceptance measurement remains the actual
|
||||
arrival→departure flood-fill at the independently placed passage endpoints.
|
||||
|
||||
### 8.2a XY edge seal (bounded-world invariant)
|
||||
`UVoxelSettings::WorldRadiusVoxels` and `EdgeSealThickness` define the bounded world in actor-space
|
||||
@@ -342,8 +348,9 @@ landing point through the pure free function `VF_SuggestLandingPoint` (`VoxelCav
|
||||
TunnelNetwork and Underwater keep the nearest hash-room vertical placement at the requested XY, with
|
||||
`MakeStrateSeed(world-seed, strate-index)` matching the room graph's existing identity; FlatPlain and
|
||||
CrystalChamber recompute their slab void band and reject column-overlap points. Maze snaps to the
|
||||
nearest roughness-safe horizontal lattice corridor within one cell, VerticalShafts to a roughness-safe
|
||||
shaft within one shaft spacing, and FloatingIslands to a validated blob top within one island spacing.
|
||||
nearest roughness-safe horizontal lattice corridor within one cell, VerticalShafts to the exact
|
||||
axis of a roughness-safe shaft on the drainage tree within one shaft spacing, and FloatingIslands
|
||||
to a validated blob top within one island spacing.
|
||||
Those three return false when no footing exists inside that explicit lateral budget. GeneratePassages
|
||||
interpolates the snapped XY through the control-point chain and recomputes its conservative bound;
|
||||
SurfaceWorld remains deliberately unanswerable because production terrain can be selected through
|
||||
@@ -373,11 +380,13 @@ driven by `EditorBrush*` props.
|
||||
sampling must not thrash the (expensive) rebuild.
|
||||
- **VerticalShafts field cache**: shaft rolls and connector decisions, including the structural
|
||||
tree and structural-spine connector endpoint, are rebuilt only when the thread-local centre cell
|
||||
or a geometry-affecting parameter changes. Each rebuild rolls 49 cells (7×7); it stores/emits
|
||||
only the inner 3×3 shafts and their capsules for the per-voxel loop. The complete 5×5 parent
|
||||
windows are contained by that 7×7 collection, so no parent decision depends on the evaluation
|
||||
cell. No cell or pair hash may move into the per-voxel loop; the operator-stack source follows
|
||||
the same cache contract.
|
||||
or a geometry-affecting parameter changes. Each rebuild rolls a wider direct-indexed geometric
|
||||
halo (stock settings: 9×9 tree-emission cells / 15×15 total roll); it stores/emits only the
|
||||
inner 3×3 shafts and their capsules for the per-voxel loop, while spatial culling selects cached
|
||||
connectors. The complete ±2 parent and ±3 fallback windows plus connector reach are contained
|
||||
by that halo, so no parent decision depends on the evaluation cell. No cell or pair hash, widened
|
||||
neighbourhood scan, or tree construction may move into the per-voxel loop; the operator-stack
|
||||
source follows the same cache contract.
|
||||
- **Per-chunk param cache** in `GetDensityAt`: GenType + param struct + disturbance cached
|
||||
thread-locally by `(DensityCacheOwnerId, ChunkCoord, LayoutVersion)`; the process-unique owner ID
|
||||
prevents cross-world reuse while adding only one `uint64` compare per voxel. Don't remove the owner
|
||||
|
||||
Reference in new issue
Block a user