Files
VoxelForge/Standalone/DESIGN_STRATA_UNITS_AND_LAYERS.md
T
Fr0zkaandClaude Opus 5.5 6f9c7bd652 docs(assembler): round L20 -- design for director / generators / mesher, layout in metres
Standalone/DESIGN_STRATA_UNITS_AND_LAYERS.md: maps today's code to the three layers, ranks 9
violations (top: no single physical stratum-bounds/interface record; SurfaceWorld 8 m vs
FloatingIslands 4 m per chunk), defines the director-resolved generator request/result contract,
moves seals/caps/voids/interfaces into shared composition, and proposes an explicit LOD-stable
interface surface for the L19 ledges. 6-step migration plan with "worlds don't move" checks;
owner decisions listed. No code or world change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX
2026-10-20 08:53:03 +02:00

27 KiB
Raw Blame History

VoxelForge strata, units, and generation layers

Design round L20 — 2026-10-20

Summary

Author strate layout in metres and give each strate a voxel size. Keep Core's internal tile at 32³ voxels; its physical width follows that strate's voxel size and the tile's LOD. Use one director to resolve the season, layout, seed, generator recipe, resolution, and interfaces before workers start. Generators then evaluate an immutable, common field contract. A shared composition layer applies strata boundaries, gaps, seals, edits, caches, and proofs. The mesher consumes that composed field and generic surface hints; it should not branch on SurfaceWorld, FloatingIslands, or feature type IDs.

The code already has parts of this shape, but not the boundaries between them. In particular, FStandaloneShapeWorld is both a dispatch layer and a collection of field adapters, while World::generateTile contains generator-aware far-surface and feature refinement. The Unreal adapter currently translates two different meanings of “chunk” into Core coordinates.

The L19 ledges are not explained by a different density recipe per LOD. The ring audit found upward-facing terrain from the same lower SurfaceWorld field at different extracted heights on adjacent LODs. At the default 25 cm Core voxel size, LOD1, LOD2, and LOD3 sample at 0.5 m, 1 m, and 2 m. The maximum LOD1→2 ring difference is 1.49862 m; LOD2→3 reaches 7.73318 m, with 8 no-hit samples among 308. An explicit shared surface contract can give all LODs common boundary geometry and a controlled transition band. A planar ceiling alone will not fix these ground ledges.

One conversion issue needs an explicit preservation target. The current Core path maps one SurfaceWorld slot chunk to 32 Core voxels (8 m at 25 cm), but one FloatingIslands slot chunk to 32 owner voxels at 12.5 cm (4 m). Converting each current interpretation independently into metres can preserve each field's current positions, but it does not make shared chunk indices one common physical boundary. The converter must record the actual per-strate bounds and any resulting gap or overlap before choosing a unified interface. Do not promise a no-movement migration from a single global “metres per chunk” constant.

Metres are not a runtime cost concern. Resolution is. Halving voxel spacing in all three axes means about eight times as many density samples for the same physical volume, plus more tiles, queue work, and cache entries because each internal tile remains 32³ voxels. With 25 cm as the default for all strates, the proposed starting resolution matches today's Core sampling resolution.

1. Current code mapped to the three layers

There is no single class called “director” today. The director work is split between Unreal's season/layout code, the Core adapter, and Core's tile scheduler.

