feat(world): the XY edge seal -- the fourth structural invariant
VF_ApplyBoundarySeal was purely VERTICAL: it sealed each strate's ceiling
and floor and there was no horizontal equivalent, because until the depths
were bounded the world had no sides. The GDD now says "bounded, but
gigantic", so a player who walked far enough reached unterminated
generation running off into nothing.
Built as a FORCING op, appended automatically and not author-omittable, so
an invented strate cannot forget its own edge. Because a forcing op proves
AllSolid in its band, the outer shell also becomes free to skip in tile
classification -- correctness and a perf win from one change.
order spine -> vertical seal -> passage carving -> XY EDGE SEAL
-> disturbances -> diff
A near-rim passage may pierce a vertical seal; the XY seal runs after and
wins. A passage must never punch a hole through the world's edge.
ClassifyBox/ForcedMarginOverBox proved via the closest AABB radius with a
one-voxel margin: 8 boxes proved, 1 tile proved, 600 voxels brute-forced,
0 violations. FXYEdgeSealOp is IsXYPure. Max rim discontinuity .187 against
a 1.0 threshold -- a ramp, not a cylinder wall.
⚠️ WorldRadiusVoxels DEFAULTS TO 0, and that is deliberate. Codex flagged
that DA_Settings and BP_VoxelSettings predate the property and so do not
serialize it -- they inherit the C++ default silently. A default of 8192
would have walled in every existing world on next editor open with nothing
in the asset to explain it. 0 is a bitwise no-op (45 interior comparisons
against the unbounded path prove it). The GDD's intended ~8192 is a value
to set in the asset, deliberately, once. Documented at the declaration.
Codex found four spec errors, three of them mine: VF_ApplyBoundarySeal
lives in VoxelDensityPrimitives.h not VoxelCaveMorphology; the asset
default trap above; and my "density increases through the band" wording
contradicts the MC sign convention (solid is more NEGATIVE) -- the test
measures increasing solidness.
Build clean, 21 succeeded / 0 failed, run and verified by me.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJiTcTSDNkKBtnNdiBtdaU
This commit is contained in:
1 parent
44938f3f98
commit
9d2324879d
19 files changed
+1006
-126
No files matched your search
+40
-2
@@ -27,7 +27,10 @@ by `GeneratorType`) and its own density function in `VoxelGenerator.cpp`, dispat
|
||||
| Underwater | `FStrateGenerationParams` + water | (reuses `GetDensityWithParams`) | tunnel rock + high water table |
|
||||
|
||||
All density fns share the convention: internal **positive=solid**, apply origin spine →
|
||||
boundary seal → inter-strate passages, then `return -Density` (MC: negative=solid).
|
||||
vertical boundary seal → inter-strate passages → XY edge seal, then `return -Density` (MC:
|
||||
negative=solid). The final MC-facing edge pass in `GetDensityAt` is repeated after disturbances so
|
||||
the global rim cannot be reopened by a post-process; the diff layer remains the explicit player
|
||||
override.
|
||||
StrateManager provides params per chunk via `GetMaze/Surface/VerticalShaft/FloatingIslandParamsForChunk`
|
||||
(macro `VF_ARCHETYPE_PARAMS_GETTER`) — no cross-boundary blend (Hard transitions between archetypes).
|
||||
On top of the archetype, an optional **biome** layer (§8.14) modulates terrain & content WITHIN a
|
||||
@@ -68,10 +71,37 @@ edge, and every chain strictly decreases distance to the origin until it reaches
|
||||
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.2a XY edge seal (bounded-world invariant)
|
||||
`UVoxelSettings::WorldRadiusVoxels` and `EdgeSealThickness` define the bounded world in actor-space
|
||||
XY around `(0,0)`. The default radius is 8192 voxels (~2.05 km at 25 cm/voxel) and the default
|
||||
solid ramp is 64 voxels. A radius of **0 is a true no-op**: the hot path returns before doing any
|
||||
arithmetic, so the legacy unbounded field is unchanged. From `R - Thickness` to `R`,
|
||||
`VF_ApplyXYEdgeSeal` raises the internal positive-solid density with the same `SmoothStep01` ramp
|
||||
idiom as the vertical seal; at and beyond `R` it forces positive `BaseDensity`. The generator
|
||||
negates once for marching cubes, so the externally visible sealed density is negative (solid).
|
||||
|
||||
This is the fourth structural post and is appended automatically by
|
||||
`FVoxelOpStack::AppendStructuralPost`, after the spine, vertical seal, and passage carve. Passage
|
||||
carving deliberately remains before the edge seal: passages may pierce a vertical strate seal, but
|
||||
a passage whose mouth or body reaches the XY rim is overwritten by the edge force and cannot open a
|
||||
hole through the world boundary. The legacy density functions apply the same post in the same
|
||||
order. `GetDensityAt` reapplies the MC-facing form after disturbances as a backstop for out-of-layout
|
||||
air and any disturbance carve; player diff edits still run last by design and remain authoritative.
|
||||
|
||||
`FXYEdgeSealOp` is `IsXYPure() == true`, uses only a squared radial early-out in the common interior
|
||||
case, and returns `AllSolid` from `ClassifyBox` only when the closest point of the whole box is at
|
||||
least one voxel into the ramp (or beyond `R`). Its `ForcedMarginOverBox` reports the actual positive
|
||||
lower bound, conservatively reduced after the one-voxel proof margin; it never treats the
|
||||
mathematical inner edge as solid. The same proof is used by the global `ClassifyTile` shell guard,
|
||||
so tiles wholly in the proven outer band can skip T1.d generation/collision work. A box touching the
|
||||
band but not meeting that proof remains `Mixed`.
|
||||
|
||||
### 8.3 Disturbance layer (the "wow" post-process)
|
||||
`FStrateDisturbanceParams` (on the definition, all archetypes). `ApplyDisturbances`
|
||||
(VoxelGenerator.cpp static, **MC convention**) runs in `GetDensityAt` after dispatch:
|
||||
chasms (carve air), bridges (solid spans), ridges (solid blades). Stays inside seal bands.
|
||||
chasms (carve air), bridges (solid spans), ridges (solid blades). The XY edge seal is reapplied after
|
||||
this layer, so disturbances cannot reopen the outer shell; the layer otherwise stays inside the
|
||||
vertical seal bands.
|
||||
Provided per chunk by `StrateManager::GetDisturbanceParamsForChunk`.
|
||||
|
||||
### 8.4 Cross-chunk determinism (the seam-prevention invariant)
|
||||
@@ -352,6 +382,11 @@ driven by `EditorBrush*` props.
|
||||
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
|
||||
or layout key, and don't move the fetch/blend back to per-voxel.
|
||||
- **XY edge seal hot path** (§8.2a): `WorldRadiusVoxels == 0` returns immediately; otherwise the
|
||||
common interior case is a squared-distance test with no square root. The forcing op receives the
|
||||
two global settings through `FVoxelOpContext` once per chunk, while `ClassifyBox`/`ClassifyTile`
|
||||
share the radial proof. This adds no cache, state, RNG, or clipmap stream, and does not touch the
|
||||
density grid's two-pass MC loop or any existing `thread_local` cache.
|
||||
- **Biome cache** (`ResolveBiomeSampleAt`/`FChunkBiomeCache`, §8.14): validity is a world-XY BOX +
|
||||
ChunkZ + Seed, NOT a chunk key — same reason as the SDF cache. The cell classification is
|
||||
noise-heavy; a chunk-key would thrash it on gradient-normal / +X/+Y boundary samples. Keep
|
||||
@@ -542,6 +577,9 @@ driven by `EditorBrush*` props.
|
||||
**Conservative guards** (anything that can carve/fill): player mods (`HasAnyModInChunkRange`) ⇒
|
||||
Mixed; passages (`AnyPassageNearBox`, bounding spheres + carve blend pad) and the (0,0) spine
|
||||
(circle/box XY) kill AllSolid; disturbance chasms kill AllSolid, bridges/ridges kill AllAir.
|
||||
The global XY edge proof runs first and returns AllSolid for a box entirely in the forced band;
|
||||
the same force is the final generated post after disturbances, so a near-rim passage cannot
|
||||
invalidate that verdict. Every other uncertain case remains Mixed.
|
||||
A false `Mixed` only costs CPU; the code must NEVER emit a false AllSolid/AllAir (that's a hole).
|
||||
Capture tiles (`bWantCapture`, density-volume shadow window) always generate — the volume wants the
|
||||
grid even for uniform cells. A sparse ~5×5 column pre-pass exits Mixed fast on surface-crossing
|
||||
|
||||
Reference in new issue
Block a user