feat(shafts): a drainage tree -- the field goes from 22% to 97% connected
The plateau was the tell. Roughness changed the component COUNT (it
manufactures noise pockets) but the giant component sat at ~0.22 in every
variant, and widening connectors past the proven roughness bound barely
moved it. That is a random graph below its percolation threshold: 0.6
sites, 0.35 bonds, and a 1.6-cell pair cutoff that drops many neighbour
pairs once jitter spreads them 1.7 (orthogonal) to 2.4 (diagonal) cells
apart.
So the lever was topology. Every shaft now emits a deterministic connector
to the nearest shaft STRICTLY closer to the origin, so chains drain inward
and terminate at the spine. The probabilistic connectors stay as texture
and loops -- the tree guarantees you can get out, the noise keeps it from
reading as a diagram.
Deliberately NOT tuned: raising CrossConnectChance/ShaftDensity/the cutoff
toward the percolation threshold buys a world connected at one seed and
shattered at the next, which is exactly the 1-in-16 behaviour we had. A
phase transition is not a knob.
seed 1337 components 30 -> 17, largest share .220 -> .975
16 seeds spine is the largest component in 16/16 (was 8/16)
largest share .58 - .9999
Seam safety, because a parent computed from a 3x3 window would differ
between chunks and the connector would exist in one and not the other:
collect over 7x7, emit only for the inner 3x3, whose 5x5 candidate windows
are then fully contained. 154 seam probes per path, 0 mismatches.
Box verdicts re-checked against the new air: 27 proved, 33 mixed,
0 violations. Stack mirror bit-identical over 20,000 samples.
Perf: 1.634 us forced rebuild, 0.100 us hot call -- the 49 rolls happen
only on cache rebuild, never per voxel.
⚠️ ACCEPTANCE STILL FAILS: arrival->departure 4/16 (was 1/16). Reported,
not tuned. With 97% of the air in one component, two mouths that both land
IN the field would essentially always connect -- so the remaining failure
is placement, not topology. Next.
Build clean, 20 succeeded / 0 warnings / 0 failed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bp7FxoQcSoiuhxjuLr6aBM
This commit is contained in:
1 parent
58e61cdefa
commit
7a20eeed0d
6 files changed
+608
-91
No files matched your search
+22
-15
@@ -46,23 +46,27 @@ 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: while rebuilding its memoised 3×3 shaft neighbourhood, an origin-adjacent
|
||||
query adds `(0,0,OriginSpineRadius)` to the local shaft set for pair generation only. The
|
||||
existing `VoxelHash::Pair` gate and `Spacing*1.6` neighbour cutoff remain in force, so the link
|
||||
is a pure function of the shaft cell and seed and is independent of evaluation order. Spine
|
||||
links use a radius above the proven `sup|FBM|=1.5` roughness envelope (`max(ConnectorRadius,
|
||||
SurfaceRoughness*VOXEL_NOISE_SCALE*1.5 + 1)`); the ordinary connector radius and ordinary shaft
|
||||
roughness are unchanged. `BuildVerticalShaftStack` receives the same runtime spine radius and
|
||||
mirrors this source rule; `VF_ApplyOriginSpine` remains the sole owner of the column itself.
|
||||
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
|
||||
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
|
||||
`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.
|
||||
- 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).
|
||||
|
||||
This connector is local topology, not a promise that independently placed shortcut mouths are
|
||||
globally connected. The acceptance measurement for `VerticalShafts` remains the actual
|
||||
arrival→departure flood-fill at the 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. It does not alter passage placement. The acceptance measurement remains
|
||||
the actual arrival→departure flood-fill at the independently placed passage endpoints.
|
||||
|
||||
### 8.3 Disturbance layer (the "wow" post-process)
|
||||
`FStrateDisturbanceParams` (on the definition, all archetypes). `ApplyDisturbances`
|
||||
@@ -337,10 +341,13 @@ driven by `EditorBrush*` props.
|
||||
no loads AND no LOD mismatches outstanding.
|
||||
- **SDF cache** (`GetDensityWithParams`): search-BOX validity, not chunk-key — gradient ±1
|
||||
sampling must not thrash the (expensive) rebuild.
|
||||
- **VerticalShafts field cache**: shaft rolls and pair decisions, including the structural-spine
|
||||
connector endpoint, are rebuilt only when the thread-local centre cell or a geometry-affecting
|
||||
parameter changes. They must not be re-hashed in the per-voxel loop; the operator-stack source
|
||||
follows the same cache contract.
|
||||
- **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.
|
||||
- **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