Intended layer Current location and symbols Current responsibility and boundary issue
Director / orchestrator VoxelStrateComposer.cpp; VoxelSeasonManifest.cpp; VoxelStrateManager.cpp (Initialize, GetGeneratorTypeForChunk, GetGenerationParams); VoxelWorld.cpp (BuildDesiredTiles, tile scheduling, epochs); VoxelForgeCoreAdapter.cpp (BuildSurfaceStratumPlan, BuildFloatingIslandStratumPlan, AttachValidatedWorldAssembly) Composes season params and stacked slots, resolves generator kind and seed, and serializes the Core snapshot. WorldAssemblyManifest, WorldAssemblyPlanV1, and their FromJson/finalize/validate functions in WorldAssemblerInternal.h are the current plan/manifest format. Resolution and bounds are not fully resolved here: Core re-derives them using generator-specific conversions. TopWorldZ, BottomWorldZ, HeightInChunks, and InterStrateGapChunks remain chunk/voxel-coordinate based in owner-facing data.
Generators SurfaceWorld: Heightfield2D in VoxelForgeCore.h and WorldAssemblerInternal.h; plan construction in VoxelForgeCoreAdapter.cpp. FloatingIslands: FloatingIslandFieldV1 in VoxelForgeCore.h and FloatingIslandsInternal.h; plan construction in the adapter. Legacy: UVoxelGenerator::GetDensityAt in VoxelGenerator.cpp, VoxelDensityOpStack.cpp, and VoxelCaveMorphology.cpp. SurfaceWorld and FloatingIslands have distinct plan and evaluator APIs. Legacy generation still asks the manager for the current chunk's params and generator type. In Core, the “generator” boundary is obscured by FStandaloneShapeWorld, which selects native fields or falls back to the copied owner generator.
Shared field / composition library Noise and fixed-point evaluation: WorldAssemblerInternal.h, FloatingIslandsInternal.h. CPU/HLSL far-field math: VoxelForgeFieldShared.h. FP contract: CoreNamespacePreamble.h. Feature culling and caches: BuildWorldAssemblyFeatureTileListV1, ScopedWorldAssemblyFeatureQueriesV1, SharedColumnCache.h, ThreadLocalStorage.h. Field bounds/proofs: Heightfield2D::interval, FloatingIslands bounds, FStandaloneShapeWorld::ClassifyBoxDetailed and BuildShapeDependencyProof. Density seals: VoxelDensityPrimitives.h. Useful shared pieces exist, but only for subsets of recipes. Noise is not one library used by every legacy and native generator. Bounds, density, and output identity still use subvoxels of a Core voxel. The canonical stratum bounds descriptor FResolvedStratumBounds is private to ShapeWorldInternal.h, not part of the plan contract. AppendStratumBoundaryCeilings is under far-shell/meshing code, even though the boundary meaning is shared composition policy.
Mesher UVoxelMarchingCubesMesher::GenerateMesh in VoxelMarchingCubesMesher.cpp; dispatch and mesh assembly in World::generateTile in VoxelForgeCore.cpp; FarShellsInternal.h; hybrid sharp support in VoxelHybridSharpMesher.h and VoxelMarchingCubesMesher.cpp. MC, skirts, far shells, connector repair panels, LOD refinement, and cap repair are spread across the production mesher and Core tile builder. World::generateTile scans recipe feature type IDs, refines type-4 shelves only at LOD0–2, selects a native SurfaceWorld horizon, and invokes separate ground/cap repair. FStandaloneShapeWorld::BuildFarFieldTileInput constructs FloatingIslands connector panels. These are recipe-aware mesh paths.

What crosses a boundary today that should not

  • FStandaloneShapeWorld reads UVoxelStrateManager, FStrateSlot, WorldAssemblyManifest, legacy generator params, and generator type. It selects a recipe, evaluates a field, resolves its bounds, applies a seal, and builds GPU far-field input. That combines director, generator adapter, shared field, and renderer responsibilities.
  • World::generateTile knows WorldAssemblyPlanV1, SurfaceWorld heightfields, feature numeric IDs, and FloatingIslands presence. The generic mesher should receive capabilities and surface data, not know those recipe identities.
  • The adapter exports one layout but encodes physical positions in WorldAssemblySubvoxelsPerVoxel. That makes a physical length's fixed integer depend on the selected Core voxel size.
  • Core WorldAssemblyStratumPlanV1 carries topChunkZ/bottomChunkZ; validation in WorldAssemblerInternal.h multiplies those values by 32. FloatingIslands additionally stores its own owner-to-Core size ratio and metric-equivalent bounds in Core-voxel units.
  • The Core boundary path calls GetResolvedStratumBounds differently for SurfaceWorld and FloatingIslands. The director should resolve a single physical boundary record once.
  • Legacy density still reaches into manager-owned params and operations. It must become a generator adapter that consumes the same resolved request as a native recipe, or remain explicitly marked as a compatibility route until it can be ported.

2. Violations, ranked by risk

Rank 1 is the highest risk to correct physical layout and cross-LOD output.

Rank Risk Violation and evidence Why it matters
1 Critical No single physical bounds/interface contract. FResolvedStratumBounds in ShapeWorldInternal.h derives SurfaceWorld bounds from Slot.*ChunkZ × 32, but derives native FloatingIslands bounds from layout*ZSubvoxels and an owner/Core scale ratio. GetResolvedStratumBounds is then reused by field classification, far-field protection, and far ceiling generation. A shared logical slot boundary can map to different metre positions depending on generator. This can make an apparent seam a gap, overlap, or misplaced terrain surface before the mesher samples it. It also makes “preserve the old heights” ambiguous unless the old per-field physical bounds are recorded.
2 Critical LOD extraction has no required shared surface representation. World::generateTile uses normal MC sampling for LOD1–3; native SurfaceWorld ground refinement is only invoked at LOD4+. L19's strict ring probe excludes skirts and masked triangles and measured 149.862 cm max at LOD1→2 and 773.318 cm max at LOD2→3. A coordinate-stable density function does not guarantee equal extracted surfaces when sample grids differ. Thin seals can be skipped, and a heightfield root can shift with coarse XY/Z samples. This is the measured ledge failure.
3 High Chunk, owner-voxel, Core-voxel, and fixed-point units are mixed. WorldAssemblyFixed in the Unreal adapter serializes lengths as 1/256 Core voxel. SurfaceWorld plan bounds use Slot.BottomChunkZ * 32 and (TopChunkZ-BottomChunkZ+1)*32. FloatingIslands uses 32 × ownerToCoreScaleNumerator / denominator. ShapeWorldInternal.h reverses Core Z to legacy chunk coordinates with / GetCoreVoxelsPerOwnerChunk(). A parameter's physical interpretation depends on generator and runtime Core size. /32, *32, and scale-ratio logic are reasonable only in a single boundary adapter or for the internal fixed tile; they are incorrect as the authored layout contract.
4 High The Unreal adapter and manager both resolve the same layout. VoxelStrateManager.cpp creates chunk slots and per-generator runtime params; VoxelForgeCoreAdapter.cpp re-reads those params and builds distinct native plans; ShapeWorldInternal.h maps Core samples back through the manager again. WorldAssemblyStratumPlanV1::topChunkZ/bottomChunkZ are checked against the manager in AttachValidatedWorldAssembly. The runtime can validate matching integer slots without validating that the resulting physical bounds mean the same thing for both generators. The future adapter should serialize already-resolved metres and per-strate resolution.
5 High Generators read director state. Legacy UVoxelGenerator::GetDensityAt asks the manager for type/params and builds/refetches operator stacks. Core native lookup also asks the manager which generator applies at a Core sample. The generator cannot be a pure evaluator of a resolved recipe. It is hard to cache, audit, or replay independently, and unit conversion is repeated in worker code.
6 High Boundary behavior is split between recipe and shared paths. FloatingIslands geometry v3 special-cases top seal evaluation in NativeFloatingIslandDensityAt; older geometry versions retain two-sided seals. VF_ApplyBoundarySeal/VF_ApplyUpperBoundarySeal provide shared density math, while AppendStratumBoundaryCeilings separately patches missing mesh coverage. SurfaceWorld also owns a heightfield cap. Density closure, boundary ownership, and visible cap geometry can disagree. A shared operator alone does not guarantee a shared, LOD-stable surface. Preserve versioned historical fields during migration, then route new plans through one contract.
7 Medium-high Mesher contains recipe policy. World::generateTile has SurfaceWorld native-horizon selection, a SurfaceWorld-only far skip condition, type-2 sharp boxes, type-4 shelf refinement, separate native ground/cap refinement, and a farHorizonVersion identity. ShapeWorldInternal.h creates connector panel repairs from FloatingIslands. A new recipe requires mesher edits and generator details leak into extraction. The mesher should consume explicit surfaces, generic feature constraints, and a requested error/refinement hint.
8 Medium-high Legacy paths do not share the new generator format. UVoxelGenerator, operator stacks, morphology caches, Core's native Heightfield2D, and FloatingIslandFieldV1 have separate recipe/parameter routes. “One generator format” is not yet true. A migration wrapper is needed so behavior can be ported in small, hash-checked steps instead of reimplementing all legacy archetypes at once.
9 Medium Shared helpers and numeric policies are partial. Core's FP preamble/self-test applies to Core translation units; fixed-point noise is implemented in assembler code, island noise is in a separate implementation, and far CPU/HLSL math has its own shared header. Feature bounds/caches are more reusable than the field interfaces. Keep the tested Core determinism contract, but promote the shared numeric/noise primitives only where their exact semantics can be versioned. “Shared” code is not automatically cross-platform identical if separate float evaluators remain.

Unit measurements relevant to migration

CHUNK_SIZE is 32 and VOXEL_SIZE is 12.5 cm, so the owner's legacy chunk is 4 m (VoxelTypes.h, Round5). The current default Core voxel is 25 cm. In the current native Core routes:

Route Current conversion Physical chunk width at default Evidence
SurfaceWorld 32 Core voxels per slot chunk 8 m BuildSurfaceStratumPlan uses chunk bounds directly in Core voxel units; L18 records the preserved topChunkZ × 32 mapping.
FloatingIslands 32 × 125000/250000 = 16 Core voxels per owner chunk 4 m BuildFloatingIslandStratumPlan serializes the ratio and applies it to layout bounds; GetCoreVoxelsPerOwnerChunk uses the same ratio.

This confirms the claimed factor of two. It does not prove that this conversion alone caused the L19 ring delta. L19 isolated the observed step to coarse extraction of lower SurfaceWorld terrain, and the reported LOD4+ horizon refiner cannot affect those LOD1→2 and LOD2→3 values.

3. Generator contract and one-time interface handling

What the director resolves before generation

The director should publish an immutable ResolvedWorld (exact type/schema is an implementation choice) containing:

  • a stable world/season identity and seed;
  • an ordered list of strates with a stable ID, generator kind and recipe version;
  • each strate's physical minimum and maximum bounds in metres, including height as their difference;
  • a voxel size in metres per strate (25 cm default for every strate);
  • route points, island anchors/connectors, and all top/bottom/ceiling positions in the same physical coordinate space;
  • one resolved interface record per adjacent pair, including any explicit void, bedrock gap, sealed contact, upper cap, passage, or open boundary;
  • any season-level values that affect world output, such as routes, islands, water datum, or biome selection, already attached to their owning strate or interface;
  • a canonical identity over the sorted resolved layout, seed, resolution map, recipes, and interface records.

Do not make generators ask the director for the active FStrateSlot, chunk number, season, or neighbour generator while evaluating a point.

Request to every generator

A worker request should give the generator:

  1. The immutable resolved recipe and stable stratum ID.
  2. Physical query/tile bounds in metres, including the exact halo requested by the mesher.
  3. The canonical sample coordinates or a clearly defined lattice origin and step. Voxel coordinates are useful as an internal derived lattice, but metres/physical fixed units are the identity space.
  4. The resolved per-strate voxel size and seed. LOD can be included as a sampling hint; the canonical field value itself must not change just because the caller chose another LOD.
  5. Read-only interface descriptors that touch the request bounds. Pass boundary records, not a pointer back to the neighbour's manager or mutable generator.

Result from every generator

The common result should provide a deterministic field evaluator and its declared capabilities:

  • signed density using one documented solid/air sign convention;
  • optional material or surface-class data;
  • conservative interval/classification proofs and conservative feature/influence bounds, or an explicit “unknown, evaluate” result;
  • optional semantic 2D surfaces such as a ground heightfield or a cap, in physical coordinates;
  • a detail envelope / maximum safe sample spacing hint. It is a hint to generic LOD/refinement policy, not permission for a recipe to choose its own mesh path;
  • generator/schema/evaluator versions for stable plan and output identity.

Keep the established fail-open behavior: an unknown interval or influence bound causes evaluation, not a solid/air skip. Existing feature candidate lists and worker caches fit this contract as optimizations beneath the evaluator.

Interfaces, seals, caps, and voids belong to shared composition

The director resolves the meaning and exact physical location of each interface once. The shared composition layer then combines generator fields and applies boundary semantics in a fixed order:

  1. Evaluate each generator only for its declared physical domain, retaining its ordinary field/feature behavior.
  2. Apply the interface contract once: for example, a physically measured seal band, a cap surface, open void, bedrock gap, or permitted passage. The contract declares ownership, sidedness, material class, collision/support policy, and whether player edits may cut it. A gap is data, not an accidental “no generator found” branch.
  3. Apply world edits through the canonical composed field, so both sides see the same edit in physical space.
  4. Give the resulting field, proofs, and any explicit surfaces to the mesher.

This centralizes the semantics currently spread across generator density wrappers, FResolvedStratumBounds, and AppendStratumBoundaryCeilings. Existing versioned manifests still need compatibility adapters: do not silently reinterpret old boundarySealSubvoxels values.

For two different generator kinds that meet at a plane, both should reference the same stable interface ID and exact physical boundary. Their density fields can remain independent; shared composition owns the boundary rule. If the plan intentionally has open space between them, the resolved layout records that metre-valued interval explicitly.

4. Why LOD changes thin features, and the interface surface contract

MC samples density at lattice corners. A thin seal or cap can be narrower than one coarse cell, so the coarse grid may see no sign change at all. If it does see a crossing, interpolation uses the coarse cell's endpoint values; its root can differ from the finer grid's root. A shared analytic density function guarantees the same values at the same physical coordinates, not the same triangles from different sample grids.

For a physical strate boundary, use an explicit surface descriptor with exact height/shape in metres and a globally stable sample phase. Every LOD must use common anchors along tile and LOD transition edges. The mesher can fill the interior at its chosen rate, then build a deterministic transition band from those common anchors into its coarser interior. A seal/cap should not rely on a sub-cell thin density strip surviving arbitrary MC sampling.

For the L19 lower-ground ledge, the same rule needs a SurfaceWorld ground profile available to the mesher as semantic surface data. A shared planar boundary cap would only close the strate ceiling; it would not align these upward-facing ground triangles. The surface can be band-limited by LOD, but the adjacent LODs must share their transition samples and be checked against a physical error tolerance. If that cannot be met with a transition band, refine only the affected boundary tiles.

The costs are extra boundary vertices/triangles and some CPU work to construct common transition strips. Collision/support must consume the same interface representation where that surface is collision-relevant. The owner would see caps and ledges hold one physical height as the camera moves between LODs, instead of steps or holes appearing at the ring. Authoring remains in metres; internal tile and transition details stay hidden.

This is not free geometry, but it is bounded to boundaries and LOD transition bands. L19 has already shown why “add a thin density seal and sample it normally” is not a sufficient all-LOD guarantee.

5. Keep the per-strate resolution door open

Yes. A 25 cm per-strate default preserves the current Core sample spacing, and the common physical field coordinate contract leaves room for later different sizes. Do not enable mixed resolutions until the Round5 boundary contract is met:

  1. Canonical physical-space field queries and explicit ownership at a shared boundary.
  2. Deterministic transition meshes with common LOD ancestry on both resolutions.
  3. Physical-space edit rasterization/replay that reaches both grids exactly once, with no holes or double application.
  4. A stable, canonically sorted strate-ID-to-size map included in plan/season identity and border hashes.
  5. Streaming and collision rules for tiles that touch or span two resolutions.

At today's one-size setting, the size map has one value (250,000 microns) for every strate. It adds no mixed-resolution mesh, edit, or streaming cost. Tile width remains 32 × voxel size × 2^LOD; different future sizes change physical tile width and workload, but never change the 32³ internal tile format.

6. Migration plan with “worlds don't move” checks

The checkpoints distinguish expected equality from a deliberately changed interface mesh. A plan or schema identity can change even when field output remains equal; record both identities and mesh hashes separately.

Step Work Expected equality / check Changes output? / Unreal PC?
0 Freeze the L19 evidence as the baseline: W12, Clang with FP contraction off; retain the whole-view hashes, full-detail hashes, strict ceiling audit, captures, and FI/SW ring tables. Baseline hashes recorded below; pin the protected SW LOD0–2 hashes too. Capture byte/pixel comparisons and ring samples must retain location, LOD, and skirt exclusion. No. No new build needed for this design round.
1 Add a versioned resolved-metre layout representation and a read-only conversion/audit path. The audit reports, per strate, old input units, resolved old physical bounds, new metre bounds, voxel size, interfaces, and identity. Keep the old evaluator active. Exact old-vs-new resolved positions at all authored top, bottom, height, route, island, and cap anchors. Old Core density/mesh/payload/topology hashes remain equal where the conversion is exactly representable; otherwise list the precise quantization delta before accepting a new baseline. No. Core parser/audit can be Linux-only; adapter export needs Unreal BuildHost on the owner's PC.
2 Change owner-facing layout authoring and season serialization to metres. Convert each old slot using its current resolved physical bounds, not one global chunk multiplier. Represent any historical gap/overlap explicitly. Default each strate's voxel size to 25 cm. Same physical top/bottom/height values and field anchors before/after. Run default W12 hashes and matched captures. The report must call out any interface whose two old generator bounds disagree instead of silently forcing them together. Intended no. Touches VoxelForgeCoreAdapter.cpp, VoxelStrateManager, season manifest/composer and likely VoxelWorld; compile and actor validation require the owner's Unreal PC.
3 Add the common generator request/result contract and adapt Heightfield2D and FloatingIslandFieldV1; wrap the legacy UVoxelGenerator route without moving it yet. Move manager lookups out of evaluator calls in stages. Per-strate exact density queries, proof classifications, full-detail hashes, whole-view hashes, W1/W8/W12 determinism, and generator-to-legacy route parity. Existing current output hashes remain equal for adapters that preserve their exact physical recipe. No intended visual change. Unreal adapter/legacy wrappers need owner-PC UHT/build and actor validation; native Core pieces can be audited on Linux.
4 Resolve interface records once in the director; route new seal, cap, void, bedrock-gap, and passage behavior through shared composition. Keep old manifest versions on compatibility adapters. Preserve density hashes where only a mesh closure was missing. Run the strict LOD0–8 ceiling interior/perimeter audit, edge pairing, same-LOD border hashes, matched captures, and field proofs. Any newly closed boundary is a named expected mesh/payload/topology delta. Affected boundary mesh may change. Adapter serialization needs owner-PC validation; Core audit can run here. This step fixes thin seals/caps, not the L19 ground ledges.
5 Add the generic explicit-surface and LOD-transition contract. Feed SurfaceWorld ground-profile data into common transition bands; prototype on the measured FI/SW interface. Produce before/after matched readable captures and full ring tables. Require zero no-hit interface samples and a chosen physical seam-error limit; retain L19 hash equality for unaffected tiles/LODs. For intended changes, list changed mesh/payload/topology hashes and keep density unchanged if the field itself is unchanged. This is the step that fixes the L19 ledges. It changes protected SurfaceWorld LOD2 output, so implement only after the owner approves that specific output change. Unreal capture/collision review is on the owner's PC.
6 Enable a per-strate size map behind the already-resolved contract, initially set to 25 cm on all strates. Add one non-default diagnostic size case only after the default remains green. At all-25-cm map, reproduce the approved default hashes and captures. Include sorted size-map identity in a negative control; boundary/transition hashes must change when one size changes. Re-run edit-across-boundary, collision, streaming, and W1/W8/W12 checks. No default change; non-default diagnostic intentionally changes sampling. Touches Unreal reflected settings, snapshot adapter, and world identity; full compile/run requires the owner's PC.

L19 reference hashes

Representative W12 Clang -ffp-contract=off, density / mesh / payload / topology:

Whole view L19 hash
SurfaceWorld c10697a8f48380c7 / 4197fc1072f0ff58 / 7b3e616ec9459851 / c879e35592bf33e5
FloatingIslands 00bc3607a7839854 / 4baa66127c2b0892 / e11ef05e851a1517 / 1278bc4b1fffb869

Protected SurfaceWorld LOD0–2 hashes are in the L19 report and should be retained through steps 1–4 except for explicitly approved boundary geometry. The whole-view hash gate alone cannot prove that a ring improved; step 5 needs the strict ring table and readable captures as well.

Owner decisions this design needs

  1. Preservation target: confirm that migration snapshots each current generator's resolved physical bounds (including any current overlap or gap) before later tuning them into a single authored metre layout. If instead the initial migration should force currently different bounds to meet, that is a deliberate world change and needs its own baseline/capture review.
  2. Ledge output: approve the specific SurfaceWorld LOD2/transition change before step 5 implementation, as required by the existing protected-output rule.