Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d3c50ca95 | ||
|
|
d0f97bf79a | ||
|
|
6ddb834b1c | ||
|
|
d6e69420e8 | ||
|
|
111cf3d262 | ||
|
|
48fa028086 | ||
|
|
4afea7c802 | ||
|
|
53a1e69af7 | ||
|
|
4661a445be | ||
|
|
22a6b30494 | ||
|
|
a3b7d2015e | ||
|
|
6953254349 | ||
|
|
61633cb492 | ||
|
|
f67b519e75 | ||
|
|
2d0cec90e4 | ||
|
|
20c6d86f0e | ||
|
|
2c20f9eaea | ||
|
|
fcb16fd265 | ||
|
|
874c796c65 | ||
|
|
d5ce5d675d | ||
|
|
1cf25eff30 | ||
|
|
61ce0dee4b | ||
|
|
7661fe9d24 | ||
|
|
d696463c68 | ||
|
|
75210e56f3 | ||
|
|
075e58187e | ||
|
|
29ca99af7f | ||
|
|
a1dfc0422e | ||
|
|
61106be142 | ||
|
|
5aa285c3ef | ||
|
|
7d1808fdf2 | ||
|
|
7d92e148a9 | ||
|
|
20acf57678 | ||
|
|
94d50edb47 | ||
|
|
bb082f3ae9 | ||
|
|
052b6de354 | ||
|
|
38f6009200 | ||
|
|
2cb3344e1b | ||
|
|
89fd9a0021 | ||
|
|
2fea2710f7 | ||
|
|
6f2d33e0a9 | ||
|
|
666dfb811c | ||
|
|
c152708a67 | ||
|
|
f322b1c089 | ||
|
|
a96fd0b95b | ||
|
|
05b1523c54 | ||
|
|
7f64e2e0aa | ||
|
|
b6ac950073 | ||
|
|
10580650cb | ||
|
|
b0da995978 | ||
|
|
f2de8fe9c6 | ||
|
|
90421d0655 | ||
|
|
41716f00b8 | ||
|
|
b0a14262b1 | ||
|
|
0e730cdbba | ||
|
|
247682a8e0 | ||
|
|
782f195bdb | ||
|
|
b61ccff549 | ||
|
|
d4b4377d30 | ||
|
|
566d1f342a | ||
|
|
de14f0042d | ||
|
|
b3af0e589e | ||
|
|
08ceed200d | ||
|
|
3b37d5702e | ||
|
|
aeeedaa73d | ||
|
|
557f6aac30 | ||
|
|
631dd57eb4 | ||
|
|
2656d81ac9 | ||
|
|
16612acba4 | ||
|
|
d4fa89204d | ||
|
|
a40b0454b3 | ||
|
|
5602a281b9 | ||
|
|
9ffe296cd1 | ||
|
|
a3cb26b5c8 | ||
|
|
6ec4a8f856 | ||
|
|
ee5b05b357 | ||
|
|
97c0455939 | ||
|
|
7e3f926352 | ||
|
|
4f4e67c8ca | ||
|
|
8c068ee2cd | ||
|
|
5441c7da5c | ||
|
|
81b7d6d0ff | ||
|
|
3f300b912a | ||
|
|
05f62cff36 | ||
|
|
a443eb8546 | ||
|
|
8d29c09512 | ||
|
|
61b1c9c329 | ||
|
|
413983f2d3 | ||
|
|
8b87749771 | ||
|
|
35e3d149d2 | ||
|
|
bc0f9ee791 | ||
|
|
ca78352e43 | ||
|
|
d6555df5fa | ||
|
|
0a7492ffab | ||
|
|
f8a46ecf8d | ||
|
|
78641da0bb | ||
|
|
d6e021a80b | ||
|
|
43c11ccaa8 | ||
|
|
fd9f809c1c | ||
|
|
41a9265ebd | ||
|
|
3b4d6855fe | ||
|
|
41b001b652 | ||
|
|
97a1874498 | ||
|
|
6d2a068308 | ||
|
|
153798f436 | ||
|
|
3de7ce1420 | ||
|
|
1568f76ca2 | ||
|
|
bf22f0ffe0 | ||
|
|
ce6812dbeb | ||
|
|
0bcd67a016 | ||
|
|
116b57f01e | ||
|
|
0b8fbc5591 | ||
|
|
593dfac1d8 | ||
|
|
f83c4bd8c1 | ||
|
|
8117d118eb | ||
|
|
63fb71fe6a | ||
|
|
6a2d701eab | ||
|
|
20619e587d | ||
|
|
41fcef692a | ||
|
|
b6529719dd | ||
|
|
3bd0fd36df | ||
|
|
c4397a5fb7 | ||
|
|
e960b9c7e3 | ||
|
|
c43eebbf9a | ||
|
|
46f7f5f7c1 | ||
|
|
26fbf0c999 | ||
|
|
c91b63d7c2 | ||
|
|
26984d38d8 | ||
|
|
6fd8a88986 | ||
|
|
3f629f407a | ||
|
|
4753416d4a | ||
|
|
1de44af7cc |
No files matched your search
@@ -9,11 +9,26 @@
|
||||
!/Source/
|
||||
!/Source/**
|
||||
|
||||
# Portable CPU generator and D3D11 viewer.
|
||||
!/Standalone/
|
||||
!/Standalone/**
|
||||
/Standalone/Build/
|
||||
/Standalone/bin/
|
||||
|
||||
# The guarded single-entry test harness is source, not generated output.
|
||||
!/Tools/
|
||||
!/Tools/**
|
||||
/Tools/**/__pycache__/
|
||||
|
||||
# Closed / historical documents.
|
||||
!/Docs/
|
||||
!/Docs/**
|
||||
|
||||
!VoxelForge.uplugin
|
||||
|
||||
# Keep every design / doc markdown at any depth (AUDIT P1 — these were untracked)
|
||||
# Keep every design / doc markdown at any depth
|
||||
!*.md
|
||||
/Standalone/data/*.log
|
||||
/Standalone/data/*.err
|
||||
/Standalone/data/*.vfmesh
|
||||
/Standalone/Research/**/*.log
|
||||
@@ -13,9 +13,9 @@ each strate can be a fundamentally different *archetype*, connected at (0,0).
|
||||
|
||||
### 8.0 Human-scale generation contract (2026-09-06)
|
||||
|
||||
All authored lengths are voxel-space values, converted at the boundary by `0.25 m/voxel`. The body
|
||||
reference is a **0.34 m radius, 1.76 m tall** capsule (`1.36 × 7.04` voxels). The default strate
|
||||
height is now **8 chunks = 256 voxels = 64 m**, which is intentionally larger than the 3-4 chunk
|
||||
Voxel-space authored lengths convert at `0.125 m/voxel`. The body reference is a **0.34 m radius,
|
||||
1.76 m tall** capsule (`2.72 × 14.08` voxels). The default strate height is **16 chunks = 512 voxels
|
||||
= 64 m**, intentionally larger than the 3-4 chunk
|
||||
boss-arena anchor so an archetype can reserve seals, vertical play, and a high ceiling together.
|
||||
|
||||
The default size families are deliberately independent:
|
||||
@@ -23,7 +23,7 @@ The default size families are deliberately independent:
|
||||
| Feature | Default metre derivation | Coupling kept sound |
|
||||
|---------|--------------------------|---------------------|
|
||||
| Room network | radius 4-10 m (8-20 m diameters), origin radius 12 m (24 m hub), cell spacing 32 m | `MaxRoomRadius 40 < RoomSpacing/2 64`; a 6 m radial margin remains around the largest room |
|
||||
| Round passages | 1.5-2 m radius (3-4 m bores); maze corridor radius 3.125 m (6.25 m nominal bore) | `0.7 × 2 × (12.5−3.75) × 0.25 = 3.0625 m` usable floor after worst roughness; Maze `12.5 < CellSize/2 32` |
|
||||
| Round passages | 1.5-2 m radius (3-4 m bores); maze corridor radius 3.125 m (6.25 m nominal bore) | `0.7 × 2 × (25−7.5) × 0.125 = 3.0625 m` usable floor after worst roughness; Maze `25 < CellSize/2 64` |
|
||||
| Slab | floor at 16 m, ceiling at 38.4 m, 22.4 m open span | floor/ceiling noise and 4-8 m column diameters remain inside the open span |
|
||||
| SurfaceWorld | 20 m terrain relief, 12 m warp, 60.8 m sky cap | terrain features are bounded below the 64 m strate envelope |
|
||||
| VerticalShafts | 4-7 m shaft diameters, 3 m connector bore, 8 m ledge interval | shaft spacing is 20 m; connector/ledge values are derived from the roughness envelope |
|
||||
@@ -36,7 +36,7 @@ radius, **0.300** against the Maze corridor radius, **0.469/0.625** against shaf
|
||||
**0.234** against the minimum island radius (**0.484** including its 6-voxel blend), and
|
||||
**0.625/0.938** against slab column radius for floor/ceiling roughness. SurfaceWorld is a
|
||||
heightfield, so its **0.047** ratio is against its 80-voxel relief scale, not a fictitious radial
|
||||
feature. FlatPlain and CrystalChamber intentionally share `GetSlabDensity` and the same slab
|
||||
feature. FlatPlain and CrystalChamber intentionally share `BuildSlabStack` and the same slab
|
||||
parameter block; they cannot acquire separate scale geometry without a generator change.
|
||||
|
||||
This scale contract is informed by [Epic's Unreal level-blockout guidance](https://dev.epicgames.com/documentation/en-us/unreal-engine/designer-01-project-setup-and-level-blockout-in-unreal-engine), which recommends a player-sized reference and gives 2-3 m hall / 3-4 m height starting guidance. The
|
||||
@@ -62,22 +62,23 @@ are not landing or box-verdict violations.
|
||||
|
||||
### 8.1 Archetypes (`ECaveGeneratorType`, VoxelStrateTypes.h)
|
||||
Each archetype has its own param `USTRUCT` (on `UVoxelStrateDefinition`, EditCondition-gated
|
||||
by `GeneratorType`) and its own density function in `VoxelGenerator.cpp`, dispatched by the
|
||||
`switch` in `GetDensityAt`.
|
||||
by `GeneratorType`) and its own **operator stack** builder in `VoxelDensityOpStack.cpp`, selected per
|
||||
chunk by `VF_BuildOpStackForChunk` in `GetDensityAt` (the same factory `ClassifyTile` uses). The stack
|
||||
is the only density path; a degenerate (zero-height) strate has none and is open air.
|
||||
|
||||
| Archetype | Params struct | Density fn | Idea |
|
||||
| Archetype | Params struct | Stack builder | Idea |
|
||||
|-----------|---------------|------------|------|
|
||||
| TunnelNetwork | `FStrateGenerationParams` | `GetDensityWithParams` | rooms+tunnels (original) |
|
||||
| FlatPlain / CrystalChamber | `FSlabGenerationParams` | `GetSlabDensity` | floor/ceiling void (original) |
|
||||
| Maze | `FMazeGenerationParams` | `GetMazeDensity` | origin-directed spanning-tree corridors on a 3D lattice, with capped loop edges and a thread-local per-cell capsule cache |
|
||||
| SurfaceWorld | `FSurfaceGenerationParams` | `GetSurfaceDensity` | heightfield terrain: domain-warped continents+ridged mtns+detail, a low-freq **relief map** (`M`) that scales mountains/elevation for plains↔highland variety, **F20 heightfield terrain ops** (`Surface|Ops` — all default off ⇒ byte-identical): **Cliff** (slope-gated steepening — where the analytic structural slope > `CliffSlopeThreshold`, push the height away from the local mean by `CliffSharpness` ⇒ gentle slopes become sheer walls/canyon faces that hug real steep ground, gentle areas untouched; 4 structural resamples only when enabled), **Terrace** (relief-gated plateau quantize + `TerraceHardness` soft-round↔crisp-mesa), **LayerLines** (sedimentary sine shelves, slope-expressed) — pure per-column height remaps in the single oracle `ComputeSurfaceTerrainZ` (`SampleSurfaceStructuralZ` = pre-op raw height, re-sampled at an XY offset for Cliff's slope), biome-selected + border-blended for free via each biome's `SurfaceParams` + the height output-lerp; plus **phase-2 Overhang** (the first VOLUMETRIC op — real jutting shelves like a cliff lip): in `SurfaceDensityFromColumn`, for AIR voxels in the window `(TerrainZ, TerrainZ+OverhangHeight]` above a steep slope, the heightfield is re-sampled UPHILL (toward the cliff) by a reach that GROWS with height and unioned in. Low in the window the shift is ~0 (borrows nearby low rock ⇒ stays air over the void); high up it reaches the far cliff (solid) ⇒ a shelf attached to the cliff, tapering out over the void with air underneath. Per-column `OverhangAmp`(=strength·slope-gate) + unit uphill dir `(DirX,DirY)` are resolved once in `ComputeSurfaceColumn` — the gradient sampled at the REACH scale (`OverhangReach`) so a point out over the void can "see" the cliff to know which way is uphill — and cached on `FSurfaceColumn`. It's genuine 3D (per-voxel structural re-eval), so it's gated hard to steep overhang columns; the union only ADDS rock (never removes), capped at `TerrainZ+OverhangHeight`, so `ClassifyTile` forces Mixed only in `(TerrainZ, TerrainZ+OverhangMargin]` (upward-only; margin = max `OverhangHeight`) — a shelf never holes a trivially-skipped tile, and only the thin cliff-edge band of air tiles is woken (NOT far-field like phase-3 spikes/holes). Overhang undersides classify as ground rock by the F17 rule (down-facing but below `TerrainZ`). Known v1 limit: applies at all LODs (Step-agnostic) — may alias far; gate to fine tiles later. Beaches at water line, high sky-cap ceiling. The cap is shapeable terrain in its own right (`ComputeSurfaceCeiling`, `Surface|Sky` params: `CeilingUndulation` broad inverted hills/valleys, `CeilingRidgeStrength` hanging ridgelines, `CeilingRoughness`+freq fine bumps, `CeilingWarp*`) — defaults (strengths 0, freq 0.04) = old flat-ish cap. (`Surface|Macro` params = the cheap precursor to biomes; `ReliefStrength=0` ⇒ old uniform terrain.) **Sky-cap vs ground is a PER-TRIANGLE surface class (F17), not a per-tile verdict**: the mesher classifies each unique vertex on the **worker** — only down-facing verts (`N.Z<-0.1`) pay a memoized `GetSurfaceHeightAt` column query; nearer `CeilSurf` ⇒ sky-cap, nearer `TerrainZ` ⇒ terrain overhang stays ground (a future SurfaceWorld cave roof — down-facing but below `TerrainZ` — also lands ground by the same rule; non-surface strates always ground). Triangles take the majority class of their 3 verts and are packed as **two contiguous index runs** (ground then cap, `FVoxelMeshData::NumCeilingTriangles`; skirts inherit their source triangle's run) → RMC **polygroups 0/1** → one **section per non-empty group** (`ApplyMeshToTile`, material slot = group index): slot 0 = strate `OverrideMaterial`/default (min-corner chunk), slot 1 = `CeilingMaterial` resolved at the tile's **TOP chunk** (mid as gap fallback, then min; fallback: ground material) — a coarse tile is 2^level chunks tall, so its min corner can sit in a lower strate/gap while the cap belongs to the strate above (this was the "far cap = ground material" residue), so the shadowless overhead rock is tinted separately instead of reading flat/bright. Shadow is **per section** via `FRealtimeMeshSectionConfig::bCastsShadow` (NOT the component `SetCastShadow` — RMC's proxy ignores the component flag; this is also why level≥2 far tiles only stopped casting once the section flag was wired): ground casts at level≤1, the cap section never casts, so the rock ceiling never shadows the terrain below it. History: v1 was a game-thread centre height-oracle (misclassified coarse far tiles → terrain material on the cap underside); v2 a whole-tile worker normal VOTE — which painted **mixed coarse tiles** (one far tile spanning terrain AND cap) entirely with the winner's material, and put sky material under terrain overhangs. The per-triangle class fixes both and is the identity channel caves/F8 will reuse. |
|
||||
| VerticalShafts | `FVerticalShaftParams` | `GetVerticalShaftDensity` | full-height shafts + horizontal connectors + partial ledges |
|
||||
| FloatingIslands | `FFloatingIslandParams` | `GetFloatingIslandDensity` | asymmetric islands: flat land top + underside tapering to a point, lobed (domain-warped) outline, in an open void |
|
||||
| Underwater | `FStrateGenerationParams` + water | (reuses `GetDensityWithParams`) | tunnel rock + high water table |
|
||||
| TunnelNetwork | `FStrateGenerationParams` | `BuildTunnelNetworkStack` (fused evaluator: `GetDensityWithParams`) | rooms+tunnels (original) |
|
||||
| FlatPlain / CrystalChamber | `FSlabGenerationParams` | `BuildSlabStack` | floor/ceiling void (original) |
|
||||
| Maze | `FMazeGenerationParams` | `BuildMazeStack` | origin-directed spanning-tree corridors on a 3D lattice, with capped loop edges and a thread-local per-cell capsule cache |
|
||||
| SurfaceWorld | `FSurfaceGenerationParams` | `BuildSurfaceStack` | heightfield terrain: domain-warped continents+ridged mtns+detail, a low-freq **relief map** (`M`) that scales mountains/elevation for plains↔highland variety, **F20 heightfield terrain ops** (`Surface|Ops` — all default off ⇒ byte-identical): **Cliff** (slope-gated steepening — where the analytic structural slope > `CliffSlopeThreshold`, push the height away from the local mean by `CliffSharpness` ⇒ gentle slopes become sheer walls/canyon faces that hug real steep ground, gentle areas untouched; 4 structural resamples only when enabled), **Terrace** (relief-gated plateau quantize + `TerraceHardness` soft-round↔crisp-mesa), **LayerLines** (sedimentary sine shelves, slope-expressed) — pure per-column height remaps in the single oracle `ComputeSurfaceTerrainZ` (`SampleSurfaceStructuralZ` = pre-op raw height, re-sampled at an XY offset for Cliff's slope), biome-selected + border-blended for free via each biome's `SurfaceParams` + the height output-lerp; plus **phase-2 Overhang** (the first VOLUMETRIC op — real jutting shelves like a cliff lip): in `FOverhangShelfMod`, for AIR voxels in the window `(TerrainZ, TerrainZ+OverhangHeight]` above a steep slope, the heightfield is re-sampled UPHILL (toward the cliff) by a reach that GROWS with height and unioned in. Low in the window the shift is ~0 (borrows nearby low rock ⇒ stays air over the void); high up it reaches the far cliff (solid) ⇒ a shelf attached to the cliff, tapering out over the void with air underneath. Per-column `OverhangAmp`(=strength·slope-gate) + unit uphill dir `(DirX,DirY)` are resolved once in `ComputeSurfaceColumn` — the gradient sampled at the REACH scale (`OverhangReach`) so a point out over the void can "see" the cliff to know which way is uphill — and cached on `FSurfaceColumn`. It's genuine 3D (per-voxel structural re-eval), so it's gated hard to steep overhang columns; the union only ADDS rock (never removes), capped at `TerrainZ+OverhangHeight`, so `ClassifyTile` forces Mixed only in `(TerrainZ, TerrainZ+OverhangMargin]` (upward-only; margin = max `OverhangHeight`) — a shelf never holes a trivially-skipped tile, and only the thin cliff-edge band of air tiles is woken (NOT far-field like phase-3 spikes/holes). Overhang undersides classify as ground rock by the F17 rule (down-facing but below `TerrainZ`). Known v1 limit: applies at all LODs (Step-agnostic) — may alias far; gate to fine tiles later. Beaches at water line, high sky-cap ceiling. The cap is shapeable terrain in its own right (`ComputeSurfaceCeiling`, `Surface|Sky` params: `CeilingUndulation` broad inverted hills/valleys, `CeilingRidgeStrength` hanging ridgelines, `CeilingRoughness`+freq fine bumps, `CeilingWarp*`) — defaults (strengths 0, freq 0.04) = old flat-ish cap. (`Surface|Macro` params = the cheap precursor to biomes; `ReliefStrength=0` ⇒ old uniform terrain.) **Sky-cap vs ground is a PER-TRIANGLE surface class (F17), not a per-tile verdict**: the mesher classifies each unique vertex on the **worker** — only down-facing verts (`N.Z<-0.1`) pay a memoized `GetSurfaceHeightAt` column query; nearer `CeilSurf` ⇒ sky-cap, nearer `TerrainZ` ⇒ terrain overhang stays ground (a future SurfaceWorld cave roof — down-facing but below `TerrainZ` — also lands ground by the same rule; non-surface strates always ground). Triangles take the majority class of their 3 verts and are packed as **two contiguous index runs** (ground then cap, `FVoxelMeshData::NumCeilingTriangles`; skirts inherit their source triangle's run) → RMC **polygroups 0/1** → one **section per non-empty group** (`ApplyMeshToTile`, material slot = group index): slot 0 = strate `OverrideMaterial`/default (min-corner chunk), slot 1 = `CeilingMaterial` resolved at the tile's **TOP chunk** (mid as gap fallback, then min; fallback: ground material) — a coarse tile is 2^level chunks tall, so its min corner can sit in a lower strate/gap while the cap belongs to the strate above (this was the "far cap = ground material" residue), so the shadowless overhead rock is tinted separately instead of reading flat/bright. Shadow is **per section** via `FRealtimeMeshSectionConfig::bCastsShadow` (NOT the component `SetCastShadow` — RMC's proxy ignores the component flag; this is also why level≥2 far tiles only stopped casting once the section flag was wired): ground casts at level≤1, the cap section never casts, so the rock ceiling never shadows the terrain below it. History: v1 was a game-thread centre height-oracle (misclassified coarse far tiles → terrain material on the cap underside); v2 a whole-tile worker normal VOTE — which painted **mixed coarse tiles** (one far tile spanning terrain AND cap) entirely with the winner's material, and put sky material under terrain overhangs. The per-triangle class fixes both and is the identity channel caves/F8 will reuse. |
|
||||
| VerticalShafts | `FVerticalShaftParams` | `BuildVerticalShaftStack` | full-height shafts + horizontal connectors + partial ledges |
|
||||
| FloatingIslands | `FFloatingIslandParams` | `BuildFloatingIslandStack` | asymmetric islands: flat land top + underside tapering to a point, lobed (domain-warped) outline, in an open void |
|
||||
| Underwater | `FStrateGenerationParams` + water | (reuses `BuildTunnelNetworkStack`) | tunnel rock + high water table |
|
||||
|
||||
All density fns share the convention: internal **positive=solid**, apply origin spine →
|
||||
vertical boundary seal → inter-strate passage tube + landing/floor → XY edge seal, then `return -Density` (MC:
|
||||
negative=solid). The final MC-facing edge pass in `GetDensityAt` is repeated after disturbances so
|
||||
All stacks share the convention: internal **positive=solid**, and `AppendStructuralPost` ends every
|
||||
stack with origin spine → vertical boundary seal → inter-strate passage tube + landing/floor → XY
|
||||
edge seal; `EvalMC` negates once (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`
|
||||
@@ -127,12 +128,9 @@ threshold** before the law gate. No corridor or threshold tuning was added. The
|
||||
gate is therefore retained as a design blocker; see `COMPOSER-NOTES.md §3.2b` and the validation log
|
||||
in §10.
|
||||
|
||||
⚠️ **The `switch` above is no longer the only density path.** All 8 archetypes also exist as
|
||||
**operator stacks**, selected per strate by
|
||||
`bUseOperatorStack` and evaluated instead of the `switch`; each is bit-identical to the function in
|
||||
its row. The design lives in `OPSTACK-PLAN.md` / `OPSTACK-DECOMPOSITION.md`, the symbol index in
|
||||
`CODEMAP §3.2d` — not repeated here. What matters for *this* document: the archetype table describes
|
||||
what the world IS, and both paths compute it.
|
||||
The operator-stack design lives in `Docs/archive/OPSTACK-PLAN.md` /
|
||||
`Docs/archive/OPSTACK-DECOMPOSITION.md`, the symbol index in `CODEMAP §3.2d` — not repeated here.
|
||||
What matters for *this* document: the archetype table describes what the world IS.
|
||||
|
||||
### 8.2 (0,0) spine & hybrid connections
|
||||
- `ApplyOriginSpine` (VoxelGenerator.cpp, static helper) builds a finite, rounded landing room with
|
||||
@@ -177,8 +175,8 @@ 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
|
||||
XY around `(0,0)`. The legacy 8192-voxel radius is ~1.024 km at 12.5 cm/voxel; use 16384 voxels
|
||||
for the same ~2.05 km world radius. The source edge-seal default is 128 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
|
||||
@@ -230,8 +228,8 @@ they are never needed for the connectivity proof.
|
||||
|
||||
The evaluation window is exactly the lower nodes in `{-1,0}³`, an 8-node / 2×2×2 local halo. The
|
||||
edge predicate checks the adjacent `+1` endpoint's parent locally, so it does not need a wider
|
||||
collect region and cannot disagree at a chunk boundary. Both `GetMazeDensity` and
|
||||
`MakeLatticeCorridorSource` rebuild those decisions in their thread-local per-cell cache; the
|
||||
collect region and cannot disagree at a chunk boundary. `MakeLatticeCorridorSource` rebuilds those
|
||||
decisions in its thread-local per-cell cache; the
|
||||
voxel loop evaluates only cached capsule SDFs. This is deliberately independent of
|
||||
`WorldRadiusVoxels` (which remains `0`) and the still-gated lateral-region system.
|
||||
|
||||
@@ -291,9 +289,11 @@ when close (separate HISMs → crossing the near boundary never touches the far
|
||||
(per grid, sharing ONE `MaxConcurrentDecorationTasks` budget — each throttles against the other's in-flight count):
|
||||
snapshot the update's decoration palette (built once on the GAME thread — biome, see below) then fire an async `UE::Tasks` march
|
||||
(`BuildCellSpawns`, `BackgroundNormal`, capped at `MaxConcurrentDecorationTasks` in flight via `InFlightCells`).
|
||||
**(3)** `ProcessDecoResults`: drain finished tasks' results (`Mpsc` queue → `ReadyResults`), epoch-guarded
|
||||
(`DecoEpoch`, bumped on clear/strate-change so stale in-flight results are discarded) + range-checked, and
|
||||
**apply (spawn) budgeted** (`MaxDecorationCellsPerFrame` — the only game-thread cost, SpawnActor/AddInstance).
|
||||
**(3)** `ProcessDecoResults`: drain finished cell marches (`DecoResults`, `Mpsc`) and fold each into its
|
||||
grid's region build (`MergeCellResult`; a result whose build is gone or whose `BuildId` no longer matches —
|
||||
region cleared + re-marched — is discarded), then **apply completed regions budgeted**
|
||||
(`MaxDecorationCellsPerFrame` regions/frame via `ApplyRegion` — the only game-thread cost; a region that left
|
||||
range while it marched is dropped unbuilt).
|
||||
`BuildCellSpawns` (worker) finds each column's surface point(s) and rolls the entries there (shared
|
||||
`PlaceAtCrossing`). Candidate columns are **snapped to INTEGER voxel XY** (integer jitter) so the generator's
|
||||
surface-column cache (T1.a, §8.10) applies — FRACTIONAL XY bypasses it and recomputes the noise-heavy
|
||||
@@ -321,7 +321,7 @@ result's `Entries` snapshot. `DecorationMaxCrossingsPerColumn` caps cave columns
|
||||
task count before UObject teardown (tasks read the Generator); `BeginDestroy` is the backstop. **Determinism:**
|
||||
pure hash of (cell, column, crossing, entry, seed) + the density surface snap. **Decorations exist ONLY in
|
||||
the player's current strate** (march is strate-bounded) → a strate change wipes + rebuilds them, and there
|
||||
is **no cross-strate light bleed to cull** (the old `SetActiveStrate` light-culling pass is SUBSUMED — gone).
|
||||
is **no cross-strate light bleed to cull**.
|
||||
**Render paths:** `ActorClass` → real actors (lights/logic, pricey game-thread spawn); `InstancedMesh` → HISM
|
||||
(no tick/actor/collision, emissive glows far), per-cell-per-entry. **Per-entry HISM tuning for dense groundcover**
|
||||
(`FStrateDecoration`, only the InstancedMesh path): `CullDistance` (cm; 0 = no cull — the lever that makes dense
|
||||
@@ -332,8 +332,8 @@ grass affordable: placed thickly, drawn only near → GPU cost bounded by area-w
|
||||
`MinSlopeAngle` (lower companion to Max — band a prop onto a tilt range, e.g. 30..70 = slopes only),
|
||||
`bWallExcludeOverhangs` (wall-only-upright: drop normals with N.Z < 0 so downward overhangs don't take wall props).
|
||||
**Shared vocabulary (2026-07-06):** these gates + spawn/transform/render fields now live on `FPlacementProfile`
|
||||
(embedded as `Profile` on `FStrateDecoration`, `FStrateLandmark`, and the coming `FStrateSetPiece`), so all three
|
||||
scatter primitives are authored identically. Rotation unified to `RotationOffset` (fixed) + `RandomRotation`
|
||||
(embedded as `Profile` on `FStrateDecoration` and `FStrateLandmark`), so both scatter primitives are
|
||||
authored identically. Rotation unified to `RotationOffset` (fixed) + `RandomRotation`
|
||||
(per-axis hash-random) — decoration's ctor defaults `RandomRotation.Yaw = 360` (full random heading, replacing the
|
||||
old `bRandomYaw`/`MinYaw`/`MaxYaw`; banded yaw = offset + a smaller random range). Distribution is unchanged; the
|
||||
exact per-instance yaw values reshuffle once (different hash mix).
|
||||
@@ -345,7 +345,7 @@ SampleMoisture / SampleBiomeAt) on demand, so it's deterministic + worker-safe (
|
||||
strate's already-resolved `FBiomeContext`, so no re-resolve). Opt-in per entry (empty list = zero cost);
|
||||
wired into both the deco worker (`BuildCellSpawns`) and landmark placement (`SpawnLandmarkInstance`). This
|
||||
is the shared core of the coming quest `FindFeature` locator (same predicate, run as an outward search) and
|
||||
the anchor gate for `FStrateSetPiece`. The BP bridge `AVoxelWorld::GetVoxelSurfaceHeightAt` exposes the
|
||||
the landmark anchor gate. The BP bridge `AVoxelWorld::GetVoxelSurfaceHeightAt` exposes the
|
||||
trace-free deterministic ground/ceiling height so authored ruin/set-piece Blueprints self-arrange on the
|
||||
real surface before it meshes. Next types (water-edge band, relief-peak local-max, slope) are additive.
|
||||
**F7 COMPANIONS (relational decoration, `FDecoCompanion`):** each `FStrateDecoration` may list `Companions`
|
||||
@@ -397,12 +397,12 @@ Static-mobility HISM re-caches its proxy — fine for player-paced digging.) Not
|
||||
grass on a freshly-exposed ledge / regrowth after fill-back — both self-correct on the next natural re-stream. **Freeze note:** a huge set-piece mesh hitches on register (game-thread proxy/distance-field build —
|
||||
NOT async-fixable; the spawn is game-thread by engine rule; the asset is a hard ref so already resident) →
|
||||
mitigate ASSET-side (Nanite on the mesh, bake distance fields), optionally budget spawns across frames.
|
||||
`ApplyDecoResult` buckets spawns per entry and builds each HISM with ONE batched `AddInstances`
|
||||
(single cluster-tree build, set cull/shadow BEFORE `RegisterComponent`) — the game-thread hitch-killer for dense cells.
|
||||
`ApplyRegion` buckets a region's spawns per mesh (all its cells merged) and builds each HISM with ONE batched
|
||||
`AddInstances` (single cluster-tree build, set cull/shadow BEFORE `RegisterComponent`) — the game-thread
|
||||
hitch-killer for dense cells.
|
||||
**Per-entry tier (`StreamTier`, default Far)** picks NearGrid or FarGrid; radius + column spacing are PER-GRID
|
||||
settings, never per-entry (a per-entry radius would re-introduce the in-place re-stream flicker — see the two-grid
|
||||
rationale above). This REPLACES the vestigial `MaxLODLevel`; the dead `DecorationActorRadiusChunks` setting is
|
||||
repurposed as `DecorationNearRadiusChunks`. `CullDistance` still bounds GPU draw on top (orthogonal to which grid
|
||||
rationale above). `CullDistance` still bounds GPU draw on top (orthogonal to which grid
|
||||
streams the entry). **No LOD area-density compensation** (placement is per real
|
||||
surface point, density-stable with distance). **SpawnDensity semantics CHANGED** vs the old vertex scatter: it
|
||||
rolls per column surface-point (not per mesh vertex) → expect a one-time density re-tune. **Settings
|
||||
@@ -436,22 +436,23 @@ null entry = "evaluated, nothing placed" so it isn't retried). Deterministic (ha
|
||||
Strate-bounded (wiped on strate change, like decorations). This is the real home for "rare prop at all
|
||||
distances" — the job the decoration FarGrid could approximate at moderate range but not at extreme radius.
|
||||
|
||||
**(B) WATER — tile-driven, level-0 only (continuous plane, never pops).** `PopulateTileWater(tile)` in
|
||||
`ApplyMeshToTile` (level-0 tiles), `ClearTileWater(tile)` in `UnloadTile`. One scaled engine plane
|
||||
(`/Engine/BasicShapes/Plane`) per water-surface chunk (per-chunk-Z plane logic assumes a single chunk's
|
||||
vertical span — hence level-0 only), keyed `TMap<FIntVector, UStaticMeshComponent*>` (reflected UPROPERTY).
|
||||
Water Z: `bHasWater` + `WaterLevelRelative` → `StrateManager::GetWaterLevelWorldZForChunk`. Biome
|
||||
`WaterMaterial` overrides `UVoxelStrateDefinition::WaterMaterial` (level stays strate-global).
|
||||
**(B) WATER — ONE strate-global ocean plane that follows the player (`UpdateWater`, from `Tick`).** A single
|
||||
scaled engine plane (`/Engine/BasicShapes/Plane`, no collision, no shadow) snapped to a coarse cell around the
|
||||
player, so it only repositions when the player crosses a cell; terrain pokes through it, so it reads as water at
|
||||
every LOD and to the horizon with no per-tile gaps (one draw). Water Z: `bHasWater` + `WaterLevelRelative` →
|
||||
`StrateManager::GetWaterLevelWorldZForChunk` (no water in the current strate ⇒ plane hidden). Material:
|
||||
`UVoxelStrateDefinition::WaterMaterial`. ⚠️ OPEN: `UVoxelBiomeDefinition::WaterMaterial` (the biome
|
||||
content profile's water override, §8.14) is not read by the single-plane path — owner to decide whether
|
||||
the biome override should come back (REVIEW_FINDINGS, *Owner decisions*).
|
||||
|
||||
`ClearAll`/`SetSeed` on `ChangeSeed`/regenerate clears both subsystems (decorations re-stream on the next
|
||||
Tick via the INT_MIN sentinels). **Per-biome content (§8.14):** decorations resolve the dominant biome **PER COLUMN** on the worker
|
||||
(`ResolveBiomeSampleAt` via the strate's `FBiomeContext`, box-cached → one rebuild per chunk footprint). The
|
||||
update builds ONE flat decoration palette (every biome's list concatenated; `CurrentEntryBiome[i]` tags entry
|
||||
update builds ONE flat decoration palette per grid (every biome's list concatenated; `EntryBiome[i]` tags entry
|
||||
`i` with its context-biome index, -1 = strate fallback), and `PlaceAtCrossing` rolls only the entries the
|
||||
column's biome owns. This replaced the old per-CELL `GetDominantBiomeAt` (one biome for a whole 8 m cell →
|
||||
axis-aligned border snapping); borders now follow the warped-Voronoi field at column resolution, no straight
|
||||
lines. Water still uses the chunk-centre `GetDominantBiomeAt` for its material. `Initialize` now also takes
|
||||
`UVoxelSettings*` (for the grid tunables). `ContentMaxLevel` is now legacy/dead for decorations.
|
||||
column's biome owns. Borders follow the warped-Voronoi field at column resolution, no straight lines (a
|
||||
per-CELL biome would snap borders to the 4 m cell grid). `Initialize` also takes `UVoxelSettings*` (for the
|
||||
grid tunables).
|
||||
|
||||
### 8.6 Atmosphere — `VoxelAtmosphereManager.h/.cpp` (NEW)
|
||||
`UVoxelAtmosphereManager` (owned by `AVoxelWorld`, gated by `bManageAtmosphere`).
|
||||
@@ -521,7 +522,7 @@ flat floor. **There is no connector to the origin landing room at `(0,0)`.** The
|
||||
those radial roads after a playtest (`d97373c`, 2026-09-08: they cut straight through everything);
|
||||
room joins are flattened to walkable height instead. A source-fit landing therefore stays local to
|
||||
the room it was fitted into (`GeneratePassages`, the `VF_BuildPassageLanding` calls).
|
||||
⚠️ Open gap (Sol's code review, `DESIGN-SOL-2026-09-15-CODE.md` §1.3/§2.4): when the footing query
|
||||
⚠️ Open gap (Sol's code review, `Docs/archive/DESIGN-SOL-2026-09-15-CODE.md` §1.3/§2.4): when the footing query
|
||||
declines, the landing falls back to the historical random reach, and nothing proves that pose joins
|
||||
the walk network. The canonical capability gate covers one scenario, not every seed or mix.
|
||||
Proposed fix, not built: retry deterministically at other candidate spots instead of falling back.
|
||||
@@ -534,14 +535,12 @@ named 15° floor-gradient limit as the default inter-strate tunnel. The room and
|
||||
therefore real walkable geometry, not a point anchor or nearest-component guess, and no
|
||||
per-passage ordering is involved.
|
||||
|
||||
The chamber dimensions come directly from the capsule: player diameter `2×0.34/0.25 = 2.72`
|
||||
voxels; required floor width `3/0.25 = 12` voxels; capsule height `2×0.88/0.25 = 7.04` voxels;
|
||||
one metre of headroom adds 4 voxels. `HalfWidth=max(7, MouthRadius+2)` leaves
|
||||
`2×(HalfWidth−1) >= 12` voxels of flat support after the one-voxel wall inset; the authored room
|
||||
is therefore at least 14 voxels (3.5 m) wide. Height is
|
||||
`max(12, 7.04+4, 2×MouthRadius+2)`; the stock 6-voxel mouth is a 14-voxel (3.5 m) room with
|
||||
an 8-voxel half-width (4 m full width).
|
||||
`FloorZ=StandingPoint.Z−0.5`, floor thickness is 3 voxels (0.75 m), and the tube centreline
|
||||
The chamber dimensions come directly from the capsule: player diameter `2×0.34/0.125 = 5.44`
|
||||
voxels; required floor width `3/0.125 = 24` voxels; capsule height `2×0.88/0.125 = 14.08` voxels;
|
||||
one metre of headroom adds 8 voxels. `HalfWidth=max(12, MouthRadius+4)` gives a 24-voxel
|
||||
minimum clear floor width (3 m). Height is `max(24, 14.08+8, 2×MouthRadius+4)`; the minimum
|
||||
room height is 24 voxels (3 m). `FloorZ=StandingPoint.Z−1`, floor thickness is 6 voxels (0.75 m),
|
||||
and the tube centreline
|
||||
meets the room at `FloorZ+MouthRadius`, so its lower tangent is the floor and there is no step lip.
|
||||
The final-density floor audit samples the flat plane and checks the player-fit capsule above it. The
|
||||
default tunnel audit samples every route step against the final density and checks the analytic and
|
||||
@@ -554,8 +553,8 @@ The landing is part of the structural post, in the fixed order
|
||||
after the passage carve and after MC-space disturbances as a support backstop, still before the
|
||||
final XY edge seal. Room/floor Z is clamped inside the vertical seal; the XY edge seal is last and
|
||||
wins over every landing or tube near the rim. Thus no landing can breach a strate boundary or the
|
||||
world edge. The op-stack `FPassageCarveOp` calls the same manager operation as the legacy path,
|
||||
including the floor, and `ClassifyTile` kills both uniform hypotheses around the bidirectional
|
||||
world edge. The op-stack `FPassageCarveOp` calls the manager's `ApplyPassageModifier`, including
|
||||
the floor, and `ClassifyTile` kills both uniform hypotheses around the bidirectional
|
||||
landing floor (`Both` in the stack), so an air proof cannot delete support.
|
||||
|
||||
The query remains pure — it does not construct an operator stack, touch a cache, or call back into
|
||||
@@ -621,8 +620,7 @@ driven by `EditorBrush*` props.
|
||||
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.
|
||||
neighbourhood scan, or tree construction may move into the per-voxel loop (`FShaftFieldSource`).
|
||||
- **Maze field cache**: parent/loop decisions are rebuilt only when the thread-local cell, seed, or
|
||||
loop parameters change. The rebuild evaluates the 24 canonical lower-node/axis edges in the
|
||||
local `{-1,0}³` window and emits at most 24 capsules; the per-voxel loop performs no hash,
|
||||
@@ -684,13 +682,27 @@ driven by `EditorBrush*` props.
|
||||
position → seamless across chunk borders (both sides use identical pure samples). NO per-vertex
|
||||
`GetDensityAt` (was ~6/vertex, often as costly as the whole grid). `ComputeGradientNormal` is now
|
||||
unused. Only NORMALS changed vs the old path; geometry is identical.
|
||||
- **Surface column cache (T1.a)** (`FSurfaceColumnCache` = LRU of `FSurfaceColumnBox`, `GetDensityAt`
|
||||
SurfaceWorld branch): the heightfield + sky-cap + biome blend are a PURE function of (XY, seed,
|
||||
- **Core hybrid sharp mesher (M2)** (`CoreMesherRecipe` / `MesherRecipe`): the property remains
|
||||
opt-in and defaults to `MarchingCubesV1`; `bUseVoxelForgeCore` remains separately default-off.
|
||||
V1 preserves the round-3 feature-aware dual-contouring path for repeatability. V2 reuses the
|
||||
existing density grid and MC case/triangle topology, then projects eligible LOD0 edge roots onto
|
||||
exact type-2 plan-box planes with feature-split normals. Only edges whose complete incident-cell
|
||||
ring is inside the tile can snap; border roots and every LOD1+ cell stay MC, with skirts unchanged.
|
||||
The recipe version participates in Core content identity. Do not alter density queries, worker
|
||||
task/epoch handling, thread-local MC grid caches, or LOD0-only collision policy. Keep
|
||||
`MarchingCubesV1` and the Core-off legacy path unchanged. V1 measured 5.58–7.11× MC on tagged
|
||||
fixtures; V2 measured at most 1.27× and passed five exact cooked contacts plus the independent
|
||||
same-LOD border check. Keep both recipes opt-in. The standalone stitched-transition suite still
|
||||
has five MC doorway topology failures. Production skirts cover these seams in the static audit
|
||||
view but their strips are visible; the owner should review them at the target camera. Skirts do not
|
||||
make the mesh topologically watertight.
|
||||
- **Surface column cache (T1.a)** (density path: the surface stack's `FSurfaceColumnSource` memo;
|
||||
`ClassifyTile`: `FSurfaceColumnCache` = LRU of `FSurfaceColumnBox`): the heightfield + sky-cap + biome blend are a PURE function of (XY, seed,
|
||||
strate) — **ZERO Z dependence** (climate/Voronoi are pure-XY; surface params are per-strate constant
|
||||
under Hard transitions) — yet sampled ~33× per column (once per Z grid-point). Cached per integer XY
|
||||
(box-valid, like the SDF cache) and reused down the column. **Keyed by (XY box, StrateKey, Seed), NOT
|
||||
ChunkZ** (`StrateKey = round(StrateBottomWorldZ)`, taken from the params so it can't disagree with
|
||||
them) and held as a small **LRU of 6 boxes** so the WHOLE vertical view-distance stack — and XY
|
||||
(box-valid, like the SDF cache) and reused down the column. **Keyed by XY box + strate identity + seed
|
||||
(plus layout version / params fingerprint), NOT ChunkZ** (the strate identity is derived from
|
||||
`StrateBottomWorldZ`, taken from the params so it can't disagree with them) and held as a small **LRU of 6 boxes** so the WHOLE vertical view-distance stack — and XY
|
||||
neighbours the scheduler interleaves — share one another's heavy column noise instead of each
|
||||
recomputing it ~once per vertical chunk (this was the dominant `GenerateMesh` cost: the same 2D
|
||||
heightfield recomputed per altitude). It also makes pure-air / pure-solid chunks cheap (they hit the
|
||||
@@ -764,28 +776,14 @@ driven by `EditorBrush*` props.
|
||||
inside a strate band), then the OUTERMOST shell keeps generating tiles outward until it covers
|
||||
the requested distance (`VF_OuterShell`, shared by `BuildDesiredTiles` and `IsTileInClipRange`
|
||||
so the cull sees the same horizon; the ring's dz sweep is pre-clamped to the vertical strate
|
||||
band). As MC tiles the ring cost grows with (distance/2^MaxClipLevel)² — which is why the ring
|
||||
defaults to **F18 SHEETS** (`bFarSheetRing`): in an open strate the far field is exactly two
|
||||
heightfields (TerrainZ + CeilSurf, per-column oracle), so the ring streams tiles at level
|
||||
`MaxClipLevel + FarSheetSpanLevels` (one sheet = 2^span MC footprints per axis → 4-16× fewer
|
||||
components) meshed by `GenerateSheetMesh` as two displaced grids — ground polygroup 0 / cap
|
||||
polygroup 1, tags true by construction, same materials/UVs/F6 masks, ~3-6× cheaper gen per
|
||||
area than band-cut MC. **XY hole**: a partially-covered sheet renders its whole footprint, so
|
||||
the MC-covered box around the player (level-MaxClipLevel box, shrunk 1 tile for a seam-overlap
|
||||
ring) is CUT out of the sheet at cell granularity (`SheetHole*Vox`, passed to
|
||||
`GenerateSheetMesh`); when the player crosses a MaxClipLevel tile the hole moves and the
|
||||
overlapping sheets re-queue via `BandRemeshQueue` — the shrink means the newly-cut area's MC
|
||||
tiles were already desired one crossing earlier (loaded before uncovered). Sheet tiles take
|
||||
the strate from the BAND (mid chunk): band unarmed
|
||||
(inter-strate gap) ⇒ empty ring until landing; non-open strates ⇒ empty sheets (their far
|
||||
ring was enclosed rock anyway); carved features (passages/spine/chasms/diff) don't show at
|
||||
sheet distance — the MC shells keep them near. (A "step cap" variant — raising CoarseTileCells
|
||||
per level so far levels keep a fine Step — was tried and rejected 2026-07-06.)
|
||||
band). The ring is made of level-MaxClipLevel MC tiles, so its cost grows with
|
||||
(distance/2^MaxClipLevel)². The far "sheet" ring (heightfield tiles past `MaxClipLevel`) was
|
||||
dropped by the owner's decision; it no longer exists in the selector or the settings.
|
||||
(A "step cap" variant — raising CoarseTileCells per level so far levels keep a fine Step — was
|
||||
tried and rejected 2026-07-06.)
|
||||
Trade-offs accepted: other strates simply don't render at far LOD (they're sealed/enclosed —
|
||||
invisible except through passage mouths, which read as dark holes); passage tubes crossing the gap
|
||||
are cut at coarse levels only (near levels mesh full).
|
||||
`GetLODForChunk` / `LODToStep` / `IsChunkInRange` / `GetStrateChunkZBounds` and the
|
||||
ViewDistance/LOD/strate-Z/ceiling settings are now DEAD/unused (left in place).
|
||||
- **SKIRTS — LOD-seam crack filler** (`GenerateMesh`, after the cell loop; `VoxelSettings::bGenerateSkirts`
|
||||
+ `SkirtCells`, wired onto the mesher at setup). Neighbouring shells mesh at different resolutions so
|
||||
their iso-surfaces don't meet along the shared face → a thin see-through crack. After meshing, every
|
||||
@@ -818,9 +816,8 @@ driven by `EditorBrush*` props.
|
||||
drain; `VoxelSettings::MaxUnloadsPerFrame`): the cull APPROVES removals (strict load-before-unload) but
|
||||
doesn't destroy in place — it queues them. `ProcessUnloadQueue` runs at most `MaxUnloadsPerFrame`
|
||||
`UnloadTile`s/frame (scaled up to 4× with backlog, capped so a huge backlog can't re-spike). WHY: a
|
||||
fast traversal culls a whole shell's worth of tiles in ONE frame, and each `UnloadTile` does
|
||||
`DestroyComponent` + (level 0) `ContentManager::ClearChunk` → `Destroy()` of every decoration actor —
|
||||
an unbudgeted burst = a game-thread spike ("stuff torn down behind you" at speed). Mesh APPLIES were
|
||||
fast traversal culls a whole shell's worth of tiles in ONE frame, and each `UnloadTile` strips and
|
||||
parks its component (T2.c pool) — an unbudgeted burst = a game-thread spike ("stuff torn down behind you" at speed). Mesh APPLIES were
|
||||
already budgeted; this matches it for DESTROYS. Re-desired tiles are cancelled out of the queue (still
|
||||
loaded → no reload). `PendingUnload` is cleared in `RegenerateAllChunks`/`EndPlay` (tiles already gone).
|
||||
- **Collision only at LEVEL 0** (`ApplyMeshToTile`): `UpdateSectionConfig(..., Tile.Level==0)`. Far tiles
|
||||
@@ -833,7 +830,7 @@ driven by `EditorBrush*` props.
|
||||
= 0; `=1` restores it as an A/B instrument). Measured on the headless game path with nested
|
||||
refinement off, the classifier plus its exact validation cost more than they saved. Mode 0 had
|
||||
ready p95 182 vs 190-195 ms, generation p95 125-127 vs 139-141 ms, and 18% fewer density calls,
|
||||
with 841/841 tiles triangle-identical (WORK-NEXT.md). Proving a 33³ core uniform costs almost
|
||||
with 841/841 tiles triangle-identical (Docs/archive/WORK-NEXT.md). Proving a 33³ core uniform costs almost
|
||||
as much as meshing 35³. The history below explains why it existed. Its soundness tests still
|
||||
guard it.
|
||||
- **Trivial-empty tile reject (T1.d) — v2 SHIPPED (2026-07-05); v1 was reverted 2026-06-26.**
|
||||
@@ -844,9 +841,11 @@ driven by `EditorBrush*` props.
|
||||
wasted) and gen throughput had become the felt gameplay limit ("standing waiting for generation").
|
||||
**Why v1 failed & how v2 avoids it:** v1 used a GLOBAL analytic ceiling bound — not conservative when
|
||||
the cap hangs low (`CeilingRoughness`/`RidgeStrength`) → holes in the roof. v2 makes NO amplitude
|
||||
guesses: it evaluates `ComputeSurfaceColumn` (the SAME function as the density path, via the SHARED
|
||||
`GSurfColCache`) **on the exact lattice the mesher would sample** (margin ring included) — same
|
||||
functions + same inputs ⇒ the same floats ⇒ the verdict is exact at the lattice, not an estimate.
|
||||
guesses: it evaluates `ComputeSurfaceColumn` (cached per thread in `GSurfColCache`) — the column
|
||||
the surface stack's height operators reproduce (ground height pinned bit for bit by
|
||||
`VoxelForge.OpStack.SurfaceHeightEquivalence`) — **on the exact lattice the mesher would sample**
|
||||
(margin ring included) — same inputs ⇒ the same floats ⇒ the verdict is exact at the lattice, not
|
||||
an estimate.
|
||||
Per-z rules: gap chunk = solid; surface seal band (ApplyBoundarySeal inequalities, `BaseDensity>0`)
|
||||
= solid; surface interior z: air ⇔ `TerrainZ ≤ z ≤ CeilSurf` (MC `D ≥ 0`); ANY other archetype /
|
||||
out-of-layout chunkZ ⇒ `Mixed` (cave interiors are not provable in v1 of this classifier — a future
|
||||
@@ -860,7 +859,7 @@ driven by `EditorBrush*` props.
|
||||
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
|
||||
tiles; a Mixed verdict leaves its columns warm in `GSurfColCache` for the GenerateMesh that follows.
|
||||
tiles; the columns stay warm in `GSurfColCache` for the full pass and for neighbouring tiles.
|
||||
- **Worker-built StreamSet (T1.f)** (`BuildTileStreamSet`, `LoadTile` task → `FChunkResult::Streams`):
|
||||
the RMC vertex/index buffers (`FRealtimeMeshStreamSet`) are built ON THE GEN WORKER, not on the game
|
||||
thread. The per-vertex builder loop was the dominant game-thread streaming cost (measured: game
|
||||
@@ -981,9 +980,9 @@ change *anything*, e.g. frequencies, which scalar multipliers couldn't.)
|
||||
- **Consumption — SURFACE (output-blend).** Per chunk, `CP_SurfaceBiomeParams[]` holds each biome's
|
||||
resolved surface params (its override when `bOverrideTerrain` + GeneratorType matches, else the
|
||||
strate's) with **structural fields forced from the strate** (Z bounds, seal, base density, water
|
||||
level). Per voxel: `ResolveBiomeSampleAt` → dominant `PD` (+ neighbour `PN`); `GetSurfaceDensity`
|
||||
computes `ComputeSurfaceTerrainZ` for `PD` and, in the border band, for `PN`, and **lerps the
|
||||
resulting HEIGHTS**. Blending heights (not params) is seamless across *any* difference (frequencies
|
||||
level). Per voxel: `ResolveBiomeSampleAt` → dominant `PD` (+ neighbour `PN`); the surface stack's
|
||||
biome-blend height source (and `ComputeSurfaceColumn`) computes the terrain height for `PD` and, in
|
||||
the border band, for `PN`, and **lerps the resulting HEIGHTS**. Blending heights (not params) is seamless across *any* difference (frequencies
|
||||
included) — what per-param blend never could. `PD==PN`, weight 0 ⇒ bit-identical, no biomes.
|
||||
- **Consumption — CAVES: structural overrides are NOT applied (determinism).** Rooms/tunnels are
|
||||
decided over a wide COLLECT region spanning chunks (§8.4); making room params vary by region would
|
||||
@@ -1080,6 +1079,362 @@ timings are intentionally diagnostic metadata and do not affect world or image c
|
||||
viewer remains a separate display-only consumer: it must load this OBJ/manifest and never evaluate
|
||||
density or generate geometry.
|
||||
|
||||
### 8.17 Standalone Core in Unreal — integration round 2
|
||||
**Status (2026-10-08):** `AVoxelWorld::bUseVoxelForgeCore` now selects a Core runtime route and is
|
||||
false by default. Core mode has independent `CoreClipRadius=3` and `CoreMaxClipLevel=8` properties;
|
||||
it does not read or write the older clip properties or owner assets. BuildHost UBT and the Core link
|
||||
symbol audit pass. A headless BuildHost actor proof matches all initial tile hashes against standalone
|
||||
Core, checks collision readiness against the expected non-empty level-0 tiles, moves a streaming
|
||||
anchor, and applies a transaction edit. The current-plugin switch-off baseline is the narrow
|
||||
`VoxelForge.Determinism.CrossPlatformDigest`; it passes but does not spawn an actor with the switch
|
||||
false, so it is a legacy-generator baseline rather than an actor-level A/B proof.
|
||||
|
||||
The game-thread asset adapter emits the immutable schema-v1 input and resolves it through
|
||||
`ResolvedWorldSnapshot::FromJson`; Core workers receive no UObjects. The Core route asks
|
||||
`World::desiredTiles` and `prioritizeTiles`, compiles and generates through Unreal tasks, and carries
|
||||
the generation `Epoch` through MPSC results. The shared payload builder, RealtimeMesh upload, and
|
||||
collision cook remain the final path. Workers observe `bShuttingDown`, and `EndPlay` drains active
|
||||
tasks. Preserve §8.10 streaming, cancellation, epoch, collision-handoff, and per-frame-budget rules.
|
||||
|
||||
Core `Vec3d` positions and tile keys are in voxel space, one voxel is 25 cm, and Core mesh vertex
|
||||
positions are already in centimetres. The bridge copies those mesh positions directly; player,
|
||||
anchor, query, and edit inputs convert world centimetres using the Core scale. High-level brushes
|
||||
that historically express dimensions and density strength in the plugin's 12.5 cm voxels scale
|
||||
radius, extent, falloff, and strength by 12.5/25 to preserve their physical effect. The low-level
|
||||
`ApplyModification` path expects active
|
||||
voxel units. Tile keys remain Core keys; only legacy material lookup maps Core tile spans into the
|
||||
existing strate chunk layout.
|
||||
|
||||
Carving drains in-flight Core calls, applies an increasing-sequence `applyEditTransaction`, advances
|
||||
the epoch, and regenerates the returned affected keys. This leaves a transaction boundary for later
|
||||
replication; no transport is implemented. Existing streaming anchors are included in Core desired
|
||||
tiles; the BuildHost shift check covers `CollisionOnly`. `FullVisual` is wired through the same
|
||||
anchor reconciliation but has not had an independent headless assertion. The owner-editor visual
|
||||
and physical play check remains the hand-off step. See
|
||||
[`Standalone/UNREAL_INTEGRATION_REPORT.md`](Standalone/UNREAL_INTEGRATION_REPORT.md) for evidence,
|
||||
known limits, and instructions.
|
||||
|
||||
### 8.18 Core world correction and biome gap — integration round 3
|
||||
**Status (2026-10-08):** the owner confirmed that Core runs in his game. His first visual review found
|
||||
two shape-world defects and two suspected streaming/feature defects. The finite origin room and its
|
||||
floor now follow the owner layout; the synthetic 256-voxel entry bore was removed, and invalid
|
||||
passage descriptors no longer create micro-landings. This is an intentional Core world-version
|
||||
change. The standalone viewer's old hashes are not compatible with this corrected field; the new
|
||||
BuildHost initial clipmap hashes match at W1/W8/W16:
|
||||
`density=a8bcb72d41092503`, `mesh=90f29ba15e24579f`, `payload=82c6b489e7bb7fd7`,
|
||||
`topology=7b99448a1f844f2c`.
|
||||
|
||||
The BuildHost actor proof now travels to the B-side landing and the surface strate. At each position,
|
||||
the desired visual tile-key set is complete, tile payload hashes match standalone Core, and adjacent
|
||||
same-LOD and transition faces have full coverage. Fine tiles compiled around the travelled player
|
||||
(960 at B and 640 at the surface position). The surface actor phase had extra stale/support keys in
|
||||
its loaded collection, so the check filters collision-only support and asserts the exact 2,553-key
|
||||
visual set; it does not claim every collision-support compile had quiesced. Null rendering still
|
||||
means this is streaming/payload evidence, not an Unreal screenshot.
|
||||
|
||||
`GetBiomeAtWorldLocation` now queries the copied owner biome evaluator in Core mode at the legacy
|
||||
12.5 cm query scale expected by that evaluator. It returns biome identity/data for the surface sample
|
||||
(`Biome_Surface_Plain`, index 2); this query does not add biome-driven terrain or decorations to the
|
||||
Core shape field. A full biome surface remains a separate feature. Recommended next design: make
|
||||
strate evaluation explicit in the resolved Core snapshot, with a deterministic `SurfaceWorld` path
|
||||
that consumes resolved biome/height parameters and uses fixed-order noise plus per-column caching.
|
||||
This is a medium-to-large port with controllable per-voxel cost and straightforward worker
|
||||
determinism. Routing SurfaceWorld through the copied legacy evaluator is a smaller fidelity bridge,
|
||||
but it duplicates the field boundary, costs roughly the legacy evaluation per sample, and inherits
|
||||
more float/platform constraints; keep it as a temporary compatibility route only if exact behavior
|
||||
is required before the port. See the round-3 table in the integration report for per-defect evidence.
|
||||
|
||||
Round 4 supersedes the SurfaceWorld status above: Core now has the temporary legacy density bridge
|
||||
described in §8.19. The native evaluator, water presentation, and other archetypes remain open work.
|
||||
|
||||
### 8.19 SurfaceWorld bridge and all-strate coverage — integration round 4
|
||||
|
||||
**Status (2026-10-08):** the copied owner evaluator is now called inside Core for samples whose
|
||||
resolved strate type is SurfaceWorld. The bridge covers Core density entry points used by the
|
||||
marching-cubes path: analytic, lattice, unclamped, and packed-run sampling. SurfaceWorld boxes fail
|
||||
closed as Mixed in the primitive-only box classifier so it cannot skip the biome heightfield. Core
|
||||
edit brushes are applied after the owner density result. The reader also retains SlabParams,
|
||||
MazeParams, VerticalShaftParams, and FloatingIslandParams in the standalone snapshot.
|
||||
|
||||
The owner/Core comparator found identical density hashes at LOD0–LOD3 in the matched-camera
|
||||
SurfaceWorld matrix and on a 686,000-sample full-resolution surface patch. At LOD0 the measured
|
||||
symmetric sampled surface distance was 0.0767 m mean / 1.0117 m max; at LOD2 it was 0.1737 m mean /
|
||||
5.264 m max with 63 unmatched sampled vertices. The viewer captures still show a visible mesh or
|
||||
presentation difference. Core does not emit the legacy water plane, biome material palette,
|
||||
decorations, or atmosphere from this density bridge.
|
||||
|
||||
The bridge is deterministic across the tested BuildHost W1/W8/W16 worker counts. It is intentionally
|
||||
not a platform-independent arithmetic port: it inherits the copied evaluator's float behavior. Its
|
||||
cost is substantial. A matched CPU viewer run with 1,294 tiles through LOD8 took 23.085 s wall and
|
||||
180.562 worker-seconds in Core, versus 23.914 s and 188.705 worker-seconds in the owner route. The
|
||||
Step29 Core baseline without this route took 0.785 s, but omitted the surface. Use the bridge as a
|
||||
fidelity fallback while a native heightfield with fixed-order noise and per-column biome/height
|
||||
caching is developed.
|
||||
|
||||
The probe table in the [integration report](Standalone/UNREAL_INTEGRATION_REPORT.md) covers all
|
||||
eight generator types, five passage types, four passage styles, and three transitions. These are the
|
||||
round-4 recommendations; the SurfaceWorld row is superseded by §8.20:
|
||||
|
||||
| Field | Recommended Core route | Reason and current gap |
|
||||
|---|---|---|
|
||||
| TunnelNetwork | Keep the native primitive representation; add missing density operators as measured. | Existing rooms, tunnels, and passage chains work, but the 16-tile probe still changed 2.17% of signs and had a 1.01 m mean sampled surface distance. |
|
||||
| FlatPlain and CrystalChamber | Native slab operator using the already copied slab parameters. | Floor, ceiling, columns, and crystal ceiling are a distinct low-cost structure; generic cave primitives do not express them. |
|
||||
| Maze | Native deterministic cell graph and corridor field using FMazeGenerationParams. | The legacy topology is seed-stable and compact; current generic caves changed 13.34% of signs and had a 5.92 m maximum sampled surface distance. |
|
||||
| SurfaceWorld | Keep the exact bridge as the Unreal runtime path until the standalone native evaluator is integrated and owner-reviewed. | The native fixed-scale heightfield, route plan and field sweep now exist in `Standalone/Core`; the plugin season/hash path and in-game review remain open. The bridge still lacks render-side water and biome materials. |
|
||||
| VerticalShafts | Native deterministic shaft/ledge field; use legacy fallback only as a short compatibility step. | Generic cave shapes produced no matching surface in the probe and failed the passage-centerline audit. |
|
||||
| FloatingIslands | Native island SDF with deterministic placement and smooth unions. | The current probe changed 95.75% of signs; an island layout is sparse and does not map well to room/tunnel primitives. |
|
||||
| Underwater | Reuse native TunnelNetwork geometry and add an explicit water render payload. | The density field can reuse cave geometry; water material/plane is separate from signed density and remains absent. |
|
||||
| Passage types and styles | Keep the generated control-point chains, but preserve each type's radius/floor contract and assert walkability. | The broad chain is already available. Worm and Cascading have solid centerline samples in the current Core audit. |
|
||||
| Strate transitions | Implement the three boundary functions in the Core layer field. | Gradient, Hard, and Interleaved produced different owner hashes but the same Core hash in the probe. |
|
||||
|
||||
The copied evaluator remains a compatibility route when exact density is required and no reviewed
|
||||
native manifest is attached. For low-cost discrete fields such as slabs, mazes, shafts, and islands,
|
||||
native support keeps the worker path explicit and avoids charging every sample for a second field
|
||||
implementation. BuildHost verifies the opt-in Unreal route and its 25 cm Core scale; it does not
|
||||
certify the missing archetypes.
|
||||
|
||||
**Standalone SurfaceWorld M1 progress (2026-10-09):** `WorldAssemblyManifest` evaluator 4 now
|
||||
serializes a finite-strate surface route with stable waypoint IDs, a 0.34 m × 1.76 m player capsule,
|
||||
a 15° grade limit and underfoot support constraints. The final composed field must pass a capsule
|
||||
sweep before a route candidate is accepted. Cliff shelves carry a two-Core-voxel minimum thickness.
|
||||
The route law applies to generation and automatic changes; player carve/fill edits remain unrestricted.
|
||||
At the end of round 4 this was standalone evidence, not yet connected to the settings/content-hash
|
||||
path. Round 5 connects the reviewed manifest to `UVoxelSettings`; see §8.20. The owner still needs to
|
||||
build and review it in his game.
|
||||
|
||||
The round-4 horizon audit traced the false low slab to the LOD8 three-dimensional marching-cubes cap
|
||||
surface sampled on a 256-voxel lattice; the Core mesh's cap classification also consulted the legacy
|
||||
owner height oracle. LOD8 now samples the planned cap directly as a 2D heightfield on a 128-voxel grid,
|
||||
clips cells at the ground and tile-Z limits, and keeps existing ceiling seam skirts. This preserves a
|
||||
closed cap surface rather than deleting its index run. The viewer defaults to a 60 FPS ceiling and
|
||||
accepts `--frame-limit=1..60`, including while its window is occluded. See
|
||||
[`Standalone/ASSEMBLER_M1_REPORT.md`](Standalone/ASSEMBLER_M1_REPORT.md) for the matched up-and-out
|
||||
captures, cap tile counts, determinism hashes, route sweep and query/collision readiness split.
|
||||
|
||||
### 8.20 Native SurfaceWorld manifest in Unreal — integration round 5
|
||||
|
||||
`UVoxelSettings` now owns optional canonical world-assembly JSON plus its Core digest. In the editor,
|
||||
`SourceWorldAssemblyManifestJson` selects the reviewed JSON and `ImportWorldAssemblyManifestJson`
|
||||
validates, canonicalizes and stores it in the cooked settings asset. Import rejects a recipe seed that
|
||||
differs from the effective settings seed. `GetCurrentSeasonContentHash()` includes the assembly
|
||||
digest only when `bUseVoxelForgeCore` is enabled; with Core disabled, it returns the existing season
|
||||
hash before reading optional assembly data. That keeps the legacy world identity stable on switch-off.
|
||||
|
||||
During Core initialization, `FVoxelForgeCoreAdapter` validates the manifest seed and requires each
|
||||
planned stratum to match exactly one resolved `SurfaceWorld` slot by stratum index and top/bottom
|
||||
chunk bounds. It inserts the canonical manifest and hash into the Core snapshot before the snapshot
|
||||
is moved to Core. A missing manifest leaves the existing cave-only Core layout untouched; malformed,
|
||||
wrong-seed, unmatched, duplicate or incomplete plans fail initialization. Core workers still receive
|
||||
only the immutable resolved snapshot, not UObjects.
|
||||
|
||||
Core mesh/collision ordering has one SurfaceWorld-specific guard: a collision-support tile requested
|
||||
outside the current desired set is promoted into the next desired rebuild, then deferred until Core
|
||||
region compilation admits it. This prevents an early isolated support job from racing the native
|
||||
region compile and publishing a tile whose hash differs from the standalone region snapshot. The
|
||||
existing `Epoch` and MPSC result paths remain in force, and collision bodies are cooked by Unreal's
|
||||
RealtimeMesh/Chaos route on the game side.
|
||||
|
||||
BuildHost passed the reviewed manifest `8933693d9560ba9f` (recipe `c37de78b759c0a4c`, plan
|
||||
`1ca84619c8600ee5`) with W1/W8/W16 determinism, cave streaming, anchor movement and carve. At the
|
||||
surface travel point `(37,165,-2011)` Core voxels, 2,553 expected visual hashes matched independent
|
||||
standalone Core output; the check covered 573 local tiles, 3,438 adjacent faces and 432 LOD
|
||||
transitions. Collision expectations covered 125 near tiles: all 36 non-empty cases had cooked bodies
|
||||
and all 89 empty cases had none. The native field sampler visited 4,096 points over 3,200 voxels and
|
||||
found three biome IDs. The complete pass log is under `Saved/BuildHost/HostProject/Plugins/VoxelForge/Saved/CoreWorldValidation/Logs/`.
|
||||
|
||||
The switch-off audit was a source-level branch/hash review, not a flag-off actor replay. It confirms
|
||||
the legacy generation branch and previous content hash remain unchanged when `bUseVoxelForgeCore`
|
||||
is false. Same-session BuildHost timing on the same 2,553 keys with hashes enabled was 96.816887 s
|
||||
native versus 197.346153 s through the legacy bridge, including region compilation (2.038× native
|
||||
speedup). This is serial headless tile generation. The final actor rerun passed in 9m20s with a
|
||||
3.61 GiB peak sampled private working set. The approximately 1.57 s full-view aim is still
|
||||
unmet: the last matched 529-tile LOD8 stream took 4.446 s. No viewer benchmark ran in round 5.
|
||||
|
||||
The owner handoff sets the manifest on the existing `DA_Settings` asset (seed 0), saves it, and
|
||||
enables `AVoxelWorld::bUseVoxelForgeCore`. Coordinates and expected surface/coast/cliff/cap views are
|
||||
in [`Standalone/ASSEMBLER_M1_REPORT.md`](Standalone/ASSEMBLER_M1_REPORT.md). BuildHost does not
|
||||
replace the owner's project build or visual walk; cap lighting and the pointed native ridge peaks
|
||||
remain owner review items.
|
||||
|
||||
### 8.21 Native SurfaceWorld height, brush and far-mesh corrections — integration round 6
|
||||
|
||||
Core tile keys and density coordinates use 25 cm voxels; the shared plugin mesher and
|
||||
`UVoxelStrateManager` retain the legacy 12.5 cm scale. The Core bridge now maps Core-local height
|
||||
queries to manager coordinates by 0.5 and maps returned bounds back by 2.0. Native content, water,
|
||||
landmark, decoration and atmosphere anchors use the same manifest `Heightfield2D` ground/cap data
|
||||
as the terrain. These conversions are gated by `bUseVoxelForgeCore`; the legacy branch keeps its
|
||||
prior units and behavior. `UVoxelMarchingCubesMesher` has an explicit geometry voxel size: Core sets
|
||||
25 cm, while legacy callers retain the 12.5 cm default. Material and biome lookups remain in their
|
||||
legacy coordinate space.
|
||||
|
||||
Blueprint-callable carve/fill and high-level brush dimensions cross the scale boundary at 0.5,
|
||||
including strength. Legacy diff modifications subtract their strength; Core edit evaluation adds its
|
||||
density offset, so the adapter negates the legacy strength once. Public carve forces the airward sign
|
||||
and fill forces the solidward sign. Low-level active-Core modification units remain Core units.
|
||||
|
||||
LOD8 far-stratum boundary repairs now sample crossings across a subdivided 4×4 coarse patch and only
|
||||
emit a patch when each sample has a source-field crossing. This replaces the former broad single
|
||||
quad. The BuildHost geometry audit keeps a two-cell maximum edge at LOD0/1 and allows four coarse
|
||||
cells at LOD7+ to include shallow far-LOD transitions while still rejecting the prior 5.66-cell
|
||||
horizon slab. It checks near-LOD normal direction and unit normals at every audited LOD. The mesher
|
||||
emits reversed triangle order (`0,2,1`) for RealtimeMesh; the audit reports that expected order
|
||||
separately from normal direction. The opt-in native heightfield path narrows vertical mesher rows to
|
||||
sampled ground/cap heights plus the Z bounds of intersecting type-2/type-4 manifest features. Hash captures and tiles
|
||||
with overlapping player edits or another strate keep the full mesh range. See round 6 of the M1
|
||||
report for per-kind streaming results and validation limits.
|
||||
|
||||
### 8.22 Core voxel scale, per-tile features and placement measurements — World Assembler M3 Round 5
|
||||
|
||||
The opt-in Core world has one physical voxel-size source on AVoxelWorld: CoreVoxelSizeCm, default
|
||||
25 cm and constrained to the supported 12.5–25 cm range for this integration. GetActiveVoxelSizeCm()
|
||||
is the runtime conversion point for Core coordinates. CoreTileVoxelCount is 32 voxels; tile length
|
||||
is derived as voxel size × 32 × 2^LOD. Core-off remains on the legacy 12.5 cm VOXEL_SIZE path.
|
||||
Owner/Core compatibility conversions use the resolved values and must not assume that a Core voxel
|
||||
is twice a legacy voxel.
|
||||
|
||||
The resolved physical size is rounded to microns and carried with tileVoxelCount in the immutable
|
||||
Core world configuration. Non-default Core size participates in plan and season identity. The
|
||||
default 25 cm value is omitted from the added identity suffix so recorded default SurfaceWorld and
|
||||
M2 recipe/fixture identities keep their prior form. Water remains fixed for the season; player
|
||||
carve/fill remains unrestricted. Collision support, clipmap keys and LOD stay in Core voxel units
|
||||
and derive centimetres at the adapter/mesh boundary.
|
||||
|
||||
Floating Islands builds a deterministic candidate feature list per tile using conservative
|
||||
inclusive bounds. Candidate IDs retain canonical feature order. A worker-scoped context lets density
|
||||
samples evaluate only the tile's candidate islands and connectors. If bounds cannot prove a feature
|
||||
irrelevant, it stays in the candidate list; future caves, shafts, pits and passages should use the
|
||||
same common tile-list builder. The Round 5 audit reports exact full-field/candidate equality and
|
||||
deliberately removes a feature as a negative control. This API does not change feature planning or
|
||||
field identity. Air-side pruning uses a conservative lower bound for the version-2 scaled root
|
||||
ellipsoids and their smooth-min reductions; a geometric AABB distance alone can overstate their
|
||||
positive density and change hashes outside the visible island bounds. BuildHost compared the staged
|
||||
owner plan's candidate field against the full scan at every queried sample and restored the Round 4
|
||||
island aggregate hashes.
|
||||
|
||||
The current configuration is per-world. A later per-strate size needs a physical-space field-query
|
||||
contract at boundaries, transition meshes with deterministic LOD ancestry, edit rasterization that
|
||||
can touch both resolutions without gaps or double application, and a stable, canonically ordered
|
||||
strate-ID-to-size map in plan/season identity. Border and LOD hashes must cover both sides; mixed-size
|
||||
edits and replays must match on every worker count. Until those rules exist, one world size is the
|
||||
supported contract.
|
||||
|
||||
Round 5 measured content evaluation per placement-grid cell and stationary manager update on staged
|
||||
owner settings. SurfaceWorld uses its known heightfield and made no density queries in the Core
|
||||
measurement. Floating Islands still uses the exact generic density march; a top-anchor shortcut
|
||||
measured slower and was reverted. The sampled authored entries produced no placements, so these
|
||||
figures measure anchor-search work and steady update overhead, not spawn/asset-instancing cost.
|
||||
See Standalone/Research/WorldAssemblerM3/Round5/REPORT.md for the test matrix, owner-plan hash proof,
|
||||
and content-placement measurements.
|
||||
|
||||
### 8.23 Shared assembler query domains — World Assembler S2 / M3 Round 6
|
||||
|
||||
Meshing, far-shell field queries and placement share `ScopedWorldAssemblyFeatureQueriesV1`.
|
||||
Each family prepares conservative candidates for an inclusive domain, including normal halos.
|
||||
Scopes borrow immutable recipe/plan pointers from the owning world/snapshot, live on the worker,
|
||||
restore the previous scope when nested, and fall back to full evaluation outside their domain.
|
||||
Queues, generation epochs, edit locks, streaming policy and canonical field arithmetic are unchanged.
|
||||
`candidateEvaluationUseful` is a cost hint resolved once per domain: the measured one-feature
|
||||
SurfaceWorld and four-island plans prefer the full evaluator. Larger plans use candidates. Validation
|
||||
can force candidates to prove the bounds even for a small plan; this never changes recipe identity.
|
||||
|
||||
The default family contract for caves, shafts, pits, passages and rivers is:
|
||||
|
||||
1. Declare conservative inclusive influence bounds, including blend, displacement and warp.
|
||||
Unknown influence retains the feature. Keep canonical operation order.
|
||||
2. If remote features can determine exact positive density, provide sound value-side certificates
|
||||
or evaluate them. Geometric AABBs alone cannot preserve arbitrary density values; island root
|
||||
ellipsoids retain S1's slope and smooth-min certificate. Optional union-group data belongs to the
|
||||
family proof, not to the engine's notion of Euclidean distance.
|
||||
3. Families with a proven air base may declare conservative solid-support bounds. The shared column
|
||||
union skips only proven air; solid-base caves and unknown support retain their ordinary march.
|
||||
Preserve the original sampling phase, crossing count, bisection and final interpolation.
|
||||
4. Audit full-scan versus candidates, halos and outside-domain fallback with equal hashes and a
|
||||
deliberately removed needed feature before enabling a new proof.
|
||||
|
||||
SurfaceWorld's biome-site search remains its bounded local lattice. Cached XY column evaluation
|
||||
uses finite feature candidates; types 1/3 affect every Z, while types 2/4 are neutral outside their
|
||||
validated boxes. Shelf source-column queries outside the scope use the full column evaluator.
|
||||
Core meshing prepares both native families, including cross-strate tiles. FarShells remains derived
|
||||
repair geometry with its existing connector/stratum filtering; its canonical field calls benefit
|
||||
from the same scope wherever their samples lie inside the domain.
|
||||
|
||||
Content snapshots declare support bounds once. Decoration cells and landmark columns prepare the
|
||||
shared domain with a gradient halo, and retain exact crossings. Core-off retains its legacy march.
|
||||
Validation compares complete placement transforms and canonical anchor residuals, and applies then
|
||||
clears an isolated populated HISM fixture under BuildHost null RHI. These CPU costs do not measure
|
||||
rendering or GPU upload. See the Round 6 report for results and limits.
|
||||
|
||||
### 8.24 Shared native far horizon — World Assembler M3 Round 7
|
||||
|
||||
The shared Core tile generator reads each family declaration from the immutable assembly snapshot.
|
||||
`WorldAssemblyPlanV1::strates` declares SurfaceWorld ground and cap profiles through `Heightfield2D`;
|
||||
`floatingIslands` declares bounded 3D features and has no planar ground/cap datum. This keeps the
|
||||
horizon rule in the shared worker path while allowing each family to provide the shape it can prove.
|
||||
|
||||
At LOD5 and above, an unedited SurfaceWorld tile builds its ground from the native heightfield on
|
||||
a globally aligned 128-voxel grid, using the tile step below 128. The cap remains on the 128-voxel
|
||||
grid so adjacent far levels share samples. Ground and cap are clipped to the tile-Z bounds and to
|
||||
`cap >= ground`; shared ground vertices replace nearby upward-facing coarse MC ground triangles,
|
||||
while existing ceiling seam skirts remain. At LOD7/8, a tile that intersects a native SurfaceWorld
|
||||
profile but intersects no island plan skips the derived stratum/connector repair
|
||||
shells: the exact native ground and cap already provide its exterior closure. Player-edited tiles
|
||||
keep the real edited field and repair path at every visible LOD. Islands-only and mixed island tiles
|
||||
keep the shared 3D field and far-shell path.
|
||||
|
||||
For the 3D connector repair path, LOD7/8 use the same 256-voxel axial station/ring spacing. LOD6 and
|
||||
nearer far-shell work retain 128-voxel stations. This reduces source-field probes while keeping a
|
||||
continuous repair mesh at the resolution used by the far tile. It does not alter canonical density
|
||||
arithmetic, feature order, water datum, `Epoch`, `ModsLock`, task ownership, collision policy, or the
|
||||
Core-off legacy route. All horizon work runs on the resolved immutable worker snapshot.
|
||||
|
||||
`TileGenerationStats` exposes classification, XY column, MC meshing, shell and horizon times, repair
|
||||
field calls, skipped repairs and replaced ground triangles. `farHorizonVersion` is part of the tile
|
||||
generation recipe and salts far mesh, payload and topology hashes at LOD5+; it does not salt density
|
||||
or LOD0–LOD3 identities. Increase it when changing far geometry again. Round 7's staged owner checks
|
||||
measure eight warmed keys per LOD and compare the shared LOD7/8 surface lattice against exact near-field
|
||||
density roots, including a 100 cm datum-offset negative control. See
|
||||
`Standalone/Research/WorldAssemblerM3/Round7/REPORT.md` for results and the visual evidence limits.
|
||||
|
||||
### 8.25 Continuous planar ceiling — World Assembler S4 / M3 Round 8
|
||||
|
||||
The cap remains a family declaration in `WorldAssemblyPlanV1::strates` and is evaluated by the shared
|
||||
Core heightfield path. `Heightfield2D::farSurfaceCapHeightSubvoxels()` clips only the rendered far
|
||||
surface to `(topChunkZ + 1) * 32` voxels, the owning strate's upper boundary; it leaves
|
||||
`Heightfield2D::point()` and its density values unchanged. `capHeightBounds()` reports conservative
|
||||
bounds for that clipped surface. Evaluator v5 opts into this render-surface contract; older serialized
|
||||
evaluators keep their recorded cap behavior. The clip joins the exact near density root at the owning
|
||||
boundary without changing LOD0–3 density hashes. Future families with planar roofs must declare their
|
||||
cap height and bounds through the family plan/provider contract; the shared tile generator consumes
|
||||
that declaration. FloatingIslands declares no planar roof, so open sky above a surface island is the
|
||||
expected view.
|
||||
|
||||
For each requested far SurfaceWorld XY column at LOD4–8, desired-tile selection adds cap-height
|
||||
bands only when they do not overlap an existing or already accepted 3D clipmap tile. The original
|
||||
clipmap remains a non-overlapping partition. Cap samples use a global XY lattice, are clipped to the
|
||||
owned strate boundary, tile Z bounds and `cap >= ground`, and share edges across tiles. LOD4 uses a 16-voxel cap step;
|
||||
LOD5–8 use 128 voxels. Core-off stays on its legacy path, and edits continue through the authoritative
|
||||
edited field. Runtime material sections are explicitly created/updated from their ground/cap index
|
||||
ranges so a cap-only far tile cannot rely on an auto-created section being present.
|
||||
|
||||
The far recipe version is 2. It salts mesh, payload and topology hashes for LOD4–8; density hashes
|
||||
and LOD0–LOD3 identities do not change. The owner audit counts requested cap bands and emitted
|
||||
positive-area bands, compares 128 shared cap points with the near density root at 2 cm tolerance,
|
||||
checks a 100 cm datum-offset negative control, and traces upward rays from route and camera points
|
||||
against emitted triangles. Round 8 records these counters, the matched captures, warmed BuildHost
|
||||
timings, clipmap overlap validation and remaining failures in
|
||||
`Standalone/Research/WorldAssemblerM3/Round8/REPORT.md`.
|
||||
|
||||
The Round 8 owner walk audit emitted every requested cap band at LOD4–8 (385/385), agreed with 128
|
||||
near roots to 0.0504 cm (2 cm gate), detected a 100 cm offset control, and hit the cap on all 2,520
|
||||
upward rays from 35 walk/camera points. No route ray missed the cap or clipmap boundary.
|
||||
|
||||
The far-cliff fluting is coarse marching-cubes geometry on steep faces: with 16 coarse cells per tile,
|
||||
LOD7 samples every 256 voxels (64 m at 25 cm) and LOD8 every 512 voxels (128 m). The native ground
|
||||
replacement removes only upward-facing coarse faces; near-vertical faces remain on that lattice.
|
||||
LOD7/8 shell repair is skipped for certified SurfaceWorld tiles, the renderer does not cull
|
||||
backfaces, and the strips predate S4 in S2/S3. Refining all steep faces would multiply far mesh work;
|
||||
S4 leaves that cost/quality tradeoff open.
|
||||
|
||||
## 9. Multiplayer model (listen-server first, dedicated-friendly)
|
||||
|
||||
> **Status: transport is DESIGN ONLY — nothing is networked in-tree yet** (no `Replicated`/`HasAuthority`/RPCs; a
|
||||
@@ -1092,10 +1447,12 @@ density or generate geometry.
|
||||
The world is a pure function of **(seed, strate layout)** (§8.4). So terrain is reconstructed identically
|
||||
on every peer from a tiny amount of shared state — it is **never streamed as geometry over the wire**:
|
||||
- Replicate the **effective seed + strate layout identity** ONCE (at join). For a cooked season the
|
||||
layout is the reviewed manifest, not a pool shuffle: peers must load the same season asset and compare
|
||||
`AVoxelWorld::GetCurrentSeasonContentHash()` before accepting play. For the legacy path the layout remains
|
||||
deterministic from the effective seed. Every peer then generates terrain locally; geometry never crosses
|
||||
the wire. The transport/RPC that performs this join handshake remains out of tree.
|
||||
layout is the reviewed season manifest; when Core is enabled with a native SurfaceWorld, the
|
||||
reviewed world-assembly digest is also part of `AVoxelWorld::GetCurrentSeasonContentHash()`. Peers
|
||||
compare that active content hash before accepting play. With Core disabled the optional assembly
|
||||
does not change the legacy hash. For the legacy path, layout remains deterministic from the
|
||||
effective seed. Every peer generates terrain locally; geometry never crosses the wire. The
|
||||
transport/RPC that performs this join handshake remains out of tree.
|
||||
- The **diff layer is the ONLY non-deterministic terrain state** (§3.9, [[voxelforge-difflayer-threading]])
|
||||
→ it is the only thing that must sync. Since carving is a minor feature, this traffic is small.
|
||||
|
||||
@@ -1117,7 +1474,7 @@ pending).** `AVoxelWorld::RegisterStreamingAnchor(Actor, Policy, RadiusChunks)`
|
||||
(BlueprintCallable) add an actor to `StreamingAnchors`. `UpdateChunksAroundPosition` prunes dead anchors +
|
||||
detects chunk crossings (rebuilds the desired set when any anchor crosses a level-0 boundary — same cadence
|
||||
as player movement, coalesced into one rebuild). `AddAnchorDesiredTiles` (inside `BuildDesiredTiles`, after
|
||||
the player clipmap + sheet ring) folds each anchor's Chebyshev box of **level-0** tiles into the SAME
|
||||
the player clipmap) folds each anchor's Chebyshev box of **level-0** tiles into the SAME
|
||||
`DesiredStamped`/`DesiredSorted` set (deduped vs the clipmap by stamp) → the existing delta cull releases an
|
||||
anchor's tiles automatically when it moves away / unregisters. Zero cost when no anchors (empty loop). The box
|
||||
defaults to a THIN shape (its chunk + 1 horizontal ring + 1 chunk below for ground safety, nothing above —
|
||||
|
||||
@@ -25,11 +25,18 @@ try to run anything.
|
||||
## Hard rules (these prevent real bugs — verify against code, don't assume)
|
||||
- **Density sign: negative = solid, positive = air** (MC convention at the mesher).
|
||||
`FVoxelModification::Strength` negative = carve. *The #1 source of confusion.*
|
||||
- **Density functions take VOXEL coords, not cm.** Conversions live in `VoxelTypes.h`.
|
||||
- **Density functions take VOXEL coords, not cm.** The legacy plugin field uses 12.5 cm voxels;
|
||||
the opt-in Core field uses 25 cm voxels and its own tile keys. One legacy 32³ chunk is 4 m.
|
||||
Conversions live in `VoxelTypes.h` and the Core bridge; do not mix scales in queries, anchors, edits,
|
||||
or surface lookups.
|
||||
- **Determinism:** all randomness = hash of (coord, seed, strateIndex). No RNG state — same seed ⇒
|
||||
same world. Player edits (diff layer) are the only non-deterministic overlay.
|
||||
- **Core world invariants:** Core density keeps the same sign (negative solid, positive air), but Core
|
||||
voxel coordinates are 25 cm. Worker-count changes must preserve hashes; Core world edits are
|
||||
intentional world-version changes and must retain finite, walkable strate entrances and exits.
|
||||
- **Async safety:** worker tasks only READ Generator/Mesher, must check `bShuttingDown`, and return via
|
||||
`ProcessQueue` (must stay `EQueueMode::Mpsc`). `EndPlay` blocks on `ActiveTaskCount → 0`.
|
||||
the result queues — `ProcessQueue` and its `CriticalProcessQueue` / `EditedProcessQueue` /
|
||||
`NearProcessQueue` siblings (all must stay `EQueueMode::Mpsc`). `EndPlay` blocks on `ActiveTaskCount → 0`.
|
||||
- **Carry the `Epoch`** through any new async path (stale results are dropped on mismatch).
|
||||
- **Don't "optimize" the perf invariants** in ARCHITECTURE `§8.10` — the `thread_local` box-valid
|
||||
caches, two-pass MC loop, SSE noise, and clipmap streaming are intentional.
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
> as code is edited, so trust the **symbol name** first and the line as a hint.
|
||||
>
|
||||
> Plugin root: `Source/VoxelForge/` · ~8,300 lines of C++ across 25 files.
|
||||
> Comments in the code are mixed **French + English**. UE module = `VoxelForge` (Runtime).
|
||||
> Comments in the code are mixed **French + English**. UE modules: `VoxelForge` and
|
||||
> `VoxelForgeCore` (Runtime), plus `VoxelForgeEditor` (Editor).
|
||||
|
||||
---
|
||||
|
||||
@@ -28,7 +29,7 @@ A **density-field voxel terrain** plugin for Unreal Engine, built around undergr
|
||||
### Core terminology
|
||||
| Term | Meaning |
|
||||
|------|---------|
|
||||
| Chunk | 32×32×32 voxels (`CHUNK_SIZE`). Voxel = 25 cm (`VOXEL_SIZE`). |
|
||||
| Chunk | 32×32×32 voxels (`CHUNK_SIZE`). Voxel = 12.5 cm (`VOXEL_SIZE`), so a chunk is 4 m. |
|
||||
| Density | Scalar field. `< 0` solid, `> 0` air, `0` = surface (`IsoLevel`). |
|
||||
| Strate | Vertical layer of the world, stacked downward. Has its own generator + params. |
|
||||
| Generator type | Archetype per strate: `TunnelNetwork`, `FlatPlain`, `CrystalChamber`, `Maze`, `SurfaceWorld`, `VerticalShafts`, `FloatingIslands`, `Underwater`. See §8. |
|
||||
@@ -54,9 +55,12 @@ A **density-field voxel terrain** plugin for Unreal Engine, built around undergr
|
||||
▼
|
||||
UVoxelGenerator::GetDensityAt(x,y,z) ← THE density entry point
|
||||
│ asks StrateManager which strate/params/generator-type applies
|
||||
├─ TunnelNetwork → GetDensityWithParams() (rooms+tunnels+ops+worms+seal+passages)
|
||||
├─ builds the chunk's operator stack once per chunk (VF_BuildOpStackForChunk;
|
||||
│ editor composer / cooked-season recipe stacks first) → every archetype
|
||||
│ TunnelNetwork/Underwater → fused evaluator = GetDensityWithParams()
|
||||
│ └─ VoxelCaveMorphology::BuildChunkCache + EvaluateSDFCached (room/tunnel SDF)
|
||||
├─ FlatPlain / CrystalChamber → GetSlabDensity() (floor+ceiling+columns+seal+passages)
|
||||
│ others → FVoxelOpStack::EvalMC (source → modifiers → structural posts)
|
||||
├─ + disturbances + structural tail (landing floor, tunnel core, XY seal)
|
||||
└─ + UVoxelDiffLayer::GetDensityOffset() (player carve/fill)
|
||||
│
|
||||
▼ FVoxelMeshData (verts/tris/uvs/normals)
|
||||
@@ -73,17 +77,55 @@ what generator type, and is there a passage/elevator SDF near here?"
|
||||
|
||||
Paths relative to `Source/VoxelForge/`. `Public/` = headers, `Private/` = impl.
|
||||
|
||||
### 3.0 Human-scale generation contract (2026-09-06)
|
||||
`VoxelStrateTypes.h` is now authored against the confirmed player capsule, not against naked voxel
|
||||
counts: one voxel is **0.25 m**, the capsule is **1.36 voxels / 0.34 m radius** and **7.04 voxels /
|
||||
1.76 m tall**, and the default strate envelope is **8 chunks / 256 voxels / 64 m**. Core defaults are
|
||||
therefore: room diameters **8-20 m**, origin hub **24 m**, room cells **32 m**, round tunnel bores
|
||||
**3-4 m**, maze cells **16 m** with a **6.25 m nominal bore** (**3.125 m** radius; **3.0625 m**
|
||||
worst-case usable floor after roughness), slab open span **22.4 m**, shaft diameters
|
||||
**4-7 m**, island diameters **12-24 m**, and ordinary inter-strate bores **3-4 m**. These are
|
||||
different derivations, not a global multiplier: passage widths follow the 2.5-4 m walking/fight
|
||||
target, room/cell spacing preserves a rock web, and the 64 m vertical envelope reserves space for
|
||||
tall rooms, surface sky, and vertical traversal.
|
||||
### 3.0 Human-scale generation contract (2026-10-01)
|
||||
`VOXEL_SIZE` is **12.5 cm**; 32³ voxels make each chunk **4 m**. The gameplay capsule remains
|
||||
**0.34 m radius / 1.76 m tall** (**2.72 / 14.08 voxels**). The default strate envelope is
|
||||
**16 chunks / 512 voxels / 64 m**; serialized owner strate assets that still say 8 chunks must be
|
||||
changed to 16. Research-field dimensions are authored in metres and converted at runtime. The live
|
||||
strate generator continues to read voxel-valued parameters from its assets, so those authored
|
||||
dimensions become half as large in metres unless their asset values are doubled.
|
||||
|
||||
The intended metre-scale families remain: room diameters **8-20 m**, origin hub **24 m**, room cells
|
||||
**32 m**, round tunnel bores **3-4 m**, maze cells **16 m** with a **6.25 m nominal bore** (**3.125 m**
|
||||
radius; **3.0625 m** worst-case usable floor after roughness), slab open span **22.4 m**, shaft
|
||||
diameters **4-7 m**, island diameters **12-24 m**, and ordinary inter-strate bores **3-4 m**. Source
|
||||
new fixed physical dimensions are authored in metres and converted to voxel counts at the boundary.
|
||||
The owner's voxel-valued live strate assets are not migrated by C++: unchanged saved values therefore
|
||||
produce half-size rooms, tunnels, features and noise periods in metres. See the step-3 migration list
|
||||
in the handoff report for the exact `DA_Settings` fields and values.
|
||||
|
||||
**Owner asset migration (no packages were edited):** preserve `DA_Settings` distances by changing
|
||||
`ViewDistanceXY 32→64`, `ViewDistanceUp/Down 10→20`, `PawnCollisionRadiusChunks 2→4`,
|
||||
`StrateViewMarginChunks 6→12`, `ClipRadius 6→12`, `RenderDistanceChunks 1536→3072`,
|
||||
`EdgeSealThickness 128→256 voxels`, decoration radius/near radius `12/6→24/12 chunks`, region
|
||||
size `8→16 cells`, spacing/far spacing `8→16 voxels`, march step `4→8 voxels`, column depth
|
||||
`320→640 voxels`, `OriginSpineRadius 28→56 voxels`, `MaxBrushRadius 30→60 voxels`, and
|
||||
`SkirtCells 1→2`. If the density volume is enabled, set resolution `256→512` and debug radius
|
||||
`12→24 cells`; it is currently disabled. Keep `MaxClipLevel=6` and zero-valued disabled/unbounded
|
||||
limits unchanged; double finite world radii and gaps, and multiply finite voxel-volume caps by 8.
|
||||
The loaded `Crystal Cave` slot in `DA_Strate3` is 64 chunks tall; use 128 chunks to preserve its
|
||||
former 512 m span. Its voxel-authored live-generator lengths remain half-size until that asset is
|
||||
re-authored; the editor-only research field is the step-3 target.
|
||||
|
||||
Step-3 six-worker scale benchmark (idle machine, `Saved/Research/step3/scale/step3_scale_cacheperf_v1`):
|
||||
the 519³ field took **7.133834–7.825292 s** across three runs (median **7.347496 s**); the 259³
|
||||
field took **1.098521–1.194640 s** (median **1.169567 s**). The inclusive sample lattices span
|
||||
64.75 m and 32.25 m between end samples respectively (the requested approximately 64 m / 32 m
|
||||
fields). Both runs were deterministic across three trials. This records evaluator time only, not game
|
||||
streaming. Six-worker 4³-brick proof, sound-bound audit, seam injection, and 1-vs-6-worker hashes
|
||||
pass. Reusing exact detail-lattice hash corners across adjacent mixed bricks reduced the earlier
|
||||
step-3 259³ median from 1.502908 s to 1.169567 s; the separate step-2f 600 ms worst-profile timing
|
||||
bar still does not pass at the finer voxel size.
|
||||
|
||||
The opt-in Core route resolves physical voxel size from `AVoxelWorld::CoreVoxelSizeCm` (default
|
||||
**25 cm**) and keeps **32 voxels per Core tile**. `GetActiveVoxelSizeCm()` is the runtime conversion
|
||||
point for coordinates, mesh vertices, collision, streaming, LOD, content, water, atmosphere, and
|
||||
brushes; `WorldConfiguration::voxelSizeMicrons` plus `tileVoxelCount` carries the resolved values
|
||||
through Core. A nondefault size participates in Core snapshot/season identity, while 25 cm preserves
|
||||
the recorded default hashes. Core-off continues using the legacy 12.5 cm grid. This is currently a
|
||||
per-world setting; per-strate sizes still need physical-coordinate sampling, transition stitching,
|
||||
cross-boundary edit rasterization, per-strate LOD agreement, and a canonically ordered size map in the
|
||||
hashed identity.
|
||||
|
||||
The reachable roughness envelope is `roughness * VOXEL_NOISE_SCALE * 1.5`, or `roughness * 1.875`
|
||||
voxels. The new defaults keep it below the relevant feature radius: **0.625** of the minimum
|
||||
@@ -112,33 +154,43 @@ remains `0`, and lateral regions remain gated off.
|
||||
### 3.1 Module & build
|
||||
| File | Role |
|
||||
|------|------|
|
||||
| `../../VoxelForge.uplugin` | Plugin manifest. Runtime module `VoxelForge` plus the Editor-only `VoxelForgeEditor` commandlet module. Beta. |
|
||||
| `VoxelForge.Build.cs` | Deps: Core, CoreUObject, Engine, **GameplayTags**, **RealtimeMeshComponent**. |
|
||||
| `Tools/VoxelForgeTest.ps1` | The single guarded test entry point. `-Scenario canonical|owner|probe|perf|tests|surface-fall|crossing|gate-stress|parity|horizon|horizon-render|determinism`, optional `-Build`, `-Assets owner|default`, `-Cvars`, absolute `-Out`, `-Label`, `-HorizonLevel`, `-HorizonRenderDistanceChunks`, and `-SharedDDC`; stages the host/assets, isolates user/DDC/config state, serializes one Unreal launch at a time, enforces the 8 GiB/30-minute guards, and emits compact `result.json`/`summary.txt` plus full logs. Horizon traces report desired/loaded tiles, resident stream geometry, worker seconds, steady wall time, and peak private memory; `horizon-render` captures the owner view; `determinism` compares per-tile hashes across the default CRT path and `-voxel.CrtFma3=0`. |
|
||||
| `../../VoxelForge.uplugin` | Plugin manifest. Runtime modules `VoxelForge` and `VoxelForgeCore`, plus the Editor-only `VoxelForgeEditor` commandlet module. Beta. |
|
||||
| `VoxelForge.Build.cs` | Deps: Core, CoreUObject, Engine, **GameplayTags**, **RealtimeMeshComponent**, `VoxelForgeCore`. `AVoxelWorld::bUseVoxelForgeCore` opts into the Core runtime route; it defaults off. |
|
||||
| `../../VoxelForgeCore/VoxelForgeCore.Build.cs` | C++20 runtime module. Reads `Standalone/Core/core_sources.txt`, validates one include shim per canonical source, and fails UBT on missing, extra, or mismatched shims. No unity/PCH; private copied implementation stays under `vfcore::detail`. |
|
||||
| `../../VoxelForgeCore/Private/Manifest/VFCore_*.cpp` | Eighteen one-source UBT shims that include canonical files under `Standalone/`; they do not copy Core implementation. The API-boundary row includes `Core/src/VoxelForgeCore.cpp` directly. |
|
||||
| `../../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/src/FloatingIslandsInternal.h` | Canonical standalone/Core floating-island planner, geometry-versioned field, manifest serializer, influence bounds, capsule-route checks, and thickness audit. |
|
||||
| `Tools/VoxelForgeTest.ps1` | The single guarded test entry point. `-Scenario canonical|owner|probe|perf|tests|surface-fall|crossing|gate-stress|visual-edit|visual-lod|visual-gap-walk|visual-gap-inject|dig-motion|parity|horizon|horizon-render|determinism|region-eval|step2d|step2e|step2f|step2f-capture|step3-scale|step3-game`, optional `-Build`, `-Assets owner|default`, `-Cvars`, absolute `-Out`, `-Label`, `-HorizonLevel`, `-HorizonRenderDistanceChunks`, `-Step2fProfile=0..7`, `-Step2fCaptureView=inside|wall-closeup`, `-ParityExportSize=32|64|128`, `-ParityExportTile=x,y,z`, `-SharedDDC`, `-SampleDensityStacks`, `-StackSampleIntervalUs`, and `-RegionHashProbe -RegionWorkers N -RegionReferenceHash HASH`; stages the host/assets, isolates user/DDC/config state, serializes one Unreal launch at a time, enforces the 8 GiB/30-minute guards, waits for 6 GiB free RAM before launches, and emits compact `result.json`/`summary.txt` plus full logs. Parity can render its game leg with `-EnableRender` and compares the selected export region against the exact streamed tile hashes. `visual-gap-walk` records camera-frustum surface-point coverage at 6000 cm/s; `visual-gap-inject` proves an early component hide is detected; `dig-motion` logs per-frame vertical velocity, gate, support readiness, and movement mode. `-Cvars @{ 'voxel.TestGapAuditLegacyHandoff'=1 }` replays the old visibility handoff for A/B audit baselines. Horizon traces report desired/loaded tiles, resident stream geometry, worker seconds, steady wall time, and peak private memory; `horizon-render` captures the owner view; `step2f-capture` selects one of eight parameter sets, finds a positive-density camera point and aims at a sampled rock wall for an inside or wall-closeup view, then saves it under `Saved/Research/step2f/captures` (pass the persistent render DDC with `-SharedDDC`); `determinism` compares per-tile hashes across the default CRT path and `-voxel.CrtFma3=0`; `region-eval` compares the live path with the default-off editor chunk program on the 259³ lattice and five-seed probe set; `step2d` retains the earlier independent field; `step2e` retains its earlier benchmark; `step2f` compares frozen step 2e and the coherent field for all eight profiles on the same 259³ ring, applies geometry gates, and checks sound bounds, seams, and 1-vs-6-worker hashes. `-RegionHashProbe -RegionWorkers N -RegionReferenceHash HASH` checks a full-ring hash at another partition count. `-SampleDensityStacks` captures profiles, and `-StackSampleIntervalUs` sets the sampling interval.
|
||||
| Step-3 scale and game validation | `step3-scale` measures the same 519³ and 259³ six-worker lattices at 12.5 cm. The cached medians are 7.347496 s and 1.169567 s respectively; determinism and brick sound-proof audits pass. `step3-game` routes in-game generation through the brick evaluator and records tile timings, spawn fill, moving frames, and profile-switch rebuilds. The visible D3D12 run measured tile generation p50/p95/max 3.326/31.045/169.867 ms, full-detail L0 fill 12.369 s for 2,765 tiles, and moving-frame p50/p95/p99/max 4.856/8.922/13.090/80.575 ms. Switching profiles rebuilt 5,815 loaded tiles. Both high-resolution screenshot requests were logged, but the harness found only one fresh PNG and it is a blank pale viewport; no lit comparison capture is accepted. Raw output: `Saved/TestHarness/step3_game_visible_final`; scale output: `Saved/Research/step3/scale/step3_scale_cacheperf_v1`. |
|
||||
| `Public/VoxelForgeModule.h` / `Private/VoxelForgeModule.cpp` | `FVoxelForgeModule` boilerplate (Startup/Shutdown just log). |
|
||||
| `Source/VoxelForgeEditor/VoxelForgeEditor.Build.cs` / `Private/VoxelForgeExploreCommandlet.*` | Editor/commandlet-only `-run=VoxelForgeExplore`; owns bounded render/`VF_` walk/OBJ export orchestration and is not linked by Game/Shipping. |
|
||||
| `Source/VoxelForgeEditor/VoxelForgeEditor.Build.cs` / `Private/VoxelForgeExploreCommandlet.*` | Editor/commandlet-only `-run=VoxelForgeExplore`; schema-v1 owner-asset export check and BuildHost Core actor tests. `-coreworldassembly=PATH` supplies a reviewed manifest transiently to `-coreworldtest=1`; it does not save a data asset. `FStandaloneCoreAdapterScheduler` runs Core work at 1/8/16 workers; `VerifyStandaloneCoreAdapter` checks hashes and walkability. Existing bounded render/`VF_` walk/OBJ orchestration and research options remain editor/commandlet-only and are not linked by Game/Shipping. |
|
||||
| `Source/VoxelForge/Public/VoxelSettings.h` / `Private/VoxelSettings.cpp` | `UVoxelSettings` owns optional canonical world-assembly JSON and its Core digest. The editor file picker plus `ImportWorldAssemblyManifestJson` imports the reviewed manifest into the cooked settings asset and validates the recipe seed against the effective settings seed. |
|
||||
| `Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp` | Validates the optional manifest against each resolved `SurfaceWorld` slot (unique strate index and exact top/bottom chunks), then places canonical `world_assembly` JSON and its hash in the Core snapshot. Invalid plans fail Core initialization. |
|
||||
| `Source/VoxelForgeEditor/Private/VoxelForgeRegionEvalExperiment.*` | Six-worker lattice drivers for step 2c through step 2f. The original path still compares live `GetDensityAt` with the editor chunk program. Research modes assign contiguous batches dynamically so surface-heavy work balances across workers; steps 2e and 2f use 4³ proof bricks and stage each evaluated brick before copying contiguous X rows, while step 2d retains 8³. Each skip requires a macro Lipschitz bound plus the complete final-detail supremum; uncertain bricks retain every 12.5 cm detail sample. Timings split classification, point evaluation, and uniform fill; step 2f compares all profiles and hashes workers 1 and 6. |
|
||||
| `Public/VoxelRedefinedDensityField.h` / `Private/VoxelRedefinedDensityField.cpp` | Two default-off `WITH_EDITOR` research fields, selected by `voxel.ResearchRedefinedDensityField` (step 2d) and `voxel.ResearchParameterizedCave` (steps 2e/2f). Step 2f retains a metre-scaled 2e baseline for its rejection audit. New profiles use strongly jittered, individually rotated elliptical rooms capped against nearest neighbors and curved cubic-Bezier passages with deterministic centerline and width variation along each edge. Room heights and feature rolls vary by cell; ledges, occasional pillars, stalactites/stalagmites, basin floors, and bounded hanging masses remain features. Point and brick detail sampling share independently rotated 96/48/24-sample bands and a smooth ridge response, preserving 12/6/3 m wavelengths at 12.5 cm per sample. The parameterized mesher evaluates the exact field through prepared 4³ bricks and the same sound skip proof in commandlet and editor-game meshing. In-editor and in-game profile changes invalidate and rebuild all loaded tiles. In an editor game with this field enabled, sealed-solid, out-of-layout, and outer-classifier verdicts are declined without changing their console switches. |
|
||||
| `Private/Tests/VoxelForgeParameterizedGenerationTest.cpp` | Separate three-seed functional sweep for the step 2f parameterized field; passage landing air/floor brackets and arrival-to-exit walking routes are checked without changing the live-world tests. |
|
||||
| `Private/Tests/VoxelForgeRedefinedDensityFieldTest.cpp` | Three-seed functional sweep for the research field: authored passage endpoints must bracket open air and solid floor, and each tested strate must have a walkable arrival-to-exit route. |
|
||||
| `Public/VoxelGeometryHash.h` | Header-only geometry digest shared by commandlet export and runtime tile-hash dump; hashes mesh counts and the vertex/normal/UV/color/triangle arrays with the historical CRC order. |
|
||||
| `Public/VoxelStats.h` / `Private/VoxelStats.cpp` | `stat VoxelForge` DWORD counters for tile classification, skipping, meshing, operator-stack verdicts, and cave-bail diagnosis. The former ambiguous `Cave Bail Not Op Stack` is split into `Sole Slot`, `Boundary Tile`, `No Layout`, and late `Recheck` counters, so each increment names one guard/context. |
|
||||
| `Public/VoxelStats.h` / `Private/VoxelStats.cpp` | `stat VoxelForge` DWORD counters for tile classification, skipping, meshing, operator-stack verdicts, and cave-bail diagnosis. Each `Cave Bail *` counter names one guard; `Cave Bail No Stack` counts slots with no buildable stack (no definition, or a degenerate strate). |
|
||||
|
||||
### 3.2 Foundational types — `Public/VoxelTypes.h` (no UClass, everyone includes it)
|
||||
| Symbol | Line | Notes |
|
||||
|--------|------|-------|
|
||||
| `CHUNK_SIZE` (32), `CHUNK_SIZE_SQUARED`, `CHUNK_VOLUME` | 19-21 | Chunk dimensions. (64³ tried for fewer draws → reverted: streaming too bursty. fps is fixed render-side instead.) |
|
||||
| `VOXEL_SIZE` (25.0f cm) | 23 | World scale. |
|
||||
| `EVoxelFace` enum + `GetFaceDirection` / `GetFaceNormal` | 33-61 | 6 cube faces. |
|
||||
| `WorldToChunkCoord` / `WorldToLocalCoord` / `ChunkToWorldPos` | 74-104 | Coord-space conversions (handle negatives via floor/positive-modulo). |
|
||||
| `LocalToIndex` / `IndexToLocal` / `IsValidLocalCoord` | 107-131 | Flat-array 3D↔1D indexing. |
|
||||
| `VoxelMath::IsFiniteFast` / `IsFinite` | ~24-53 | Exact IEEE bit test (`voxel.FastIsFinite` A/B switch). |
|
||||
| `VoxelMath::DetSinCos` / `DetSin` / `DetCos` | ~63-195 | **The only sin/cos allowed where the world is decided** (2026-09-16, `10fd0b6`). Fixed double period reduction and float polynomial, no CRT: `FMath::Sin/Cos` are CRT `sinf/cosf`, whose FMA3 variant is picked by CPU. Pinned by `VoxelForge.Determinism.DetSinCos`; `-voxel.CrtFma3=0` flips the CRT path to prove it. |
|
||||
| `SmoothStep01` | 140 | 3x²-2x³ — used everywhere for blends. |
|
||||
| `VOXEL_NOISE_SCALE` (1.25f) | 147 | Rescales UE PerlinNoise3D to ~[-1,1]. |
|
||||
| `EVoxelTileClass` enum (`Mixed`/`AllSolid`/`AllAir`) | — | T1.d verdict. **MOVED here from `VoxelGenerator.h` 2026-07-27** so `VoxelDensityOp.h` can share it without a UCLASS dependency. A false `AllSolid`/`AllAir` is a HOLE; a false `Mixed` only costs CPU. |
|
||||
| `FVoxelMeshData` struct | 157-173 | Mesher output (Vertices/Triangles/UVs/Normals/**Colors**). Plain C++, not USTRUCT. `Colors` = F6 material masks (R=dominant biome palette, G=slope, B=border blend weight, A=neighbour biome palette). §8.15. |
|
||||
| `CHUNK_SIZE` (32) | 211 | Chunk dimensions. 64³ would cut draw calls but makes streaming too bursty; fps is fixed render-side instead. |
|
||||
| `VOXEL_SIZE` (12.5f cm) | 213 | World scale; one 32³ chunk is 4 m. |
|
||||
| `EVoxelFace` enum | 266 | 6 cube faces. |
|
||||
| `WorldToChunkCoord` | 287 | World cm → chunk coord (handles negatives via floor). |
|
||||
| `VoxelMath::IsFiniteFast` / `IsFinite` | 24 / 45 | Exact IEEE bit test (`voxel.FastIsFinite` A/B switch). |
|
||||
| `VoxelMath::DetSinCos` / `DetSin` / `DetCos` | 63 / 181 / 189 | **The only sin/cos allowed where the world is decided** (2026-09-16, `10fd0b6`). Fixed double period reduction and float polynomial, no CRT: `FMath::Sin/Cos` are CRT `sinf/cosf`, whose FMA3 variant is picked by CPU. Pinned by `VoxelForge.Determinism.DetSinCos`; `-voxel.CrtFma3=0` flips the CRT path to prove it. |
|
||||
| `SmoothStep01` | 306 | 3x²-2x³ — used everywhere for blends. |
|
||||
| `VOXEL_NOISE_SCALE` (1.25f) | 313 | Rescales UE PerlinNoise3D to ~[-1,1]. |
|
||||
| `EVoxelTileClass` enum (`Mixed`/`AllSolid`/`AllAir`) | 231 | T1.d verdict. Lives here so `VoxelDensityOp.h` can share it without a UCLASS dependency. A false `AllSolid`/`AllAir` is a HOLE; a false `Mixed` only costs CPU. |
|
||||
| `FVoxelMeshData` struct | 365-400 | Mesher output (Vertices/Triangles/UVs/Normals/**Colors**). Plain C++, not USTRUCT. `NumSurfaceVertices` marks the real isosurface prefix before LOD seam skirts are appended; visibility audit witnesses sample only this prefix. `NumSkirtTriangles` counts appended render skirts for collision-cook profiling without changing indices. `Colors` = F6 material masks (R=dominant biome palette, G=slope, B=border blend weight, A=neighbour biome palette). §8.15. |
|
||||
|
||||
### 3.2b Density operator stack contract — `Public/VoxelDensityOp.h` (plain C++, no UHT)
|
||||
**Production path behind a per-strate opt-in** — the stack is built/evaluated by `GetDensityAt` for
|
||||
all eight archetypes when `bUseOperatorStack` is enabled, while the legacy `switch` remains the
|
||||
reference path for opt-out strates. See [OPSTACK-PLAN.md](OPSTACK-PLAN.md) for the decomposition.
|
||||
**The production density path** — `GetDensityAt` builds and evaluates a stack for every strate of
|
||||
all eight archetypes; there is no other path. See [Docs/archive/OPSTACK-PLAN.md](Docs/archive/OPSTACK-PLAN.md) for the decomposition.
|
||||
|
||||
| Symbol | Notes |
|
||||
|--------|-------|
|
||||
@@ -159,33 +211,29 @@ manager passage post, **moved here 2026-07-27** so the generator and
|
||||
the operator stack share ONE copy. The XY helper is a radial smooth ramp in actor-space and is a
|
||||
true no-op when `WorldRadiusVoxels == 0`; `VF_XYEdgeSealForcedMarginOverBox` is the shared sound
|
||||
proof for `ClassifyBox` and T1.d's global shell skip. `VoxelGenerator.cpp` keeps same-named
|
||||
`static FORCEINLINE` forwarders for the original three helpers; the new edge helper is called
|
||||
directly. **Convention: INTERNAL (positive = solid); `VF_ApplyXYEdgeSealMC` is the output wrapper.**
|
||||
`static FORCEINLINE` forwarders for `ApplyBoundarySeal` and `ApplyOriginSpine`; the passage and
|
||||
edge helpers are called directly. **Convention: INTERNAL (positive = solid); `VF_ApplyXYEdgeSealMC` is the output wrapper.**
|
||||
|
||||
### 3.2d Operator stack — `Public/VoxelDensityOpStack.h` + `Private/VoxelDensityOpStack.cpp`
|
||||
⚠️ **Feeds the game, behind a per-strate opt-in** (Phase 1 step 3). `GetDensityAt` builds the stack
|
||||
in its per-chunk refetch block and evaluates it *instead of* the `switch` only when
|
||||
`UVoxelStrateManager::UsesOperatorStackForChunk` says so — strate ticked `bUseOperatorStack` **and**
|
||||
archetype in the ported list, which is now **all 8 of 8**: Maze, FlatPlain, CrystalChamber,
|
||||
SurfaceWorld, VerticalShafts, FloatingIslands, TunnelNetwork, Underwater. A strate that has not
|
||||
ticked the box still takes the `switch`, unchanged — the flag is the only thing that switches paths.
|
||||
**`ClassifyTile` IS wired now**, for CAVE archetypes only and behind the same per-strate opt-in:
|
||||
where it used to `return Mixed` without a call, it builds the strate's stack through the *same*
|
||||
factory `GetDensityAt` uses (`VF_BuildOpStackForChunk`) and folds `ClassifyBox`. SurfaceWorld and
|
||||
bedrock gaps keep their hand-written proofs — the exact-lattice column test is better than any box
|
||||
bound. Guards, all failing to `Mixed`: one cave slot per tile, no mixed cave/surface/gap tile, the
|
||||
opt-in true on *every* chunk the box touches, the params **bit-identical** across every chunk coord
|
||||
the box touches (blended transition bands make one stack unable to represent the tile — `AUDIT §C2`),
|
||||
a 27-chunk-coord cap, and the disturbances folded in by hand since they are applied after the stack.
|
||||
⚠️ **This IS the game's generation path.** `GetDensityAt` builds the stack in its per-chunk refetch
|
||||
block for every strate — **all 8 archetypes**: Maze, FlatPlain, CrystalChamber, SurfaceWorld,
|
||||
VerticalShafts, FloatingIslands, TunnelNetwork, Underwater. An editor composer candidate or a
|
||||
cooked-season recipe supplies its own stack; otherwise (or if that stack cannot be built) the
|
||||
archetype's native stack is used. A degenerate (zero-height) strate has no stack and is open air.
|
||||
`UVoxelStrateManager::UsesOperatorStackForChunk` only says whether a slot has a known archetype.
|
||||
**`ClassifyTile`**, for CAVE archetypes, builds the strate's stack through the *same* factory
|
||||
`GetDensityAt` uses (`VF_BuildOpStackForChunk`) and folds `ClassifyBox`. SurfaceWorld and bedrock
|
||||
gaps keep their hand-written proofs — the exact-lattice column test is better than any box bound.
|
||||
Guards, all failing to `Mixed`: one cave slot per tile, no mixed cave/surface/gap tile, a stack on
|
||||
*every* chunk the box touches, the params **bit-identical** across every chunk coord the box touches
|
||||
(blended transition bands make one stack unable to represent the tile — `AUDIT §C2`), a
|
||||
27-chunk-coord cap, and the disturbances folded in by hand since they are applied after the stack.
|
||||
Brute-forced end to end by `VoxelForge.OpStack.ClassifyTileSoundness`.
|
||||
⛔ Never run both paths in one world. **Comparing them IS legitimate now** — the ~1 ULP residue of
|
||||
AUDIT §C10 is gone since `FPSemantics = Precise`, and all eight equivalence tests compare bit for
|
||||
bit. They are port-correctness oracles, not fidelity checks: the acceptance bar is §2.6.1 (same seed
|
||||
⇒ same world on every peer), which does not require resembling the pre-refactor world.
|
||||
|
||||
| Symbol | Role | Notes |
|
||||
|--------|------|-------|
|
||||
| `FVoxelOpStack` | — | Ordered move-only list of `TUniquePtr` entries. `Add` snapshots channel metadata; `PrepareChunk` / `EvalInternal` / `EvalMC` / `ClassifyBox` (the fold lets a later forcing post overwrite a dead hypothesis). |
|
||||
| `VoxelDensityProfile::BucketFromName` (`Private/VoxelDensityProfile.cpp`) | 710 | Maps operator stack labels to profiler buckets. The sampled game stack attributed 4.86% of resolved leaves here; the linear name scan is now a CRC-keyed candidate lookup with exact string comparison for collision-safe bucket identity. |
|
||||
| `FVoxelOpStack::ValidateChannelOrder` | — | Assembly/diagnostic-only channel/resource DAG validator. It requires prior producers, rejects write-only clobbers and invalid additive declarations, and requires room/shaft/surface state providers before consumers. It permits the one root SDF identity fold used by Maze. `VoxelForge.OpStack.ChannelDAG` validates all 8 shipping stacks. |
|
||||
| `FVoxelOpStack::AppendStructuralPost` | 4 | Appends spine → vertical seal → passage tube/landing floor → XY edge seal **in that fixed order**. An author cannot omit or reorder them. The diff layer is NOT here yet — it still lives in `GetDensityAt` after the MC negate, with disturbances. |
|
||||
| `VoxelDensityOps::MakeConstantRockSource` | 1 | `Density = BaseDensity`. `ClassifyBox` → **AllSolid**, exact and free. Shared by TunnelNetwork, Maze, VerticalShafts and bedrock gaps. Class is `FConstantFieldSource` (one class, two factories). |
|
||||
@@ -197,7 +245,7 @@ bit. They are port-correctness oracles, not fidelity checks: the acceptance bar
|
||||
| `VoxelDensityOps::MakeSlabVoidSource` | 1 | Floor surface + ceiling surface → void field. **XY-pure** since §3.1, which is what gives it an **exact `ClassifyBox` with no sampling**: the proved FBM supremum `1.5` bounds both surfaces into known Z bands. Serves FlatPlain **and** CrystalChamber. |
|
||||
| `VoxelDensityOps::MakeGridColumnMod` | 3 | Infinite-height cylinders on a world grid, 3×3 cell memo. Adds solid only ⇒ `FillOnly` when a column reaches the box, `Identity` otherwise — and that `Identity` is what lets the source's `AllAir` verdict survive. |
|
||||
| `FShaftFieldSource` (internal) | 1 | VerticalShafts' memoised inner 3×3 shaft/capsule source. Each rebuild uses a direct-indexed wider halo (default 9×9 tree-emission / 15×15 roll) so every emitted shaft has its complete 5×5 parent window plus capsule reach; the nearest strictly more-central shaft in ±2 cells is the deterministic parent, with a deterministic lower-origin ±3 fallback and a direct `(0,0)` spine fallback. Thus every shaft has a provable path to the spine. Existing `VoxelHash::Pair`/`Spacing*1.6` links remain additive texture/loops. Tree and spine capsules use a radius strictly above `sup|FBM|=1.5`, and tree Z is chosen in a ledge-free band; the structural post still owns the column and the ledge modifier keeps its mathematical axis boundary open. The source publishes a conservative interval covering both shafts and connector capsules; it does not answer for the ledge or converter. |
|
||||
| `VoxelDensityOps::BuildSlabStack` | — | 6 ops, **no branch on archetype**: FlatPlain and CrystalChamber differ only in defaults, exactly as `GetSlabDensity` already had it. 8 archetypes → 7. |
|
||||
| `VoxelDensityOps::BuildSlabStack` | — | 6 ops, **no branch on archetype**: FlatPlain and CrystalChamber differ only in defaults. |
|
||||
| `FSurfaceColumnSource` (internal) | 1 | The bridge between the two spaces: consumes the ground + sky-cap **height** stacks and produces density. `IsXYPure()` **false** — the heights are XY-pure, a distance to them never is. Owns a **six-box spatial LRU** of direct-indexed per-column cells, keyed by `PrepareChunk` on `(StrateBottomWorldZ, LayoutVersion, Seed, ParamsFingerprint)` so it is **shared down the whole vertical strate stack**, exactly like `GSurfColCache`. Six 81×81 boxes preserve hot columns across interleaved regions at roughly 0.79 MiB TLS before padding (more memory, fewer whole-cache recenter/recompute misses). Fractional XY remains direct-compute. `FSurfaceColumn` is deliberately trivial: all five outputs are written before the cache's `Computed` publish, so removing its default initialization removes a TLS constructor leaf without changing a value. |
|
||||
| `VoxelDensityOps::BuildSurfaceStack` | — | SurfaceWorld, complete: column + overhang + 4 structural, plus biome blending when `PerBiomeParams` is non-empty. Takes ownership of an `IVoxelBiomeField`. |
|
||||
| `VoxelDensityOps::BuildVerticalShaftStack` | — | 9 ops, and **three are Maze's reused unchanged** (`ConstantRock`, `SdfRoughness`, `SdfCarve`) with different tuning (freq 0.1 vs 0.12, window `rough+4` vs `R+rough+2`). Passes the runtime spine radius into `FShaftFieldSource`, which mirrors the generator's connector-only origin endpoint and roughness-safe spine capsule. The measured proof of `OPSTACK-PLAN §2.5`'s reuse claim. |
|
||||
@@ -213,7 +261,7 @@ bit. They are port-correctness oracles, not fidelity checks: the acceptance bar
|
||||
| `FRoomGraphSource::LocalParams()` | — | **The per-room op override** (DECOMPOSITION §2's "no clean home"). Strate params + the nearest room's `UVoxelTerrainOpDefinition::ApplyTo`, memoised once per voxel and read by eleven modifiers. One op owns the shared state, the rest read it — the same pattern as `FOverhangShelfMod` ← `FSurfaceColumnSource`. The detail consumers use the propagated interval for their shared near/far gate; unknown remains conservative, and the per-room parameter override is never used to claim an unproved box identity. |
|
||||
| `FIslandBlobSource` (internal) | 1 | Hash-placed tapered flat-top blobs, `SmoothMin`'d, in a **domain-warped XY frame** (the warp stays inside the op — see the deviation note vs DECOMPOSITION §7). SDF channel only. The source publishes an interval covering the full authored radius envelope (`max(MinRadius, MaxRadius)`), the two-axis warp·**√2** envelope, top surface and SmoothMin dip; the fill converter makes its own decision. **No lower Z bound exists** — a hairline thread of matter hangs below each island down its axis, so only the TOP may reject. |
|
||||
| `VoxelDensityOps::BuildFloatingIslandStack` | — | 8 ops, and **the stack runs backwards**: void source + fill instead of rock source + carve, using the *same* classes with the opposite sign. Only the blob source is new. Reuse by **inversion** — a stronger result than reuse by identity, since it says the abstract axis (the density sign) is the right one. |
|
||||
| `VoxelDensityOps::BuildMazeStack` | — | The 8-op Maze stack (4 structural posts included). Its lattice source mirrors the origin-directed tree + capped loops and rebuilds the 2×2×2 lower-node window once per cell; the per-voxel loop only evaluates cached capsules. If this ever becomes one op, the refactor failed its own test (§2.5). Callers must skip it on a **degenerate strate** (top−bottom ≤ 0): `GetMazeDensity` early-outs to air there and the stack has no such early-out by design — `GetDensityAt` falls back to the `switch`. |
|
||||
| `VoxelDensityOps::BuildMazeStack` | — | The 8-op Maze stack (4 structural posts included). Its lattice source mirrors the origin-directed tree + capped loops and rebuilds the 2×2×2 lower-node window once per cell; the per-voxel loop only evaluates cached capsules. If this ever becomes one op, the refactor failed its own test (§2.5). Callers must skip it on a **degenerate strate** (top−bottom ≤ 0): the stack has no such early-out by design — `VF_BuildOpStackForChunk` refuses it and `GetDensityAt` returns air. |
|
||||
|
||||
#### Lateral region parent (Tier 4d)
|
||||
|
||||
@@ -252,29 +300,32 @@ is a **column** property, or one opaque op (`OPSTACK-PLAN §2.5`'s failure mode)
|
||||
| `VoxelHeightOps::BuildSurfaceHeightStack` | 5 ops in `ComputeSurfaceTerrainZ`'s order — structural → cliff → terrace → layer lines → beach. **Order is not negotiable.** |
|
||||
|
||||
### 3.3 Chunk identity
|
||||
`VoxelChunk.h` (the old `FVoxelChunk` coord wrapper) was DELETED — dead since the tile
|
||||
redesign; tile identity lives in `FVoxelTileKey` (VoxelWorld.h).
|
||||
Tile identity lives in `FVoxelTileKey` (VoxelTypes.h:326).
|
||||
|
||||
### 3.4 Settings — `Public/VoxelSettings.h`
|
||||
`UVoxelSettings : UPrimaryDataAsset` — the single tuning asset assigned on `AVoxelWorld`.
|
||||
| Group | Fields (line) |
|
||||
|-------|---------------|
|
||||
| Streaming | `ViewDistanceXY=16`, `ViewDistanceUp/Down=5`, `MaxConcurrentTasks=16`, `MaxMeshAppliesPerFrame=4` (defaults — actual values live on the data asset) |
|
||||
| Clipmap | `ClipRadius`, `MaxClipLevel=5`, `FullResClipLevels`, `CoarseTileCells`, `RenderDistanceChunks=768` (measured ship defaults; custom horizontal reach: the outermost shell keeps generating until it covers this many chunks; 0 = off), `bFarSheetRing=false` by default, plus `FarSheetSpanLevels` (F18 — the optional render-distance ring streams per-surface SHEETS: level MaxClipLevel+span heightfield tiles instead of MC, see `GenerateSheetMesh`), skirts, `LODOctaveDrop` (T2.b octave drop on coarse tiles — 0 = off/byte-identical). Existing serialized data assets retain their authored values until deliberately migrated. |
|
||||
| Streaming | `ViewDistanceXY=16`, `ViewDistanceUp/Down=5`, `MaxConcurrentTasks=16`, `MaxMeshAppliesPerFrame=4`, `PawnCollisionRadiusChunks=1` (defaults — actual values live on the data asset; the collision radius is independent from the render clipmap) |
|
||||
| Clipmap | `ClipRadius`, `MaxClipLevel=5`, `FullResClipLevels`, `CoarseTileCells`, `RenderDistanceChunks=768` (measured ship defaults; custom horizontal reach: the outermost shell keeps generating until it covers this many chunks; 0 = off), skirts, `LODOctaveDrop` (T2.b octave drop on coarse tiles — 0 = off/byte-identical). Existing serialized data assets retain their authored values until deliberately migrated. |
|
||||
| World bounds | `WorldRadiusVoxels=8192` and `EdgeSealThickness=64` (actor-space XY radial shell; radius 0 = true legacy no-op) |
|
||||
| Lighting | `bEnableDensityVolume` + DensityVolume* tunables (§3.11 density clipmap / mini-sun shadows) |
|
||||
| Rendering | `VoxelMaterial` (61) |
|
||||
| Lighting | `bEnableDensityVolume=false` + DensityVolume* tunables (§3.11 density clipmap / mini-sun shadows). The disabled default creates no volume object, fill thread, uploads, or Tick updates; terrain MIDs receive neutral `VolP0..4=black`, `VolP5=(0,25,0)` (`z` enable is 0), `VolP6..9=black`, and `VolTex0..2=null`. |
|
||||
| Rendering | `VoxelMaterial` (377) |
|
||||
| Strates | Optional cooked `Season`; effective seed/spine/radius/season accessors read it when assigned. Otherwise `Seed`, `CurrentSeason=1`, `StratePool`, `FixedStrates`, and `TotalStrates=10` retain the authored path. |
|
||||
| Carving budget | `MaxModifications=0` (97), `MaxBrushRadius=15` (102), `MaxTotalVolume=0` (107). 0 = unlimited. |
|
||||
| Carving budget | `MaxModifications=0` (449), `MaxBrushRadius=15` (454), `MaxTotalVolume=0` (459). 0 = unlimited. |
|
||||
|
||||
### 3.5 World orchestrator — `Public/VoxelWorld.h` + `Private/VoxelWorld.cpp`
|
||||
`AVoxelWorld : AActor` — owns everything, drives streaming. Also `FChunkResult` struct
|
||||
(VoxelWorld.h:35) = async task payload (coord, chunk, meshdata, LOD, **Epoch**, request timing,
|
||||
worker-built stream geometry counts/bytes, desired-set epoch and obsolete/cancellation state).
|
||||
(VoxelWorld.h:99) = async task payload (coord, chunk, meshdata, LOD, **Epoch**, request timing,
|
||||
worker-built stream geometry counts/bytes, desired-set epoch and obsolete/cancellation state). Explicit
|
||||
visual-gap audits may add 24 sparse pre-skirt surface witnesses per non-empty tile; normal streaming
|
||||
does not capture or retain them.
|
||||
|
||||
**Owned objects (UPROPERTY):** `Settings`, `Generator`, `Mesher`, `StrateManager`,
|
||||
`DiffLayer` (VoxelWorld.h:55-78). **Storage:** `Chunks` map, `ChunkMeshes` map,
|
||||
`ChunkLODs`, `ProcessQueue` (TQueue), `PendingChunkCoord` (TSet) (VoxelWorld.h:85-312).
|
||||
`DiffLayer`, `ContentManager`, `AtmosphereManager`, `DensityVolume` (VoxelWorld.h:188-228).
|
||||
**Storage:** `LoadedTiles` (TSet), `TileComponents` map + `TileComponentPool` (VoxelWorld.h:260-308),
|
||||
the four MPSC result queues `CriticalProcessQueue` / `EditedProcessQueue` / `NearProcessQueue` /
|
||||
`ProcessQueue`, and `PendingTiles` (TSet) (VoxelWorld.h:908-912).
|
||||
**Async state:** `bShuttingDown`, `ActiveTaskCount` (atomics), `GenerationEpoch`, and the
|
||||
game-thread `PendingTileCancellation` map whose per-request atomic flags are read-only on workers.
|
||||
|
||||
@@ -293,45 +344,58 @@ casse cette arithmetique *structurellement* — `BeginPlay` loggue une **Error**
|
||||
|
||||
| Method | .cpp line | Role |
|
||||
|--------|-----------|------|
|
||||
| `AVoxelWorld()` ctor | 12 | Enables Tick. |
|
||||
| `RegenerateAllChunks()` | 21 | Bumps epoch, unloads all → Tick reloads. CallInEditor button. |
|
||||
| `AVoxelWorld()` ctor | 176 | Enables Tick. |
|
||||
| `RegenerateAllChunks()` | 530 | Bumps epoch, unloads all → Tick reloads. CallInEditor button. |
|
||||
| `ValidateDeterminism()` | — | **F2 CallInEditor button (PIE)**: re-samples boundary points under left- vs right-chunk cache warm-ups + a same-alignment repeat; any non-zero delta = window-invariance regression (§8.4). Run after every "bit-identical" hot-path refactor. |
|
||||
| `GetMaxConcurrentTasks()` | — | T2.d — asset `MaxConcurrentTasks` capped to logical cores − 2 (all three budget checks use it). |
|
||||
| `PostEditChangeProperty` | 45 | Editor live-edit hook. |
|
||||
| `OnObjectModifiedInEditor` | 58 | Regenerates when a strate asset is edited (if `bLiveEditStrates`). |
|
||||
| `EndPlay` | 140 | Sets `bShuttingDown`, **waits for `ActiveTaskCount`→0**, unbinds delegate. |
|
||||
| `BeginPlay` | 177 | Constructs Generator/Mesher/StrateManager/DiffLayer, wires services, seeds. |
|
||||
| `Tick` | 220 | `UpdateChunksAroundPosition(player, heading)` + `ProcessPendingChunks()`; the clean LOD0 latency summary is flushed at `EndPlay`. |
|
||||
| `GetPlayerPosition` | 231 | Pawn position or zero. (`GetLODForChunk`/`LODToStep`/`IsChunkInRange` removed — dead since the clipmap.) |
|
||||
| `ProcessPendingChunks` | 301 | Drains ProcessQueue under per-frame budget; reads the per-request cancellation token before removing its map entry, drops obsolete/aborted results, then applies each live result via `ApplyTileResult`. |
|
||||
| `GetActiveGenerationTaskCount()` | — | Normal mode returns the existing pending count. With `voxel.Step1PrequeueVisibleLOD0>0`, completed held results are excluded from active worker admission so a full render corpus can be generated before the upload release. |
|
||||
| `PostEditChangeProperty` | 1000 | Editor live-edit hook. |
|
||||
| `OnObjectModifiedInEditor` | 1022 | Regenerates when a strate asset is edited (if `bLiveEditStrates`). |
|
||||
| `EndPlay` | 1118 | Sets `bShuttingDown`, **waits for `ActiveTaskCount`→0**, unbinds delegate. |
|
||||
| `BeginPlay` | 1576 | Constructs Generator/Mesher/StrateManager/DiffLayer, wires services, seeds. Creates `UVoxelDensityVolume` only when `bEnableDensityVolume` is true; otherwise seeds terrain MIDs with the disabled neutral parameters and null volume textures. Explicitly parses command-line profiling switches and the `voxel.TestFullResClipLevels` measurement override because headless module CVar startup ordering can otherwise leave the authored/default value active. |
|
||||
| `RebuildStrates` | 724 | Reinitializes the volume only when enabled; when disabled, shuts down and clears any existing volume and reapplies neutral terrain material params. |
|
||||
| `Tick` | 5715 | `UpdateChunksAroundPosition(player, heading)` + `ProcessPendingChunks()`; the clean LOD0 latency summary is flushed at `EndPlay`. Density-volume update/material work is inside the non-null volume branch, so it is absent when disabled. |
|
||||
| `GetPlayerPosition` | 4831 | Pawn position or zero. |
|
||||
| `AdvanceHeadlessDigMotionTest` / `RecordHeadlessDigMotionFrame` | 3060 / 3217 | Explicit `-voxel.TestDigMotion=1` repro: optional near digs, then repeats underfoot digs and samples `Generator->GetDensityAt` immediately after each edit, on its first subsequent simulated frame, and on every following observation frame. `-voxel.TestDigMotionFieldOverride=1` is a measurement-only prototype that clears `CurrentFloor` and switches CMC to falling when the sampled post-edit density is air; it is inactive by default. The test counts false support, false air and downward velocity beyond one gravity step. `-voxel.TestDigMotionUnderfootDigCount` is clamped to 1–100; `-voxel.TestDigMotionUnderfootIntervalSeconds=0` performs per-frame rapid edits. The accompanying gap audit is scoped to the dig session. |
|
||||
| `MaybeFinishHeadlessStreamingTest` | — | The existing `voxel.TestExitSeconds` diagnostic emits `VoxelForgeStreamingExitDiag` at most every 30 elapsed seconds, including threshold and early-return test flags; no output when the timer is unconfigured. |
|
||||
| `UpdateVisualStalenessAudit` | 5034 | Test-only camera-frustum coverage audit: tracks up to 24 real pre-skirt isosurface points per visible component, then counts frames where no visible tile component covers a previously visible, still-desired point (excluding edited bounds). `voxel.TestVisualGapInjectEarlyHide=1` hides a valid component for three frames and must be caught. `voxel.TestGapAuditLegacyHandoff=1` restores old handoff culling for A/B baselines. |
|
||||
| `ProcessPendingChunks` | 7435 | Drains critical, edited, near, then ordinary result queues under the per-frame budget; reads the per-request cancellation token before removing its map entry, drops obsolete/aborted results, then applies each live result via `ApplyTileResult`. Collision profiling timestamps the result's arrival here to separate worker completion from game-thread apply wait. Step 1's `voxel.Step1PrequeueVisibleLOD0` holds completed results until the requested non-empty L0 count is ready, then releases them through this same drain and emits the `VFStep1UploadCorpusReleased` CSV event for UE's delayed CSV capture. |
|
||||
| `ApplyTileResult` | — | **Shared game-thread apply** for one `FChunkResult` (async drain + sync carve): discards stale generation epochs and keys no longer desired, marks loaded, ingests capture, EMPTY releases the tile's existing component (a re-gen can flip content→empty on band change — old geometry must not linger), else `ApplyMeshToTile`. Returns true iff a visible mesh uploaded (counts the budget). Doesn't touch `PendingTiles` (caller's). |
|
||||
| `FVoxelStackSampler` / `FScopedVoxelStackRegistration` (`VoxelStackSampler.h/.cpp`) | — | **Diagnostic raw stack sampler, off by default.** A sampler thread captures registered generation workers' stacks (`CaptureThreadStackBackTrace`) every N µs and tags each with the tile's LOD. It writes bounded raw PCs to CSV plus a symbolization-free summary under `Saved/`; symbol resolution is deferred until after process exit so the measured process never initializes/calls DbgHelp for this diagnostic. Lifecycle teardown is serialized and registration TLS is session-tagged. Registration is per tile TASK in `LoadTile` (and the export's tasks), never per sample; disabled costs one relaxed atomic load. Game `-voxel.SampleStacks=<us>`, commandlet `-samplestacks[=us]`. |
|
||||
| Room-cache performance (2026-09-13): `VF_FindPlayerFitPointForRoomMemoized` / `GPlayerFitMemo` (`VoxelCaveMorphology.cpp`), `voxel.TileCacheWindow` + `VoxelGenLOD::TileOriginVoxels/TileCellsPerAxis` (`VoxelGenerator.h`), `FChunkSDFSpatialIndex` | — | **Player-fit memo:** exact 97-word key, process-wide, `voxel.PlayerFitMemo`. **Tile cache window:** the room cache covers the requesting tile's footprint plus a fixed collect margin. It is set via `TGuardValue` in `GenerateMesh`/classify, and is window-invariant by construction (the margin does not grow with the tile). **LOD-gated:** fused path at LOD ≥ `voxel.TileCacheWindowMinLOD` (3), op-stack at LOD ≥ `voxel.TileCacheWindowOpStackMinLOD` (4); lower LODs keep the 4-chunk worker window, because the tile window slowed LOD0 evaluation ~6%. Strates whose authored terrain ops exceed the proven 256-voxel extent (or are malformed/unloaded) fall back to the legacy window (`VF_ComputeTerrainOpEnvelope`). `voxel.SpatialIndex` is -1 auto (off at fused LOD0, on elsewhere), 0 or 1. **Spatial index:** ordered XY buckets over cache primitives, exact rejection. The `static_assert` in `VoxelStrateTypes.h` ties `VF_STRATE_PARAM_FIELDS` to `FStrateGenerationParams` (83 fields + 8 B padding). |
|
||||
| `GenerateTileResult` | — | **Shared worker-side gen** for one tile (async `LoadTile` task + sync `SyncRemeshTile`): [ClassifyTile (T1.d) + exact validation, only when `voxel.OuterClassifierMode=1`; **default 0 since 2026-09-13**] → `GenerateMesh`/`GenerateSheetMesh` → `BuildTileStreamSet`; records vertex count and resident stream allocation bytes for the horizon trace. Reads Generator/Mesher only → safe on a worker or the game thread; fills `FChunkResult`, no enqueue. |
|
||||
| `GenerateTileResult` | — | **Shared worker-side gen** for one tile (async `LoadTile` task + sync `SyncRemeshTile`): sealed-solid proof, out-of-layout air proof, optional ClassifyTile (T1.d) + exact validation only when `voxel.OuterClassifierMode=1`; **default 0 since 2026-09-13** → `GenerateMesh` → `BuildTileStreamSet`; records vertex count and resident stream allocation bytes for the horizon trace. Density capture is requested only when the enabled volume can use that L0 tile; with the volume off, neither proof nor classifier is bypassed for capture. The explicit gap audit may request sparse surface witnesses, otherwise none are retained. Near the player it can capture the exact pre-edit L0 lattice into the result; a cache-hit sync remesh passes an edited per-call shared lattice. Reads Generator/Mesher only → safe on a worker or the game thread; fills `FChunkResult`, no enqueue. |
|
||||
| `RecordTileHash` / `WriteTileHashDump` | — | Headless `-voxel.TileHashDump=<absolute path>` parity instrument. Records the exact `FVoxelMeshData` used by the streaming result, including empty/classified tiles, then writes sorted absolute tile keys, LOD step/cells, content-band limits, settings digest context, and the shared `VoxelGeometryHash` at `EndPlay`. |
|
||||
| `SyncRemeshTile` | — | **INSTANT DIG**: level-0 same-frame re-mesh on the game thread (`GenerateTileResult` + `ApplyTileResult` inline, Cells=CHUNK_SIZE/Step=1 + strate band, no capture). Used for the tile under the brush centre so a carve is visible THIS frame; one full-res gen on the game thread. |
|
||||
| `UpdateChunksAroundPosition` | 362 | Builds desired set, cancels pending keys that left it, sorts from the **true pawn position**, submits the occupied/support LOD0 tile and the next tile along the pawn heading as an absolute `BackgroundHigh` prefix, then loads/unloads and handles LOD changes. **Delta cull**: `BuildDesiredTiles` returns the LEAVERS (stamped `DesiredStamped` map, one sweep) — only those + `TransitionHold` are considered per crossing, not every loaded tile. `BuildDesiredTiles` also applies `RenderDistanceChunks`: the outermost shell widens to cover the distance — as level-MaxClipLevel MC tiles, or (F18 `bFarSheetRing`) as a SHEET ring at level MaxClipLevel+span (covered-check vs the MaxClipLevel box; `VF_OuterShell` shared with `IsTileInClipRange` so the cull sees the same horizon), dz pre-clamped to the vertical band. **§9.3 anchors:** also prunes dead `StreamingAnchors` + detects their chunk crossings → rebuilds the desired set when an anchor moves/(un)registers (`bAnchorsMoved`/`bForceDesiredRebuild`), same cadence as player movement. §8.10. |
|
||||
| `AdvanceHeadlessCollisionGateStressTest` / `ObserveHeadlessCollisionGateStressTest` / `MaybeFinishHeadlessCollisionGateStressTest` | — | Explicit `-voxel.TestCollisionGateStress=1` harness only. The `GetDensityAt` column at capsule feet defines the local air/solid surface; each frame records actor position, velocity, direct foot tile and gate support readiness, gate state, signed feet clearance, and a ≤0.5-voxel sweep between positions. PASS requires no measured clearance below −5 cm; support submission/readiness, travel, and holds are diagnostics. Result logs include the terminal density column and the first crossing below spawn height. |
|
||||
| `BuildDesiredTiles` | ~722 | Builds `DesiredSorted`+`DesiredStamped` (player clipmap shells + F18 sheet ring) then `AddAnchorDesiredTiles()` before the leaver sweep; it orders from the true local pawn position and builds `CriticalDesiredTiles` for the occupied/support and heading tiles. Also rebuilds `DesiredTransitionDescendants` (ancestor → desired descendants) for the reconcile pass. |
|
||||
| `VoxelClipmapDesiredTiles::Build` / `AlignedRingBounds` (`Private/VoxelClipmapDesiredTiles.h`) | — | The pure clipmap selector. **Every finer ring snaps outward to a child-pair boundary** (`AlignedRingBounds`, alignment 2; the last MC ring aligns to the sheet grouping width), so a coarser tile is wholly inside or wholly outside the finer ring: **no two desired tiles of different levels overlap** (a 7-wide odd ring used to leave one half-covered coarse tile per axis side, drawn over the fine one: 635 pairs at the owner config). Cost: LOD0 ring 343 → 512 tiles. Guarded by `VoxelForge.Streaming.ClipmapDesiredTilesNoOverlapCoverage`. Anchor tiles appended later are outside this guarantee. |
|
||||
| `ReconcileReadyTransitionVisibility` | — | Visibility-only pass while streaming is unsettled: hides a retired tile once all desired tiles over its footprint are loaded, reveals new tiles once no visible retired tile overlaps them. **Indexed** (dyadic ancestors + `DesiredTransitionDescendants`, visible retired tiles collected once per pass): moving p95 32 ms → 0.31 ms. `voxel.MeasureReadyTransitionVisibility=1` logs per-frame samples. |
|
||||
| `RequestCollisionGateSupportTile` / `UpdatePawnCollisionGate` | — | The gate submits the pawn's level-0 support tile **immediately** as `CollisionCritical`, even before the first desired set exists (the desired rebuild then adopts the same pending key). The gate holds downward Z only, with **no timeout** (the old 2 s release fired at every startup, while the game thread applied nothing for ~2.2 s). |
|
||||
| `AddAnchorDesiredTiles` | — | **§9.3 multi-anchor:** folds each `FVoxelStreamingAnchor`'s thin level-0 box (`XYRadiusChunks`/`ZBelowChunks`/`ZAboveChunks`) into the SAME desired set (deduped by stamp) so AI/remote players keep collision loaded around them; delta cull releases them on move/unregister. **§9.4:** a tile only a CollisionOnly anchor wants (clipmap didn't stamp it) → `CollisionOnlyTiles` → hidden at apply. Zero cost when no anchors. |
|
||||
| `SyncRemeshTile` | — | **INSTANT DIG**: level-0 same-frame re-mesh on the game thread. Uses `PreEditDensityCache` when the tile is resident in the bounded local cache, replays the current DiffLayer over that exact procedural lattice, then meshes/applies inline so a carve is visible THIS frame. A cache miss uses normal generation; it captures the unedited lattice if the tile needs meshing, while uniform-tile proofs remain enabled. |
|
||||
| `PreEditDensityCache` / `VoxelForgePreEditDensityCache::BuildEditedGrid` | — | Per-world cache of the pawn's current L0 tile: exact 35³ pre-edit samples, max **171,500 bytes**. It is pruned when the pawn crosses chunk boundaries and cleared on `RegenerateAllChunks`. `BuildEditedGrid` snapshots touched diff chunks once and reproduces `GetDensityAt`'s final `pre-edit - EvaluateMods` operation before the per-call shared-grid mesher path. |
|
||||
| `UpdateChunksAroundPosition` | 8603 | Builds desired set, cancels pending keys that left it, sorts from the **true pawn position**, submits the occupied/support LOD0 tile and the next tile along the pawn heading as an absolute `BackgroundHigh` prefix, then loads/unloads and handles LOD changes. **Delta cull**: `BuildDesiredTiles` returns the LEAVERS (stamped `DesiredStamped` map, one sweep) — only those + `TransitionHold` are considered per crossing, not every loaded tile. `BuildDesiredTiles` also applies `RenderDistanceChunks`: the outermost shell widens to cover the distance as level-MaxClipLevel MC tiles (`VF_OuterShell` shared with `IsTileInClipRange` so the cull sees the same horizon), dz pre-clamped to the vertical band. **§9.3 anchors:** also prunes dead `StreamingAnchors` + detects their chunk crossings → rebuilds the desired set when an anchor moves/(un)registers (`bAnchorsMoved`/`bForceDesiredRebuild`), same cadence as player movement. §8.10. |
|
||||
| `AdvanceHeadlessCollisionGateStressTest` / `ObserveHeadlessCollisionGateStressTest` / `MaybeFinishHeadlessCollisionGateStressTest` / `MeasureHeadlessCollisionGateStressTerrain` | — | Explicit `-voxel.TestCollisionGateStress=1` harness only. The normal spawn-height cvar offsets from the measured density surface; `voxel.TestCollisionGateStressDeepDrop=1` carves a process-local box shaft from above the top to just above the floor tile, extending along the post-landing route so the pawn does not run into the shaft sidewall, temporarily raises only the transient test DiffLayer brush-radius allowance for that fixture, removes only the local pawn's redundant streaming anchor for this isolated repro, proves every sample in two complete outside-ring level-0 tiles is air before seeding their known-empty status, prewarms the known-empty in-ring column, and asserts that the support walk reaches the in-layout floor outside the aligned level-0 render ring before the pawn drops. The log reports whether the fixed gate retains that otherwise out-of-ring target. Other streaming anchors remain active. The fixture rejects a target or floor outside the layout, a non-air empty-tile proof, or a floor inside the aligned level-0 ring. The `GetDensityAt` column at capsule feet defines the local air/solid surface; the deep-drop column crossing is cached after its first exact scan. Each frame records actor position, velocity, direct foot tile and gate support readiness, gate state, signed feet clearance, and a ≤0.5-voxel sweep between positions. PASS requires no measured clearance below −5 cm; deep-drop gate-on also requires horizontal travel during a hold. Result logs include the terminal density column, first crossing below spawn height, hold count/time, and gated horizontal progress. |
|
||||
| `BuildDesiredTiles` | 6482 | Builds `DesiredSorted`+`DesiredStamped` (player clipmap shells), then adds streaming anchors and the pending player's collision-only support column before the leaver sweep. It orders from the true local pawn position and builds `CriticalDesiredTiles` for the unresolved support target, occupied/support and heading tiles. Also rebuilds `DesiredTransitionDescendants` (ancestor → desired descendants, including face/edge-touching replacements) for the reconcile pass. |
|
||||
| `CollisionGateDesiredTiles` / `RequestCollisionGateSupportTile` | 5062 | Retains an unresolved support target and up to three level-0 tiles below it across desired-set rebuilds. The additional keys are hidden collision-only tiles, queued ahead of ordinary streaming, and pruned once resolved or outside the active column. |
|
||||
| `IsPawnCapsuleOverlappingPendingFloor` / `UpdatePawnCollisionGate` | 4987 / 5195 | Uses the current-to-next-step swept capsule bounds to start a hold only when the pending support tile is about to intersect the pawn. When current support is ready, it also requests and checks the next-step support tile before a horizontal boundary crossing. Character holds use gravity-free `MOVE_Flying` with horizontal input live, then restore movement mode, gravity, and saved downward speed when collision is ready. |
|
||||
| `VoxelClipmapDesiredTiles::Build` / `AlignedRingBounds` (`Private/VoxelClipmapDesiredTiles.h`) | — | The pure clipmap selector. **Every finer ring snaps outward to a child-pair boundary** (`AlignedRingBounds`, alignment 2; the outermost ring needs none), so a coarser tile is wholly inside or wholly outside the finer ring: **no two desired tiles of different levels overlap** (an unaligned 7-wide odd ring leaves one half-covered coarse tile per axis side, drawn over the fine one: 635 pairs at the owner config). Cost: LOD0 ring 343 → 512 tiles. Guarded by `VoxelForge.Streaming.ClipmapDesiredTilesNoOverlapCoverage`. Anchor tiles appended later are outside this guarantee. |
|
||||
| `VF_AppendTouchingAncestors` / `ReconcileReadyTransitionVisibility` / `LogReadyTransitionVisibilityMeasure` | 5041 / 7939 / 5599 | Boundary-safe visibility handoff: a desired tile sharing a closed face/edge with a retired tile counts as a replacement, so the old surface remains visible until the boundary neighbor is loaded. `NeedsHold` uses the same contact-inclusive readiness. Contact does not imply volumetric overlap; overlap audit remains strict. **Indexed** (dyadic ancestors + `DesiredTransitionDescendants`, visible retired tiles collected once per pass): moving p95 32 ms → 0.31 ms. `voxel.MeasureReadyTransitionVisibility=1` logs per-frame game-thread time, sampled component counts, and 128-component count buckets. |
|
||||
| `IsTileCollisionDesired` / `ReconcileCollisionDesiredTiles` / `ApplyMeshToTile` | — | Collision is independent of rendering: the player's level-0 `PawnCollisionRadiusChunks` box, each anchor's authored box, and the gate's retained support column union into `CollisionDesiredTiles`. Level-0 meshes outside that set remain visible with `NoCollision` and do not submit an RMC cook. Entering the set queues a remesh for already-loaded render tiles; leaving disables their component immediately; a late cook cannot restore readiness outside the set. |
|
||||
| `VoxelCollisionDesiredTiles::AddBox` (`Private/VoxelCollisionDesiredTiles.h`) | — | Shared pure box selector for the pawn collision radius and anchor extents; tested for pawn radius bounds and an independent anchor box. |
|
||||
| `RequestCollisionGateSupportTile` / `UpdatePawnCollisionGate` | — | The gate submits the pawn's level-0 support tile **immediately** as `CollisionCritical`, even before the first desired set exists, and retains up to three more support-column tiles across desired-set rebuilds. A previously rendered no-collision tile is remeshed before the gate can accept it. The gate holds only at swept contact, keeps horizontal input live, has no timeout, and restores movement mode, gravity and saved fall speed on release. |
|
||||
| `AddAnchorDesiredTiles` | — | **§9.3 multi-anchor:** folds each `FVoxelStreamingAnchor`'s thin level-0 box (`XYRadiusChunks`/`ZBelowChunks`/`ZAboveChunks`) into the SAME desired set and the independent collision set, so AI/remote players keep collision loaded around them; delta cull releases them on move/unregister. **§9.4:** a tile only a CollisionOnly anchor wants (clipmap didn't stamp it) → `CollisionOnlyTiles` → hidden at apply. Zero cost when no anchors. |
|
||||
| `ReconcileAnchorTileVisibility` | — | **§9.4:** after each rebuild, toggle `SetVisibility` on ALREADY-LOADED tiles that flipped render↔collision-only (diff `CollisionOnlyTiles` vs prev — bounded, no O(loaded) scan; unhide only if still desired). No-op without CollisionOnly anchors. |
|
||||
| `RegisterStreamingAnchor` / `UnregisterStreamingAnchor` | — | **BlueprintCallable §9.3:** add/remove an actor as a streaming anchor (`EVoxelAnchorPolicy` CollisionOnly/FullVisual + XY/ZBelow/ZAbove box). Idempotent; forces a rebuild next Tick. §9.4: CollisionOnly tiles cook collision but are hidden (no draw/VSM) unless the player clipmap wants them too. |
|
||||
| `LoadTile` | 445 | Budget check → `UE::Tasks::Launch` background gen+mesh; RAII task guard. Worker runs `Generator->ClassifyTile` first (T1.d): AllSolid/AllAir ⇒ skip `GenerateMesh`, tile stays empty (capture tiles always generate). It carries `GenerationEpoch`/desired epoch and checks `bShuttingDown`, generation pause, and its read-only cancellation token between expensive stages; obsolete work returns through `ProcessQueue`. STRATE CONTENT CUT: tiles ≥ `StrateContentCutMinLevel` pass the player-strate band (`MeshBandChunkLo/Hi` → voxels) to `GenerateMesh` + stamp it on `FChunkResult::BandChunkLo/Hi`; band change re-queues via `BandRemeshQueue` (see `UpdateChunksAroundPosition`). TOO-COARSE SKIP: if one cell is taller than the band (`Step > band height` — level ≥7 territory) the tile is enqueued EMPTY without launching a task (cell-granular cut could only render garbage). F18: `Tile.Level > MaxClipLevel` = SHEET tile → routed to `GenerateSheetMesh` (band mid-chunk = strate ref; band unarmed ⇒ empty; MC-ring sampling density, cells capped 128/axis; carries the XY hole `SheetHole*Vox` — hole moves ⇒ overlapping sheets re-queue via `BandRemeshQueue`, see `UpdateChunksAroundPosition`). §8.10. |
|
||||
| `LoadTile` | 9033 | Budget check → `UE::Tasks::Launch` background gen+mesh; RAII task guard. Worker runs `Generator->ClassifyTile` first (T1.d): AllSolid/AllAir ⇒ skip `GenerateMesh`, tile stays empty (capture tiles always generate). It carries `GenerationEpoch`/desired epoch and checks `bShuttingDown`, generation pause, and its read-only cancellation token between expensive stages; obsolete work returns through `ProcessQueue`. STRATE CONTENT CUT: tiles ≥ `StrateContentCutMinLevel` pass the player-strate band (`MeshBandChunkLo/Hi` → voxels) to `GenerateMesh` + stamp it on `FChunkResult::BandChunkLo/Hi`; band change re-queues via `BandRemeshQueue` (see `UpdateChunksAroundPosition`). `voxel.TestFullResClipLevels` is a measurement-only runtime override; default -1 uses the authored setting. TOO-COARSE SKIP: if one cell is taller than the band (`Step > band height` — level ≥7 territory) the tile is enqueued EMPTY without launching a task (cell-granular cut could only render garbage). §8.10. |
|
||||
| `UnloadTile` | — | Clears tile state; marks any matching generation request obsolete without removing its pending slot, so a late result cannot clear a replacement request; the component is PARKED in the pool (T2.c), not destroyed. |
|
||||
| `ApplyMeshToTile` | — | Upload geometry. One component per tile (clipmap keeps count low; supersedes the old region batching); worker-built streams (T1.f) → `CreateSectionGroup(MoveTemp)`. Reuses the component's existing `URealtimeMesh` (no per-apply mesh alloc). **F17: two polygroups** (0 ground / 1 sky-cap, per-triangle class from the mesher) → RMC auto-section per non-empty group; slot 0 = override/default material, slot 1 = `CeilingMaterial` (fallback ground); config gated by `FChunkResult::bHasGroundTris/bHasCeilingTris`. Takes `FChunkResult&`; strate lookups clamp Z into `Result.BandChunkLo/Hi` (strate content cut) — ground at clamped bottom chunk, cap at clamped top (mid = gap fallback). Collision level-0 only (T1.c) both groups; shadow per SECTION: ground casts at level≤1, cap never. **§9.4:** `SetVisibility(false)` when the tile is in `CollisionOnlyTiles` (anchor-only, hidden — collision still cooks). §8.10 + ARCHITECTURE SurfaceWorld row. |
|
||||
| `ApplyMeshToTile` | 9838 | Upload geometry. One component per tile (clipmap keeps count low; supersedes the old region batching); worker-built streams (T1.f) → `CreateSectionGroup(MoveTemp)`. Reuses the component's existing `URealtimeMesh` (no per-apply mesh alloc). **F17: two polygroups** (0 ground / 1 sky-cap, per-triangle class from the mesher) → RMC auto-section per non-empty group; slot 0 = override/default material, slot 1 = `CeilingMaterial` (fallback ground); config gated by `FChunkResult::bHasGroundTris/bHasCeilingTris`. Takes `FChunkResult&`; strate lookups clamp Z into `Result.BandChunkLo/Hi` (strate content cut) — ground at clamped bottom chunk, cap at clamped top (mid = gap fallback). Collision level-0 only (T1.c) both groups; shadow per SECTION: ground casts at level≤1, cap never. Ordinary same-tile re-cooks retain the installed body while it is valid. A carve intersecting the pawn's current support tile and capsule bounds suppresses that old body until the replacement cook is installed. `voxel.ProfileCollisionCookStages` reports generation/apply waits, combined RMC time, completion hops, triangle/skirt counts and collision settings. With `voxel.Step1PrequeueVisibleLOD0>0`, logs apply frame and waits for `GetLastRenderTime` to advance for first-visible frame; `voxel.Step1UploadDisableCollision=1` isolates render upload. Routes each terrain base through shared MIDs; disabled volume MIDs bind neutral `VolP*` and null `VolTex*`. **§9.4:** `SetVisibility(false)` when the tile is in `CollisionOnlyTiles` (anchor-only, hidden — collision still cooks). §8.10 + ARCHITECTURE SurfaceWorld row. |
|
||||
| `HandleRealtimeMeshCollisionBodyUpdated` / `HandleTileCollisionCookComplete` / `PollStep1SupportSweepProbes` | 6964 / 7192 / — | Correlate RMC body-install event and collision-future completion. `voxel.ProfileCollisionCookStages=1` logs worker queue/gen, result wait, apply/upload/config submission, RMC submit→body update, body event→future, future→game-thread hop, submit frames, pending VoxelForge cooks, priority, collision flags, and triangle/skirt totals; it also reports mesh-generation-end→ready. `voxel.Step1CollisionProbe=1` sweeps the pawn capsule against the exact component pointer, logs both the first hit and whether it is the target body, and checks every body in the captured support cohort. RMC's end-frame dequeue, async worker start, geometry extraction and core cook remain inside the external plugin and are not individually timestampable from this module. |
|
||||
| `FVoxelStep1CoreCookWork` / `FStep1CoreCookPoolOwner` | — | Optional `voxel.Step1CoreCookBenchmark=1` captures the pawn's first exact radius box (normally 27 L0 tiles) on the game thread, tags those results through worker meshing, and sends their streams plus any out-of-box critical support tile to a dedicated one-thread high-priority pool. It records queue delay, extraction and core cook separately. One approximately 4k-triangle cohort stream is retained and replayed after `Step1PauseGenerationAfterSeconds` pauses generation. The benchmark sidecar is not installed as the tile BodySetup and is disabled by default. |
|
||||
| Step 1 measurement CVars | — | `voxel.Step1GenerationCap=-1` uses the normal cap (`0` pauses, positive values cap worker tasks); `voxel.Step1ExitAfterFrames=0` disables a frame-counted clean test exit; `voxel.Step1PauseGenerationAfterSeconds=0` disables the timed pause; `voxel.Step1MaxMeshApplies=0` uses the asset cap; `voxel.Step1PrequeueVisibleLOD0=0` disables the held upload corpus; `voxel.Step1UploadDisableCollision=0`, `voxel.Step1CollisionProbe=0`, `voxel.Step1CoreCookBenchmark=0`, and `voxel.Step1CsvCaptureOnBeginPlay=0`. All default off. The launch-only `voxel.TestClipRadius` transiently duplicates settings and clamps a positive requested radius to 1–16; it is absent by default and leaves the authored asset unchanged. The prequeue release emits the default-global `VFStep1UploadCorpusReleased` CSV event; `voxel.Step1CsvCaptureOnBeginPlay=1` emits `VFStep1GameBeginPlay`. Use UE's `-csvStartOnEvent=<event>` to begin capture after RHI initialization. |
|
||||
| `AcquireTileComponent` / `ReleaseTileComponent` | — | **T2.c component pool** (`TileComponentPool`, bounded): park on unload (geometry+collision stripped, hidden, stays registered), pop on apply — no `NewObject`/`RegisterComponent`/GC churn during travel & regen. §8.10. |
|
||||
| `GetStrateAtPosition` | 965 | Gameplay query → strate index. |
|
||||
| `GetStrateAtPosition` | 8351 | Gameplay query → strate index. |
|
||||
| `GetBiomeAtWorldLocation` | — | **BlueprintCallable** biome probe at a world point (undoes actor xf → voxel → `Generator::QueryBiomeAt`). Returns `FVoxelBiomeQuery` for BP debug ("what biome / how many decos under the cursor?"). |
|
||||
| `GetVoxelSurfaceHeightAt` | ~1534 | **BlueprintCallable** ground finder (F7 bridge): world XY → terrain + sky-cap world-Z via `Generator::GetSurfaceHeightAt`, NO trace/collision, deterministic, available before the area meshes → self-arranging prefab/ruin BPs snap their parts to the real ground. False (outs=input Z) on non-SurfaceWorld strates; ignores passage/spine carving. |
|
||||
| `CarveAtPosition` / `FillAtPosition` | 691 / 709 | Build `FVoxelModification` → `ApplyModification`. |
|
||||
| `ApplyModification` | ~1581 | Single funnel for all brushes: DiffLayer → **sync-remesh the brush-centre level-0 tile** (`SyncRemeshTile`, instant hole; skipped if that tile is mid-gen — would race a stale in-flight result) → `RemeshDirtyChunks(..., excludeCenter)` for the neighbours → `RemoveDecorationsInSphere`. |
|
||||
| `ClearAllModifications` | 726 | Clears diff layer, regenerates. |
|
||||
| `ChangeSeed` | 740 | **Legacy season reset**: new seed everywhere, clear diffs, bump season, reload. Rejected while a cooked season owns the seed. |
|
||||
| `GetCurrentSeed` / `GetCurrentSeason` / `GetCurrentSeasonContentHash` | — | Effective cooked-or-legacy identity; the hash is the join-time stale-season comparison value. |
|
||||
| `RemeshDirtyChunks` | 798 | Queue loaded level-0 dirty tiles onto `DirtyRemeshQueue` (async re-mesh, no pop) + `MarkDirtyVoxelBox` the volume. Optional `ExcludeTile` = the sync'd centre. Drained FIRST in the submit loop at **BackgroundHigh** (ahead of streaming/band) so a dig never waits behind streaming; in-flight tiles stay QUEUED (not dropped) so a stale pre-carve result is corrected once it lands — fixes "hole shows up a beat late / not until I move". |
|
||||
| `GetVoxelSurfaceHeightAt` | 8385 | **BlueprintCallable** ground finder (F7 bridge): world XY → terrain + sky-cap world-Z via `Generator::GetSurfaceHeightAt`, NO trace/collision, deterministic, available before the area meshes → self-arranging prefab/ruin BPs snap their parts to the real ground. False (outs=input Z) on non-SurfaceWorld strates; ignores passage/spine carving. |
|
||||
| `CarveAtPosition` / `FillAtPosition` | 10230 / 10239 | Build `FVoxelModification` → `ApplyModification`. |
|
||||
| `ApplyModification` | 10248 | Single funnel for all brushes: DiffLayer → when a negative brush intersects the pawn's capsule bounds and affects its ready support tile, disable that old body and withdraw readiness before remesh → **sync-remesh the brush-centre level-0 tile** (`SyncRemeshTile`, instant hole; skipped if that tile is mid-gen — would race a stale in-flight result) → `RemeshDirtyChunks(..., ExcludeTile)` for the neighbours → `RemoveDecorationsInSphere`. A successful replacement cook restores collision/readiness; neighboring edits that miss the capsule bounds keep the same-tile recook handoff. |
|
||||
| `ClearAllModifications` | 8696 | Clears diff layer, regenerates. |
|
||||
| `ChangeSeed` | 8710 | **Legacy season reset**: new seed everywhere, clear diffs, bump season, reload. Rejected while a cooked season owns the seed. |
|
||||
| `GetCurrentSeed` / `GetCurrentSeason` / `GetCurrentSeasonContentHash` | — | Effective cooked-or-legacy identity; the hash is the join-time stale-season comparison value. The optional world-assembly digest joins the hash only while `bUseVoxelForgeCore` is active, so switch-off retains the previous legacy hash. |
|
||||
| `RemeshDirtyChunks` | 8801 | Queue loaded level-0 dirty tiles onto `DirtyRemeshQueue` (async re-mesh, no pop) + `MarkDirtyVoxelBox` the volume. Optional `ExcludeTile` = the sync'd centre. Drained FIRST in the submit loop at **BackgroundHigh** (ahead of streaming/band) so a dig never waits behind streaming; in-flight tiles stay QUEUED (not dropped) so a stale pre-carve result is corrected once it lands — fixes "hole shows up a beat late / not until I move". |
|
||||
|
||||
> **Game-thread profiling (Perf):** `AVoxelWorld::Tick` and its sub-steps are wrapped in `TRACE_CPUPROFILER_EVENT_SCOPE` — `VoxelForge_Tick / UpdateChunks / BuildDesiredTiles / CullTiles / SubmitTiles / ProcessPending / ProcessUnload / UpdateDecorations / UpdateWater`. Capture a `Count/Incl/Excl` Insights timer export and read the `Excl` column to see which step owns the per-frame cost (the actor tick shows as `BP_VoxelWorld_C` if subclassed in BP). `VoxelForge_ClassifyTile` (T1.d) / `VoxelForge_GenerateMesh` + `VoxelForge_BuildStreams` are worker-side (off the frame): the RMC `FRealtimeMeshStreamSet` is now built on the gen worker (`BuildTileStreamSet`) and carried on `FChunkResult::Streams` (TSharedPtr), so `ApplyMeshToTile` is game-thread-cheap — just material/ceiling resolve + `CreateSectionGroup(MoveTemp)`. See ARCHITECTURE §8.10 "Worker-built StreamSet (T1.f)".
|
||||
|
||||
@@ -343,31 +407,31 @@ This is **where terrain shape lives.**
|
||||
| Symbol | .cpp line | Role |
|
||||
|--------|-----------|------|
|
||||
| `UVoxelGenerator` / `DensityCacheOwnerId` | — | Constructor allocates a process-unique integer identity (relaxed atomic, once per object). `GetDensityAt` includes it in the `CP_*` thread-local key, preventing a worker from serving another generator/world's params, biome context, `CP_UseOpStack`, or stack when `(ChunkCoord, LayoutVersion)` happens to match. Hot-path cost: one `uint64` compare per voxel. Scope is deliberately only the proved `CP_*` path. |
|
||||
| `FractalNoise3D` (static) | 25 | fBM (layered Perlin). |
|
||||
| `RidgedNoise3D` (static) | 55 | Ridged multifractal — craggy. |
|
||||
| `CellularNoise3D` (static) | 101 | Worley/cellular — grotto/scallop. |
|
||||
| `ApplyBoundarySeal` (static) | 170 | Solidifies strate top/bottom shells. |
|
||||
| `ApplyPassageCarving` (static) | 197 | Punches passages/elevator through the seal. |
|
||||
| `InitializeSettings` | 211 | Copies the effective seed, spine radius, and global XY world-bound settings from `UVoxelSettings` (the season owns all three when assigned); radius 0 preserves the unbounded field. |
|
||||
| **`GetDensityAt`** | 218 | **Entry point.** Picks strate + generator type, materialises a cooked-season recipe on the existing per-chunk refetch when present, dispatches, then adds disturbances/diff. Its `CP_*` state is keyed by `(DensityCacheOwnerId, ChunkCoord, LayoutVersion)`; workers copy immutable recipe/vector data and never read editor composer state. |
|
||||
| **`GetDensityWithParams`** | 277 | TunnelNetwork pipeline (~1000 lines). See §4. ⚠️ Takes **required** `ParamsFingerprint` + `LayoutVersion` since the AUDIT §C2 fix (2026-07-28) — they go into the SDF cache key so a chunk can no longer be evaluated against a neighbour's rooms. Callers compute the CRC **once per chunk** (`CP_TunnelFP`), never per voxel. The optional `voxel.WormBlockSkip` path (default 0) uses `VF_TryGetWormBlockSkip` + a worker-local 4³ lattice-block cache to prove `|N1| >= WormThreshold` and remove both worm noise calls only; it never changes `NetworkMask` or the fallback field path. |
|
||||
| **`GetSlabDensity`** | 1306 | FlatPlain/CrystalChamber pipeline. See §4.2. |
|
||||
| `FractalNoise3D` (static) | 931 | fBM (layered Perlin). |
|
||||
| `RidgedNoise3D` (static) | 949 | Ridged multifractal — craggy. |
|
||||
| `CellularNoise3D` (static) | 976 | Worley/cellular — grotto/scallop. |
|
||||
| `ApplyBoundarySeal` (static) | 996 | Solidifies strate top/bottom shells. |
|
||||
| `InitializeSettings` | 2691 | Copies the effective seed, spine radius, and global XY world-bound settings from `UVoxelSettings` (the season owns all three when assigned); radius 0 preserves the unbounded field. |
|
||||
| **`GetDensityAt`** | 2712 | **Entry point.** Picks strate + generator type and, on the per-chunk refetch, builds the chunk's operator stack (editor composer → cooked-season recipe → native archetype stack); evaluates it (TunnelNetwork/Underwater through the fused `GetDensityWithParams`), then adds disturbances and structural posts. Optional `OutPreEditDensity` receives the exact final procedural field immediately before the player DiffLayer tail, allowing the near-player dig cache to replay edits without regenerating the operator stack. Its ordinary one-argument behavior is unchanged. A degenerate strate has no stack and is air; `CP_*` state remains keyed by `(DensityCacheOwnerId, ChunkCoord, LayoutVersion)`. |
|
||||
| **`EvaluateDensityChunk`** | 2728 | **Editor-only, default-off research API.** Accepts a lattice region contained by one 32-voxel chunk. Routes once, reuses `FVoxelDensityBlockSession::FillChunk` / `EvalBlock` for supported TunnelNetwork and Underwater operator stacks, and applies the existing post-stack writers across all chunk points before returning. Unsupported routes use scalar `GetDensityAt` and return both fallback and general-path entry counts. The region evaluator prepares bounded cache/index state once per region or chunk. The API is absent from non-editor builds; streaming and gameplay continue through the original scalar API. |
|
||||
| **`GetDensityWithParams`** | 3749 | TunnelNetwork pipeline (~1000 lines) — the fused evaluator of the TunnelNetwork/Underwater stack in `GetDensityAt`. See §4. ⚠️ Takes **required** `ParamsFingerprint` + `LayoutVersion` since the AUDIT §C2 fix (2026-07-28) — they go into the SDF cache key so a chunk can no longer be evaluated against a neighbour's rooms. Callers compute the CRC **once per chunk** (`CP_TunnelFP`), never per voxel. The optional `voxel.WormBlockSkip` path (default 0) uses `VF_TryGetWormBlockSkip` + a worker-local 4³ lattice-block cache to prove `|N1| >= WormThreshold` and remove both worm noise calls only; it never changes `NetworkMask` or the fallback field path. |
|
||||
| `SampleSurfaceStructuralZ` | — | **F20:** the RAW SurfaceWorld heightfield (continents+mountains+detail), BEFORE any terrain op; returns terrain Z + relief M. Cliff re-samples it at an XY offset for a cheap analytic slope. |
|
||||
| `ComputeSurfaceTerrainZ` / `GetSurfaceDensity` | — | SurfaceWorld heightfield → terrain Z, then density; biome **output-blend** lerps dominant/neighbour heights (`ParamsD`/`ParamsN`/weight). **F20 surface ops** (`FSurfaceGenerationParams`, biome-selected + slope/relief-conditioned, all default off): Cliff (slope-gated STEEPENING — push height from local mean where steep ⇒ sheer walls; 4 structural resamples only when on), Terrace (relief-gated + `TerraceHardness`), LayerLines (sedimentary shelves) — pure per-column height REMAPS applied here so the single height oracle stays consistent (MC/sheets/ClassifyTile/deco/BP bridge). **Phase 2 OVERHANG** (volumetric — real jutting shelves): in `SurfaceDensityFromColumn`, for AIR voxels in a window `(TerrainZ, TerrainZ+OverhangHeight]` above a steep slope, the heightfield is re-sampled UPHILL (toward the cliff) by a reach that GROWS with height (tiny low ⇒ air over the void, full high ⇒ borrows the far cliff rock) and unioned in ⇒ a shelf attached to the cliff, tapering out over the void with air beneath (the sketch). Per-column `OverhangAmp`(=strength·slope-gate) + unit uphill `(DirX,DirY)` resolved once in `ComputeSurfaceColumn` (gradient sampled at the REACH scale so a spot over the void can see the cliff), cached on `FSurfaceColumn`. Genuine 3D (per-voxel structural re-eval, gated to steep overhang columns). Off ⇒ byte-identical. The field-preserving LOD0 pass removes only needless temporaries/default construction; every output remains bit-identical. §8.14. |
|
||||
| `VF_BuildOpStackForChunk` (file-static) | — | **The archetype → stack mapping, written down once.** `GetDensityAt` and `ClassifyTile` both call it; params are passed in, never fetched here. A second copy would be the worst bug available in this file — a tile skipped on the verdict of a stack that is not the one producing its density is a hole. Returns false (⇒ caller falls back to the `switch`) for an unported archetype, missing params, or a **degenerate strate**, since five archetype functions early-out to air there and the stack deliberately has no such early-out. `Refs.Surface == nullptr` makes it refuse SurfaceWorld, which is how `ClassifyTile` keeps its own exact-lattice proof. |
|
||||
| `ComputeSurfaceTerrainZ` / `ComputeSurfaceColumn` | — | SurfaceWorld heightfield → terrain Z (+ sky-cap ceiling and overhang gate per column); biome **output-blend** lerps dominant/neighbour heights (`ParamsD`/`ParamsN`/weight). **F20 surface ops** (`FSurfaceGenerationParams`, biome-selected + slope/relief-conditioned, all default off): Cliff (slope-gated STEEPENING — push height from local mean where steep ⇒ sheer walls; 4 structural resamples only when on), Terrace (relief-gated + `TerraceHardness`), LayerLines (sedimentary shelves) — pure per-column height REMAPS applied here so the single height oracle stays consistent (MC/ClassifyTile/deco/BP bridge). **Phase 2 OVERHANG** (volumetric — real jutting shelves): in the stack's `FOverhangShelfMod`, for AIR voxels in a window `(TerrainZ, TerrainZ+OverhangHeight]` above a steep slope, the heightfield is re-sampled UPHILL (toward the cliff) by a reach that GROWS with height (tiny low ⇒ air over the void, full high ⇒ borrows the far cliff rock) and unioned in ⇒ a shelf attached to the cliff, tapering out over the void with air beneath (the sketch). Per-column `OverhangAmp`(=strength·slope-gate) + unit uphill `(DirX,DirY)` resolved once in `ComputeSurfaceColumn` (gradient sampled at the REACH scale so a spot over the void can see the cliff), cached on `FSurfaceColumn`. Genuine 3D (per-voxel structural re-eval, gated to steep overhang columns). Off ⇒ byte-identical. The field-preserving LOD0 pass removes only needless temporaries/default construction; every output remains bit-identical. §8.14. |
|
||||
| `VF_BuildOpStackForChunk` (file-static) | — | **The archetype → stack mapping, written down once.** `GetDensityAt` and `ClassifyTile` both call it; params are passed in, never fetched here. A second copy would be the worst bug available in this file — a tile skipped on the verdict of a stack that is not the one producing its density is a hole. Returns false for an unknown archetype, missing params, or a **degenerate strate** (the stack deliberately has no degenerate early-out): `GetDensityAt` then returns air and `ClassifyTile` returns `Mixed`. `Refs.Surface == nullptr` makes it refuse SurfaceWorld, which is how `ClassifyTile` keeps its own exact-lattice proof. |
|
||||
| `ClassifyTile` | — | **T1.d trivial-tile reject. ⛔ Not called by streaming by default since 2026-09-13** (`voxel.OuterClassifierMode=0`; net-negative in game, see ARCHITECTURE §8.10). Cooked/editor recipes now build the exact recipe stack and call its proved `ClassifyBox`; a tile crossing a slot/gap, any diff, or any disturbance returns `Mixed`. This replaces the old global `HasComposerRecipeOverride()` force-to-Mixed. Native cave/surface/gap proofs and all §8.10 caches remain unchanged. |
|
||||
| `SampleRelief` / `SampleMoisture` | — | Climate fields (pure XY, [0,1]). Relief = shared source of truth for the relief map M. §8.14. |
|
||||
| `SampleBiomeAt` | — | Warped-Voronoi + climate biome query (dominant + neighbour + weight). Reference used by the preview bake + `GetDominantBiomeAt`. §8.14. |
|
||||
| `ResolveBiomeSampleAt` / `RebuildBiomeGrid` | — | Hot-path biome resolve (FBiomeSample) via a box-validated per-chunk cell-grid cache. Bit-identical to `SampleBiomeAt`. §8.14, §8.10. |
|
||||
| `GetDominantBiomeAt` | — | Game-thread query → dominant biome ASSET (content/atmosphere). §8.14. |
|
||||
| `QueryBiomeAt` | — | Rich game-thread biome probe → `FVoxelBiomeQuery` (dominant/neighbour asset, relief/moisture, blend weight, dominant deco count). Diagnostic behind `AVoxelWorld::GetBiomeAtWorldLocation`. §8.14. |
|
||||
| `EvaluateTerrainConditions` | ~2548 | **F7 aware placement:** AND-evaluate an entry's `FTerrainCondition[]` (relief/moisture/biome-border) at a candidate voxel XY. Empty = true (zero cost). Pure query (SampleRelief/SampleMoisture/SampleBiomeAt) → worker-safe + game-thread; caller passes the strate's `FBiomeContext` (freq/contrast + Voronoi map). Consumed by deco `BuildCellSpawns` + `SpawnLandmarkInstance`; shared core of the future quest FindFeature locator. |
|
||||
| `EvaluateTerrainConditions` | 6973 | **F7 aware placement:** AND-evaluate an entry's `FTerrainCondition[]` (relief/moisture/biome-border) at a candidate voxel XY. Empty = true (zero cost). Pure query (SampleRelief/SampleMoisture/SampleBiomeAt) → worker-safe + game-thread; caller passes the strate's `FBiomeContext` (freq/contrast + Voronoi map). Consumed by deco `BuildCellSpawns` + `SpawnLandmarkInstance`; shared core of the future quest FindFeature locator. |
|
||||
|
||||
> **Per-voxel hot-path memos (perf pass 2, all bit-identical — same hashes/math, hoisted per
|
||||
> chunk/cell):** `GetDensityAt` uses the DiffLayer snapshot cache (§3.9); `GetDensityWithParams`
|
||||
> memoizes the strate index per (chunkZ, `GetLayoutVersion()`); `thread_local` per-cell lattice
|
||||
> bakes cover slab columns (`GetSlabDensity` step 4), maze open edges, vertical shafts +
|
||||
> cross-connectors, floating-island constants, and the disturbance chasms/bridges/ridges; worm
|
||||
> bakes cover slab columns (`FGridColumnMod`), maze open edges (`FLatticeCorridorSource`), vertical
|
||||
> shafts + cross-connectors (`FShaftFieldSource`), floating-island constants (`FIslandBlobSource`),
|
||||
> and the disturbance chasms/bridges/ridges; worm
|
||||
> tunnels short-circuit the 2nd Perlin when N1 ≥ WormThreshold (N2 ≥ 0 ⇒ can't carve). With
|
||||
> `voxel.WormBlockSkip=1`, a worker-local cache proves whole 4³ lattice blocks only when the
|
||||
> actual 3D Perlin bound (`L = 8.118988160`, including `VOXEL_NOISE_SCALE`) plus a `1e-3`
|
||||
@@ -432,28 +496,36 @@ serialization before they are written. The commandlet keeps `WorldRadiusVoxels=0
|
||||
authored asset or diff layer, and enforces the default 25-minute / maximum 30-minute wall-clock
|
||||
budget, reporting partial artifacts and completed modes on truncation. `-gameconfig=1` opts into the
|
||||
authored settings' skirts/LOD octave drop and permits export-only parity of a single-strate asset;
|
||||
the historical interior-slot requirement remains in force for walk/probe runs. `VoxelGeometryHash.h`
|
||||
is the shared digest used by `Tools/VoxelForgeTest.ps1 -Scenario parity`.
|
||||
the optional `-exporttilex/y/z` arguments choose a specific level-0 tile for export/game parity,
|
||||
while omitting them preserves center-derived region selection. The historical interior-slot
|
||||
requirement remains in force for walk/probe runs. `VoxelGeometryHash.h` is the shared digest used
|
||||
by `Tools/VoxelForgeTest.ps1 -Scenario parity`.
|
||||
|
||||
### 3.7 Cave morphology (SDF rooms/tunnels) — `Public/VoxelCaveMorphology.h` + `.cpp`
|
||||
Header is rich with inline docs. Two namespaces + a per-chunk cache system.
|
||||
|
||||
- `namespace VoxelSDF` (h:46): `Sphere`, `Ellipsoid`, `Capsule`, `RoundedBox`,
|
||||
- `namespace VoxelSDF` (h:78): `Sphere`, `Ellipsoid`, `Capsule`, `RoundedBox`,
|
||||
`TaperedCapsule`, `SmoothMin`, `SmoothMax` — all FORCEINLINE SDF primitives.
|
||||
- `namespace VoxelHash` (h:158): `Mix`, `Cell`, `Pair`, `ToFloat01`, `ToFloatSigned`
|
||||
- `namespace VoxelHash` (h:210): `Mix`, `Cell`, `Pair`, `ToFloat01`, `ToFloatSigned`
|
||||
— deterministic hashing for room/tunnel placement (no storage, infinite worlds).
|
||||
- Cache structs (h:224-330): `FCachedRoom`, `FCachedTunnel`, `FCachedPit`,
|
||||
- Cache structs (h:520-950): `FCachedRoom`, `FCachedTunnel`, `FCachedPit`,
|
||||
`FCachedChimney`, `FCachedColumn`, `FChunkSDFCache`.
|
||||
- `FChunkSDFBrickIndex` is an editor-only-at-use 8-voxel 3D bin beside the existing XY index. The
|
||||
default-off `SetResearchBrickBinningEnabled` selector builds/selects it only for the explore
|
||||
commandlet. `VF_BuildResearchBrickIndices` uses the cached room cull reach (shape, floor relief,
|
||||
blend and terrain-op envelope), room-floor-join bounds, and the union of SDF/world tunnel
|
||||
centerline influence and bounding-sphere bounds. Source item indices stay in ascending order;
|
||||
invalid or oversized grids fall back to today's XY broad phase. This step-2 candidate-bin
|
||||
ablation still routes every lattice point through the canonical `GetDensityAt` final field.
|
||||
- `namespace VoxelCaveMorphology`:
|
||||
| Function | .cpp line | Role |
|
||||
|----------|-----------|------|
|
||||
| `BuildChunkCache` | 47 | **Phase 1** (once/chunk): collect rooms, guaranteed backbone (`bTunnelsFlowTowardOrigin`: tree rooted at the (0,0) hub — every room reachable, links flow inward; false = legacy NN forest), slope-aware link metric (`TunnelHorizontalBias` now applies to backbone too), decide tunnels, **cull zero-connection rooms** (no sealed bubbles), store rooms by their OWN reach (fixes origin-room clipping at `MaxInfluence`), pre-bake pits/chimneys/columns via the shared `BakeRoomFeature` hash-placement skeleton (one gate/XY/radius pattern + per-type Emit lambda), hash-roll per-room terrain op. |
|
||||
| `EvaluateSDFCached` | 757 | **Phase 2** (per voxel): SmoothMin over cached rooms/tunnels; returns nearest room idx for terrain-op lookup. **Signature changed (perf pass 2): `RoomShapeVariety` param REMOVED** — the shape roll + capsule trig are pre-baked into `FCachedRoom` (`ShapeType/ShapeA/ShapeB/ShapeR`) by `BuildChunkCache`, bit-identical. |
|
||||
| `EvaluateSDF` | 738 | Convenience wrapper (builds temp cache) for one-off queries. |
|
||||
| `BuildChunkCache` | 6782 | **Phase 1** (once/chunk): collect rooms, guaranteed backbone (`bTunnelsFlowTowardOrigin`: tree rooted at the (0,0) hub — every room reachable, links flow inward; false = legacy NN forest), slope-aware link metric (`TunnelHorizontalBias` now applies to backbone too), decide tunnels, **cull zero-connection rooms** (no sealed bubbles), store rooms by their OWN reach (fixes origin-room clipping at `MaxInfluence`), pre-bake pits/chimneys/columns via the shared `BakeRoomFeature` hash-placement skeleton (one gate/XY/radius pattern + per-type Emit lambda), hash-roll per-room terrain op. |
|
||||
| `EvaluateSDFCached` | 8569 | **Phase 2** (per voxel): SmoothMin over cached rooms/tunnels; returns nearest room idx for terrain-op lookup. **Signature changed (perf pass 2): `RoomShapeVariety` param REMOVED** — the shape roll + capsule trig are pre-baked into `FCachedRoom` (`ShapeType/ShapeA/ShapeB/ShapeR`) by `BuildChunkCache`, bit-identical. |
|
||||
|
||||
`MakeStrateSeed` (h:~477) is the shared pure world-seed/strate-index salt used by both
|
||||
`MakeStrateSeed` (h:965) is the shared pure world-seed/strate-index salt used by both
|
||||
`BuildChunkCache` and the TunnelNetwork/Underwater destination landing queries.
|
||||
`VF_SuggestLandingPoint` (h:~550, implemented in `VoxelCaveMorphology.cpp`) is the pure player-fit
|
||||
`VF_SuggestLandingPoint` (h:1121, implemented in `VoxelCaveMorphology.cpp`) is the pure player-fit
|
||||
landing-site query: it evaluates the local archetype source through the same capsule/support
|
||||
contract as the measurement pass — support within step height, walkable slope, and full capsule
|
||||
clearance. Room queries use a room-floor core, slabs keep their requested XY, and the sparse
|
||||
@@ -467,71 +539,70 @@ SurfaceWorld remains refused because its production height can depend on manager
|
||||
context. It never touches a generator, operator stack, manager, cache, or mutable state.
|
||||
|
||||
`FVoxelPassageLanding`, `VF_BuildPassageLanding`, `VF_EvaluatePassageLandingSDF`, and
|
||||
`VF_IsPassageLandingFloor` (h:~56/~189, implemented near `.cpp:2059`) turn each inter-strate mouth
|
||||
`VF_IsPassageLandingFloor` (h:57/184-199, implemented near `.cpp:6468`) turn each inter-strate mouth
|
||||
into a deterministic rounded room plus a hard support slab. The room is sized from the player
|
||||
capsule (minimum 12 voxels / 3 m flat floor, 12 voxels / 3 m clear height, and at least 14 voxels /
|
||||
3.5 m authored width); the `(0,0)` origin landing is enlarged for the reserved future shaft, while
|
||||
capsule (minimum 24 voxels / 3 m flat floor and clear height); the `(0,0)` origin landing is enlarged
|
||||
for the reserved future shaft, while
|
||||
inter-strate mouths remain local rooms and deliberately have no landing-to-root connector road.
|
||||
The manager's shared thread-local passage shortlist serves tube, room, and floor evaluation; no
|
||||
landing search occurs per voxel. `GeneratePassages` stores the full descriptors and the
|
||||
order-independence test compares them bit-for-bit.
|
||||
|
||||
Performance note (h:209-220): caching rooms/tunnels once per chunk instead of per
|
||||
Performance note (h:520-531): caching rooms/tunnels once per chunk instead of per
|
||||
voxel is the single biggest CPU win.
|
||||
|
||||
### 3.8 Strate system
|
||||
**`Public/VoxelStrateTypes.h`** — shared structs/enums (1228 lines, the data vocabulary):
|
||||
| Symbol | Line | Role |
|
||||
|--------|------|------|
|
||||
| `EVoxelPassageType` | 38 | Sloped/Vertical/Spiral/Cascading/Crack passage shapes. |
|
||||
| `ESurfaceType` | 78 | Floor/Wall/Ceiling/Any (decoration placement). |
|
||||
| `EVoxelNoiseType` | 99 | FBM/Ridged/Mixed/Cellular. |
|
||||
| `ECaveGeneratorType` | 146 | TunnelNetwork / FlatPlain / CrystalChamber. |
|
||||
| `EVoxelStrateTransition` | 183 | Gradient / Hard / Interleaved boundary blends. |
|
||||
| **`FStrateGenerationParams`** | ~350 | The giant TunnelNetwork param bag (rock, worms, rooms, tunnels, warp, roughness, live terrain-detail fields, dead terrain-op transport slots, boundary seal). `Lerp()` static blends two sets at boundaries — it expands the **`VF_STRATE_PARAM_FIELDS` X-macro** (defined just above the struct): **adding a field to the struct? add it to that list** or blends silently reset it to default. |
|
||||
| `FStrateTerrainOpEntry` | 965 | Soft-ptr to a terrain op + Weight + Probability. |
|
||||
| **`FSlabGenerationParams`** | 1019 | Floor/ceiling heights, roughness, columns, seal — for slab generators. |
|
||||
| **`FPlacementProfile`** | ~1747 | **Shared placement vocabulary** for every scatter primitive (`FStrateDecoration`, `FStrateLandmark`, coming `FStrateSetPiece`): spawn (ActorClass/InstancedMesh), Filter gates (surface/slope/overhang/water/RequiredBiome + **F7 awareness `Conditions[]`** — `FTerrainCondition` relief/moisture/biome-border predicates, AND-ed, evaluated by `Generator::EvaluateTerrainConditions`), Transform (align/offsets/RotationOffset+RandomRotation/scale), Render (cull/shadow). Each primitive embeds it as `Profile` + keeps only its own DISTRIBUTION fields. Per-primitive defaults set in each struct's ctor (deco: scale 0.8-1.2 + RandomRotation.Yaw=360; landmark: Ceiling + no align). |
|
||||
| `FStrateDecoration` / `FStrateLandmark` | ~1830 / ~1900 | `Profile` + distribution: deco = StreamTier/SpawnDensity/MaxPerChunk; landmark = SpacingChunks/JitterFraction/SpawnProbability/StreamRadiusChunks + Light-Orb block. |
|
||||
| `ELandmarkAnchor` (on `FStrateLandmark`) | ~1940 | F7: `AnchorMode` = HashLattice / PassageMouth + passage toggles + exclusion (`ExclusionRadiusChunks`/`Priority`) folded into `FStrateLandmark` (set-pieces merged in — one primitive, one `Landmarks` list, `UpdateLandmarks`). |
|
||||
| `FStrateAmbientActor` / `FStrateCreature` | ~2090 / ~2110 | Content spawn entries (consumed by future systems). |
|
||||
| `EVoxelPassageType` | 42 | Sloped/Vertical/Spiral/Cascading/Crack passage shapes. |
|
||||
| `ESurfaceType` | 81 | Floor/Wall/Ceiling/Any (decoration placement). |
|
||||
| `EVoxelNoiseType` | 128 | FBM/Ridged/Mixed/Cellular. |
|
||||
| `ECaveGeneratorType` | 175 | TunnelNetwork / FlatPlain / CrystalChamber. |
|
||||
| `EVoxelStrateTransition` | 228 | Gradient / Hard / Interleaved boundary blends. |
|
||||
| **`FStrateGenerationParams`** | 373 | The giant TunnelNetwork param bag (rock, worms, rooms, tunnels, warp, roughness, live terrain-detail fields, dead terrain-op transport slots, boundary seal). `Lerp()` static blends two sets at boundaries — it expands the **`VF_STRATE_PARAM_FIELDS` X-macro** (defined just above the struct): **adding a field to the struct? add it to that list** or blends silently reset it to default. |
|
||||
| `FStrateTerrainOpEntry` | 1169 | Soft-ptr to a terrain op + Weight + Probability. |
|
||||
| **`FSlabGenerationParams`** | 1223 | Floor/ceiling heights, roughness, columns, seal — for slab generators. |
|
||||
| **`FPlacementProfile`** | 2000 | **Shared placement vocabulary** for every scatter primitive (`FStrateDecoration`, `FStrateLandmark`; set-pieces are landmarks): spawn (ActorClass/InstancedMesh), Filter gates (surface/slope/overhang/water/RequiredBiome + **F7 awareness `Conditions[]`** — `FTerrainCondition` relief/moisture/biome-border predicates, AND-ed, evaluated by `Generator::EvaluateTerrainConditions`), Transform (align/offsets/RotationOffset+RandomRotation/scale), Render (cull/shadow). Each primitive embeds it as `Profile` + keeps only its own DISTRIBUTION fields. Per-primitive defaults set in each struct's ctor (deco: scale 0.8-1.2 + RandomRotation.Yaw=360; landmark: Ceiling + no align). |
|
||||
| `FStrateDecoration` / `FStrateLandmark` | 2207 / 2271 | `Profile` + distribution: deco = StreamTier/SpawnDensity/MaxPerChunk; landmark = SpacingChunks/JitterFraction/SpawnProbability/StreamRadiusChunks + Light-Orb block. |
|
||||
| `ELandmarkAnchor` (on `FStrateLandmark`) | 2251 | F7: `AnchorMode` = HashLattice / PassageMouth + passage toggles + exclusion (`ExclusionRadiusChunks`/`Priority`) folded into `FStrateLandmark` (set-pieces merged in — one primitive, one `Landmarks` list, `UpdateLandmarks`). |
|
||||
| `FStrateAmbientActor` / `FStrateCreature` | 2405 / 2425 | Content spawn entries (consumed by future systems). |
|
||||
|
||||
**`Public/VoxelStrateDefinition.h`** — `UVoxelStrateDefinition : UPrimaryDataAsset`
|
||||
(line 36). One asset = one strate *type*. Fields: identity, `StrateHeightInChunks`(60),
|
||||
`TransitionType`(79)/`TransitionBlendChunks`(89), `GeneratorType`(102),
|
||||
`GenerationParams`(113), `SlabParams`(124), `Biomes[]`+`BiomeMapParams` (the biome list +
|
||||
field tuning — empty ⇒ unchanged world, §8.14), `TerrainOperations`(147), visuals/fog/light,
|
||||
content lists, audio, `GameplayTags`(223), `bUseOperatorStack` (the OPSTACK A/B opt-in — only bites
|
||||
if the archetype is in `UsesOperatorStackForChunk`'s ported list). EditConditions show/hide param
|
||||
groups by generator type.
|
||||
(line 46). One asset = one strate *type*. Fields: identity, `StrateHeightInChunks`(75),
|
||||
`TransitionType`(93)/`TransitionBlendChunks`(103), `GeneratorType`(116),
|
||||
`GenerationParams`(137), `SlabParams`(148), `Biomes[]`+`BiomeMapParams` (the biome list +
|
||||
field tuning — empty ⇒ unchanged world, §8.14), `TerrainOperations`(252), visuals/fog/light,
|
||||
content lists, audio, `GameplayTags`(393). EditConditions show/hide param groups by generator
|
||||
type.
|
||||
|
||||
**`Public/VoxelStrateManager.h` + `.cpp`** — `UVoxelStrateManager : UObject` (h:108).
|
||||
**`Public/VoxelStrateManager.h` + `.cpp`** — `UVoxelStrateManager : UObject` (h:152).
|
||||
Maps depth→strate at runtime; owns passages.
|
||||
- `FVoxelPassage` (h:39): standing endpoints, radius, type, wandering control points, and
|
||||
- `FVoxelPassage` (h:46): standing endpoints, radius, type, wandering control points, and
|
||||
upper/lower local landing descriptors (room, floor, and door; no root connector).
|
||||
- `FStrateSlot` (h:84): definition + chunk-Z range + index.
|
||||
- `FStrateSlot` (h:128): definition + chunk-Z range + index.
|
||||
| Method | .cpp line | Role |
|
||||
|--------|-----------|------|
|
||||
| `Initialize` | 10 | Keeps the existing sequencing through `GeneratePassages`, but selects one slot source: a validated cooked season (exact ordered bounds/vector/recipe) or the unchanged authored fixed+sorted/shuffled pool. Assigned-invalid seasons fail closed. Authored source definitions are duplicated as content bags; generated slots use deterministic C++ defaults and zero auto-passages because schema 2 does not store passage configuration. |
|
||||
| `GeneratePassages` | 247 | Deterministic passages between consecutive strates (per-strate `PassageConfig::Style` control points; auto passages retain the existing `EVoxelPassageType` default); placement and shape values are independently salted hashes of seed + boundary slot + connection index. Each mouth independently queries its own strate's pure player-fit `VF_SuggestLandingPoint` with its contract seed (cave room salt, otherwise world seed), may move within its archetype-specific budget, then receives a body-sized local room/floor. The origin landing is reserved for the future shaft; no landing-to-root road is generated. Unsupported/no-fit answers preserve the old random reach; a deterministic level dog-leg is inserted when the final ramp needs it to stay ≤44°. All descriptors and bounds are generated without reading another passage. |
|
||||
| `EvaluateModifierSDF` | 357 | SDF of passage tubes plus local landing rooms at a point. A per-chunk `thread_local` shortlist (`PassagesVersion`-stamped) → far chunks return `FLT_MAX` without walking `Passages`; the same cache serves floor membership. `FPassageEvaluationCache::FloorProjections` reuses native/generic floor projections and walkable-air results once per `(sample, passage)` using exact double XYZ equality and unchanged float outputs; `voxel.FloorRound1PassageProjectionCache=0` disables only this A/B cache. §8.10. |
|
||||
| `ApplyPassageModifier` | 1319 | Shared legacy/op-stack passage post: carves tube and local landing air through the idempotent landing primitive, then restores the local landing support slab in internal positive-solid density. |
|
||||
| `VF_ApplyPassageLandingCarving` | — | Idempotent density-independent landing-air threshold shared by the structural pass and the post-disturbance MC backstop; keeps legacy and operator-stack results bit-identical. |
|
||||
| `ApplyPassageLandingFloorMC` | 1347 | Reasserts the same support slab after MC-space disturbances and before the final XY edge seal. |
|
||||
| `Initialize` | 1164 | Keeps the existing sequencing through `GeneratePassages`, but selects one slot source: a validated cooked season (exact ordered bounds/vector/recipe) or the unchanged authored fixed+sorted/shuffled pool. Assigned-invalid seasons fail closed. Authored source definitions are duplicated as content bags; generated slots use deterministic C++ defaults and zero auto-passages because schema 2 does not store passage configuration. |
|
||||
| `GeneratePassages` | 1659 | Deterministic passages between consecutive strates (per-strate `PassageConfig::Style` control points; auto passages retain the existing `EVoxelPassageType` default); placement and shape values are independently salted hashes of seed + boundary slot + connection index. Each mouth independently queries its own strate's pure player-fit `VF_SuggestLandingPoint` with its contract seed (cave room salt, otherwise world seed), may move within its archetype-specific budget, then receives a body-sized local room/floor. The origin landing is reserved for the future shaft; no landing-to-root road is generated. Unsupported/no-fit answers preserve the old random reach; a deterministic level dog-leg is inserted when the final ramp needs it to stay ≤44°. All descriptors and bounds are generated without reading another passage. |
|
||||
| `EvaluateModifierSDF` | 2643 | SDF of passage tubes plus local landing rooms at a point. A per-chunk `thread_local` shortlist (`PassagesVersion`-stamped) → far chunks return `FLT_MAX` without walking `Passages`; the same cache serves floor membership. `FPassageEvaluationCache::FloorProjections` reuses native/generic floor projections and walkable-air results once per `(sample, passage)` using exact double XYZ equality and unchanged float outputs; `voxel.FloorRound1PassageProjectionCache=0` disables only this A/B cache. §8.10. |
|
||||
| `ApplyPassageModifier` | 2772 | Shared legacy/op-stack passage post: carves tube and local landing air through the idempotent landing primitive, then restores the local landing support slab in internal positive-solid density. |
|
||||
| `VF_ApplyPassageLandingCarving` | — | Idempotent density-independent landing-air threshold shared by the structural pass and the post-disturbance MC backstop, so the two agree bit for bit. |
|
||||
| `ApplyPassageLandingFloorMC` | 2983 | Reasserts the same support slab after MC-space disturbances and before the final XY edge seal. |
|
||||
| `AnyPassageNearBox` | — | Conservative sphere-vs-AABB test of every passage's bound against a voxel box (+carve blend pad). Per TILE (ClassifyTile guard), never per voxel. |
|
||||
| `AnyPassageLandingFloorNearBox` | — | Conservative local-landing-floor AABB guard. It kills the `AllAir` hypothesis wherever a proved landing floor can occur; the op-stack returns `Both` for the same box. |
|
||||
| `FindSlotIndexForChunkZ` | 427 | Z → layout index. |
|
||||
| `GetStrateAt` / `GetStrateIndex` | 443 / 455 | World-Z queries. |
|
||||
| `GetLayoutVersion` | h:161 (inline) | Layout/passage generation counter (= `PassagesVersion`, bumped by every `Initialize` and by the editor composer override). Hot-path callers key `thread_local` memos on it (strate-index memo in `GetDensityWithParams`, passage shortlist) so live changes never serve stale data. |
|
||||
| `GetStrateForChunk` | 466 | Chunk → definition. |
|
||||
| `GetGeneratorTypeForChunk` | 476 | Chunk → generator type. |
|
||||
| `UsesOperatorStackForChunk` | 559 | Chunk → should `GetDensityAt` take the operator stack? `bUseOperatorStack` on the definition **AND** archetype in the ported list — **now all 8 of 8** (Maze, FlatPlain, CrystalChamber, SurfaceWorld, VerticalShafts, FloatingIslands, TunnelNetwork, Underwater). **That list is written down here and nowhere else.** With every archetype ported the flag is now the *only* thing that decides the path, so ticking the box is no longer a no-op anywhere — it is a real switch onto the operator stack for that strate. |
|
||||
| `FindSlotIndexForChunkZ` | 6111 | Z → layout index. |
|
||||
| `GetStrateAt` / `GetStrateIndex` | 6127 / 6139 | World-Z queries. |
|
||||
| `GetLayoutVersion` | h:250 (inline) | Layout/passage generation counter (= `PassagesVersion`, bumped by every `Initialize` and by the editor composer override). Hot-path callers key `thread_local` memos on it (strate-index memo in `GetDensityWithParams`, passage shortlist) so live changes never serve stale data. |
|
||||
| `GetStrateForChunk` | 6150 | Chunk → definition. |
|
||||
| `GetGeneratorTypeForChunk` | 6176 | Chunk → generator type. |
|
||||
| `UsesOperatorStackForChunk` | 6203 | Chunk → does its slot have an operator stack? True for any in-layout slot with a definition whose archetype is one of the 8 (Maze, FlatPlain, CrystalChamber, SurfaceWorld, VerticalShafts, FloatingIslands, TunnelNetwork, Underwater); false outside the layout. **That list is written down here and nowhere else.** `ClassifyTile` guards on it; `GetDensityAt` uses it to share TunnelNetwork stacks by chunk Z. |
|
||||
| `GetRecipeForChunk` | — | Runtime immutable recipe/vector/strate-seed copy for cooked seasons; editor slot overrides use the same worker hand-off. |
|
||||
| `GetSlabParamsForChunk` | 490 | Slab params with runtime Z bounds (no blend — slabs use Hard). |
|
||||
| `GetSlabParamsForChunk` | 6263 | Slab params with runtime Z bounds (no blend — slabs use Hard). |
|
||||
| `GetBiomeContextForChunk` | — | Flatten the strate's `Biomes[]` + `BiomeMapParams` into a POD `FBiomeContext` for the biome field. Empty ⇒ biomes disabled. §8.14. |
|
||||
| `GetGenerationParams` | 515 | **Blended** TunnelNetwork params (handles Gradient/Hard/Interleaved transitions). |
|
||||
| `GetGenerationParams` | 6509 | **Blended** TunnelNetwork params (handles Gradient/Hard/Interleaved transitions). |
|
||||
| `SetComposerOverrideForStrate` / `GetComposerOverrideForChunk` | editor-only | Temporary density-only candidate overlay for one existing slot. Copies immutable params/recipe into the worker refetch path, leaves layout/content/passages intact, and bumps `PassagesVersion` to invalidate generator memos. |
|
||||
| `BuildParamsFromDefinition` (static) | 1399 | Returns the definition's base `GenerationParams` only. Terrain-op assets are passed separately to `BuildChunkCache`, which applies one selected op per room and pre-bakes Column/Pit/Chimney features. |
|
||||
| `BuildParamsFromDefinition` (static) | 6815 | Returns the definition's base `GenerationParams` only. Terrain-op assets are passed separately to `BuildChunkCache`, which applies one selected op per room and pre-bakes Column/Pit/Chimney features. |
|
||||
|
||||
**`Public/VoxelTerrainOpDefinition.h` + `.cpp`** — `UVoxelTerrainOpDefinition : UPrimaryDataAsset`
|
||||
(h:67). One asset = one terrain op. `EVoxelTerrainOpType` (h:36): Terrace, LayerLines,
|
||||
@@ -556,7 +627,7 @@ atmosphere override, `WaterMaterial`, `MaterialPaletteIndex` (F6 — baked to ve
|
||||
`UVoxelStrateDefinition::Biomes[]`. Generator-agnostic (surface biomes now, cave biomes later). §8.14.
|
||||
|
||||
**`Public/VoxelStrateComposer.h` + `Private/VoxelStrateComposer.cpp`** (Tier 4a/4b + Tier 5) — offline
|
||||
`FVoxelStrateCorpus`, `VF_RollStrateParams`, and `VF_RollStrateStructure`. `LoadFromAssetRegistry` enumerates every project
|
||||
`FVoxelStrateCorpus`, `VF_RollStrateParamsDetailed`, and `VF_RollStrateStructure`. `LoadFromAssetRegistry` enumerates every project
|
||||
`UVoxelStrateDefinition` through the Asset Registry, excludes `Saved/Autosaves` and `Saved/Cooked`
|
||||
copies, and adds one `VoxelStrateTypes.h` default vector for each of the eight exact archetypes.
|
||||
The corpus is grouped by exact `ECaveGeneratorType`: the 2026-09-04 run loaded **4 project
|
||||
@@ -668,31 +739,31 @@ text plus independently copied metadata/hash, and retains soft references to any
|
||||
`UVoxelSettings::Season` is the only activation switch.
|
||||
|
||||
### 3.9 Player edits — `Public/VoxelDiffLayer.h` + `.cpp`
|
||||
`UVoxelDiffLayer : UObject` (h:77). Stores `FVoxelModification` (h:43: Center/Radius/Strength;
|
||||
`UVoxelDiffLayer : UObject` (h:137). Stores `FVoxelModification` (h:60: Center/Radius/Strength;
|
||||
**negative Strength = carve, positive = fill**) grouped by chunk in `TMap ChunkMods`.
|
||||
| Method | .cpp line | Role |
|
||||
|--------|-----------|------|
|
||||
| `SetBudget` | 10 | From VoxelSettings carving caps. |
|
||||
| `CanModify` | 20 | Budget check (no consume) — for UI. |
|
||||
| `GetRemainingModifications` / `GetRemainingVolume` | 47 / 53 | -1 = unlimited. |
|
||||
| `ApplyModification` | 63 | Enforces budget, stores in all overlapped chunks, returns dirty coords. |
|
||||
| `GetDensityOffset` | 131 | Per-voxel combined diff (smoothstep falloff, additive). |
|
||||
| `HasModifications` | 160 | Fast reject for hot path. |
|
||||
| `HasAnyMods` / `GetModsVersion` | h:238 / h:241 (inline) | Lock-free atomics: any-mod-exists flag + monotonic mod-state version (bumped by `ApplyModification`/`Clear`). |
|
||||
| `SetBudget` | 102 | From VoxelSettings carving caps. |
|
||||
| `CanModify` | 112 | Budget check (no consume) — for UI. |
|
||||
| `GetRemainingModifications` / `GetRemainingVolume` | 169 / 175 | -1 = unlimited. |
|
||||
| `ApplyModification` | 185 | Enforces budget, stores in all overlapped chunks, returns dirty coords. |
|
||||
| `GetDensityOffset` | 299 | Per-voxel combined diff (smoothstep falloff, additive). |
|
||||
| `HasModifications` | 421 | Fast reject for hot path. |
|
||||
| `HasAnyMods` / `GetModsVersion` | h:246 / h:249 (inline) | Lock-free atomics: any-mod-exists flag + monotonic mod-state version (bumped by `ApplyModification`/`Clear`). |
|
||||
| `GetChunkModsSnapshot` | — | Copy one chunk's mod list under ONE read lock. Workers snapshot per (chunk, version) instead of locking per voxel — `GetDensityAt` keys a `thread_local` 64-slot direct-mapped cache on it (~27 lock ops per tile task instead of ~86k once any carve exists). |
|
||||
| `HasAnyModInChunkRange` | — | Any modified chunk key in an inclusive chunk box? One key walk under a read lock — ClassifyTile's diff guard (per tile, conservative by construction: mods are stored in every chunk their radius overlaps). |
|
||||
| `EvaluateMods` (static) | — | Lock-free pure evaluation of a mod list at a voxel — shared core of `GetDensityOffset` and the generator's snapshot path. |
|
||||
| `Clear` | 170 | Wipe all (season reset). |
|
||||
| `GetTotalModificationCount` / `GetModifiedChunkCount` | 182 / 192 | Stats. |
|
||||
| `Clear` | 435 | Wipe all (season reset). |
|
||||
| `GetTotalModificationCount` / `GetModifiedChunkCount` | 454 / 465 | Stats. |
|
||||
|
||||
### 3.10 Mesher — `Public/VoxelMarchingCubesMesher.h` + `.cpp`
|
||||
`UVoxelMarchingCubesMesher : UObject` (h:21). Holds `Generator` ptr, `IsoLevel=0`, skirt params.
|
||||
(The dead trio `GetDensity`/`InterpolateEdge`/`ComputeGradientNormal` + `GradientOffset` was
|
||||
removed — since T1.b the pre-sampled grid supplies positions AND gradients inline.)
|
||||
`UVoxelMarchingCubesMesher : UObject` (h:42). Holds `Generator` ptr, `IsoLevel=0`, skirt params.
|
||||
The pre-sampled grid supplies positions AND gradients inline (T1.b).
|
||||
| Method | .cpp line | Role |
|
||||
|--------|-----------|------|
|
||||
| **`GenerateSheetMesh`** | ~455 | **F18 far-field SHEET** (render-distance ring, `Tile.Level > MaxClipLevel`): two displaced heightfield grids per tile — ground (polygroup 0) + sky-cap (polygroup 1) from `GetSurfaceHeightAt` columns (StrateChunkZ = band mid identifies the strate; non-SurfaceWorld ⇒ empty). Classes true BY CONSTRUCTION (no vote/probes). Same conventions as `GenerateMesh` (world-cm positions, planar UVs, F6 colour masks, −N double-faced perimeter skirts per bucket, ground‖cap + `NumCeilingTriangles`). Margin ring keeps normals continuous between sheets. XY HOLE params: cells fully inside the MC-covered box around the player (`AVoxelWorld::SheetHole*Vox`, shrunk 1 tile for seam overlap) are skipped — a partially-covered sheet must not overlay near terrain; hole-edge cells get no skirt. Carved features (passages/spine/chasms) + diff layer NOT represented — accepted at sheet distance. |
|
||||
| **`GenerateMesh`** | ~15 | The MC loop over cells; `Step` controls LOD sampling. Sets the T2.b octave bias for the tile (`TGuardValue` on `VoxelGenLOD::OctaveBias`, from `LODOctaveDrop` × log2(Step); 0 at LOD0/off). Edge `t` + grid-gradient normals computed inline (`SampleG`/`GradAt`). Optional `OutCaptureGrid` (4th arg) = CAPTURE-DURING-MESHING: when non-null + full-res (`CellsPerAxis==CHUNK_SIZE`), copies the already-sampled `CHUNK_SIZE³` density grid (quantized via `VF_QuantizeDensity`, VoxelTypes.h) so the density clipmap reuses it instead of re-sampling `GetDensityAt`. Pure read of the grid — §8.10 untouched. **F17 surface class**: each unique vertex is classified sol/sky-cap in `GetOrCreateVertex` (down-facing only → memoized `GetSurfaceHeightAt`, nearer `CeilSurf` = cap); triangles bucket by majority into `GroundTris`/`CapTris` (thread_local), skirts emit per bucket, then `Triangles = ground‖cap` + `FVoxelMeshData::NumCeilingTriangles` (→ polygroups in `BuildTileStreamSet`). Same tris, only index ORDER changes. STRATE CONTENT CUT: optional `BandZMin/MaxVox` params restrict the cz cell loop (+ gz sampling rows) to the player-strate band — coarse straddling tiles mesh ONE strate (kills far-LOD inter-strate aliasing holes); meshed cells bit-identical. |
|
||||
| **`GenerateMesh`** | 96 | The MC loop over cells; `Step` controls LOD sampling. Sets the T2.b octave bias for the tile (`TGuardValue` on `VoxelGenLOD::OctaveBias`, from `LODOctaveDrop` × log2(Step); 0 at LOD0/off). Edge `t` + grid-gradient normals computed inline (`SampleG`/`GradAt`). Optional `OutCaptureGrid` (4th arg) captures the already-sampled `CHUNK_SIZE³` R8 density clipmap. Optional `OutPreEditDensityGrid` captures exact float samples before player edits for full-res L0 tiles; optional `InSharedDensityGrid` is per-call (not the mesher's shared mutable setting) so sync carve reuse cannot race async tile tasks. Capture/reuse leaves §8.10's thread-local scratch, two-pass MC and density-grid semantics intact. **F17 surface class**: each unique vertex is classified sol/sky-cap in `GetOrCreateVertex` (down-facing only → memoized `GetSurfaceHeightAt`, nearer `CeilSurf` = cap); triangles bucket by majority into `GroundTris`/`CapTris` (thread_local), skirts emit per bucket and increment diagnostic `NumSkirtTriangles`, then `Triangles = ground‖cap` + `FVoxelMeshData::NumCeilingTriangles` (→ polygroups in `BuildTileStreamSet`). Same tris, only index ORDER changes. STRATE CONTENT CUT: optional `BandZMin/MaxVox` params restrict the cz cell loop (+ gz sampling rows) to the player-strate band — coarse straddling tiles mesh ONE strate; meshed cells bit-identical. |
|
||||
| `FVoxelMeshData::NumSurfaceVertices` | 375 | Prefix length for density-isosurface vertices before seam skirt vertices are appended. The test-only visual-gap audit samples this prefix so artificial skirts cannot become expected terrain points. |
|
||||
| `FVoxelMeshData::NumSkirtTriangles` | — | Counts skirt triangles appended by `GenerateMesh`; consumed only by opt-in collision-cook stage reports. |
|
||||
|
||||
**`Public/MarchingCubesTables.h`** — `EdgeTable` + `TriTable` reference data (Paul
|
||||
Bourke). Cube corner/edge layout documented at top (lines 7-37). Rarely needs editing.
|
||||
@@ -702,7 +773,7 @@ Bourke). Cube corner/edge layout documented at top (lines 7-37). Rarely needs ed
|
||||
|------|------|
|
||||
| `Public/Private/VoxelContentManager.h/.cpp` | `UVoxelContentManager` — distance-based world-grid decoration scatter (no LOD pop, surface-snapped via `GetDensityAt`) + level-0 water planes. **F7 companions:** each `FStrateDecoration` may list `FDecoCompanion` satellites (rocks/mushrooms around a tree) — emitted per placed parent in `PlaceAtCrossing`, deterministic (pure fn of the parent hash), inherit the parent's surface point; `FDecoSpawn::CompanionIdx` routes each back to its `Companions[ci].Profile` at apply. **TWO streaming grids** (`FDecoGrid` Near/Far, picked per entry via `FStrateDecoration::StreamTier`): NearGrid = short radius + fine column grid (groundcover); FarGrid = full radius + coarse grid (cheap rare/large props). **Plus `UpdateLandmarks`** — rare deliberately-placed objects: mini-suns, ruins, shrines, monuments (`FStrateLandmark`; set-pieces folded in 2026-07-06). Per entry an `AnchorMode`: **HashLattice** (coarse hash lattice, cell = SpacingChunks chunks → cheap at any radius, no per-chunk freeze) or **PassageMouth** (`GetPassages()` endpoints landing in this strate). Gated by `Profile` + `Profile.Conditions`; optional deterministic **exclusion radius** (Priority+hash rank, `ExclusionRadiusChunks`; 0 = off → pure scatter is unchanged, skips the O(n²) resolve). Synchronous, deterministic, strate-wide; spawns via `SpawnFromProfile` (+ orb wrapper). Exclusion is POP-FREE (gathers a `MaxExcl` ring of suppress-only candidates so a piece's fate is player-position-independent). `bSuppressDecorationsUnder`/`SuppressRadiusChunks` clear grass under a footprint (on spawn + re-cleared in `ApplyRegion`). **`RemoveDecorationsInSphere`** (world sphere → both grids' HISMs via `GetInstancesOverlappingSphere`+`RemoveInstances`) is the shared primitive, also called by `AVoxelWorld::ApplyModification` so player digging removes floating grass instantly. Owned by `AVoxelWorld`. §8.5. |
|
||||
| `Public/Private/VoxelAtmosphereManager.h/.cpp` | `UVoxelAtmosphereManager` — per-strate fog/skylight + persistent ceiling/floor layer actors + full `AtmosphereActor` override. Owned by `AVoxelWorld`. §8.6. |
|
||||
| `Public/Private/VoxelDensityVolume.h/.cpp` | `UVoxelDensityVolume` — player-centred DENSITY CLIPMAP (N toroidal R8 levels, fine near / coarse far) streamed to GPU `UVolumeTexture`s for the mini-sun raymarched shadow march. Fills run on ONE dedicated thread (`FVoxelDensityFillRunnable`, off the task pool); level 0 is mostly fed by CAPTURE-DURING-MESHING (mesher grid reuse, gated by `IsTileCaptureUseful` so only tiles near the shadow window pay the capture). Carve → `MarkDirtyVoxelBox` refills locally. `VolumeEpoch` drops stale fills. Owned by `AVoxelWorld` (`bEnableDensityVolume`); shader params pushed via shared per-base-material MIDs (`AVoxelWorld::UpdateTerrainMaterialParams`, change-detected). |
|
||||
| `Public/Private/VoxelDensityVolume.h/.cpp` | `UVoxelDensityVolume` — opt-in player-centred DENSITY CLIPMAP (`bEnableDensityVolume` defaults false) with N toroidal R8 levels streamed to GPU `UVolumeTexture`s for mini-sun shadows. When enabled, fills run on ONE dedicated thread (`FVoxelDensityFillRunnable`); level 0 is mostly fed by CAPTURE-DURING-MESHING (gated by `IsTileCaptureUseful`), and carve → `MarkDirtyVoxelBox` refills locally. `VolumeEpoch` drops stale fills. When disabled, no object/thread/fill/upload/Tick update/capture/proof bypass exists; `SetDensityVolumeMaterialParamsDisabled` sets `VolP0..4`/`VolP6..9` black, `VolP5=(0,25,0)`, and `VolTex0..2` null on shared per-base MIDs. `SetVolumeParamsOnMID` carries these values to newly created MIDs. |
|
||||
|
||||
> The big 2026 redesign (8 archetypes, (0,0) spine, inter-strate gap, per-strate passages,
|
||||
> disturbances, content/atmosphere, brush shapes, perf invariants) is documented in **§8** —
|
||||
@@ -711,8 +782,10 @@ Bourke). Cube corner/edge layout documented at top (lines 7-37). Rarely needs ed
|
||||
---
|
||||
|
||||
### 3.12 Automation tests — `Private/Tests/` (added 2026-07-27, `#if WITH_DEV_AUTOMATION_TESTS`)
|
||||
The plugin's first tests (`OPSTACK-PLAN.md` Phase 0.5). Run them from the editor's
|
||||
**Session Frontend → Automation**, filter `VoxelForge`.
|
||||
The plugin's first tests (`Docs/archive/OPSTACK-PLAN.md` Phase 0.5). Run them from the editor's
|
||||
**Session Frontend → Automation**, filter `VoxelForge`. The `OpStack.*Equivalence` names are
|
||||
historical: those tests check the shipped stacks themselves and no longer compare against a
|
||||
second density path.
|
||||
|
||||
| File | Test name | What it proves |
|
||||
|------|-----------|----------------|
|
||||
@@ -724,61 +797,235 @@ The plugin's first tests (`OPSTACK-PLAN.md` Phase 0.5). Run them from the editor
|
||||
| `VoxelForgeClassifyTileTest.cpp` | `VoxelForge.OpStack.BoxVerdictFold` | Pure-logic walk of the fold in `VoxelDensityOp.h`, case by case — including the seal-forces-AllSolid case that justifies `ClassifyBox` existing. Also the only `.cpp` that includes the op header, so the build actually sees it. |
|
||||
| `VoxelForgeHeightStackTest.cpp` | `VoxelForge.OpStack.SurfaceHeightEquivalence` | The height-space stack vs `ComputeSurfaceTerrainZ`, in **altitudes**. Runs twice: defaults, then **all F20 terrain ops ON** — the load-bearing pass, since the ops are off by default and the defaults pass exercises only the structural source. Also brute-forces `MaxDisplacement` (a false bound would be a hole). Bar is bit-identity; a height delta is a visibly different world, not rounding. |
|
||||
| `VoxelForgeCrossPlatformTest.cpp` | `VoxelForge.Determinism.CrossPlatformDigest` | SHAPE digest (sign of density = the world) + FIELD digest (bit-for-bit) over a fixed integer grid, plus `NearIso` bounding how many samples could flip sign. Reports rather than asserts until pinned. Run on Windows and Linux and compare. |
|
||||
| `VoxelForgeOpStackSlabTest.cpp` | `VoxelForge.OpStack.SlabEquivalence` | **Phase 2's first port.** The same 6-op slab stack vs `GetSlabDensity` over 20k points, run twice — FlatPlain **and** CrystalChamber — which is what demonstrates the two archetypes really are one op. Plus window-invariance and box-verdict brute force. Compares against the reference **as it is now** (post Z-term removal), so green = pure refactor and any visual delta is attributable to §3.1 alone. |
|
||||
| `VoxelForgeOpStackSlabTest.cpp` | `VoxelForge.OpStack.SlabEquivalence` | The same 6-op slab stack run three times — FlatPlain, CrystalChamber and a tuned CrystalChamber (the worst case for the `ClassifyBox` amplitude bounds): finite density over 20k points, window-invariance across order and threads, and box-verdict brute force. |
|
||||
| `VoxelForgeOpStackTunnelTest.cpp` | `VoxelForge.OpStack.TunnelNetworkSpineEquivalence` | **Stage A of the last port.** Zeroes the 13 detail-op amplitudes so the *original* takes the path stage A ported — that is what makes an incomplete stack verifiable now. Samples in **clusters** (24 chunks × 250 points), because the SDF cache rebuilds when a query leaves its box and uniform sampling would rebuild per point on both paths. Check 3 (two param sets, A/B interleaved) compares each stack **to itself alone, never to the original** — the original would fail it, see AUDIT §C2. Asserts **zero** box verdicts, which is the honest stage-A result. |
|
||||
| `VoxelForgeOpStackShaftTest.cpp` | `VoxelForge.OpStack.VerticalShaftEquivalence` | The port that tests **reuse**, not fidelity: three of the five ops are Maze's, unchanged. Forces connectors + ledges on, because both are off or negligible at defaults and a resting param is an untested operator. The source and mirror now share a deterministic inner-3×3-emitted drainage tree whose parents come from rebuild-only ±2/±3 windows in a wider direct-indexed halo (default 9×9 emit / 15×15 roll), plus the old random links. Tree connector Z is exact and ledge-free; tree and random capsules are spatially culled from the cached rebuild. **Fixed 2026-07-29:** its box query used to return `CarveOnly` because a shaft merely *existed* within a `Spacing*1.6` halo — true almost everywhere at `ShaftSpacing 55 / ShaftDensity 0.6`, hence **0 of 60** tiles. It now publishes an isolated interval for tree and random capsules (same row-major cell order ⇒ same `VoxelHash::Pair`, so symmetry of `Pair()` is not assumed), with **Z exact** and XY conservative; the ledge and converter decide their own effects. The sampler was also widened from ±48 voxels to ±440. Every proved tile is brute-forced over its full lattice, and the test reports rebuild-vs-hot cache cost. |
|
||||
| `VoxelForgeOpStackIslandTest.cpp` | `VoxelForge.OpStack.FloatingIslandEquivalence` | The port that runs the stack **backwards** — void + fill vs rock + carve, same classes with the opposite sign. Counts interior-solid and open-void samples separately (on this archetype an aggregate "N solid" is dominated by the seal bands and says nothing about the islands). Counts `AllSolid` and `AllAir` verdicts **separately** too: `AllAir` is the one no cave archetype could ever prove, and it is the entire perf argument here. |
|
||||
| `VoxelForgeOpStackMazeTest.cpp` | `VoxelForge.OpStack.MazeEquivalence` | **Phase 1's load-bearing test.** The 8-op Maze stack vs `GetMazeDensity` over 20k points (bit-identity; a side-of-iso disagreement is the hard fail), plus purity across workers, a 64-seed spanning-tree/connectivity audit, corridor morphology, cache rebuild-vs-hot timing, and brute force on every box verdict the stack emits. Latest audit: **0 tree edge-count violations, 0 tree-disconnected, 0 Maze-disconnected**; interior degrees `d1=15.91%`, `d2=36.60%`, `d3=31.73%`, `d4=12.95%`, `d5=2.56%`, `d6=0.25%`, mean **2.504** versus a cubic grid's degree-6/mean-6.000; runs `min=1 / median=1 / p90=3 / max=18`. Latest focused cache timing: **1.436 μs rebuild**, **0.150 μs hot call**; final namespace timing: **1.130 μs rebuild**, **0.145 μs hot call**. Latest box audit: **104 proved, 152 Mixed, 138,424 voxels, 0 violations**. |
|
||||
| `VoxelForgeOpStackMazeTest.cpp` | `VoxelForge.OpStack.MazeEquivalence` | The 8-op Maze stack: decomposition, purity across workers, a 64-seed spanning-tree/connectivity audit, corridor morphology, cache rebuild-vs-hot timing, and brute force on every box verdict the stack emits. Latest audit: **0 tree edge-count violations, 0 tree-disconnected, 0 Maze-disconnected**; interior degrees `d1=15.91%`, `d2=36.60%`, `d3=31.73%`, `d4=12.95%`, `d5=2.56%`, `d6=0.25%`, mean **2.504** versus a cubic grid's degree-6/mean-6.000; runs `min=1 / median=1 / p90=3 / max=18`. Latest focused cache timing: **1.436 μs rebuild**, **0.150 μs hot call**; final namespace timing: **1.130 μs rebuild**, **0.145 μs hot call**. Latest box audit: **104 proved, 152 Mixed, 138,424 voxels, 0 violations**. |
|
||||
| `VoxelForgeStrateParamCoverageTest.cpp` | `VoxelForge.Determinism.StrateParamBlendCoverage` | **The X-macro guard** (added 2026-08-17). `FStrateGenerationParams::Lerp` blends the hand-written `VF_STRATE_PARAM_FIELDS` list, **not** the struct — so a field added to one and not the other compiles, tests green, and silently takes its **default** inside every Gradient/Interleaved transition band. This expands the X-macro a **third** way (after LERP and SNAP), into a name list, and diffs it against the struct's UObject reflection. Pure shape test: no fixture, no world, instant. `GExemptFieldNames` is **empty** — every reflected field is covered today, and any exemption must be written down as a decision. Stakes rise with the world composer, which intends to invent parameter sets through this same `Lerp` (`COMPOSER-NOTES.md`). |
|
||||
| `VoxelForgeComposerParameterRollTest.cpp` | `VoxelForge.Composer.ParameterRoll` | Asset-Registry corpus audit + complete per-archetype spread/exclusion/clamp table; asserts bit-identical deterministic rerolls and same-archetype parents; measures 64 transient candidate strates with `VF_MeasureStrate` plus the exact unsnapped arrival→departure law; brute-forces every rolled production box verdict. World-scale run: **1,422 applications / 168 distinct fields**; **36/64 survival**, 60 non-vacuous, 58 largest-component, 40 exact-law passes; **1,411 Mixed + 731 AllSolid + 418 AllAir = 1,149 proved boxes**, **1,529,319 lattice voxels checked, 0 violations**, 148.387 s. |
|
||||
| ″ | `VoxelForge.Composer.TerrainDetailLiveness` | Fixed 4,096-point `GetDensityAt` lattice, legacy and operator-stack paths; changes one terrain-detail group at a time with an empty terrain-op pool. Proves 9 live groups / 23 fields and 3 dead groups / 11 fields; all 24 rows match. |
|
||||
| ″ | `VoxelForge.Composer.TerrainDetailLiveness` | Fixed 4,096-point `GetDensityAt` lattice on the TunnelNetwork slot; changes one terrain-detail group at a time with an empty terrain-op pool. Proves 9 live groups / 23 fields and 3 dead groups / 11 fields; all 12 rows must match. |
|
||||
| `VoxelForgeComposerPromotionTest.cpp` | `VoxelForge.Composer.Promotion` | Re-measures the 12 project/default members, simulates five deterministic 24-candidate seasons with normalized measured-metric novelty (`<0.20`), cap 6, cumulative JSON promotion, provenance counts, corpus-hash checks, fresh-load gate verification, spread/survival reporting, and deliberate stale-metric corruption. The final landing validation passed the strict existing gates; no assertion was weakened. |
|
||||
| `VoxelForgeComposerStructureRollTest.cpp` | `VoxelForge.Composer.StructureRoll` | Rolls root polarity → legal shape source → polarity-derived conversion → 4–8 declaration-legal modifiers → mandatory structural posts; blends the six native parameter families independently, measures 64 novel stacks, captures the same grid for the deterministic filled/contour XZ/XY preview, runs a separate step-1 radius-64 ROI pass for the 33 survivors centred on `LargestComponentPoint`, checks exact arrival→departure connectivity, rerolls every recipe/stack for determinism, and brute-forces every uniform box verdict. World-scale run: **33/64 survival (51.6%)**, **64 distinct recipes**, **0 invalid recipes**, **715 proved custom boxes / 951,665 lattice voxels / 0 violations**, blank plan/card **4/33→5/33**, **174.854 s**, **0 refusals**. |
|
||||
| `VoxelForgeComposerShowcaseTest.cpp` | `VoxelForge.Composer.Showcase` | Exhausts the bounded parameter-roll set (seeds **0, 7331**, indices **0–63**), excludes multi-region rolls while the lateral gate is off, and measures every missing-family candidate in all six interior target slots at step 4 / radius 256 / `MaxCells=8,000,000`. Hard gates are non-vacuous, largest air share ≥ **0.50**, and exact unsnapped arrival→departure connectivity; selection score is floor-area fraction + clearance tie-break + projected-surface tie-break. It asserts roll/manifest determinism, density sign, zero `WorldRadiusVoxels`, bit-identical metric reruns, and writes one alphabetized card per archetype to `Saved/VoxelForge/Showcase/index.html`, with step-1 radius-64 filled/contour plan + vertical ROI images centred on `LargestComponentPoint`. The final landing regeneration evaluated **256** rolls (**82** eligible single-region, **174** skipped multi-region), produced **7 fine renders, 1 refusal, 0 blanks**, and retained a diagnostic hard-gate survivor set of **0/8**. |
|
||||
| `VoxelForgeComposerSeasonTest.cpp` | `VoxelForge.Composer.Season` | Existing compose/review/393,216-sample round trip plus schema hash tamper rejection, two independent runtime manager/generator instances, season-authoritative seed/spine/radius, unset-season regression, and brute-force verification of every sampled non-Mixed recipe tile verdict (up to 64). |
|
||||
| `VoxelForgeComposerCorpusFreeTest.cpp` | `VoxelForge.Composer.CorpusFree` | Shares each structure recipe across today's corpus blend, naive independent uniform rolls, and constraint-sampled rolls. The completed equal-arm world-scale run uses **16 candidates per arm**, step 4 / radius 256 / `MaxCells=8,000,000`, fixed passage-law mouths, and 40 box probes per candidate. Result: **10/16, 9/16, 8/16** survival; survivor feature-scale ranges are **8..268**, **8..60**, **8..92** voxels. Box checks: **185/246,235**, **260/346,060**, **299/397,969** proved/voxels, **0 violations** in every arm; constraint parameter and box-stop violations were also 0. |
|
||||
| `VoxelForgeLayoutOrderIndependenceTest.cpp` | `VoxelForge.Determinism.LayoutOrderIndependence` | Builds a known transient soft-pointer pool, then rebuilds it in original, reversed, and swapped orders. Requires a non-empty layout and passage set, and compares every slot's definition/Z/height plus passage endpoints, landing descriptors, control geometry, and bounds bit-for-bit. |
|
||||
| `VoxelForgePassageOpenSpaceTest.cpp` | `VoxelForge.Determinism.PassageLandsInOpenSpace` | Uses the real fixture density path to check both generated mouths against independent source queries, then audits all inter-strate landing ends: body-derived room dimensions, final-density flat-floor slope, local floor/join geometry, seal containment, hot-call/rebuild timing, and targeted `ClassifyTile` box soundness. Reports source-fit/refused mouths and per-archetype landing slopes; fails on any floor, join, seal, or box violation. |
|
||||
| `VoxelForgePassageOpenSpaceTest.cpp` | `VoxelForge.Determinism.PassageLandsInOpenSpace` | Uses the real fixture density path to check both generated mouths against independent source queries (Maze/VerticalShafts/FloatingIslands footings are probed on the archetype stack built without a strate manager, i.e. without passage carving), then audits all inter-strate landing ends: body-derived room dimensions, final-density flat-floor slope, local floor/join geometry, seal containment, hot-call/rebuild timing, and targeted `ClassifyTile` box soundness. Reports source-fit/refused mouths and per-archetype landing slopes; fails on any floor, join, seal, or box violation. |
|
||||
| `VoxelForgeStrateConnectivityTest.cpp` | `VoxelForge.Generation.StrateConnectivity` / `VoxelForge.Generation.StrateConnectivityRefinement` / `VoxelForge.Generation.VerticalShaftSeamFreedom` | Bounded strate metrics with density-polarity and solid-gap controls, deterministic route rechecks, and refinement sweeps. The refinement test uses the bounded 4-chunk density fixture, fits each measurement AABB to the arrival/departure mouth pair plus three shaft spacings (the prescribed local tree fallback), and caps the fitted control at 120,000,000 cells. It reports exact before/after seed-6 cell counts, reruns every negative with the fitted margin doubled, measures tree orphan candidates/path reachability and roughness-bubble proxies, asserts mouths remain inside the deterministic shaft feature core (the exact axis is an unsupported open cylinder), checks source/mirror physical paths, and requires 16/16 effective arrival→departure results across the 16 VerticalShafts seeds. Margin-binding negatives are reported as measurement limits, never as gap findings. The shaft seam test re-evaluates cell-boundary positions after warming distinct neighbouring chunk contexts. |
|
||||
| `VoxelForgeMazeSeamTest.cpp` | `VoxelForge.Generation.MazeSeamFreedom` | Re-evaluates **42** cell-boundary probes after warming six different neighbouring chunk contexts in both legacy and operator-stack paths. Latest result: **0 legacy mismatches, 0 operator mismatches**. The Maze source's 2×2×2 lower-node window is deliberately local; no wide collect is involved. |
|
||||
| `VoxelForgeClipmapNoOverlapTest.cpp` | `VoxelForge.Streaming.ClipmapDesiredTilesNoOverlapCoverage` | Sweeps 6,174 centres (negative coords, every parity) × radius/level/render-distance/vertical-clamp/sheet combinations: **zero overlapping desired pairs AND coverage ⊇ the frozen `7486c29` selector**. Failed on `7486c29` (1,022,679 overlapping pairs) as intended. |
|
||||
| `VoxelForgeMazeSeamTest.cpp` | `VoxelForge.Generation.MazeSeamFreedom` | Re-evaluates **42** cell-boundary probes through `GetDensityAt` after warming six different neighbouring chunk contexts; must report **0 mismatches**. The Maze source's 2×2×2 lower-node window is deliberately local; no wide collect is involved. |
|
||||
| `VoxelForgeClipmapNoOverlapTest.cpp` | `VoxelForge.Streaming.ClipmapDesiredTilesNoOverlapCoverage` | Sweeps 5,145 cases (343 centres with negative coords and every parity × 5 radius/level/render-distance settings × 3 vertical clamps): **zero overlapping desired pairs AND coverage ⊇ the frozen `7486c29` selector**. Failed on `7486c29` (1,022,679 overlapping pairs) as intended. |
|
||||
| `VoxelForgeCollisionDesiredTilesTest.cpp` | `VoxelForge.Streaming.CollisionDesiredTiles.PawnAndAnchors` | Checks the pawn collision box stays within its configured radius, includes support tiles below, and unions an independent anchor box at its authored XY/Z extents. |
|
||||
| `VoxelForgePreEditDensityCacheTest.cpp` | `VoxelForge.Determinism.PreEditDensityCacheCarveParity` | Captures one exact 35³ L0 pre-edit lattice, applies 16 deterministic random carves cumulatively, and requires each cached remesh's complete geometry hash to match full `GetDensityAt` regeneration; also requires non-empty carve geometry. |
|
||||
| `VoxelForgeDensityBlockEquivalenceTest.cpp` | `VoxelForge.Density.OperatorBlockMatchesScalarGrid` | Block evaluation (`EvalBlock` after `BuildActiveOpList` pruning) vs scalar, **bit-identical**: 5.1M mesher-grid samples (6 seeds × 8 archetypes × levels 0–5, with edits) + 300k direct `EvalBlock` samples. Scopes `voxel.UseFusedEvaluator=0` so the real block path runs. An injected optimistic `FPassageCarveOp::EffectOverBox` produced 5,612 mismatches. |
|
||||
| `VoxelForgeWorldEdgeSealTest.cpp` | `VoxelForge.Generation.WorldEdgeSeal` | Positive samples outside the radius across every fixture strate, bit-identical interior negative control against radius 0, monotonic smooth-ramp check, radius-0 no-op, radial `ClassifyBox`/T1.d proofs brute-forced for every reported voxel, and synthetic plus generated near-rim passage coverage. Emits one aggregate summary with proved-box and voxel counts. |
|
||||
| `VoxelForgeCorrectnessRoundTest.cpp` | `VoxelForge.Determinism.DiffApiBoundary` / `DiffLayerIdentity` / `EmptyPassageVersion` / `SurfaceBiomeCacheIdentity` / `TileCacheContextSwitch` / `ZeroTransitionBlend` | Regression coverage for the cold-audit correctness fixes (diff-layer API edges, cache identities, empty-passage layout version, zero transition blend). |
|
||||
| `VoxelForgeDescentCorridorTest.cpp` | `VoxelForge.Descent.OwnerPassageCorridor` / `VoxelForge.Descent.OwnerSeedSweep` | Samples the authored A-to-B passage as a body-sized walking corridor in the final owner field. |
|
||||
| `VoxelForgeDetSinCosTest.cpp` | `VoxelForge.Determinism.DetSinCos` | Pinned contract for `VoxelMath::DetSin` / `DetCos` / `DetSinCos`. |
|
||||
| `VoxelForgeIsFiniteTest.cpp` | `VoxelForge.Math.IsFiniteExact` | Bit-exact equivalence of the generation-path finite predicate (`VoxelMath::IsFinite`). |
|
||||
| `VoxelForgeLargeSeedTest.cpp` | `VoxelForge.Determinism.LargeSeedSurvives` | AUDIT C1: the world must still be a world at a large seed (bounded `VoxelHash::SeedOffset`). |
|
||||
| `VoxelForgeLateralRegionsTest.cpp` | `VoxelForge.Composer.LateralRegions` | Tier 4d lateral region partition, density blending, conservative box proofs, and the arrival→departure gate (asserts `VF_LateralRegionsAreShippable` stays false while the seam rate is imperfect). |
|
||||
| `VoxelForgeOpStackChannelTest.cpp` | `VoxelForge.OpStack.ChannelDAG` | Channel/resource declarations are executable stack-assembly metadata (`FVoxelOpStack::ValidateChannelOrder`). |
|
||||
| `VoxelForgeScaleDiagnosisTest.cpp` | `VoxelForge.Composer.ScaleDiagnosis` | Measurement-only scale diagnosis for the player-fit showcase. |
|
||||
| `VoxelForgeSealedSolidProofTest.cpp` | `VoxelForge.Determinism.SealedSolidProofSoundness` | Sealed-solid tile proof soundness and invalidation (one-sided: may say false too often, never a wrong true). |
|
||||
| `VoxelForgeTilePostReachTest.cpp` | `VoxelForge.Correctness.TilePostReachProof` | The tile-reach proof must fail loudly when its envelope is deliberately made too small. |
|
||||
| `VoxelForgeVerticalStrateReachTest.cpp` | `VoxelForge.Streaming.VerticalStrateReach` | Surface-strate streaming keeps the ground visible when the pawn is high in the same strate. |
|
||||
| `VoxelForgeWormBlockSkipTest.cpp` | `VoxelForge.Determinism.WormBlockSkipSoundness` | Soundness and coverage of the worker-local worm N1 block proof (`voxel.WormBlockSkip`). |
|
||||
| `VoxelForgePlayerFitWindow.h` | — | Shared measurement-window policy for player-fit diagnostics (test/editor side only). |
|
||||
|
||||
### 3.13 Core runtime integration (rounds 2–5)
|
||||
Scale symbols: AVoxelWorld::CoreVoxelSizeCm is the opt-in per-world Core size setting and
|
||||
AVoxelWorld::GetActiveVoxelSizeCm() is the authoritative runtime conversion. CoreTileVoxelCount in
|
||||
the public Core API is 32 voxels per tile. UVoxelContentManager::MeasurePlacementCostForValidation
|
||||
is a headless diagnostic only; it measures placement-grid cell queries and steady manager update
|
||||
cost without changing normal spawning. FloatingIslandTileListAudit.cpp and
|
||||
BuildFloatingIslandTileListAudit.ps1 under WorldAssemblerM3/Round5 hold the candidate-list
|
||||
benchmark and dropped-feature negative control. See ARCHITECTURE §8.22.
|
||||
|
||||
The public Core API is linked as the `VoxelForgeCore` runtime module, with canonical implementation
|
||||
sources still under `Standalone/`. UBT validates the 18-row manifest and its shims; BuildHost linked
|
||||
the module and passed `CheckCoreSymbols.ps1` against the actual shim objects. See
|
||||
[`Standalone/UNREAL_INTEGRATION_REPORT.md`](Standalone/UNREAL_INTEGRATION_REPORT.md) for build and
|
||||
headless evidence and the run ledger.
|
||||
|
||||
World assembler field types and `AuditHeightfieldBounds`, `AuditHeightfieldShelfThickness`, and
|
||||
`AuditHeightfieldRoute` are declared in
|
||||
[`Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h`](Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h)
|
||||
and implemented in [`Standalone/Core/src/WorldAssemblerInternal.h`](Standalone/Core/src/WorldAssemblerInternal.h).
|
||||
`FloatingIslandRecipeV1`, `FloatingIslandManifestV1`, `FloatingIslandFieldV1`,
|
||||
`AuditFloatingIslandBounds`, `AuditFloatingIslandRoute`, and `AuditFloatingIslandThickness` are in the
|
||||
same public header; their canonical implementation is
|
||||
[`Standalone/Core/src/FloatingIslandsInternal.h`](Standalone/Core/src/FloatingIslandsInternal.h).
|
||||
M3 round 2 added geometry version 2's thick crust, lobed roots, bounded detail and arched spans while
|
||||
preserving version-1 field and hash behavior. The standalone audit driver remains under
|
||||
[`Standalone/Research/WorldAssemblerM3/Round1/FloatingIslandsAudit.cpp`](Standalone/Research/WorldAssemblerM3/Round1/FloatingIslandsAudit.cpp);
|
||||
round-2 matched capture source and results are under
|
||||
[`Standalone/Research/WorldAssemblerM3/Round2/`](Standalone/Research/WorldAssemblerM3/Round2/).
|
||||
`RefineLod8NativeCap` in [`Standalone/Core/src/VoxelForgeCore.cpp`](Standalone/Core/src/VoxelForgeCore.cpp)
|
||||
replaces coarse LOD8 ceiling surfaces with the planned 2D cap field while retaining ceiling skirts.
|
||||
The standalone SurfaceWorld plan composer and capture/audit entry point live in
|
||||
[`Standalone/Source/StandaloneViewer.cpp`](Standalone/Source/StandaloneViewer.cpp).
|
||||
|
||||
`RunStandaloneWorldExport` in `VoxelForgeExploreCommandlet.cpp` serializes owner assets on the game
|
||||
thread to schema-v1 JSON. The adapter parses it through `ResolvedWorldSnapshot::FromJson` and keeps
|
||||
UObjects off Core workers. The runtime switch is `AVoxelWorld::bUseVoxelForgeCore` (default false),
|
||||
with independent `CoreClipRadius=3` and `CoreMaxClipLevel=8`; it does not read or mutate the old clip
|
||||
settings or assets.
|
||||
|
||||
When enabled, `AVoxelWorld` uses Core desired keys and priorities, compiles/generates through Unreal
|
||||
tasks, carries `Epoch` through the existing MPSC result queues, and uploads converted payloads through
|
||||
RealtimeMesh and its collision cook. Core voxel coordinates use the size resolved from
|
||||
`CoreVoxelSizeCm` (25 cm by default); Core tile keys remain in 32-voxel units, and mesh vertices are
|
||||
in centimetres. Legacy brush dimensions and density strength are converted from the 12.5 cm owner
|
||||
grid using the resolved Core size, preserving their physical effect. SurfaceWorld owner-chunk mapping
|
||||
uses the owner/Core ratio as well. Edits use sequenced `applyEditTransaction` calls after in-flight
|
||||
Core work drains, then regenerate Core's affected keys under a new epoch. Existing streaming anchors
|
||||
participate; the headless move proof covers `CollisionOnly` policy.
|
||||
|
||||
The reusable `BuildWorldAssemblyFeatureTileListV1` accepts conservative inclusive feature bounds.
|
||||
`ScopedWorldAssemblyFeatureQueriesV1` replaces the island-only ShapeWorld worker context: meshing,
|
||||
derived shells, decoration cells and landmark columns share its nestable worker scope. Recipe/plan
|
||||
identity and an inclusive domain guard each query; outside queries fall back to the full evaluator.
|
||||
See ARCHITECTURE §8.23 and WorldAssemblerM3/Round6/REPORT.md.
|
||||
|
||||
| Symbol | Canonical file | Role |
|
||||
|---|---|---|
|
||||
| `WorldAssemblyFeatureTileListV1`, `WorldAssemblyFeatureGroupBoundsV1`, `WorldAssemblyFeatureQueryV1` | `Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` | Stable-order candidates, optional exact value-side group certificates and borrowed recipe/plan identity. Old island names remain aliases. |
|
||||
| `ScopedWorldAssemblyFeatureQueriesV1::{find,ScopedWorldAssemblyFeatureQueriesV1}`, `WorldAssemblyFeatureTileListV1::candidateEvaluationUseful` | `Standalone/Core/src/WorldAssemblerInternal.h` / public header | Worker-local domain, cost hint resolved once per query, forced validation, nested restoration and full-scan fallback; replaces ShapeWorld's island context. |
|
||||
| `Heightfield2D::buildFeatureTileList`, `world_assembly_detail::FeatureRange` | `Standalone/Core/src/WorldAssemblerInternal.h` | XY bounds for column writers; exact no-op bounds for finite boxes/shelves; canonical operation order. |
|
||||
| `FloatingIslandFieldV1::{solidSupportBounds,buildFeatureTileList,densitySubvoxels}` | `Standalone/Core/src/FloatingIslandsInternal.h` | Finite support declarations, root-aware distance certificates, automatic shared-scope consumption. |
|
||||
| `WorldAssemblyFeatureColumnRangeV1`, `BuildWorldAssemblyFeatureColumnRangeV1` | Public Core header / `Standalone/Core/src/WorldAssemblerInternal.h` | Conservative column support union for air-base families; invalid bounds are unknown. |
|
||||
| `VF_ContentFeatureQueries`, `VF_ConstrainContentMarch` | `Private/VoxelContentManager.cpp` | Immutable queries prepared per cell/landmark; skips proven air while preserving march phase and exact crossing refinement. |
|
||||
| `UVoxelContentManager::MeasurePlacementCostForValidation` | `Private/VoxelContentManager.cpp` | Authored/populated full-versus-candidate transforms, canonical anchor checks, batch and isolated HISM application cost. |
|
||||
| `UVoxelContentManager::ApplyRegion` | `Private/VoxelContentManager.cpp` / `Public/VoxelContentManager.h` | Normal region application retains landmark footprints; its default-true footprint flag isolates the temporary populated validation fixture. |
|
||||
| `RunCoreSharedFeatureCostAudit`, `RunCoreContentPlacementCostAudit` | `Source/VoxelForgeEditor/Private/VoxelForgeExploreCommandlet.cpp` | Paired owner-plan tile/shell timings and hashes, placement metrics and transient BuildHost plan exports. |
|
||||
| `TileGenerationOptions::useAssemblyFeatureQueries`, `TileGenerationStats::{featureListMilliseconds,farShellMilliseconds}` | Public Core header / `Standalone/Core/src/VoxelForgeCore.cpp` | Default-on candidate route, full-scan validation control and stage timing; no identity change. |
|
||||
| `SharedFeatureListAudit.cpp`, `BuildSharedFeatureListAudit.ps1` | `Standalone/Research/WorldAssemblerM3/Round6/` | Owner-plan/stress hashes, shared-scope probes and deliberately dropped needed features. |
|
||||
|
||||
`-coreworldtest=1` in the BuildHost editor commandlet validates initial tiles, expected collision
|
||||
bodies/empty tiles, anchor movement, a carve transaction, travel to the B landing and native surface
|
||||
strate, full desired visual-key coverage, neighbour/LOD face coverage, and W1/W8/W16 hash parity.
|
||||
`-coreworldassembly=PATH` supplies a reviewed manifest transiently for this test. The native surface
|
||||
probe samples `Heightfield2D` directly from the Core snapshot; every one of its 2,553 expected visual
|
||||
tile hashes is compared with an independent standalone Core reference. The 125 collision-desired
|
||||
surface tiles are checked for expected cooked bodies or empty geometry. `VoxelForge.Determinism.CrossPlatformDigest`
|
||||
remains the standalone legacy digest check. Switch-off was reviewed at the source branch and hash
|
||||
boundary; there is no separate flag-off actor replay. See the report for exact hashes, timing, run
|
||||
ledger, and owner-editor instructions. Core remains opt-in and the legacy path remains default.
|
||||
|
||||
Round 4 adds a temporary SurfaceWorld density bridge in
|
||||
[ShapeWorldInternal.h](Standalone/Core/src/ShapeWorldInternal.h). SurfaceWorld samples, including
|
||||
biome resolution and terrain operations, route through the copied owner evaluator at full resolution;
|
||||
the Core edit layer is applied afterward. Surface density hashes match the owner field across the
|
||||
tested LOD0–LOD3 tiles, and the BuildHost actor layout identifies Biome_Surface_Plain at the surface
|
||||
travel point. The CPU bridge costs approximately the legacy field route: 23.085 s wall / 180.562
|
||||
worker-seconds for the matched 1,294-tile CPU view.
|
||||
|
||||
Round 5 adds the native surface route. `UVoxelSettings` stores canonical reviewed manifest JSON and
|
||||
its Core digest. The `SourceWorldAssemblyManifestJson` file picker plus
|
||||
`ImportWorldAssemblyManifestJson` editor button populate the cooked data asset. The Core adapter
|
||||
requires an exact unique match for every planned `SurfaceWorld` stratum and adds the canonical plan
|
||||
to the resolved Core snapshot. `GetCurrentSeasonContentHash()` includes this digest only while
|
||||
`bUseVoxelForgeCore` is enabled; disabling Core retains the legacy generation route and legacy
|
||||
content hash. The collision gate defers out-of-desired-set Core support tiles until Core region
|
||||
compilation admits them, so collision work cannot publish a competing tile before the native
|
||||
surface region snapshot. Existing cave Core strates still use the no-manifest snapshot path.
|
||||
|
||||
Round 6 corrects the Core 25 cm ↔ legacy 12.5 cm boundary in `VoxelWorld.cpp`,
|
||||
`VoxelContentManager.cpp`, and `VoxelAtmosphereManager.cpp`. `VoxelMarchingCubesMesher` now exposes
|
||||
`GeometryVoxelSizeCm` so Core geometry is emitted at 25 cm while legacy stays at its 12.5 cm
|
||||
default. The Core bridge adapts carve/fill signs and scales Blueprint brush radius, extent, falloff
|
||||
and strength. `Standalone/Core/src/FarShellsInternal.h` subdivides LOD7+ far-stratum repairs, and
|
||||
`Standalone/Core/src/VoxelForgeCore.cpp` enables a guarded heightfield Z-band path for unhashed
|
||||
native tiles, combining ground/cap heights with intersecting bounded 3D feature extents; overlapping
|
||||
edits keep the full mesh path. The BuildHost commandlet adds height-anchor agreement, brush
|
||||
sign/size, geometry and per-tile-kind pawn-lag checks; see round 6 in
|
||||
[`Standalone/ASSEMBLER_M1_REPORT.md`](Standalone/ASSEMBLER_M1_REPORT.md).
|
||||
|
||||
M2 round 3 adds `EVoxelForgeCoreMesherRecipe` / `CoreMesherRecipe` in
|
||||
[`Source/VoxelForge/Public/VoxelWorld.h`](Source/VoxelForge/Public/VoxelWorld.h), defaulting to
|
||||
`MarchingCubesV1`; `VoxelWorld.cpp` maps opt-in hybrid choices to Core `MesherRecipe` values and
|
||||
adds the selected version to Core content identity. V1 retains the original feature-aware DC path.
|
||||
V2 keeps MC topology and snaps only eligible interior LOD0 edge roots to exact type-2 box planes;
|
||||
the mesher support is in `VoxelHybridSharpMesher.h` and `VoxelMarchingCubesMesher.cpp`. Coarse LODs
|
||||
and roots whose incident cells reach a tile border remain MC. `VoxelForgeExploreCommandlet.cpp`
|
||||
owns the five production-mesher fixtures, border probes and Core actor collision contacts. BuildHost evidence
|
||||
and remaining doorway transition failures are recorded in
|
||||
[`Standalone/UNREAL_INTEGRATION_REPORT.md`](Standalone/UNREAL_INTEGRATION_REPORT.md) and
|
||||
[`Standalone/Research/WorldAssemblerM2/REPORT.md`](Standalone/Research/WorldAssemblerM2/REPORT.md).
|
||||
Likely compile touchpoints are `VoxelWorld.h/.cpp`, `VoxelMarchingCubesMesher.h/.cpp`,
|
||||
`VoxelHybridSharpMesher.h`, `Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h`,
|
||||
`Standalone/Core/src/VoxelForgeCore.cpp`, and the editor commandlet/build rules.
|
||||
|
||||
[OwnerWorldLoader.h](Standalone/Source/OwnerWorldLoader.h) now reads SlabParams, MazeParams,
|
||||
VerticalShaftParams, and FloatingIslandParams. The shape-world evaluator still lacks their native
|
||||
terrain fields: FlatPlain, CrystalChamber, Maze, VerticalShafts, FloatingIslands, and water
|
||||
presentation remain missing or approximate. Passage type/style chains are extracted from manager
|
||||
control points, but Worm and Cascading currently fail the centerline walkability audit. Gradient,
|
||||
Hard, and Interleaved transitions produce the same Core probe hash and are not faithfully represented.
|
||||
See the [round-4 coverage table and small matched captures](Standalone/UNREAL_INTEGRATION_REPORT.md)
|
||||
and [probe artifacts](Standalone/Research/OwnerShapeWorld/core_strate_coverage/).
|
||||
|
||||
World Assembler M3 Round 7 adds a shared far-horizon path. `WorldAssemblyPlanV1::strates` is the
|
||||
SurfaceWorld declaration: `World::generateTile` evaluates native ground on a globally aligned grid
|
||||
with step min(tile step, 128 voxels), and cap on the shared 128-voxel grid. It clips both surfaces
|
||||
to the tile and to `cap >= ground`, and replaces nearby upward coarse-MC ground faces. An unedited
|
||||
SurfaceWorld tile at LOD7/8 that intersects a native surface plan and no island plan uses those
|
||||
profile surfaces instead of derived far stratum/connector repairs. Tiles with edits or intersecting
|
||||
FloatingIslands plans keep the canonical field and repairs. FloatingIslands keeps the shared 3D field
|
||||
and repair path, with a common 256-voxel axial station/ring grid at LOD7/8; closer LODs retain
|
||||
128-voxel stations. `TileGenerationStats` reports classification, column, meshing, shell and horizon
|
||||
time, repair field calls, skipped repairs and replaced triangles. `farHorizonVersion` salts far
|
||||
mesh/payload/topology hashes only. The BuildHost commandlet owns warmed eight-key timings and the
|
||||
exact-density-root LOD7/8 agreement audit with its datum-offset negative control.
|
||||
|
||||
World Assembler S4 repairs the planar cap datum and far tile selection. `Heightfield2D::farSurfaceCapHeightSubvoxels`
|
||||
clips the rendered cap to `(topChunkZ + 1) * 32` voxels while preserving `Heightfield2D::point` density;
|
||||
`capHeightBounds` provides the matching conservative bands. `World::desiredTiles` adds only non-overlapping
|
||||
cap-height keys at LOD4–8, and `RefineFarNativeCap` emits the globally aligned cap surface (16 voxels at
|
||||
LOD4, 128 at LOD5–8). Evaluator v5 opts into this render-surface contract; older serialized evaluator
|
||||
versions retain their cap behavior. Far recipe v2 salts mesh/payload/topology from LOD4 upward. The
|
||||
standalone viewer audit measures cap-band census, near-root agreement and upward-ray coverage; the editor
|
||||
BuildHost check measures the staged owner actor and W1/W8/W16. `VoxelWorld::ApplyMeshToTile` uses explicit
|
||||
section ranges for ground and cap. Likely compile spots are the Core public/internal heightfield API and
|
||||
`VoxelForgeCore.cpp`, `VoxelWorld.cpp`, the editor commandlet, and the standalone viewer. See
|
||||
ARCHITECTURE §8.25 and
|
||||
[`Standalone/Research/WorldAssemblerM3/Round8/REPORT.md`](Standalone/Research/WorldAssemblerM3/Round8/REPORT.md).
|
||||
|
||||
## 4. The density pipeline (most-edited hot path)
|
||||
|
||||
### 4.1 `GetDensityWithParams` (TunnelNetwork) — VoxelGenerator.cpp:277
|
||||
### 4.1 `GetDensityWithParams` (TunnelNetwork) — VoxelGenerator.cpp:3749
|
||||
Stage order (negative=solid throughout). Each stage's anchor:
|
||||
| Step | Line | What |
|
||||
|------|------|------|
|
||||
| 1 — Vertical scale | 307 | Stretch Z before noise (`VerticalScale`). |
|
||||
| 2 — Base density | 316 | Everything starts solid at `BaseDensity`. |
|
||||
| 3 — Cave warp | 321 | Domain-warp the SDF query coords (organic shapes). |
|
||||
| 4 — SDF morphology | 368 | Rooms+tunnels via `BuildChunkCache`/`EvaluateSDFCached`. |
|
||||
| 4b — Surface roughness | 564 | Volumetric noise near surfaces (fBM/Ridged/Mixed). |
|
||||
| 4c–4h — Terrain ops | 688 | Per-room op applied near surfaces. Sub-anchors below. |
|
||||
| · Terracing | 727 | Step-like ledges. |
|
||||
| · Layer lines | 823 | Horizontal grooves (sin of Z). |
|
||||
| · Ribbing | 853 | Parallel ridges (sin of Z). |
|
||||
| · Overhangs | 882 | Low-Z-freq noise shelves. |
|
||||
| · Cliff sharpening | 918 | Amplify vertical gradient. |
|
||||
| · Scallop | 962 | Cellular erosion bowls. |
|
||||
| · Arch/Bridge | 998 | Hash-placed capsules across voids. |
|
||||
| 4d — Columns | 1053 | Pre-baked vertical cylinders. |
|
||||
| 4g — Domes | 1077 | Room-relative hemispherical ceilings. |
|
||||
| 4h — Pinch | 1142 | Passage bottlenecks. |
|
||||
| 5 — Worm tunnels | 1241 | abs(noise1)+abs(noise2), masked by distance-to-network (`WormNetworkRange`: braids hugging rooms/tunnels, no far-field speckle; 0 = legacy unmasked). |
|
||||
| 6 — Boundary seal | 1274 | Solid top/bottom shells (`ApplyBoundarySeal`). |
|
||||
| 7 — Inter-strate passages + landings | `GetDensityWithParams`: 2217; `GetSlabDensity`: 2434+ | Carve wandering passage tubes and deterministic local landing rooms, restore their support floors, then leave the final XY seal to the outer MC post (`ApplyPassageModifier` / `ApplyPassageLandingFloorMC`). |
|
||||
| 1 — Vertical scale | 3799 | Stretch Z before noise (`VerticalScale`). |
|
||||
| 2 — Base density | 3808 | Everything starts solid at `BaseDensity`. |
|
||||
| 3 — Cave warp | 3813 | Domain-warp the SDF query coords (organic shapes). |
|
||||
| 4 — SDF morphology | 3863 | Rooms+tunnels via `BuildChunkCache`/`EvaluateSDFCached`. |
|
||||
| 4b — Surface roughness | 4267 | Volumetric noise near surfaces (fBM/Ridged/Mixed). |
|
||||
| 4c–4h — Terrain ops | 4395 | Per-room op applied near surfaces. Sub-anchors below. |
|
||||
| · Terracing | 4437 | Step-like ledges. |
|
||||
| · Layer lines | 4534 | Horizontal grooves (sin of Z). |
|
||||
| · Ribbing | 4564 | Parallel ridges (sin of Z). |
|
||||
| · Overhangs | 4593 | Low-Z-freq noise shelves. |
|
||||
| · Cliff sharpening | 4629 | Amplify vertical gradient. |
|
||||
| · Scallop | 4673 | Cellular erosion bowls. |
|
||||
| · Arch/Bridge | 4709 | Hash-placed capsules across voids. |
|
||||
| 4d — Columns | 4738 | Pre-baked vertical cylinders. |
|
||||
| 4g — Domes | 4766 | Room-relative hemispherical ceilings. |
|
||||
| 4h — Pinch | 4831 | Passage bottlenecks. |
|
||||
| 5 — Worm tunnels | 4905 | abs(noise1)+abs(noise2), masked by distance-to-network (`WormNetworkRange`: braids hugging rooms/tunnels, no far-field speckle; 0 = legacy unmasked). |
|
||||
| 6 — Boundary seal | 4987 | Solid top/bottom shells (`ApplyBoundarySeal`). |
|
||||
| 7 — Inter-strate passages + landings | 5008 | Carve wandering passage tubes and deterministic local landing rooms, restore their support floors, then leave the final XY seal to the outer MC post (`ApplyPassageModifier` / `ApplyPassageLandingFloorMC`). |
|
||||
|
||||
### 4.2 `GetSlabDensity` (FlatPlain / CrystalChamber) — VoxelGenerator.cpp:1306
|
||||
| Step | Line | What |
|
||||
|------|------|------|
|
||||
| 1 — Floor surface | 1317 | Noisy floor height. |
|
||||
| 2 — Ceiling surface | 1343 | Formations hang downward (`abs(noise)`). |
|
||||
| 3 — Void→base density | 1379 | Solid outside [floor,ceiling]. |
|
||||
| 4 — Columns | 1399 | World-space hash grid, full-height. |
|
||||
| 5+6 — Seal + passages | 1461 | Same seal, passage-tube, landing-room, connector, and support-floor path as TunnelNetwork. |
|
||||
### 4.2 `BuildSlabStack` (FlatPlain / CrystalChamber) — VoxelDensityOpStack.cpp
|
||||
| Step | Op | What |
|
||||
|------|----|------|
|
||||
| 1 — Floor surface | `FSlabVoidSource` | Noisy floor height (XY-pure). |
|
||||
| 2 — Ceiling surface | `FSlabVoidSource` | Formations hang downward (`abs(noise)`). |
|
||||
| 3 — Void→base density | `FSlabVoidSource` | Solid outside [floor,ceiling]. |
|
||||
| 4 — Columns | `FGridColumnMod` | World-space hash grid, full-height. |
|
||||
| 5+6 — Seal + passages | `AppendStructuralPost` | Spine, vertical seal, passage tube/landing floor, XY seal — the same structural post as every stack. |
|
||||
|
||||
---
|
||||
|
||||
@@ -786,25 +1033,25 @@ Stage order (negative=solid throughout). Each stage's anchor:
|
||||
|
||||
| Goal | Location |
|
||||
|------|----------|
|
||||
| Chunk size / voxel scale | `VoxelTypes.h:19-23` (rebuild everything). |
|
||||
| Chunk size / voxel scale | `VoxelTypes.h:211-213` (rebuild everything). |
|
||||
| View distance / task budget / LOD distances | `VoxelSettings.h` (no recompile of logic — data asset). |
|
||||
| LOD step mapping | `AVoxelWorld::LODToStep` VoxelWorld.cpp:268; `GetLODForChunk` :242. |
|
||||
| How chunks stream in/out | `UpdateChunksAroundPosition` VoxelWorld.cpp:362. |
|
||||
| Async threading / stale-result handling | `LoadTile` :445, `ProcessPendingChunks` :301, generation Epoch + per-request cancellation token. |
|
||||
| LOD step mapping | `LoadTile` VoxelWorld.cpp:9033, effective cell selection near :9071: extent `CHUNK_SIZE << Level`, cells from `FullResClipLevels`/`CoarseTileCells`, `Step = Extent / Cells`; tile selection in `VoxelClipmapDesiredTiles::Build` (`Private/VoxelClipmapDesiredTiles.h`). |
|
||||
| How chunks stream in/out | `UpdateChunksAroundPosition` VoxelWorld.cpp:6870. |
|
||||
| Async threading / stale-result handling | `LoadTile` :9033, `ProcessPendingChunks` :7435, generation Epoch + per-request cancellation token. |
|
||||
| Add a new cave feature / terrain op | Add enum in `VoxelTerrainOpDefinition.h:36`, params there, `ApplyTo` (.cpp:6), transport fields in `FStrateGenerationParams`, consume it in a new Step inside `GetDensityWithParams`. |
|
||||
| Tweak room/tunnel shapes | `VoxelCaveMorphology.cpp` `BuildChunkCache` :47 / `EvaluateSDFCached` :757. |
|
||||
| Worm tunnel behavior | `GetDensityWithParams` Step 5, VoxelGenerator.cpp:1241. |
|
||||
| Strate stacking / which strate where | `UVoxelStrateManager::Initialize` :10. |
|
||||
| Tweak room/tunnel shapes | `VoxelCaveMorphology.cpp` `BuildChunkCache` :6782 / `EvaluateSDFCached` :8569. |
|
||||
| Worm tunnel behavior | `GetDensityWithParams` Step 5, VoxelGenerator.cpp:4905. |
|
||||
| Strate stacking / which strate where | `UVoxelStrateManager::Initialize` :1164. |
|
||||
| Publish/use an offline season | Create `UVoxelSeasonAsset`, set `SourceManifestJson`, press `ImportSeasonManifestJson`, then assign it to `UVoxelSettings::Season`. |
|
||||
| Boundary blend between strates | `GetGenerationParams` :515 + `FStrateGenerationParams::Lerp` (expands `VF_STRATE_PARAM_FIELDS`, StrateTypes.h — new fields go in that list). |
|
||||
| Passages / landing geometry between strates | `GeneratePassages` :146 + `EvaluateModifierSDF` :371 + `ApplyPassageModifier` / `ApplyPassageLandingFloorMC` (StrateManager.cpp) + `ClassifyTile` floor guard (Generator.cpp). |
|
||||
| Player carve/fill | `CarveAtPosition`/`FillAtPosition` VoxelWorld.cpp:691/709 → `UVoxelDiffLayer::ApplyModification` :63. |
|
||||
| Boundary blend between strates | `GetGenerationParams` :6509 + `FStrateGenerationParams::Lerp` (expands `VF_STRATE_PARAM_FIELDS`, StrateTypes.h — new fields go in that list). |
|
||||
| Passages / landing geometry between strates | `GeneratePassages` :1659 + `EvaluateModifierSDF` :2643 + `ApplyPassageModifier` / `ApplyPassageLandingFloorMC` (StrateManager.cpp) + `ClassifyTile` floor guard (Generator.cpp). |
|
||||
| Player carve/fill | `CarveAtPosition`/`FillAtPosition` VoxelWorld.cpp:10230/10239 → `AVoxelWorld::ApplyModification` :10248 → `UVoxelDiffLayer::ApplyModification` :185. |
|
||||
| Mesh smoothness / normals | Grid-gradient in `GenerateMesh` (`GradAt` lambda), `IsoLevel` (h). |
|
||||
| New slab/flat-world generator | `GetSlabDensity` Generator.cpp:1306 + `FSlabGenerationParams` (StrateTypes.h:1019). |
|
||||
| New slab/flat-world generator | `VoxelDensityOps::BuildSlabStack` + `FSlabVoidSource` / `FGridColumnMod` (VoxelDensityOpStack.cpp) + `FSlabGenerationParams` (StrateTypes.h:1223). |
|
||||
| Biome placement / layout | `BiomeMapParams` on the strate (cell size, warp, climate freqs) + each biome's climate box. Bake `AVoxelWorld::BakeBiomePreview` to tune. §8.14. |
|
||||
| What a biome does to terrain | A full archetype param override on the biome (`bOverrideTerrain` + `SurfaceParams`); surface output-blends dominant/neighbour heights in `GetSurfaceDensity`. Caves = content/atmosphere only (determinism, §8.14). |
|
||||
| What a biome does to terrain | A full archetype param override on the biome (`bOverrideTerrain` + `SurfaceParams`); surface output-blends dominant/neighbour heights in the surface stack (`BuildSurfaceStack` with per-biome params) and in `ComputeSurfaceColumn`. Caves = content/atmosphere only (determinism, §8.14). |
|
||||
| Add a biome / biome content | New `UVoxelBiomeDefinition` asset → add to the strate's `Biomes[]`. §8.14 / §8.12. |
|
||||
| Season reset | `AVoxelWorld::ChangeSeed` :740. |
|
||||
| Season reset | `AVoxelWorld::ChangeSeed` :8710. |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ composer fills in; nobody opens it in the editor any more.
|
||||
|
||||
### This is op-stack "Phase 3", and it is REOPENED
|
||||
|
||||
The 2026-08-16 `OPSTACK-HANDOFF.md` says in bold *"Do not start Phase 3."* **That is superseded.**
|
||||
The 2026-08-16 `Docs/archive/OPSTACK-HANDOFF.md` says in bold *"Do not start Phase 3."* **That is superseded.**
|
||||
Asked what the world was missing, Jahni described exactly Phase 3 plus a composer above it.
|
||||
|
||||
It is **not** a restart of the refactor. The eight operators are live, composable, and production. What
|
||||
@@ -1490,7 +1490,7 @@ roughness and should stay late in the stack.
|
||||
|
||||
Kept because each cost real time or would otherwise be repeated.
|
||||
|
||||
- ⛔ **"Do not start Phase 3" is SUPERSEDED.** `OPSTACK-HANDOFF.md` still says it; believe this file.
|
||||
- ⛔ **"Do not start Phase 3" is SUPERSEDED.** `Docs/archive/OPSTACK-HANDOFF.md` still says it; believe this file.
|
||||
- ⛔ **Jahni does not want to author strates either.** The composer must invent them. An earlier reading
|
||||
had him authoring strates while the system arranged them — one level too shallow, and it shaped a
|
||||
whole afternoon of wrong design.
|
||||
@@ -1553,7 +1553,7 @@ build succeeded (14 actions). The full headless suite reported **22 succeeded, 0
|
||||
determinism, box-verdict, and connectivity checks stayed green. The 4b→4h fuse was deliberately
|
||||
not applied after the intervening-channel audit above.
|
||||
|
||||
**⭐ This closes the pending-verification thread opened by `OPSTACK-HANDOFF.md`.** That file flagged
|
||||
**⭐ This closes the pending-verification thread opened by `Docs/archive/OPSTACK-HANDOFF.md`.** That file flagged
|
||||
commits `4d33321` (Sol's boundary fold) and `91585ea` as *built but NOT re-verified*, with two
|
||||
acceptance conditions. Both now met:
|
||||
- **`violations` = 0** everywhere. Measured: TunnelNetwork at production defaults **12 of 40 tiles
|
||||
|
||||
@@ -0,0 +1,783 @@
|
||||
# Astra (gpt-6-astra, max): how else could we do it? — 2026-10-02
|
||||
|
||||
Prompt: scratchpad astra-think.md (free thinking, nothing off the table). Answer verbatim below.
|
||||
|
||||
**I would bet on your coarse-structure/fine-surface idea, extended in two ways: generate surfaces directly wherever possible, and make edits immediately queryable without waiting for replacement meshes.**
|
||||
|
||||
The largest opportunity is to make the ordinary cost scale with **surface area, visible detail, and recent changes**. Dense volumetric work should be reserved for places where complicated three-dimensional intersections actually require it.
|
||||
|
||||
I read the [research report](</E:/Projet Unreal/VoxelM/Plugins/VoxelForge/RESEARCH-SPEED-2026-09.md>) and use the newer timings in your prompt below. All performance estimates here are arithmetic or architectural estimates, not measurements of these proposed designs.
|
||||
|
||||
At 100 ns per point, 140 million points require about **2.3 seconds on six perfectly scaling workers**, before anything else happens. That makes the central problem clear: further local optimization cannot carry the full burden.
|
||||
|
||||
There is also an important distinction inside “detail”:
|
||||
|
||||
| What the player perceives | What must represent it |
|
||||
|---|---|
|
||||
| A passage, hole, thin wall, or floating island | Actual solid/air topology |
|
||||
| A foothold, climbable edge, or projectile obstruction | Gameplay geometry |
|
||||
| Rock relief that changes silhouettes and stereo depth | Actual rendered surface displacement or geometry |
|
||||
| Grain, mineral variation, tiny scratches, wetness | Shading and material information |
|
||||
|
||||
A finer lattice spends more on all four together. Your game can give each its own representation and still present one coherent world.
|
||||
|
||||
The difficult negotiation is whether every visible grain must also be an independently collidable, destructible object. If that is required at arbitrarily increasing resolution, the physical complexity must eventually be paid for. If the requirement is increasingly fine **visible rock**, there is much more room.
|
||||
|
||||
**1. Give fine detail a surface representation. This is the strongest immediate direction.**
|
||||
|
||||
A cave wall can have a relatively simple structural surface and an extremely detailed skin.
|
||||
|
||||
A useful version would contain:
|
||||
|
||||
- Structural geometry for chambers, passages, ledges, holes, and substantial protrusions.
|
||||
- A bounded surface displacement for shallow fractures, erosion, and rock relief.
|
||||
- Normal, roughness, colour, and wetness variation below that.
|
||||
- Explicit small geometry for features such as thin stalactites that cannot be represented by shallow displacement.
|
||||
|
||||
This follows the useful idea behind film-style micropolygon rendering: keep a compact description of a surface, then subdivide and shade it according to what the image needs. Pixar’s original [Reyes architecture](https://graphics.pixar.com/library/Reyes/paper.pdf) is a foundational example.
|
||||
|
||||
The crucial improvement over “coarse geometry plus nicer materials” is that the surface layer should include **real geometric relief where stereo or silhouette makes it necessary**. Normal mapping alone will fail the owner’s close inspection. Parallax mapping has useful applications, but I would not make it responsible for rock silhouettes or convincing near-hand VR geometry.
|
||||
|
||||
There are two traps.
|
||||
|
||||
First, tessellating a coarse, faceted mesh does not automatically recover a smooth underlying shape. The refined vertices need a meaningful curved surface to follow: a procedural patch, a suitable subdivision surface, or projection onto a local structural field. Otherwise, finely tessellated facets remain facets.
|
||||
|
||||
Second, displacement cannot create arbitrary new topology. A six-centimetre hole through a wall needs an explicit hole or a locally finer volumetric representation. It cannot be recovered by adding surface roughness to a mesh that sealed it.
|
||||
|
||||
For your reference image, that distinction is favourable. Broad ledges, columns, hanging formations, and fractured slabs supply the geometry. Much of the apparent richness comes from correlated relief, material, moisture, lighting, and water.
|
||||
|
||||
**What it gives:** returning the structural lattice from 12.5 cm to 25 cm reduces the dense sample count by approximately **8×**, while the rendered surface can become finer than either lattice. This is a sample-count reduction, not an 8× whole-game guarantee.
|
||||
|
||||
**What it costs:** continuous GPU work, surface refinement, coherent material coordinates, and careful boundaries between patches. At 90 Hz, the entire frame has approximately **11.1 ms**; displacement and its shadows must share that budget.
|
||||
|
||||
**What it takes away:** automatic physical significance for every visible bump. You need an explicit displacement envelope and contact tolerance. Where precise hand placement or tool contact matters, the CPU can evaluate the same relief locally, rather than generating that detail throughout a 64 m cube. Robust contact queries still require more than checking a few points.
|
||||
|
||||
**Determinism:** retain canonical structural geometry and deterministic surface parameters. Any relief used for gameplay must have a canonical evaluator too. Lighting and final pixel calculations can remain device-dependent without changing the world’s gameplay answers.
|
||||
|
||||
A signal-processing lesson matters here: **preserve the detailed surface definition, but filter its image**. Trying to display every tiny frequency at every distance causes shimmer rather than fidelity. Procedural noise can be designed with controlled frequency content and filtering; [Gabor noise research](https://graphics.cs.kuleuven.be/publications/LLDD09PNSGC/) is one useful example. Finer detail can be inexpensive when it comes from a reusable filtered texture or compact surface function. Additional geometry, shadowing, and contact complexity are never literally free.
|
||||
|
||||
There is an Unreal-specific qualification. UE 5.7 documents Nanite tessellation as experimental, limited to Nanite meshes, and subject to displacement cracks at discontinuous attributes. Its standard `UDynamicMeshComponent` does not support Nanite. Nanite/Lumen XR support is also experimental and requires PC deferred rendering with DX12. Your PC-linked Quest fits the PC category, but this is an integration and performance risk, not a checkbox solution. [Nanite tessellation](https://dev.epicgames.com/documentation/en-us/unreal-engine/working-with-naniteenabled-content?application_version=5.7), [Dynamic Mesh limitations](https://dev.epicgames.com/documentation/en-us/unreal-engine/geometry-scripting-users-guide-in-unreal-engine?application_version=5.7), [XR support](https://dev.epicgames.com/documentation/en-us/unreal-engine/nanite-and-lumen-for-xr-in-unreal-engine?application_version=5.7).
|
||||
|
||||
I would validate the surface concept independently of whether Nanite becomes its eventual implementation.
|
||||
|
||||
**2. The radical direction: make the generator describe the cave boundary directly.**
|
||||
|
||||
Today, the generator knows how rooms and tunnels were constructed, but the mesher largely rediscovers their boundaries by asking millions of questions about space.
|
||||
|
||||
I would investigate preserving that construction knowledge all the way to geometry.
|
||||
|
||||
A chamber could generate floor patches, ceiling patches, and connecting wall patches. A tunnel could generate a swept cross-section along a curve. Columns and stalactites could generate tapered, deformed surfaces. Ledges could follow bedding planes. Floating masses could be closed procedural objects.
|
||||
|
||||
These are local two-dimensional surfaces embedded in three dimensions. Multiple overlapping levels and arbitrarily oriented patches provide overhangs and stacked caves; this does not require a globally single-valued heightfield.
|
||||
|
||||
At complicated junctions, blends, and heavily edited regions, retain your volumetric system. Those regions become exceptions with explicit boundaries.
|
||||
|
||||
The result is a world recipe containing:
|
||||
|
||||
- A graph describing connections and traversable routes.
|
||||
- Surface-producing features with conservative bounds.
|
||||
- Local volumetric regions for difficult topology.
|
||||
- Surface detail parameters and player edits.
|
||||
|
||||
This changes the generator’s responsibility. A feature should be able to supply its boundary, its bounds, and its relevant gameplay queries—not just a density value at an arbitrary position.
|
||||
|
||||
There is an artistic reason to consider this too. Geology produces strong correlations: bedding, fracture directions, drainage paths, erosion, and deposition. Those correlations are an excellent form of compression. One bedding description can explain thousands of related ledges and fractures.
|
||||
|
||||
Research on [geologically coherent cave networks](https://perso.liris.cnrs.fr/apeytavi/website/publication/hal-03331697/) demonstrates useful controls through faults, horizons, permeability, and network skeletons. That paper ultimately constructs an SDF; my proposal is to retain additional boundary knowledge through the rendering pipeline.
|
||||
|
||||
Here is the potential dimensional advantage.
|
||||
|
||||
**Illustrative estimate:** suppose the nearby region contains 20,000 m² of rock surface. The actual area in your game must be measured.
|
||||
|
||||
| Representation at 12.5 cm spacing | Approximate sample locations |
|
||||
|---|---:|
|
||||
| Your full cube, including its sampling overhead | 140 million |
|
||||
| A surface band with total thickness of four cells | 5.1 million |
|
||||
| Direct surface sampling, roughly one location per \(h^2\) | 1.3 million |
|
||||
|
||||
The narrow-band estimate is:
|
||||
|
||||
\[
|
||||
N \approx \frac{A(4h)}{h^3}=\frac{4A}{h^2}.
|
||||
\]
|
||||
|
||||
For fixed surface area and complexity, halving spacing makes surface sampling roughly **4×** larger; dense volume sampling becomes **8×** larger.
|
||||
|
||||
These are counts, not interchangeable operation costs or promised speedups. Surface intersections, charts, normals, transitions, and acceleration structures all add work.
|
||||
|
||||
Most importantly, **your existing uniform-brick skipping already captures some of this benefit**. The table cannot be claimed as an additional 27× improvement over that implementation.
|
||||
|
||||
The new opportunity is to enumerate likely surface regions directly, or emit surface geometry directly, so that you avoid both expensive field evaluations and a large search through candidate bricks. If a proposed hierarchy still performs essentially the same uncertain-brick evaluations, it has not changed your problem.
|
||||
|
||||
For the volumetric exceptions, narrow-band storage is well established; [VDB](https://museth.org/Ken/Publications_files/Museth_TOG13.pdf) provides relevant techniques. Its data structure does not, by itself, make your procedural field cheaper. Your report’s failed generic interval experiment is a good reason to insist on cheap, construction-specific bounds.
|
||||
|
||||
A useful intermediate technique preserves fine detail exactly: give the coarse description a **conservative envelope containing the full detailed boundary**, then evaluate the complete fine function only inside that envelope. This can retain the original detail where it matters. It works only if the envelope is cheap and sufficiently tight. Sampling coarse corners alone cannot certify that a thin feature is absent.
|
||||
|
||||
**What it gives:** potentially the largest lasting reduction in generation work, faster distant representations, better artistic controls, and a natural place to construct guaranteed routes.
|
||||
|
||||
**What it costs:** a substantial generator redesign, patch stitching, solid/interior classification, and maintaining a volumetric fallback. Junctions are the hard part.
|
||||
|
||||
**What it takes away:** the convenience of expressing every shape as unrestricted noise and arbitrary field operators. You can preserve output freedom through exceptions, but a world composed almost entirely of tangled exceptions will recover the expensive behaviour.
|
||||
|
||||
**Determinism:** replicate feature IDs, parameters, graph/layout, content version, and edits. Use canonical rules for patch boundaries and gameplay queries. Each client can build different temporary caches while deriving the same defined geometry.
|
||||
|
||||
This also offers a better target for precomputation. Bake a reusable library of rock profiles, fracture relief, and formation families. For scale, **256 relief tiles at 256² texels and four bytes per texel occupy 64 MiB before mipmaps**. That is an illustrative shared asset library, not a storage estimate for your whole art set. It can be reused across every seed and season.
|
||||
|
||||
**3. Make a crater immediately evaluable, then rebuild its acceleration structures later.**
|
||||
|
||||
There is a useful geometric fact behind excavation.
|
||||
|
||||
After subtracting a volume from rock, the resulting surface consists of:
|
||||
|
||||
- Surviving parts of the old rock surface.
|
||||
- Parts of the excavation boundary that lie inside the old rock.
|
||||
|
||||
For a spherical blast, the new interior surface comes from the sphere boundary. The volume in the middle becomes known air.
|
||||
|
||||
That suggests treating a spell as an immediately active geometric operation, with meshes serving as cached representations of its result.
|
||||
|
||||
For scale, a 10 m radius sphere has approximately **1,257 m²** of surface. At 12.5 cm spacing, that corresponds to roughly **80,000 surface locations**, before intersections and other overhead. Its 20 m bounding cube contains approximately **4.17 million lattice locations**.
|
||||
|
||||
This is not an 80,000-sample implementation promise. It explains why a boundary-oriented edit algorithm has a fundamentally different opportunity from rebuilding the affected volume.
|
||||
|
||||
A possible immediate path is:
|
||||
|
||||
1. Commit one canonical excavation command.
|
||||
2. Make movement and terrain queries observe it immediately.
|
||||
3. Clip affected old surfaces and render the newly exposed boundary through a local CSG or implicit rendering path.
|
||||
4. Build the enduring mesh and collision caches in the background.
|
||||
5. Replace the temporary rendering only when the replacement is complete and equivalent.
|
||||
|
||||
Image-based constructive solid geometry is an established family of techniques: [OpenCSG](https://opencsg.org/) renders Boolean combinations without first constructing their final mesh boundary. That establishes the possibility; it does not supply a ready-made Unreal terrain solution.
|
||||
|
||||
The hard parts are substantial:
|
||||
|
||||
- The new crater surface must be clipped correctly against existing caves and earlier excavations.
|
||||
- Removing the first visible wall must reveal whatever is behind it.
|
||||
- Depth, shading, shadows, and both eyes must agree.
|
||||
- Independent cap and terrain tessellation need consistent intersection boundaries.
|
||||
- The temporary and cached representations must describe the same surface closely enough to avoid a visible changeover.
|
||||
|
||||
A single regional implicit rendering path can avoid some mesh-boundary problems, but then it needs robust field traversal. Your signed density cannot automatically be treated as a safe sphere-tracing distance.
|
||||
|
||||
For overlapping spells, render the composed result. Independently displaying several crater shells produces internal surfaces that should not exist.
|
||||
|
||||
This is also where the database analogy helps. The seed/layout and ordered edits are the authoritative record. Meshes, collision bodies, and GPU pages are derived indexes. An edit can become semantically active before every index is rebuilt, provided queries correctly combine the old index with pending changes.
|
||||
|
||||
**What it gives:** a credible architecture for same-frame excavation whose latency does not depend on draining dozens of component updates.
|
||||
|
||||
**What it costs:** custom rendering and terrain-query integration, plus management of overlapping edits and temporary representations.
|
||||
|
||||
**What it takes away:** a cheap universal implementation for arbitrarily complicated edit operators. Spheres, capsules, and bounded procedural blasts are favourable. Unbounded numbers of overlapping edits still require work; the supported spell load needs a defined budget.
|
||||
|
||||
**Determinism:** distribute canonical edit parameters and ordering. Immediate queries and later meshes must implement the same composition rules. A CPU-produced canonical lattice or a specified integer GPU evaluator can provide agreement where the rendering path needs authoritative signs.
|
||||
|
||||
“Same frame” should mean the first submitted frame containing the accepted command. Remote machines necessarily see commands after network delivery; local prediction can hide that latency. Within each machine, the whole affected region must use the same edit state.
|
||||
|
||||
I would regard this as a complement to your surface-detail idea. Faster generation alone does not create atomic visual publication.
|
||||
|
||||
**4. Reopen GPU generation specifically through defined integer arithmetic.**
|
||||
|
||||
Rejecting ordinary cross-vendor floating-point generation was reasonable. There is still a separate option: a deliberately restricted, integer-defined world evaluator running on both CPU and GPU.
|
||||
|
||||
The reason to revisit fixed point would be access to GPU parallelism while preserving the numeric contract. It would not be an assumption that integers are inherently faster than your optimized CPU floats.
|
||||
|
||||
The contract needs explicit widths, rounding, overflow behaviour, lookup tables, coordinate decomposition, and operation ordering. Canonical decisions cannot quietly pass through vendor-dependent floating-point functions or texture filtering. Microsoft’s [DXIL specification](https://github.com/microsoft/DirectXShaderCompiler/blob/main/docs/DXIL.rst) defines relevant integer operations, including full multiplication results and unfiltered data loads.
|
||||
|
||||
Quantizing an ordinary float result at the end is insufficient: two computations can land on opposite sides of a quantization boundary.
|
||||
|
||||
A practical division would be:
|
||||
|
||||
- GPU evaluates bulk geometry or surface refinement.
|
||||
- CPU evaluates the small subset required for immediate gameplay.
|
||||
- Both implement the same defined rules.
|
||||
- Gameplay avoids waiting for GPU readback.
|
||||
|
||||
**What it gives:** potentially large throughput while retaining seed-and-edits networking.
|
||||
|
||||
**What it costs:** a numerical implementation project, validation across supported vendors, GPU scheduling, and less freedom in procedural operators. Integer divisions or wide arithmetic can be expensive.
|
||||
|
||||
**What it takes away:** convenient use of arbitrary floating-point shader math for authoritative geometry. It also consumes the GPU budget needed for stereo rendering.
|
||||
|
||||
**Determinism:** matching canonical outputs is a release requirement, tested across CPU and GPU implementations and supported devices. It is achievable through a carefully specified implementation; the word “integer” alone does not prove it.
|
||||
|
||||
For scale, 140 million 16-bit samples occupy approximately **280 MB** before halos and other buffers. Storage alone is manageable on your target GPU. Producing and using those values quickly enough remains the actual question.
|
||||
|
||||
I would pursue this after reducing the amount of work. Otherwise, every future resolution increase retains the same cubic growth.
|
||||
|
||||
There are three requirements that deserve independent treatment regardless of the representation.
|
||||
|
||||
**Player readiness should depend on the player’s reachable surroundings.** A 4 m cube at 12.5 cm spacing contains about **36,000 lattice points**. At your stated evaluator rate, that is roughly **3–5 ms of aggregate evaluation work**, excluding setup, meshing, and collision installation. It is an illustration of the scale difference, not a proposed universal collision neighbourhood.
|
||||
|
||||
A moving or falling player needs a region covering the swept capsule and its near-future motion, including walls and ceilings. A cold teleport needs a valid destination and immediate terrain queries or a small ready collision patch. None of that inherently requires the complete 64 m visual neighbourhood.
|
||||
|
||||
The engineering challenge is reliable swept contact and edit-aware support. Your measured cook queue problem reinforces the need to keep that deadline independent. Retaining an obsolete floor after excavation is as incorrect as temporarily losing a floor that still exists.
|
||||
|
||||
**Kilometres of visibility should be served by compact distant surfaces and conservative visibility.** Underground occlusion can eliminate large amounts of rendering work, while a vast open chamber still needs a cheap distant representation.
|
||||
|
||||
However, generating only currently visible surfaces is incompatible with guaranteed immediate revelation by blasting. Keep inexpensive distant representations available and detailed geometry within the region that nearby edits can expose. Destruction also needs to update any portal or occlusion assumptions.
|
||||
|
||||
**Walking connectivity must include support.** A connected graph of air spaces is insufficient. Construct at least one entrance-to-exit route with constrained slope, step height, capsule clearance, and continuous supporting floor. Bound later geometric decoration against that route.
|
||||
|
||||
If this guarantee must survive arbitrary player edits, there is a real design conflict: excavation can remove the only bridge while increasing air connectivity. Maintaining the guarantee then requires protected support, alternative routes, or repair. That is a gameplay rule that no representation can avoid.
|
||||
|
||||
I would spend the next experimental effort on **a deterministic structural world with genuinely detailed surface refinement and an immediate edit representation**.
|
||||
|
||||
The first prototype could retain your current generator at 25 cm for ordinary structure, preserve finer volumetric regions for small holes and thin features, and add the surface layer. Your measured 25 cm result makes this a much more grounded starting point than a complete renderer replacement.
|
||||
|
||||
I would judge it through four demonstrations:
|
||||
|
||||
- Close VR inspection: silhouettes, grazing views, near-hand relief, and patch boundaries meet the owner’s quality bar.
|
||||
- A cold teleport: valid support becomes available independently of the full visual region.
|
||||
- A 10 m blast: the complete result appears together, including an underfoot edit and an opening into another cave.
|
||||
- Cross-machine comparison: canonical geometry and terrain-query results agree.
|
||||
|
||||
If the surface experiment fails because the required detail is truly topological or physically interactive, preserve that fine geometry and move harder toward direct surface generation and narrow volumetric exceptions.
|
||||
|
||||
My longer-term bet is the second direction: **a generator that retains explicit knowledge of its surfaces**. It fits the geological structure of your target, gives walkability a natural foundation, and offers a route to increasing detail without repeatedly multiplying the work throughout every cubic metre of the world.
|
||||
|
||||
---
|
||||
|
||||
# Round 2 (fork): shorelines, fluid-looking rock, invention — 2026-10-02
|
||||
|
||||
Prompt: scratchpad astra-think-2.md. Answer verbatim below.
|
||||
|
||||
**I would now give important contacts their own geometry, and give moving rock a stable “material space” in which cuts persist.** Those two additions address the new problems and strengthen the earlier surface-based direction.
|
||||
|
||||
The shoreline exposes a weakness in treating independently generated representations as if they will automatically agree. Moving rock exposes a different weakness: defining a world only by where its surface happens to be this frame.
|
||||
|
||||
Below, “proposed” means an architectural suggestion that needs testing. “Speculative” means I see a plausible mechanism, but would not plan production around it yet. Numerical estimates are arithmetic, not measured Unreal performance.
|
||||
|
||||
**1. Make the shoreline a shared geometric boundary. Established geometry techniques; proposed terrain integration.**
|
||||
|
||||
A flat water surface is a reasonable representation of a still cave pool. It is also an unusually effective instrument for revealing geometric error.
|
||||
|
||||
Consider a shallow bank whose height rises by 2 cm per horizontal metre. Locally,
|
||||
|
||||
\[
|
||||
\text{shoreline position error}\approx
|
||||
\frac{\text{terrain height error}}{\text{bank slope}}.
|
||||
\]
|
||||
|
||||
A **1 cm height error on that 2% slope can move the shoreline by approximately 50 cm**. This explains why terrain that looks acceptable elsewhere can look conspicuously wrong at water level.
|
||||
|
||||
Marching cubes produces piecewise planar surfaces, so its intersection with a plane is piecewise linear. Additional stair-like appearance can come from density quantization, displacement, LOD transitions, or a water mask derived from another representation. Smooth normals can improve lighting, but cannot repair the geometric outline.
|
||||
|
||||
My proposal is to make the intersection an explicit curve shared by both surfaces.
|
||||
|
||||
For a flat pool at height \(H\), find the curve where the intended rock surface meets \(z=H\). Then:
|
||||
|
||||
- Make the nearby rock triangles conform to that curve.
|
||||
- Make the water boundary use the same curve.
|
||||
- Derive wetness, deposits, and near-bank effects from it.
|
||||
- Refine the rock in a narrow surrounding strip to preserve the correct shape and normals.
|
||||
|
||||
The “intended rock surface” must include any displacement that materially changes the contact. Computing the curve from coarse collision geometry while rendering a substantially displaced bank simply introduces another disagreement.
|
||||
|
||||
This is related to boundary representation in CAD: intersections are first-class geometry. Guenter and Gavriliu’s [Generative CSG Models for Real Time Graphics](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/tr-2005-43.pdf) explicitly represents intersection curves and uses them for adaptive triangulation. Its expensive preprocessing makes it a conceptual precedent, not evidence that arbitrary cave intersections can be reconstructed instantly.
|
||||
|
||||
The favourable part is dimensionality. **A shoreline is primarily a one-dimensional problem.**
|
||||
|
||||
For illustration, a 100 m shoreline sampled every 2 cm has approximately **5,000 curve locations**. A surrounding strip 40 cm wide, sampled at the same spacing, contains roughly **100,000 surface locations**. Root finding, transitions, normals, and topology add work, but this is a very different expenditure from refining a volume.
|
||||
|
||||
There is a useful rendering refinement here too: maintain separate tolerances for **position and normal direction**. Accurate contact positions fix the outline; accurate surface normals fix the faceted reflection. One tolerance does not guarantee the other.
|
||||
|
||||
- **Cost and loss:** substantial meshing work around intersections, plus handling curve splitting and merging after edits. Near tangencies, an intersection can appear or disappear, so tracing one existing curve is insufficient to discover every component.
|
||||
- **Multiplayer:** derive the curve from the canonical shape, water level, and edit version using specified numeric rules. Different clients can tessellate between shared boundary constraints differently for rendering. Gameplay-relevant bank geometry remains canonical.
|
||||
- **First cheap experiment:** use one analytically defined bowl with a ledge, intersected by flat water. Render unlit colours first, then reflective materials. Shift the sampling-grid origin while holding the underlying bowl fixed. Add a conforming shoreline strip and check whether the contact becomes stable without refining the whole bowl. Also raise the water through the ledge to expose topology failures.
|
||||
|
||||
**2. Generate pools from their shorelines and geological history. Proposed construction method, with strong natural precedents.**
|
||||
|
||||
For generated pools, there is an even cheaper possibility: **choose the shoreline before constructing the surrounding bank**.
|
||||
|
||||
The recipe could start with:
|
||||
|
||||
- Water elevation and an irregular closed boundary.
|
||||
- A bank profile along that boundary: steep fracture, shallow shelf, undercut, sediment apron, or mineral lip.
|
||||
- A basin interior.
|
||||
- A transition into the surrounding cave surfaces.
|
||||
|
||||
The rock is then constructed to meet the water deliberately. Noise modifies that construction within limits that preserve the intended contact.
|
||||
|
||||
This is particularly suitable for your visual target because natural caves already provide expressive shoreline structures. Flowstone forms through mineral deposition by flowing films; rimstone dams hold pools and grow through deposition where water passes over them. The [National Park Service’s cave geology account](https://www.nps.gov/grba/planyourvisit/cave-geology-in-depth.htm) describes these processes and formations.
|
||||
|
||||
A pool could therefore have a fine scalloped mineral lip, a darker wet band, submerged sediment, and occasional fractured interruptions. Those features explain the contact visually and give the generator meaningful parameters.
|
||||
|
||||
Recent research also supports treating contours as primary terrain descriptions. [Huftier and colleagues’ 2026 contour-based terrain work](https://perso.liris.cnrs.fr/apeytavi/website/publication/hal-05608822/) generates and edits terrain through elevation contours. It addresses heightfield terrain, so extending it to cave overhangs and intersecting chambers remains a separate problem. I would apply the idea locally around pools.
|
||||
|
||||
The water itself can contribute appropriate detail:
|
||||
|
||||
- Small waves whose behaviour changes near banks.
|
||||
- A restrained meniscus.
|
||||
- Wetness and sediment colour gradients.
|
||||
- Refraction that reveals the submerged bank.
|
||||
- Foam where there is actual agitation.
|
||||
|
||||
However, a meniscus is a small-scale effect. Water’s capillary length is approximately **2.7 mm**, so it should not be expected to conceal decimetre-scale geometric problems in a broad pool. [MIT’s capillarity notes](https://web.mit.edu/nnf/education/wettability/gravity.html) give that scale. Similarly, a wide foam outline around every still cave pool would become its own procedural signature.
|
||||
|
||||
I would distinguish **current contact** from **historical deposits**. Lowering the water should reveal old mineral marks. Blasting a rim should remove it; the generator should not immediately grow a replacement decorative lip around the crater.
|
||||
|
||||
- **Cost and loss:** more authoring logic and a preference for pools with intentional bank structure. Freely changing water levels still require fresh intersection handling outside the originally constructed contact.
|
||||
- **Multiplayer:** pool outlines, profiles, historical water levels, and structural deposits belong to the seeded recipe. Current water levels must be authoritative wherever swimming, drainage, or access depends on them. Tiny ripples and wet highlights can be cosmetic.
|
||||
- **First cheap experiment:** generate one pool with three bank styles from a shared curve. Lower its water, then cut through its rim. The experiment succeeds only if the bank remains convincing, the old watermark stays put, and the excavation does not trigger decorative “healing.”
|
||||
|
||||
**3. Give stone the visual language of flowing material before moving its bulk. Established rendering techniques; artistic hypothesis.**
|
||||
|
||||
There are several distinct ways for rock to look fluid:
|
||||
|
||||
| Appearance | What actually changes |
|
||||
|---|---|
|
||||
| Frozen waves, folds, or drapery | Static geometry |
|
||||
| Veins and surface patterns flowing over stone | Material coordinates and shading |
|
||||
| Ripples crossing a wall | Shading plus shallow displacement |
|
||||
| A wall visibly bulging into a passage | Gameplay geometry, if reachable |
|
||||
| Stone pouring, splitting, or merging | Material transport and topology |
|
||||
|
||||
The first three can provide a large part of the desired effect at much lower cost than the last two.
|
||||
|
||||
For a living wall, I would try a combination of stationary large fractures and slowly travelling mineral ridges. The motion would have a direction and coherent scale: sweeping upward, curling around columns, spreading from impacts, or descending into basins.
|
||||
|
||||
A flow map supplies local direction and speed. Valve demonstrated this approach for animated normal and colour textures in [Water Flow in Portal 2](https://advances.realtimerendering.com/s2010/index.html#Water%20Flow%20in%20Portal%202). Applied to stone, it could transport veins, roughness variation, and shallow relief.
|
||||
|
||||
For more organic motion, [curl noise](https://www.cs.ubc.ca/~rbridson/docs/bridson-siggraph2007-curlnoise.pdf) supplies procedural fluid-like velocity fields with mechanisms for respecting boundaries. It does not require running a full fluid simulation. Transporting textures or particles through that field still needs an appropriate animation scheme; an attractive velocity field alone does not solve stretching, looping, or temporal consistency.
|
||||
|
||||
My artistic hypothesis is that **coherent folds and motion at a few scales will read as flowing stone more convincingly than many animated noise octaves**. Keep the reflectance recognizably mineral unless the intention is explicitly wet or molten material.
|
||||
|
||||
Frozen waves deserve their own inexpensive approach: run an expensive viscous simulation during asset creation, select compelling states, then turn them into reusable surface motifs. Research on [viscous buckling, folding, and coiling](https://www.cs.ubc.ca/labs/imager/tr/2008/Batty_ViscousFluids/) demonstrates precisely the kinds of shapes that could become stone draperies and frozen cascades. Runtime generation would place and deform the resulting recipes or assets; it would not reproduce the fluid simulation.
|
||||
|
||||
- **Cost and loss:** shader sampling, texture transport, and possibly fine surface geometry. Flowing shading has limited power at silhouettes and under close VR inspection. Frozen motifs need enough variation to avoid repetition.
|
||||
- **Multiplayer:** fixed structural geometry remains the authority. Shared assets and seeds define frozen forms. Cosmetic motion can use a common phase without requiring identical pixels. Any displacement large enough to alter cover, contact, or digging must move into the authoritative category.
|
||||
- **First cheap experiment:** use one static wall and compare three versions: flowing material only, material plus shallow relief, and a frozen folded surface. Inspect them in VR from the side and with a stationary hand or tool nearby. This quickly reveals how much physical movement the desired effect actually needs.
|
||||
|
||||
**4. Animate a few controlled deformation patterns. Established animation mathematics; proposed gameplay integration.**
|
||||
|
||||
For actual breathing geometry, I would begin with a small set of motion patterns:
|
||||
|
||||
\[
|
||||
S(u,v,t)=S_0(u,v)+\sum_k a_k(t)\,B_k(u,v).
|
||||
\]
|
||||
|
||||
Here, \(S_0\) is the resting surface. Each \(B_k\) is a pattern such as a bulge, travelling ridge, or twist. The coefficient \(a_k(t)\) controls its strength.
|
||||
|
||||
The useful property is that the shape can be evaluated directly at a requested time. A client arriving late does not have to simulate every previous frame.
|
||||
|
||||
Patterns can be designed to vanish along protected boundaries: the floor, a doorway, a pool edge, or the edges of an animated region. Where reflections reveal changes in slope, make the deformation’s first derivative vanish there too.
|
||||
|
||||
Impact-driven motion has a particularly relevant precedent: [Dynamic Kelvinlets](https://graphics.stanford.edu/~djames/publication/dynamic-kelvinlets-secondary-motions-based-on-fundamental-solutions-of-elastodynamics/). These provide analytical elastic responses such as ripples and waves from localized forces, avoiding a conventional volumetric simulation. They are models of elastic secondary motion, not complete fluid or fracture simulation.
|
||||
|
||||
A spell could therefore produce two effects:
|
||||
|
||||
1. A permanent excavation command.
|
||||
2. A temporary elastic wave, defined by its origin, start time, strength, and decay.
|
||||
|
||||
For an initial version, I would keep the walkable floor stationary while nearby walls respond. Later, genuinely moving floors can use the same evaluator for geometry and terrain contacts.
|
||||
|
||||
**Motion speed and acceleration matter as much as displacement amplitude.** For a sinusoidal movement:
|
||||
|
||||
- An amplitude of 3 cm at 0.5 Hz gives peak acceleration of approximately **0.30 m/s²**.
|
||||
- An amplitude of 20 cm at 2 Hz gives approximately **32 m/s²**, over three times gravity.
|
||||
|
||||
The latter is not a gently breathing floor. Correct physics would make it a powerful moving platform.
|
||||
|
||||
A rough work estimate: a 10 m square patch sampled every 5 cm has about **40,000 vertices**. Eight patterns require approximately **320,000 pattern contributions per geometry pass**. Stereo, shadows, normals, and shading increase that work. This is an operation count, not a frame-time prediction, but it shows why surface animation can be much cheaper than regenerating a density volume every frame.
|
||||
|
||||
- **Cost and loss:** deformation evaluation, updated normals and bounds, correct shadows and previous-frame positions, and custom terrain contact handling for substantial movement. Ordinary shader vertex displacement does not automatically update collision. The result has a controlled motion vocabulary rather than unrestricted fluid behaviour.
|
||||
- **Multiplayer:** use authoritative ticks and a versioned evaluator. Evaluate coefficients from time and compact events, avoiding frame-by-frame accumulation. Gameplay-relevant arithmetic needs the same determinism discipline as terrain generation; a shared sine expression alone is insufficient.
|
||||
- **First cheap experiment:** animate one wall with four patterns, pin its lower edge, and trigger one impact wave. Evaluate the same timestamps after different frame-rate histories and a late join. Then make a small platform move and test whether contact velocity is handled correctly. Stop if it requires recooking terrain collision every frame.
|
||||
|
||||
A deformation also needs limits on spatial variation. Small amplitude does not prevent very short waves from folding a surface over itself.
|
||||
|
||||
**5. Let moving stone remember cuts in material coordinates. Speculative architecture; established invertible-map mathematics.**
|
||||
|
||||
This is the direction I find most interesting.
|
||||
|
||||
Imagine every piece of rock carries permanent coordinates, like markings embedded throughout a block of clay. The clay can deform, but those coordinates stay attached to its material.
|
||||
|
||||
Define the rock and its excavations in those coordinates. A time-dependent mapping moves the material into the visible world:
|
||||
|
||||
\[
|
||||
x=T_t(q),
|
||||
\qquad
|
||||
F_t(x)=F_{\text{edited}}\!\left(T_t^{-1}(x)\right).
|
||||
\]
|
||||
|
||||
In plain terms:
|
||||
|
||||
- To render a point of rock, move it forward through the deformation.
|
||||
- To query the rock at a world position, undo the deformation and query the stored shape.
|
||||
- A hole follows the material because it is stored with the material.
|
||||
|
||||
Between edits, the reference geometry can remain cached. Animation changes its positions rather than repeatedly rediscovering its topology.
|
||||
|
||||
The difficult requirement is a cheap, reliable inverse.
|
||||
|
||||
One construction uses successive nonlinear shears. Shift \(x\) by an amount depending on \(y,z\); then shift \(y\) by an amount depending on the updated \(x,z\). Each step can be undone by subtraction in reverse order. Smooth additive shears preserve volume individually and remain invertible. The same mathematical construction appears as additive coupling layers in [NICE](https://arxiv.org/html/1410.8516v6); no neural network or training is required for this use.
|
||||
|
||||
My speculative application is a collection of such maps producing slow eddies, waves, and folds inside selected rock regions.
|
||||
|
||||
An additional experiment would be:
|
||||
|
||||
> Apply an x-shear, then a y-shear, then undo the x-shear, then undo the y-shear.
|
||||
|
||||
Because the operations act on already changed coordinates, they generally leave a deformation inside their interaction region. With appropriately localized shear functions, their effects can cancel outside a bounded surrounding region. That suggests a localized, invertible “stone eddy.” The support bounds and resulting distortion would need to be verified.
|
||||
|
||||
The digging rule is essential.
|
||||
|
||||
If a spherical blast occurs while the rock is bent, **pull the entire blast shape back into material coordinates**. Transforming only the sphere’s centre and keeping its radius would usually be wrong: a nonlinear deformation turns its inverse image into a more complicated shape.
|
||||
|
||||
A compact edit can retain the blast parameters and the frozen deformation parameters from the instant of impact. The cut then remains attached to the removed material as the rock continues moving.
|
||||
|
||||
A permanently world-fixed exclusion volume is another possible game mechanic, but it has different semantics. It continually clips whatever material enters it. I would make that distinction explicit rather than accidentally letting craters slide or appear to heal.
|
||||
|
||||
There are several important limits:
|
||||
|
||||
- Invertibility preserves topology during deformation; it does not preserve walkable slope or clearance.
|
||||
- A deformed signed field is generally no longer a true distance field.
|
||||
- Inverse-transforming a capsule does not leave an ordinary capsule under a nonlinear map.
|
||||
- The exact smooth surface may remain valid while an insufficiently subdivided triangle approximation develops artifacts.
|
||||
|
||||
Consequently, this does not make collision trivial. It makes the shape and edit history compact and queryable.
|
||||
|
||||
- **Cost and loss:** a significant research and physics-integration effort, inverse mappings on queries, distortion bounds, and material-space edit evaluation. Deformation alone cannot reproduce fluid breakup or merging; those require separate topology operations.
|
||||
- **Multiplayer:** canonical mapping parameters, simulation time, and material-space edits define the same world. Carefully defined fixed-point shears can even have exactly reversible coordinate updates when overflow is excluded, although that does not by itself prove the whole collision system correct.
|
||||
- **First cheap experiment:** one deforming slab, one spherical excavation, and a few ray/contact queries. Cut it at maximum deformation, return to rest, then deform it again. Check that the same material stays missing. Replay at different frame rates and test forward/inverse round trips. This should be settled before attempting a whole animated chamber.
|
||||
|
||||
**6. Make contacts and protected routes part of the generator’s input. Speculative unifying design.**
|
||||
|
||||
The broader architectural change I would now investigate is to give a region an explicit collection of important geometric constraints:
|
||||
|
||||
- A pool’s contact boundary.
|
||||
- A doorway’s edge.
|
||||
- A continuously supported walking route.
|
||||
- The attachment of a column.
|
||||
- The boundary between stationary and moving rock.
|
||||
- The rim created by a recent excavation.
|
||||
|
||||
Surface patches are then generated and refined around those constraints. Detail and motion have budgets relative to them.
|
||||
|
||||
For example, a chamber could contain a still pool and a stationary path beside it. The rock higher on the wall could ripple strongly. Its deformation would fade smoothly to zero at the protected boundaries. Fine mineral patterns could continue flowing through those zones because they do not alter support.
|
||||
|
||||
That permits visibly dramatic motion without moving every gameplay-critical surface.
|
||||
|
||||
It also suggests a better refinement policy. Allocate geometry according to the error that matters locally:
|
||||
|
||||
| Location | Important error |
|
||||
|---|---|
|
||||
| Water contact | Position of the intersection |
|
||||
| Reflective rock | Surface-normal direction |
|
||||
| Narrow passage | Minimum capsule clearance |
|
||||
| Animated floor | Contact position and velocity |
|
||||
| Ordinary distant wall | Projected image error |
|
||||
|
||||
A uniform spatial resolution is a poor proxy for these different requirements.
|
||||
|
||||
Motion envelopes help here. A generator can prove that a wall moving within a known envelope cannot enter a protected corridor. But that envelope is a clearance or broad-phase tool: it cannot replace the actual surface for close player contact without creating visible collision discrepancies.
|
||||
|
||||
I would also keep these constraints independent of each client’s camera. Freezing a wall only when the local player approaches is liable to create conflicting worlds. Use fixed protected zones or an explicit authoritative state change.
|
||||
|
||||
- **Cost and loss:** more information in the world recipe, coupled generation rules, and resolving conflicting constraints. Some combinations of motion, thin walls, and route clearance must be rejected or altered.
|
||||
- **Multiplayer:** the constraints and motion limits belong to the canonical layout. Rendering can vary its subdivision while respecting the same boundaries and gameplay surface.
|
||||
- **First cheap experiment:** construct one chamber containing a pool, a door, a walking strip, and an animated wall. Keep those contacts stable while varying the surrounding shape and motion. If this requires special-case fixes at every junction, the representation needs simplifying before expansion.
|
||||
|
||||
The division I would use for moving rock is therefore:
|
||||
|
||||
| Feature | May be cosmetic? |
|
||||
|---|---|
|
||||
| Colour, roughness, veins, fine normal variation | Yes, unless used to communicate gameplay state |
|
||||
| Very small relief | Yes, within a tested visual/contact tolerance |
|
||||
| Large movement in an unreachable region | Potentially; still needs appropriate bounds and visual interactions |
|
||||
| Reachable bulges, footholds, cover, passage clearance | Requires authoritative geometry and queries |
|
||||
| Excavation and material removal | Requires authoritative edit semantics |
|
||||
|
||||
For implementation research, **the shared shoreline curve is my strongest near-term bet**. It concentrates precision where the image is unusually sensitive and can be tested without replacing the whole terrain system.
|
||||
|
||||
For invention, **the invertible material-space wall is the experiment I would fund**. It offers a plausible way to combine motion, persistent digging, compact networking, and cached geometry. Its first success criterion should be modest and strict: one moving slab whose hole, visible surface, and contact queries continue to agree.
|
||||
|
||||
I would revise the earlier surface-first recommendation accordingly: retain explicit knowledge of the surfaces, their important intersections, and the material coordinates that survive animation. Those are useful facts the generator can provide once and reuse throughout rendering, editing, and gameplay.
|
||||
|
||||
---
|
||||
|
||||
# Round 3 (fork): performance only, unrealistic goal (whole view < 1 s) — 2026-10-02
|
||||
|
||||
Prompt: scratchpad astra-think-3.md. Answer verbatim below.
|
||||
|
||||
**I would bet on a generator that emits boundaries and solid intervals directly.** The distant world would come from a hierarchy of surface pieces. Nearby Boolean operations—joining caves, digging, blasting—would operate on intervals and packed bits. A density function could remain available for queries, but generating a view would no longer mean filling its volume with density samples.
|
||||
|
||||
I can construct a **900 ms engineering budget** for this design. That is a target to test, not a measured prediction. Its essential condition is that the world’s composition limits the amount of surface work required. An arbitrary field can contain arbitrarily much geometry; no representation can guarantee a second without some complexity contract.
|
||||
|
||||
The proposed architecture combines established techniques. The particular composition rules, division of work, and budget below are my proposal.
|
||||
|
||||
**There is a larger opportunity in your current far field than the word “clipmap” suggests.**
|
||||
|
||||
Your source supports expanding the outermost ring at a fixed maximum LOD. The checked-in settings use `RenderDistanceChunks=1536` and `MaxClipLevel=5`: that gives an outer radius of 48 tiles, approximately **97 × 97 horizontal positions per vertical slice**, before exclusions. Those are defaults, not a census of your running world. Nevertheless, this can turn the horizon into a large fixed-resolution grid. The settings explain why: coarser volume sampling can miss thin strata. [Settings](</E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Public/VoxelSettings.h:166>), [outer-ring calculation](</E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelClipmapDesiredTiles.h:46>).
|
||||
|
||||
That is a representation problem with a performance consequence. **A distant floor should survive because it is explicitly a floor, rather than because a coarse lattice happens to intersect it.**
|
||||
|
||||
There is a similarly useful distinction nearby: your column evaluator already shares setup across heights, but still loops through every Z sample. The opportunity below is to remove that loop by solving where the column enters and leaves rock. [Column evaluator](</E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelRedefinedDensityField.cpp:2851>).
|
||||
|
||||
**First, make each world piece know how to produce its surface.**
|
||||
|
||||
A room, tunnel segment, shelf, shaft, pillar or floating mass would carry a small description with:
|
||||
|
||||
- Its bounds and connections.
|
||||
- A way to emit its surface at several resolutions.
|
||||
- A way to intersect an axis-aligned line with its solid.
|
||||
- Shared boundary curves for joining neighbouring pieces.
|
||||
- A limit on how much refinement and Boolean work it can require.
|
||||
|
||||
An untouched tunnel would emit a swept surface. A chamber would emit patches with explicitly constructed openings. A floating mass would emit a closed surface. These operations evaluate positions **on the surface**, without searching a surrounding volume for it.
|
||||
|
||||
The difficult part is junctions. Drawing overlapping closed room and tunnel meshes leaves unwanted internal walls. I would begin with a bounded family of junction constructions whose openings, trimming and transitions are already defined. More unusual intersections would use the local interval compiler described below.
|
||||
|
||||
This is a substantial generator redesign. Its advantage is that it makes the cost of a feature predictable.
|
||||
|
||||
The hierarchy must also be **constructed from coarse to fine**. A parent knows the major chamber boundaries, floor sheets and passages before its children exist. Children add bounded shape variation and smaller features. A distant parent can therefore be rendered without generating and simplifying all its descendants.
|
||||
|
||||
That does **not** make a coarse mesh an exact copy of every fine surface. What can be exact is the parent’s enclosure, connection information and refinement contract. The visible surface remains an approximation with a specified error.
|
||||
|
||||
For deterministic connections, neighbouring pieces can derive their shared opening from the same integer face key. Each side then receives identical opening geometry. Matching-boundary composition has a precedent in Wang tiles; extending it to a hierarchical cave grammar with guaranteed connectors is my proposed adaptation. It needs a constructive connector for every allowed boundary configuration, otherwise a search problem returns. [Cohen et al., *Wang Tiles for Image and Texture Generation*](https://graphics.uni-konstanz.de/publikationen/Cohen2003WangTilesImage/index.html).
|
||||
|
||||
Build the initial walkable route into this hierarchy, with enforced clearance and floor slope. Its existence then follows from construction; discovering it does not require generating the whole cave and running navigation over it.
|
||||
|
||||
**The far field would contain selected surface patches, including explicit thin strata. It would contain no density tiles.**
|
||||
|
||||
All selected LODs must be ready by the deadline. Showing a coarse preview while the requested near surface is still missing would not count.
|
||||
|
||||
**Second, replace point evaluation with interval algebra wherever Boolean composition is necessary.**
|
||||
|
||||
Consider a spherical cavity. At a fixed horizontal position \((x,y)\), define
|
||||
|
||||
\[
|
||||
d=r^2-(x-c_x)^2-(y-c_y)^2.
|
||||
\]
|
||||
|
||||
If \(d<0\), that column misses the cavity. Otherwise its entire air interval is
|
||||
|
||||
\[
|
||||
[c_z-\sqrt d,\;c_z+\sqrt d].
|
||||
\]
|
||||
|
||||
One calculation has answered every height in that column.
|
||||
|
||||
Multiple rooms and tunnels produce lists of intervals. Joining cavities becomes interval union. Adding a pillar removes an interval from air. A crater adds an air interval. There is no need to evaluate the composed function at every Z coordinate.
|
||||
|
||||
This is related to **dexel representations**, used in machining and virtual sculpting: solids are represented by entry and exit depths along parallel lines, and cutting becomes interval operations. This is established machinery, not a new claim about solid modelling. [Peng et al., *Surface Reconstruction from Dexel Data for Virtual Sculpting*](https://scholarsmine.mst.edu/mec_aereng_facwork/1226/).
|
||||
|
||||
For your world, I would use:
|
||||
|
||||
- Quadrics for ellipsoids, cones and suitable chamber shapes.
|
||||
- Piecewise analytic segments for tunnels.
|
||||
- Explicit floor/ceiling intervals for shelves and room interiors.
|
||||
- Bounded local exceptions for shapes outside that family.
|
||||
|
||||
Using line families along **all three axes** provides boundary intersections in each direction, including on vertical walls. A 256-cell cube has approximately **198,000 lattice lines across the three directions**, versus roughly 17 million lattice points. If each line encounters an average of 12 candidate primitives, that is about **2.4 million primitive-line tests**.
|
||||
|
||||
That is an operation-count estimate, not a speedup measurement: root solving and interval merging cost more than a single comparison. However, each test can replace many expensive calls to your complete density evaluator.
|
||||
|
||||
This method becomes unattractive if every line crosses hundreds of tiny features. The generator must keep that complexity bounded, or represent those features directly as surfaces.
|
||||
|
||||
**There are two useful ways to exploit 64-bit words here.**
|
||||
|
||||
The first is to represent which primitives are active while sweeping through sorted interval endpoints. With at most 64 local primitives, their membership fits in one word. At a crossing, update the corresponding bit. Between crossings, membership is constant, so the local Boolean expression has one answer for the entire span.
|
||||
|
||||
For a restricted construction such as “rock minus rooms and tunnels, plus pillars, minus craters,” the answer needs only a few tests of those membership masks. Arbitrary CSG trees require more work; the simple expression is a deliberate composition rule.
|
||||
|
||||
The second is to store **64 consecutive rock/air signs in one word**:
|
||||
|
||||
- Union, intersection and subtraction become OR, AND and AND-NOT.
|
||||
- A long uniform span writes whole words.
|
||||
- XOR with a neighbouring column identifies changed signs along X or Y.
|
||||
- XOR with a shifted word identifies changed signs along Z.
|
||||
- Bit scans enumerate only the interesting locations.
|
||||
|
||||
For marching cubes, align the eight corner-sign words for 64 cells. Then
|
||||
|
||||
\[
|
||||
\text{active}=\operatorname{OR}(\text{eight words})
|
||||
\;\land\;
|
||||
\neg\operatorname{AND}(\text{eight words}).
|
||||
\]
|
||||
|
||||
That identifies 64 potentially intersected cells using word operations. `std::countr_zero` and clearing the lowest set bit can enumerate the survivors. Binary greedy meshers demonstrate the usefulness of processing occupancy and face masks this way. Their block-shaped output is not what I propose using for cave surfaces. [Binary Greedy Meshing](https://github.com/cgerikj/binary-greedy-meshing).
|
||||
|
||||
The smooth surface comes from the **actual boundary crossings**, with quantized positions and normals, not from placing vertices halfway between binary samples.
|
||||
|
||||
Two qualifications matter:
|
||||
|
||||
1. Packing an arbitrary field into bits does not make generating those bits cheap. The interval construction is what removes the evaluations.
|
||||
2. Equal corner signs can hide a thin feature entirely inside a cell. Such features need explicit primitive-intersection tags, refinement, or direct surface geometry. Binary signs alone cannot preserve them.
|
||||
|
||||
A near ring with 17.4 million signs needs approximately **2.2 MB**, compared with approximately **69.6 MB for float densities**. Boundary data and meshes are additional, but the interior representation becomes small.
|
||||
|
||||
**There is a particularly interesting mathematical family to investigate: piecewise triquadratic fields.**
|
||||
|
||||
This is an optional extension, not something the first prototype must depend on.
|
||||
|
||||
A polynomial that is quadratic separately in X, Y and Z has 27 coefficients. It offers two useful properties:
|
||||
|
||||
- Restrict it to any axis-aligned line, and it becomes a quadratic: at most two isolated crossings unless the polynomial degenerates.
|
||||
- Express it in the Bernstein basis over a box, and its value lies between the smallest and largest coefficients.
|
||||
|
||||
Consequently, one small coefficient block supplies both cheap line intersections and conservative whole-box sign bounds. All coefficients positive means the entire box is air; all negative means rock. Subdivision can tighten the bounds.
|
||||
|
||||
Bernstein bounds and subdivision are established mathematics used in implicit-surface research. **My speculative proposal is to make this restricted polynomial family part of the cave’s native construction language**, rather than fit it to an already expensive noise field. [Bán and Valasek, *Generalized Lipschitz Tracing of Implicit Surfaces*](https://onlinelibrary.wiley.com/doi/full/10.1111/cgf.15202).
|
||||
|
||||
This distinction matters: fitting an arbitrary existing field would reintroduce sampling and approximation problems.
|
||||
|
||||
Bounded detail can be handled similarly. If a parent function has a known range \([L,U]\), and all omitted detail together is bounded by \(R\), then
|
||||
|
||||
\[
|
||||
F_{\text{complete}}\in[L-R,\;U+R].
|
||||
\]
|
||||
|
||||
Whenever that interval excludes zero, all descendants are irrelevant to occupancy there. The amplitude bound must come from the construction rules; estimating it from a few samples would not be a proof.
|
||||
|
||||
This approach spends a small, predictable amount on bounds supplied by each primitive. Your report’s failed generic interval experiment is good evidence against wrapping the existing entire evaluator in a more expensive arithmetic system.
|
||||
|
||||
**The most radical version would upload surface instructions instead of most vertices.**
|
||||
|
||||
An untouched surface patch can be described by its type, coefficients, transform, boundary identifiers and tessellation level. The GPU can draw a shared index pattern and calculate vertex positions from that description. This is ordinary rasterized geometry; it does not require raymarching the whole world each frame.
|
||||
|
||||
As an illustrative estimate:
|
||||
|
||||
- 20,000 patch descriptions × 128 bytes = **2.56 MB**.
|
||||
- A conventional mesh with 1.2 million vertices and 2.4 million triangles, using 24-byte vertices and 32-bit indices, is **57.6 MB**.
|
||||
|
||||
Both still produce geometry for the GPU to process. The saving is CPU generation, storage, transfer and object setup.
|
||||
|
||||
Edited regions would switch to explicit generated patches. Untouched regions could remain compact procedural instances indefinitely.
|
||||
|
||||
The costs are a custom rendering path, restricted patch families, careful transitions, and GPU arithmetic repeated in depth and shadow passes. Some patches should therefore be expanded once into GPU buffers and retained. This is a credible research direction, but I would **not require it to pass the first CPU experiment**.
|
||||
|
||||
**The rest of the pipeline needs to be designed around bulk work.**
|
||||
|
||||
Your recorded **639 mesh applies per second** is sufficient to make thousands of separate publications a problem even if generation becomes free. Your report also separates fast collision cooking from multi-second queue delays. Those are measured reasons to change the publication and support paths for this larger objective. [Recorded results](</E:/Projet Unreal/VoxelM/Plugins/VoxelForge/RESEARCH-SPEED-2026-09.md:35>).
|
||||
|
||||
I would use pooled geometry buffers and a small number of spatial render pages. Tiles or patches retain their own bounds and update ranges inside those pages. A small edit must update its ranges rather than rebuild a large page.
|
||||
|
||||
GPU culling and indirect drawing can reduce CPU submission work while retaining many independently culled pieces. Direct3D provides the necessary indirect command mechanism; integrating it into Unreal’s scene, material and shadow paths is real implementation work. [Microsoft, *Indirect Drawing*](https://learn.microsoft.com/en-us/windows/win32/direct3d12/indirect-drawing).
|
||||
|
||||
Only the player’s immediate support neighbourhood belongs on the initial collision deadline. Give it a priority path through generation, cooking and installation. Treat “physics state installed and a capsule sweep succeeds” as completion, rather than “cook task submitted.”
|
||||
|
||||
Boundary ownership must also be explicit. Adjacent patches use identical quantized border samples and defined transition strips. Edited replacement patches meet those shared borders. This work belongs in the meshing budget; overlapping skirts are not a general proof of a watertight result.
|
||||
|
||||
**Here is where I would allocate the second.**
|
||||
|
||||
These are **proposed acceptance budgets**, not benchmark results. The workload assumed is:
|
||||
|
||||
- A 64 m near region at roughly **25 cm structural resolution**, with finer appearance handled without a finer volume lattice.
|
||||
- All selected distant surfaces to the requested horizon, including stacked layers.
|
||||
- At most about 50,000 hierarchy records, 2–4 million nearby primitive-line tests, and **2.4 million output triangles**.
|
||||
- Six generation workers.
|
||||
- No seed-dependent geometry or field cache.
|
||||
|
||||
The structural resolution and triangle count are explicit concessions. This budget does not establish the same result for every geometric feature in the current 12.5 cm field.
|
||||
|
||||
| Stage | Work assumption | Principal data | Wall-time budget |
|
||||
|---|---|---:|---:|
|
||||
| Generate/select hierarchy and connections | Up to 50,000 records; order of 10–20 M simple operations | 3–8 MB | 30 ms |
|
||||
| Solve and compose nearby intervals | 2–4 M primitive-line tests; order of 100–300 M arithmetic/comparison operations, plus roots | Paged scratch, target ≤24 MB | 150 ms |
|
||||
| Produce sign masks and active cells | Up to roughly 1 M word groups; tens of millions of bit operations | 2–8 MB | 30 ms |
|
||||
| Emit all near/far meshes and transitions | 1.2 M vertices, 2.4 M triangles; order of 100–250 M geometry operations | 57.6 MB output | 220 ms |
|
||||
| Cook and install immediate support collision | Approximately the current small support neighbourhood | A few MB | 65 ms |
|
||||
| Bulk GPU upload and scene publication | At most roughly 64 page publications | Approximately 60 MB | 100 ms |
|
||||
| Final fence and first completed frame | Includes frame scheduling | — | 25 ms |
|
||||
| Allocation, imbalance and integration contingency | — | — | 280 ms |
|
||||
| **Total** | | | **900 ms** |
|
||||
|
||||
The budgets are added conservatively; I have not counted overlapping stages as an extra saving.
|
||||
|
||||
At an **assumed effective upload rate of 10 GB/s**, 60 MB takes approximately 6 ms of transfer time. That illustrates why publication, allocation and synchronization could matter more than raw bytes. It is not a measurement of your Unreal path.
|
||||
|
||||
Likewise, the operation counts do not automatically imply the listed milliseconds. Roots, unpredictable branches and cache misses differ enormously in cost. The experiment must measure them.
|
||||
|
||||
The largest unknown is the **required output complexity**. If a representative complete view needs 20 million triangles under the chosen surface error, this particular budget fails. The remedy would have to be better surface construction, instancing or approximation—not a claim that packed occupancy removed the output cost.
|
||||
|
||||
“Cold” here means the application is running, shaders are available, and the seed/layout/edit data have arrived, but no world-specific result exists. Starting Unreal, compiling shaders or downloading an unbounded edit history requires a separate budget.
|
||||
|
||||
**The 5800X3D makes a compact implementation particularly attractive.**
|
||||
|
||||
AMD specifies eight cores, sixteen threads, 96 MB of L3, AVX2 and dual-channel DDR4-3200 for this processor. Its theoretical dual-channel memory rate is 51.2 GB/s; actual application bandwidth must be measured. [AMD specifications](https://www.amd.com/en/products/processors/desktops/ryzen/5000-series/amd-ryzen-7-5800x3d.html).
|
||||
|
||||
I would organize the implementation as follows:
|
||||
|
||||
- **Keep repeatedly accessed data around 40–60 MB.** The 96 MB is shared with the rest of the application. Stream finished mesh output rather than keeping every intermediate alive.
|
||||
- **Process local pages with worker-owned scratch.** A 64³ sign block is only 32 KiB before borders. Finish its interval, boundary and mesh work while the relevant data are close.
|
||||
- **Use arrays of primitive coefficients grouped by type.** AVX2 should process similar intersections together, without virtual dispatch or scattered feature lookups inside the loop.
|
||||
- **Use AVX2 for batches, ordinary words for bitsets.** One vector handles four 64-bit masks. This is useful after the representation has removed most work; it is not a claimed 256-fold generator acceleration.
|
||||
- **Exploit forward differences.** Along an integer scanline, \((x+1)^2-x^2=2x+1\). Quadratic terms can advance with additions; hash and coefficient setup happen once per feature or row.
|
||||
- **Use Morton order between pages and simple linear order inside them.** This preserves spatial locality without expensive addressing in every inner iteration.
|
||||
- **Measure six versus eight physical workers before using extra SMT workers.** Leave capacity for the game thread, renderer and collision path. Sixteen hardware threads do not supply sixteen independent cores.
|
||||
|
||||
Avoid per-column allocation, shared append locks and global vertex hash tables. Count locally, assign deterministic output ranges, and write disjoint buffers.
|
||||
|
||||
**Multiplayer agreement would come from a small, explicit numerical contract.**
|
||||
|
||||
Use integer world keys, a versioned hash, fixed parameter quantization and stable feature identifiers. Define:
|
||||
|
||||
- Rounding and sign-at-zero rules.
|
||||
- Endpoint inclusion and coincident-crossing rules.
|
||||
- Boolean operation order and material priority.
|
||||
- Shared edge ownership and vertex quantization.
|
||||
- Arithmetic ranges, widening and overflow behaviour.
|
||||
|
||||
For quadrics, an integer square root or an exact predicate can determine the canonical crossing bracket. A floating-point estimate may accelerate the search only if exact arithmetic verifies and corrects the result.
|
||||
|
||||
GPU floating-point surface shading can vary without changing gameplay. If GPU-generated geometry itself makes authoritative decisions, its integer decisions must match the CPU reference. Merely changing the language’s numeric type does not establish determinism.
|
||||
|
||||
Clients independently generate the same descriptions and process the same ordered edits. Physics simulation need not become deterministic lockstep merely because world geometry is deterministic.
|
||||
|
||||
For a 10 m spherical crater at 25 cm spacing, its projected footprint covers approximately
|
||||
|
||||
\[
|
||||
\pi(10/0.25)^2 \approx 5{,}000
|
||||
\]
|
||||
|
||||
columns per axis. That is roughly 15,000 affected lines across three directions, rather than evaluating the whole 20 m bounding cube.
|
||||
|
||||
That makes immediate editing much more promising. It does **not** prove same-frame publication. The replacement surfaces must still be generated and switched together across every affected page and LOD. CPU support queries must observe the new edit immediately, even while replacement collision bodies are pending.
|
||||
|
||||
**What this design gives up is mathematical generality, rather than three-dimensionality.**
|
||||
|
||||
Overhangs, stacked chambers, floating masses, shafts and craters remain possible. The restrictions are on how they are composed:
|
||||
|
||||
| Restriction | Performance obtained |
|
||||
|---|---|
|
||||
| A bounded primitive and junction family | Predictable surface emission and intersection cost |
|
||||
| Limited overlap and crossings per local region | Short interval lists and cheap Boolean composition |
|
||||
| Coarse structure constrains later refinement | Far geometry exists without generating descendants |
|
||||
| Fine appearance does not all become collision topology | Surface work avoids growing with microscopic detail |
|
||||
| Edited regions become local exceptions | The untouched majority keeps its compact representation |
|
||||
| Finite supported scene and edit complexity | A meaningful loading-time target |
|
||||
|
||||
The long-term risk is that edits accumulate until most regions become expensive exceptions. Local edit histories therefore need deterministic pruning or compaction into a bounded replacement representation. An unlimited edit log cannot have a fixed cold-load guarantee.
|
||||
|
||||
A full-view raymarcher would remove mesh construction but move substantial work into every stereo frame. Global FFTs and tensor decompositions can be excellent for suitable restricted functions, but do not automatically survive arbitrary local cuts cheaply. I would pursue those after the boundary-producing construction has been tested.
|
||||
|
||||
**The first experiment should test the interval compiler and count the output—not attempt a new renderer.**
|
||||
|
||||
A one-day standalone C++ spike could answer the central question:
|
||||
|
||||
1. **Build a representative geometric corpus.** Use room and tunnel records resembling your actual parameter ranges, plus pillars, shelves, floating solids, thin strata, rotated shapes and intersecting craters. Include crowded junctions. A sparse collection of spheres is insufficient.
|
||||
|
||||
2. **Implement two versions of the same restricted world.** One evaluates occupancy point by point as a reference. The other generates intervals, composes them, fills sign words and extracts boundaries. They must describe the same new geometry; matching every detail of the old noise field is not the experiment.
|
||||
|
||||
3. **Produce real mesh buffers.** Include boundary positions, indices, borders and deterministic ownership. A fast occupancy-only result can conceal an expensive reconstruction stage.
|
||||
|
||||
4. **Exercise the far-world rule separately.** Generate the complete selected hierarchy and its coarse surface buffers without constructing fine descendants. Record nodes, patches, vertices and bytes. If implementing the full hierarchy exceeds the day, this part remains explicitly unproved.
|
||||
|
||||
5. **Measure fresh-process runs on the 5800X3D.** Include allocation and first-touch costs. Test one, six and eight workers; report median, p95 and worst across seeds. Validate outside the timed region against the pointwise reference, including negative coordinates, page borders, tangencies, thin features and coincident edits.
|
||||
|
||||
I would use these initial decision gates:
|
||||
|
||||
| Result | Decision |
|
||||
|---|---|
|
||||
| All CPU geometry fits approximately **430 ms**, with output around **60 MB** | Proceed to the Unreal publication and collision test |
|
||||
| Interval work fits its **150 ms** allocation, but meshing exceeds the budget | Investigate direct patches, reconstruction and output size |
|
||||
| Only the simple cases pass; representative junctions produce huge interval lists | Reject or restrict that composition family |
|
||||
| Far geometry requires fine descendants or dense strata sampling | The proposed hierarchy has not removed the principal cost |
|
||||
| Any missing thin feature or inconsistent shared boundary | Correctness fails regardless of speed |
|
||||
|
||||
The standalone test can establish whether the computational core deserves investment. The final proof is an Unreal run timed through **the first complete visible frame and successful player-support collision**, followed by a separate check that steady rendering meets the VR frame budget.
|
||||
|
||||
I would spend the first day on **analytic intervals plus direct far surfaces**. They attack both sources of unnecessary work: repeatedly discovering nearby boundaries, and reconstructing distant surfaces from a volume that the player will never interact with.
|
||||
@@ -0,0 +1,220 @@
|
||||
# VoxelForge speed research — 29 September 2026
|
||||
|
||||
## Consolidated decision
|
||||
|
||||
**Build the spatial/evaluation redesign first and keep the current precise float procedural math.** Compile the seed, layout, composer and operator stack once per region into a stable local program; bin rooms, tunnel segments and passages into 8³-cell bricks; compute cheap *final-field* solid/air certificates; share lattice samples across tiles and LODs; contour uncertain bricks. Use a versioned, explicitly rounded **16-bit lattice density** as the common mesh/edit/query cache. Keep the procedural float evaluator as the cache producer and on-demand *lattice-sample* fallback; player collision queries interpolate the same quantized lattice, including newly generated cells, so visual and collision signs cannot diverge through an off-grid float query. The quantization scale, zero tie and interpolation rule belong to the new world ABI. This changes the world version without requiring a full fixed-point rewrite of Perlin and every content operator. [Build flags](Source/VoxelForge/VoxelForge.Build.cs) already pin `FPSemanticsMode.Precise`; [noise](Source/VoxelForge/Public/VoxelNoise.h) has table-free Perlin and a scalar/SSE4.1 path written op for op; [geometry math](Source/VoxelForge/Public/VoxelTypes.h) uses `VoxelMath::DetSinCos`. This supports the current same-build CPU contract, although full-world hashes on the actual supported CPU/compiler combinations remain a release gate. MSVC documents both the ordering guarantees and limits of `/fp:precise`; it is not a GPU-vendor parity mechanism. [MSVC floating-point semantics](https://learn.microsoft.com/en-us/cpp/build/reference/fp-specify-floating-point-behavior?view=msvc-170), [MSVC cross-CPU cautions](https://learn.microsoft.com/en-us/cpp/porting/floating-point-migration-issues?view=msvc-170).
|
||||
|
||||
**For the player, provisionally choose field-aware movement against the versioned lattice, with cooked Chaos bodies as a compatible background path.** This is the route that can use the new edit epoch immediately when a dig removes support; a delayed cook cannot be authoritative in that frame. Before committing to Mover, instrument the existing cook pipeline and try reserving generation capacity. If a small set of player-support tiles cooks quickly *and* a CMC edit-time field override passes the same-frame floor/velocity test, retain CMC plus a prioritized-cook/field-overlay hybrid. A fast cook by itself does not solve an underfoot edit. RMC's [manager](../RealtimeMeshComponent/Source/RealtimeMeshComponent/Private/RealtimeMeshManaged.cpp) defers collision to end of frame, its [future helper](../RealtimeMeshComponent/Source/RealtimeMeshComponent/Public/Core/RealtimeMeshFuture.h) schedules work on `GThreadPool`, and its [data path](../RealtimeMeshComponent/Source/RealtimeMeshComponent/Private/Data/RealtimeMeshData.cpp) returns to the game thread to apply BodySetup/physics state; the observed 0.4–1.4 s is **submit-to-ready, not measured Chaos compute**. UE task priority does not preempt already running generation, and RMC's pool is a distinct queue whose threads still compete for CPU. [Epic UE Tasks](https://dev.epicgames.com/documentation/unreal-engine/tasks-systems-in-unreal-engine), [Epic BodySetup readiness](https://dev.epicgames.com/documentation/unreal-engine/API/Runtime/Engine/UBodySetup/CreatePhysicsMeshesAsync).
|
||||
|
||||
**For rendering, measure the current RMC drain before grouping.** `MaxMeshAppliesPerFrame=4` and `MaxMeshApplyMilliseconds=2` are settings, not a hardware limit. The source comment's “~0.1 ms per 32³ apply” is an estimate, not a captured sustained upload rate; no surviving `[VoxelForgeApplyFrame]`/render-thread trace in the repo establishes one. With ~341 visible nonuniform tiles and a 0.35 s generation budget, the remaining 0.65 s requires **≥525 visible applies/s**, or **9/frame at 60 Hz** and **6/frame at 90 Hz** if results are ready together. At 4/frame the caps are 240/s and 360/s, respectively. If all 512 tiles are nonuniform, the 60 Hz requirement rises to **14/frame**. Raise the cap in an isolated saturation test and measure game-thread drain, RMC render enqueue, RHI completion and first visible frame. Group 2³ tiles into at most 64 spatial pages, retaining independently updateable sections, **only if** per-component or render-thread overhead misses that rate. The archived 128³ export emitted 7.435 MB of logical mesh output for 64 tiles, an average 116 kB/tile; extrapolating that mix to 341 visible tiles is ~39.6 MB of payload, not a measured GPU transfer or component cost. [Local attribution](Docs/archive/PERF-ATTRIBUTION.md).
|
||||
|
||||
**A large spell still needs a single visual transaction.** At radius 10 m the round-two probe found 714 touched 8³ bricks and up to 64 current tiles; its cached-field composition was 0.861–0.914 ms, excluding upload. Keep a GPU-resident copy of the visible quantized lattice and apply an integer edit command to the whole affected region in one render dependency, with a complete-region mesh or bounded raymarch fallback. Match CPU and GPU quantized sign, interpolation and zero ties; publish all touched tiles/LODs at one edit sequence. The GPU need only perform integer **edit composition and mesh decisions**. Replacing the entire procedural float generator with integer is conditional on a later full-GPU base-generation win. This retains cross-machine world authority on the CPU and avoids treating the round-two integer AVX2 probe as a production speedup.
|
||||
|
||||
### Separate the two proposed changes
|
||||
|
||||
| Change | Evidence and expected contribution | Decision |
|
||||
|---|---|---|
|
||||
| **(a) Region-local evaluation and sparse sampling** | At the supplied ~2 µs/sample, shared lattice alone cuts 21.952 M requests to 17.374 M and an ideal six-worker clock from **7.32 s to 5.79 s**: only **1.26×**. The proposed **0.35 s** worker budget requires a further **16.5× effective reduction** in cost or evaluated points after sharing: `(17.374 M × 2 µs)/(6 × 0.35 s)`. This is the required result, **not a measured gain**. The scalar synthetic probe's ~170 ns of aggregate worker CPU/sample (six workers × ~0.47 s / 16.8 M points) shows hardware headroom but uses a different, much cheaper graph. Brick certificates, candidate binning, removal of per-point routing and local compiled ops are the mechanisms to test; no number from another workload is assigned to them. | **Necessary first experiment.** Keep float while measuring it, so gains can be attributed. |
|
||||
| **(b) Full integer/fixed-point procedural generator** | The round-two explicit AVX2 kernel beat its *own integer scalar reference* **4.25–5.86×**; that comparison says nothing reliable about integer versus the plugin's optimized float/SSE noise or its feature-search cost. Merely changing numeric type leaves the ~30.7 counted candidate visits/sample and cache routing intact. There is **no measured production speed credit**, so assign **zero guaranteed speedup** toward the 16.5× goal. New world version permission removes compatibility cost, but not implementation or proof risk. | **Defer full conversion.** Use deterministic quantization at the lattice boundary and integer edit/mesh decisions on GPU. Convert base generation only if an exact CPU/GPU float-versus-integer production ablation proves a faster end-to-end path. |
|
||||
|
||||
The ~2 µs sample is an end-to-end `GetDensityAt` cost, not a Perlin operation. The available attribution closes **where** time goes but does **not** supply additive CPU shares for each nested operation. In the archived 128³ TunnelNetwork export, 590.923/606.128 ms (**97.49%**) was shared-grid fill; the following cell scan/mesh/merge was ~15.2 ms. Dividing 590.923 ms by its 2,248,091 unique samples yields only **263 ns/sample in that different workload/window**. It therefore cannot be substituted for the owner's current ~2 µs/sample or used as an additive timing breakdown of it. The export generated these *work counters*: **11.10 M room candidates / 1.73 M evaluated**, **28.95 M tunnel candidates / 3.46 M evaluated**, and **28.95 M tunnel-core candidates / 1.88 M evaluated**. That is about **4.94 + 12.88 + 12.88 = 30.7 candidate visits per sample** across the three reported counters, with about **3.15 actual evaluations/sample**. Candidate passes may revisit the same feature, and the counters are **not additive time buckets**. Binning local features once per brick attacks this work directly. [Local attribution](Docs/archive/PERF-ATTRIBUTION.md).
|
||||
|
||||
| Per-sample work visible in code | What the redesign removes; what it cannot claim |
|
||||
|---|---|
|
||||
| `GetDensityAt` recomputes chunk coordinates and checks manager/layout/lifetime, routing, tunnel cache, tile-window policy and diff version. The measured tunnel cache had **2,248,043 hits and 48 misses**: nearly every point pays lookup/key predicates despite few builds. `GetDensityWithParams` performs further box/fingerprint/terrain-op/tile-policy guards. | A prepared immutable region program moves these decisions to region/brick admission. This can remove repeated checks and indirection; their individual nanoseconds were not measured. These are multiple **key comparisons**, not a dozen expensive cache misses. |
|
||||
| Rooms, tunnel segments, passage checks and warped search boxes are queried inside field evaluation; the candidate counts above show repeated local traversal. | Spatially bin features with conservative warp/blend reach, then evaluate only the local list. A false-negative bin is a geometry bug; exact sign tests across stratum and passage boundaries are mandatory. |
|
||||
| The generic composer/operator path can visit roughly twenty ops, including locally inert ones. **But the archived primary TunnelNetwork run uses the already fused evaluator**, which bypasses `EvalMC`'s per-op dispatch. | Compiling away generic op visits helps custom/composer and other nonfused recipes; it **cannot** be credited for the primary fused TunnelNetwork timing. That route still contains room/tunnel candidate work, routing and the common tail. |
|
||||
| `GetDensityAt` follows the core with disturbance/structural/passage/landing-floor repairs, XY seal and diff application. The attribution lists structural-tail, density-post and passage-post *sampled nested* work, not wall shares. | A region program can pre-resolve which post passes can affect a brick and fuse their ordered math; the route/floor invariant must survive. Do not subtract the nested timings from 590.923 ms or assume the tail disappears entirely. |
|
||||
| The code enters per-sample profiling scopes. The owner's latest stack sample attributes **4.9% to the profiling leaf**; the archived same-argument profiled/unprofiled control differed by only **0.63%**. | Disable profiling for acceptance timing and retain a diagnostic build. The 4.9% sample share is not a demonstrated 4.9% clean-build gain; sampling and instrumentation mode differ. |
|
||||
|
||||
### Ordered build plan for the orchestrator and coding agent
|
||||
|
||||
The steps below describe **future plugin experiments**. This research pass makes no `Source/` change and does not run Unreal. Use the same machine, fixed seeds/layouts, cold world caches, and 20 spawn/teleport trials per UE gate; report the **maximum** as well as median because 20 trials are too few for a meaningful p99. Preserve gameplay while replacing one pipeline stage at a time.
|
||||
|
||||
1. **Day-one falsification: stage the existing collision and upload clocks.** Add timestamps for collision request, RMC end-of-frame dequeue, `GThreadPool` worker start, geometry extraction end, `CookComplexMesh` start/end, game-thread `ApplyCollisionUpdate` start/end, physics-state ready and first successful capsule sweep. Keep existing generation, mesh and gameplay behavior while profiling. Test one ~4,000-triangle tile and the pawn's current 3³=27-tile support neighborhood, idle at the current 14 generation tasks, and with the generation cap reduced to 12. If the cook's queue wait dominates, test a dedicated priority cook dispatched directly after player-support mesh extraction; compare its core cook and install times with RMC's route. In parallel, prequeue a 341-tile render corpus and run caps 4/8/16 with the same 2 ms drain guard; record visible applies/s, game/render/RHI clocks and first-visible frame. **Pass the cook shortcut only if** core cook p95 ≤10 ms, the 27-tile player support is ready ≤150 ms after meshes exist under load, and an edit-time field override can switch floor/support within the first simulated/rendered frame with zero invented velocity in 100 repeated underfoot digs. If any edit test fails, implement field-aware Mover as the player authority; if the shortcut passes all tests, use prioritized cooks for steady terrain plus field queries while an edit's cook is pending. **Pass the ungrouped upload path only if** it sustains ≥525 visible applies/s at 60 Hz with game-thread drain ≤2 ms/frame and no render/RHI backlog. These are decision thresholds, not existing measurements.
|
||||
**Step 1 results (2026-09-30, commit `41b001b`):** core cook **1.50 ms p50 / 2.28 ms p95** (passes); through RMC's collision route the same support bodies were ready **8.5–10.8 s** after their meshes existed (fails): the wait is RMC's path, not Chaos, so player-support collision must bypass it. CMC with a field override: **883 false-support frames in 100 underfoot digs** (fails): step 4 is **field-aware Mover**. Uploads: cap 16 = **639 applies/s**, drain p95 2.22 ms (misses 2 ms by 0.22 ms), no render/RHI backlog, so tune the drain and **do not group yet**. The "8× per-sample gap" was a denominator artefact: 263 ns was export *wall* time with 15 workers on a shared lattice (≈3.4 µs CPU per sample); the real per-sample CPU cost is ~2–3.4 µs in both paths, so the 12–16× reduction target stands.
|
||||
2. **Exact-float region-evaluator experiment.** Use an isolated, editor-only explore-commandlet path inside the plugin because the live composer, strate manager, generator and SDF cache depend on Unreal types. Keep gameplay default-off and compare at the same lattice points and operator order. The reference is the chosen **new-world float definition**, not old-season geometry. **Pass:** zero 16-bit quantized-value disagreements and zero route/capsule regression across at least five seeds; ≤**600 ms** for the full 259³ cold ring on six workers before certificates. Record candidate visits, cache checks and worker CPU. Failure means local compilation did not remove enough work; investigate before rewriting numeric math.
|
||||
|
||||
**Step 2 verdict (2026-09-30, attempts 2a/2b/2c, commits `41a9265`, `fd9f809`, `43c11cc`):** all exact (zero Q16/sign disagreements over five seeds, reach-shrink control caught, same hash on 1 and 6 workers), none fast. Best (2c): setup once per 32³ chunk, **1.595 µs/sample vs 1.678 live, 7.9 s vs 7.1 s wall**. Binning cut candidate *visits* 21→16 per sample, but room/tunnel/tunnel-core *evaluations* did not fall (48 M / 5 M / 38 M for 17.4 M points): the cost is the geometry evaluation itself, not the search; noise is ~2.6%. **600 ms is out of reach while every lattice point must reproduce today's density exactly.** That exactness bar was a comparison tool, not a requirement: the owner accepts a new world version, so the next step may *redefine* the evaluator (for example coarser room/tunnel geometry with interpolation, or skipping bricks proven uniform), judged on shape tolerance, walkability and cross-machine determinism instead of equality with the old path.
|
||||
**Step 2 result (2026-09-30, attempt C; pass bar not met, full compiler untested):** five-seed Q16 comparison and two-run raw-float determinism pass. The deliberate room-bin reach reduction to 0.5 is caught (18,107 Q16 differences, 735 sign differences), with no saturated samples. On the 17,373,979-point cold ring, the live `GetDensityAt` p50 is **6.776 s**, **1.679 µs worker CPU/sample**, and **29.164 worker CPU seconds**; the editor-only 8³ SDF-bin variant is **19.543 s**, **2.879 µs worker CPU/sample**, and **50.023 worker CPU seconds**. Both measured variants observed six workers. Sampled visits fall from **21.19 to 16.38/sample** (about 23%), but wall time is 2.88× slower and the 600 ms target is missed. The variant still calls `GetDensityAt` for every lattice point; its bins cover rooms, room-floor joins and whole tunnel chains, while segment loops, route/cache decisions, passages, landing posts, disturbances and player edits remain in the canonical per-sample path. This is a **candidate-bin ablation, not the planned compiled region program**; it falsifies per-sample 3D bin lookup as a sufficient optimization but does not falsify hoisting region decisions or compiling the full ordered program. Step 2 remains open until that full program is measured; do not start step 3's certificate work against this slower baseline. Full report: `Saved/BuildHost/HostProject/Plugins/VoxelForge/Saved/TestHarness/step2_region_eval_20260930_c_region-eval/region_eval.json`.
|
||||
|
||||
**Launch record:** attempt A's guarded UBT invocation logged `Result: Failed` (JSON writer and `ParallelFor` overload compile errors); it exited before launching a commandlet. Attempt B built successfully but `-regioneval=1` was ignored by Unreal argument parsing, so the ordinary explore commandlet exited successfully without the expected JSON. Attempt C used a bare `-regioneval`, rebuilt the editor module, and completed the intended experiment. No attempt timed out, crossed the 8 GiB private-memory limit, or produced a crash report; C peaked at 7,137,738,752 private bytes. The runner was corrected to detect UBT's logged failure even when its process exit code is zero.
|
||||
3. **Add certificates, shared lattice and contouring.** Prove final-sign bounds after all writers and edits; classify 8³ bricks, evaluate uncertain ones, share global corners, and mesh them with today's marching-cubes table. Keep the old tile route as a versioned reference while the new route is validated. **Pass:** zero false-uniform bricks and zero sign mismatch at all 17,373,979 ring lattice sites for the test corpus, cold worker generation ≤**350 ms**, and max render-visible/full ring ≤**900 ms** in the later UE gate. If certification consumes more time than it saves, fall back to evaluating uncertain regions without that proof; do not repeat the archived 0.588→2.057 s interval-control mistake.
|
||||
3b. **AVX2 check against the new norm (owner's request, 2026-09-29).** Once step 3's brick evaluator is the baseline, try an explicit 8-lane AVX2 version of its hot loop (the plugin's current noise path is 4-lane SSE4.1). **The owner does not require AVX2 to match the scalar path bit for bit** (clarified 2026-09-29): only the shipped product must be deterministic across players' machines. If adopted, the AVX2 path *is* the canonical world definition (FMA allowed, as long as it is always used), and every machine must run that same path: require AVX2 at startup rather than falling back to a scalar path that would produce a different world. **Keep it only if** it is faster on the step-3 corpus and the AVX2 path gives the same lattice hash on two runs and on two different AVX2 CPU models; otherwise record the number and drop it. The round-two integer AVX2 probe (4.25–5.86× over its own scalar) is the reference for what explicit vectorisation can buy.
|
||||
4. **Choose and implement player collision.** Use step 1 to select prioritized Chaos+edit overlay or field-aware Mover; keep ordinary collision with nonterrain actors and keep NPC/projectile field-query backup. Version floor/contact state by edit sequence. **Pass:** at every teleport the player can perform an actual blocked capsule movement on the intended terrain ≤**900 ms** after the request, 100 underfoot digs add **0** spurious velocity impulses, and 100 excavations with no cave below have **0** frames of false support or false air. The full visible ring must also meet the separate ≤1,000 ms hard deadline. A successful cook timing without the edit tests is insufficient.
|
||||
5. **Decide render batching from measured throughput.** Keep per-tile RMC if it sustains ≥525 visible applies/s and the first-visible gate ≤900 ms. Otherwise group 2³ tiles per page while retaining tile-addressable sections and the same materials/LOD transition topology; rerun the same corpus and require ≥**99 page applies/s** for at most 64 pages in 0.65 s, ≤2 ms/frame game-thread drain and no render-thread tail past the 1 s deadline. If grouping causes whole-page reupload on small edits or still misses, use a dedicated batched render proxy. Compare first-visible time against the same ≤900 ms gate.
|
||||
6. **Version the edit and GPU visual protocol.** Quantize float-produced lattice density with specified rounding/saturation; use fixed integer edit composition, stable per-cell triangle counts/prefix sums, global edge ownership and one edit sequence across touched pages/LODs. Keep CPU procedural generation, composer, route graph and ordered edit replication as the authority. Test AMD and NVIDIA output hashes against CPU reference; on a single machine, two full cold runs must yield the **same field/sign/mesh hash**. **Pass:** a radius-10 m crater (up to 714 bricks/64 tiles) appears throughout its region in the **first submitted render frame containing the edit**, no gap/ghost/ripple at borders or LOD seams, CPU/GPU sign disagreement **0**, and added GPU cost ≤**2 ms** within an 11.1 ms 90 Hz VR frame in 20 varied positions. If direct GPU remeshing exceeds that, test the complete-region raymarch overlay with the same visual and frame gates; do not publish a partial mesh.
|
||||
7. **Long-range and multiplayer gate.** Provide LOD1 first at 16 cells per tile, refine later, reuse coincident lattice points and keep full 3D geometry for caves/overhangs/islands. Replay ordered edit transactions, including crater commands, on two supported CPU models and compare world-versioned hashes. **Pass:** first LOD1 preview ≤**3 s** after cold teleport, an 8 m/s five-minute run with **0** missing player support or visible near-ring gaps, equal hashes for two runs and both CPU models, and a successful late join after replay/checkpoint. If float parity fails on a supported CPU/compiler pair, lock an exact scalar FP path or then promote a full integer procedural ABI.
|
||||
|
||||
**Still unproved:** production float region-program throughput, brick-proof efficiency on the full composer, the actual RMC sustained apply rate, the Chaos queue-versus-compute split, whether CMC can support an edit-time field override, field-aware Mover query cost, and same-frame GPU/VR crater rendering. Each has an explicit gate above. The standalone integer result proves only that its synthetic 3D kernel can run quickly and match its own scalar reference on this Ryzen. Neither a source paper's scene timing nor a comment's estimated per-tile apply time closes these gaps.
|
||||
|
||||
## Earlier evidence and alternatives
|
||||
|
||||
**The consolidated choice above supersedes the round-two integer-first and unconditional grouping suggestions.** The useful core remains a spatially pruned 8³-brick evaluator, shared lattice, conservative certificates and transaction-wide crater visual. Keep the 25 cm lattice, volumetric operators, walkable route graph and seed/layout/edit replication. Recent work on pruning SDF construction trees directly supports region-local programs, but its speedups on other scenes are not VoxelForge measurements. [Lipschitz Pruning, Eurographics 2025](https://wbrbr.org/publications/LipschitzPruning/documents/LipschitzPruning_submitted_to_EG25.pdf), [Accelerating Signed Distance Functions, 2025](https://perso.liris.cnrs.fr/eric.galin/Articles/2025-lod.pdf), [OpenVDB paper](https://museth.org/Ken/Publications_files/Museth_TOG13.pdf).
|
||||
|
||||
**The one-second target is plausible but not proved.** The round-two six-worker AVX2 probe processed a *synthetic* 256³ composition in **80.3–107.5 ms** across three three-trial runs with exact scalar parity; 17.374 M unique ring points at that measured throughput extrapolate to **83.2–111.3 ms**, leaving roughly 0.89 s for classification, mesh extraction, upload and visibility. This is an arithmetic extrapolation across different data and cache patterns, not a measured UE ring. The scalar probe took **430.3–496.2 ms**, a **4.25–5.86×** paired speedup from explicit AVX2. The recommendation is conditional on a production-stack parity/timing experiment and a UE upload/VR edit spike. The arithmetic is decisive about what must change:
|
||||
|
||||
| Quantity | Arithmetic or measurement | Consequence |
|
||||
|---|---:|---|
|
||||
| Current tile requests | 512 × 35³ = **21,952,000** samples | At the supplied ~2 µs/sample this is 43.9 CPU-seconds before collision/upload. |
|
||||
| Shared lattice for 64 m cube plus halo | 259³ = **17,373,979** unique points | Sharing alone removes 20.9% of requests, nowhere near enough. |
|
||||
| Required density rate if generation gets 0.5 s on six cores | 17.374 M / 0.5 / 6 = **5.79 M samples/core/s**, or **173 ns/sample** | Current ~2 µs/sample needs about **11.6×** improvement even after sharing if every point is evaluated. This ignores meshing and upload. |
|
||||
| Current render apply cap | ~341 nonuniform tiles if one third are uniform; 4 applies/frame = 85.3 frames, **1.42 s at 60 Hz** if all were queued together | This is the **default configured cap**, not measured sustained throughput. Raise it and measure game/render/RHI time first; group only if the actual rate misses the deadline. |
|
||||
| Current collision | Owner's 0.4–1.4 s submit-to-ready **per tile** | This interval includes end-of-frame deferral, pool queueing, extraction, actual Chaos cook and game-thread installation; **the cook's own CPU duration is unknown**. Reserve capacity and instrument the stages before choosing field-only versus prioritized-cook hybrid. [Epic BodySetup API](https://dev.epicgames.com/documentation/unreal-engine/API/Runtime/Engine/UBodySetup/CreatePhysicsMeshesAsync), [RMC source](https://github.com/TriAxis-Games/RealtimeMeshComponent). |
|
||||
| Running at 8 m/s ([local design brief](Docs/archive/DESIGN-SOL-2026-09-15.md)) | One 8 m tile crossing/s brings one 8×8 face = **64 new tiles/s**; 64 × 35³ = **2.744 M requests/s** | At current ~2 µs/request, that is ~5.49 CPU-seconds/s, or ~0.915 s on six ideal workers before upload/collision. A shared 32-cell slab is 259² × 32 = 2.147 M new points, still ~0.716 s on six ideal workers. Field collision on demand is also the safety net for unexpected movement or stalls. |
|
||||
|
||||
**Measured standalone feasibility probe on this machine:** A synthetic 256³ 3D field with 48 integer sphere features and three octaves of integer lattice noise took **471–481 ms** for 16,777,216 samples using six threads. Conservatively classifying 32,768 8³ bricks took **15.46 ms**; evaluating only the 7,563 uncertain bricks then took **106–130 ms**, with **zero sign mismatches** against the full evaluation over all 16,777,216 points. An eight-thread repeat took 349–367 ms full or 86–110 ms classified plus 15.59 ms classification. This is a *kernel probe*, not VoxelForge or a measured 512-tile load. Its primitive list and noise are far simpler than the current composer stacks. It shows that the required rate is physically credible on this CPU when work is converted to bounded integer kernels; it does **not** establish that the full game, render upload, or Chaos field integration finishes in one second. Benchmark details and limitations are below.
|
||||
|
||||
No evidence here proves that UE can present a cold 64 m cube in under 1 s or a radius-10 m crater in the same VR frame. The ordered gates above decide those questions. If they fail, change the render/collision implementation or explicitly revise the target; do not silently infer success from a synthetic density benchmark.
|
||||
|
||||
## Local evidence and what it means
|
||||
|
||||
The code currently requests 35³ values for each 32-cell marching-cubes tile, and `GetDensityAt` builds/evaluates the stack in `VoxelGenerator.cpp`; `VoxelMarchingCubesMesher.cpp` spends most worker time filling the grid. The existing attribution measured **590.923 ms of 606.128 ms** (97.49%) for a shared-grid fill in a 128³ TunnelNetwork export, with only ~15.2 ms for later meshing/merge. It also measured an interval-classifier route that grew from **0.588 s to 2.057 s** instead of helping. Thus “add generic interval recursion” is already a failed local control; the new plan must compile *cheap, conservative bounds and feature lists per brick*, and fall back to evaluation if a bound is uncertain. [Local attribution](Docs/archive/PERF-ATTRIBUTION.md), [failed early-out control](Docs/archive/CODEX-TASK-2-PERF-REPORT.md).
|
||||
|
||||
The owner's **0.4–1.4 s** collision figure is submit-to-ready in the current observation window. The archived 12 September headless run reported a **0.052945 s p95 cook stage** in its different workload and instrumentation window. Neither number is a per-triangle CPU cost, and they should not be pooled or multiplied into a 512-tile total. The planned Unreal spike must capture the new ring's cold maximum, not reuse either statistic. [Archived local timing](Docs/archive/PERF-ATTRIBUTION.md).
|
||||
|
||||
The runtime already prioritizes edited and collision-critical work, but it applies at most four visible meshes per frame by default (`VoxelSettings.h:65`) and `ApplyModification` synchronously remeshes only the center tile while neighbors are asynchronous (`VoxelWorld.cpp:10369`). Underfoot carving can disable the old support component until recook (`VoxelWorld.cpp:10290`), directly explaining a transient disappearance. RMC can preserve old cooked collision while a replacement cooks (`VoxelWorld.cpp:9858`), which explains ghost support if the removed surface is still in that body. The default pawn collision box is radius one tile, **3³ = 27 tiles**; the requested 512-tile collidable ring is a much stronger target than this implementation currently attempts (`VoxelSettings.h:88`, `VoxelWorld.cpp:8168`). These are code observations, not new timing measurements.
|
||||
|
||||
### Standalone measurement, reproducible
|
||||
|
||||
Code: [`Saved/Research/brick_kernel.cpp`](Saved/Research/brick_kernel.cpp). Built only this standalone program with installed MSVC; Unreal was not built or launched. From `Saved\Research` in PowerShell:
|
||||
|
||||
```powershell
|
||||
cmd /c '"E:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat" >nul && cl /nologo /O2 /std:c++20 /EHsc /arch:AVX2 brick_kernel.cpp /Fe:brick_kernel.exe'
|
||||
./brick_kernel.exe
|
||||
cmd /c '"E:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat" >nul && cl /nologo /O2 /std:c++20 /EHsc /arch:AVX2 /DVF_WORKERS=6 brick_kernel.cpp /Fe:brick_kernel_6.exe'
|
||||
./brick_kernel_6.exe
|
||||
```
|
||||
|
||||
The `vcvars64.bat` initialization printed a harmless missing-`vswhere.exe` notice; `cl` compiled and both programs exited successfully. Values are wall milliseconds from `std::chrono::steady_clock`, three repeats, Windows on the stated machine:
|
||||
|
||||
| Stage | Eight workers (ms) | Six workers (ms) | Work |
|
||||
|---|---|---|---|
|
||||
| Full evaluation | 362.538, 367.161, 349.001 | 479.017, 470.581, 481.011 | 16,777,216 grid points, each checks 48 spheres and computes 3 octave integer value noise. |
|
||||
| Brick classification | 15.590 | 15.464 | 32,768 bricks: 24,778 solid, 427 air, 7,563 uncertain. Conservative sphere/AABB extrema plus a proven ±224 noise envelope. |
|
||||
| Classified evaluation | 109.641, 86.959, 86.439 | 106.184, 129.769, 108.978 | Full evaluation only for uncertain bricks; classified bricks retain their correct sign. |
|
||||
| Verification | 0 sign mismatches / 16,777,216 | 0 sign mismatches / 16,777,216 | The sign of every classified point was checked against the full run. Density *magnitudes* in certified uniform bricks were intentionally not preserved. |
|
||||
|
||||
The probe lacks passages, smooth Boolean operators, strata boundaries, edits, meshing, CPU cache competition with UE, upload and collision. Its 76.9% uniform-brick rate is **synthetic** and must never be substituted for the game's observed one-third uniform *tile* rate. The timing is evidence for the kernel's potential only. Full production sign/mesh/collision parity across seeds, strates, edits and CPU models is a release gate. The probe uses scalar integer code compiled with `/arch:AVX2`; that switch alone does not prove SIMD vectorization. AVX2 is a later optimization target, not a factor claimed in these numbers.
|
||||
|
||||
## Three architectures considered
|
||||
|
||||
| Architecture | Representation and pipeline | Main advantage | Main cost/risk | Verdict |
|
||||
|---|---|---|---|---|
|
||||
| **A. Sparse compiled float bricks + CPU contouring + field-aware player edits** | Seed/layout compile to a stable feature plan. Bin primitives to touched bricks. Each 8³ brick stores a final-sign certificate, quantized density cache and edit version. Mesh uncertain bricks with marching cubes; measure per-tile RMC upload before grouping. Use field queries for immediate player edits; test prioritized cook for steady collision. GPU edit transactions cover crater-scale changes. | Eliminates repeated feature traversal, supports edits and 3D topology, and avoids waiting on all ring cooks for player readiness. | Conservative certificates, movement integration and same-frame crater rendering still need UE/VR proof. | **Recommended staged experiment.** |
|
||||
| **B. Analytic CSG/BVH + raymarch render and collision** | Keep a spatially indexed graph of implicit primitives and bounded noise, no resident sampled terrain except caches. GPU raymarches image-space rays through an acceleration structure; CPU queries analytic field for physics. Edits add CSG nodes. | Very long view and same-frame edits can avoid remeshing entirely. | Full-screen/stereo ray cost, non-SDF operators break safe sphere tracing, shadows/materials/UE integration; many edits enlarge BVH and query cost. It changes visual appearance. | Radical alternative if meshing/upload still dominate after A. |
|
||||
| **C. Integer GPU brick generation + compute meshing** | CPU generates canonical integer plan/brick list; compute shader evaluates fixed-point density and emits deterministic mesh indices by prefix-sum, while CPU uses the same integer evaluator for authoritative player collision. | Could offload 17–22 M sample and contour work without collision readback; GPU Gems 3 demonstrates fully 3D GPU generation rather than a heightfield. | Cross-vendor integer ABI, duplicate CPU/GPU evaluators, VR frame contention, runtime mesh integration. | Research branch only until exact AMD/NVIDIA and CPU/GPU hash parity is demonstrated. |
|
||||
|
||||
Architecture A should treat the **plan as the source of truth** and bricks as disposable caches. Compile op nodes once per region into a fixed order and collect local feature references by feature bounding box, with an explicit bound for noise, warps and edits. A brick certificate must enclose the **final** composed field: an operator that can add solid after a carve invalidates a carve-only proof. If any op cannot supply a safe bound, mark the brick uncertain. The current interval route's measured slowdown is a warning that a proof must cost less than the points it avoids. A local feature raster pass reverses the present “each point searches nearby features” loop and keeps shape data hot in cache. The sparse hierarchy stores occupied, empty and uncertain bricks; a compact, flat Morton or Z-major page table is likely better for the active 64 m cube than a pointer-heavy tree. OpenVDB provides a precedent for sparse, editable narrow-band fields, while sparse voxel octrees/DAGs are more compelling for static repeated geometry than for heavily edited procedural bricks. [OpenVDB](https://museth.org/Ken/Publications_files/Museth_TOG13.pdf), [Laine and Karras SVO report](https://research.nvidia.com/sites/default/files/pubs/2010-02_Efficient-Sparse-Voxel/laine2010tr1_paper.pdf), [Kämpe et al. sparse voxel DAG](https://www.cse.chalmers.se/~uffe/HighResolutionSparseVoxelDAGs.pdf).
|
||||
|
||||
Retain marching cubes initially: in the local measured export, evaluation dominates, so changing the mesher first has little expected payoff. A later surface-nets or dual-contouring trial may reduce vertex/triangle output or preserve sharp authored features, but QEF placement, topology and transitions add complexity; benchmark the *whole upload path* before changing. Dual contouring's paper addresses Hermite samples and adaptive octrees; Transvoxel has explicit transition cells for adjacent marching-cubes LODs. The current skirts can visually hide cracks but are not a geometric continuity proof. [Dual contouring paper](https://people.eecs.berkeley.edu/~jrs/meshpapers/JuLosassoSchaeferWarren.pdf), [Transvoxel author](https://transvoxel.org/), [surface nets paper](https://www.sciweavers.org/publications/constrained-elastic-surface-nets-generating-smooth-surfaces-binary-segmented-data).
|
||||
|
||||
For A's collision, store an immutable field snapshot and expose a broad-phase AABB per resident spatial page. Its exact local ray/shape queries must use the canonical quantized lattice or a triangle patch made from it. The provisional player route is a purpose-built field-aware capsule sweep whose support state belongs to the current edit epoch; the staged cook and CMC override experiment in the consolidated plan may justify retaining prioritized cooked collision for steady terrain. For arbitrary Chaos rigid bodies, test a custom `FImplicitObject`/geometry-query adapter only if field-query fallback is insufficient; UE 5.7's installed `Chaos/ImplicitObject.h` exposes virtual `PhiWithNormal` and `Raycast`, but that does **not** prove the complete broad-phase, GJK/contact, CharacterMovement, navmesh or replication integration. A conservative collision box cannot stand in for a cave: it would block playable air. A signed density is not necessarily a distance bound, so ordinary sphere tracing may step through a thin wall; use interval subdivision/voxel traversal or a certified Lipschitz bound. [Chaos API](https://dev.epicgames.com/documentation/unreal-engine/API/Runtime/Chaos), [Hart sphere tracing](https://experts.illinois.edu/en/publications/sphere-tracing-a-geometric-method-for-the-antialiased-ray-tracing/).
|
||||
|
||||
## Determinism contract
|
||||
|
||||
**A:** Preserve the current precise-float CPU operator math and deterministic feature order as the first implementation. Pin build flags and CPU feature path, retain the scalar/SSE parity checks, and run full ring/edited-world hashes on every supported CPU/compiler pair. Define a versioned *lattice* ABI: integer world coordinates, float-to-int16 rounding/clamping, sign at zero, fixed interpolation/ties, ordered integer edit composition and material choice. Brick jobs may finish in any order because each writes a disjoint key and shared corners have one owner. The GPU consumes the quantized cache; it need not reproduce float Perlin for edit visuals. A full fixed-point procedural generator and AVX2 path remain conditional experiments. FastNoise2's own FAQ warns that automatically selected floating SIMD levels can differ across machines, so do not import that mode as world authority without locked semantics and hash tests. [FastNoise2 FAQ](https://github.com/Auburn/FastNoise2/wiki/FAQ), [MSVC floating-point rules](https://learn.microsoft.com/en-us/cpp/build/reference/fp-specify-floating-point-behavior?view=msvc-170).
|
||||
|
||||
**B:** Analytic shape transforms and BVH construction need a stable sort and the same versioned CPU float/lattice contract. A GPU raymarch can remain a visual cache if it consumes canonical quantized samples or if its independently evaluated field passes CPU/GPU parity; arbitrary GPU float `sin`, `sqrt` and FMA cannot be assumed to match. `/fp:precise` helps source ordering but is not a cross-build magic guarantee, and CRT transcendental implementations can vary. The existing `DetSinCos` avoids one such dependency. [MSVC `/fp`](https://learn.microsoft.com/en-us/cpp/build/reference/fp-specify-floating-point-behavior?view=msvc-170), [CRT FMA3 selection](https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/get-fma3-enable-set-fma3-enable?view=msvc-170).
|
||||
|
||||
**C:** Floating-point GPU density is ruled out for world decisions: Vulkan itself is not pixel exact, and available shader float controls have capability/operation limits. An integer GPU path is *potentially* deterministic only after restricting operations to defined 32-bit integer add/XOR/shift/multiply, explicitly emulating wider arithmetic if needed, forbidding unordered atomics for output order, using deterministic prefix sums, and hashing complete density/sign/mesh buffers on AMD, NVIDIA and CPU after every shader/compiler/driver class change. Until those parity tests pass, never make the GPU result authoritative for collision or replicated world state. [Vulkan invariance](https://docs.vulkan.org/spec/latest/appendices/invariance.html), [float controls proposal](https://github.com/KhronosGroup/Vulkan-Docs/blob/main/proposals/VK_KHR_shader_float_controls2.adoc). GPU Gems 3 demonstrates speed opportunities for 3D terrain, but its floating-point GPU pipeline is no determinism proof. [GPU Gems 3](https://developer.nvidia.com/gpugems/gpugems3/part-i-geometry/chapter-1-generating-complex-procedural-terrains-using-gpu).
|
||||
|
||||
## Preserving the game
|
||||
|
||||
**3D and composition.** The plan retains volumetric SDF/CSG regions, mountains, caves, overhangs and islands. A surface heightfield clipmap alone is disallowed: the original geometry-clipmap paper explicitly targets heightfields. Each current archetype, composer tag, structural post, passage and diff becomes a compiled op with a finite or explicitly unbounded influence declaration. [Geometry clipmaps paper](https://hhoppe.com/geomclipmap.pdf). The shipped examples support broad direction, not a claimed matching performance number: No Man's Sky describes its voxel-to-polygon pipeline and reports a later dual-marching-cubes rewrite improved loading; Astroneer describes re-polygonizing modified chunks; neither publishes a directly comparable one-second/64 m benchmark. Teardown's movable voxel volumes and packed occupancy hierarchy solve a different destructible-object problem, and Enshrouded's developer account confirms editable voxel terrain without exposing a transferable throughput figure. Dual Universe demonstrates a multiplayer editable voxel world, but its published material likewise gives no comparable generation latency. [NMS GDC talk](https://www.gdcvault.com/play/1024265/), [NMS update](https://www.nomanssky.com/worlds-part-I-update/), [Astroneer developer account](https://www.gamedeveloper.com/design/what-i-astroneer-i-s-devs-learned-while-leaving-early-access), [Teardown developer's voxel rendering account](https://blog.voxagon.se/2018/10/17/from-screen-space-to-voxel-space.html), [Enshrouded developer AMA](https://enshrouded.com/pl-PL/news/enshrouded-world-design-team-reddit-ama-recap), [Dual Universe developer diary](https://www.youtube.com/watch?v=MWcuwt9bjHo).
|
||||
|
||||
**Walk guarantee.** Keep the composer-generated entrance/exit route as an explicit graph of swept, capsule-clear walking corridors and landing floors. Apply that route as a late, protected operator whose minimum clearance and maximum slope have margins below the player capsule's mechanical limit; validate the final sampled collision representation with capsule sweeps. Decorative ops cannot narrow the protected clearance. For edits, protect a minimal support/clearance tube along one certified route; clip a crater's destructive part where it would remove the last walkable support or add an automatically generated bypass as part of the same transaction. A crater may therefore leave a thin protected bridge. This is a concrete rule that preserves the guarantee; the design of its visible cue is a gameplay/content task. The current route tests do not establish post-edit traversal, so verify it after each affected edit graph change.
|
||||
|
||||
**Clean digging.** Treat one edit as a transaction: allocate a monotonically numbered edit ID, compute every affected brick and one-cell halo from a single immutable pre-edit snapshot, and publish one canonical field/edit epoch before player movement in that frame. Border vertices use global lattice coordinates and a single owner, with identical edit IDs on both sides. The player queries the new quantized field immediately; a cooked body may serve steady terrain only while its edit epoch matches, and an obsolete cook cannot restore stale support. Character velocity is carried across an edit from actual displacement/input only; recompute grounded state without an invented downward impulse. A 1 m-radius interactive brush touches at most eight 2 m bricks at a grid corner before halo expansion. The render path switches the complete affected region in one frame using the GPU transaction described below, never a sequence of independent RMC tile updates. **This does not yet prove same-frame edits**: the UE/VR test must measure the whole transaction including GPU completion, stencil/depth, shadowing and stereo. Network edits carry server order and ID; prediction displays the same transaction locally and reconciles on authoritative order.
|
||||
|
||||
**Streaming and long view.** Keep a 3D clipmap but make LOD1 available first at 16 cells per tile: 19³/35³ = **0.160** of today's per-tile sample requests, an arithmetic count, not a measured 6.25× wall speedup. Applying that ratio to the supplied 16.5 M LOD1 requests yields **2.64 M requests**; at the old ~2 µs/request this is **5.28 CPU-seconds**, or **0.88 s on six ideally scaled cores**, before upload. This arithmetic makes “a few seconds” credible for the preview even without the new brick speed, but it is no end-to-end guarantee. Refine to 32 cells later. Reuse lattice coordinates when coarse samples coincide with resident fine samples; outside the fine cube, evaluate coarse bricks independently. A far shell should be surface-aware and perhaps use a much lower-resolution 3D contour plus impostor for sealed distant strata; preserve visible caves/islands rather than flattening to terrain height. Use Transvoxel or a tested equivalent when meshes at unequal LOD meet. Prioritize spawn/teleport center, movement direction, falling support and edits; apply back-pressure to far work. UE Tasks priorities affect scheduling but do not preempt a running expensive tile; divide work into cancellable bricks. [UE Tasks documentation](https://dev.epicgames.com/documentation/unreal-engine/tasks-systems-in-unreal-engine), [Transvoxel](https://transvoxel.org/).
|
||||
|
||||
**Multiplayer.** Replicate seed, numeric ABI/content hash, compact plan/layout ID and ordered edit log. Do not replicate bricks, meshes or cooked physics. Hash canonical brick signs, surface vertices and collision answers on clients; reject incompatible builds rather than silently diverging. An edit arrives as one transaction with all affected brick IDs, never independent tile messages. For late joins, bound and checkpoint the edit log: Teardown's developer describes why replaying a deterministic command stream saves bandwidth but can become too slow after many edits. This is a qualitative design warning, not a VoxelForge timing estimate. [Teardown multiplayer technical account](https://blog.voxagon.se/2026/03/13/teardown-multiplayer.html).
|
||||
|
||||
## Unreal rendering and Nanite
|
||||
|
||||
Keep RMC for the first experiment and measure its per-tile sustained apply rate with a raised frame cap. Group spatial pages and separate render pages from collision only if the measured game/render/RHI path misses the consolidated throughput gate. Replacing RMC with ProceduralMeshComponent only changes the wrapper around the same asynchronous cook; Epic documents `bUseAsyncCooking`, not a latency guarantee. `UDynamicMeshComponent` has useful edit representation but Epic lists no Nanite/LOD support and warns about render/memory efficiency. RMC's current upstream documentation claims runtime Nanite, and the locally installed RMC 5.4 source contains a runtime Nanite module and provider checks. **Revisit Nanite as an isolated *far/mostly static render* benchmark**, including PC VR stereo, page-build time, edit rebuild time and fallback mesh cost. It does not solve density evaluation or Chaos collision, and the first one-second ring should not wait for Nanite conversion. Nanite collision uses a fallback mesh, not Nanite triangles. [PMC API](https://dev.epicgames.com/documentation/unreal-engine/API/Plugins/ProceduralMeshComponent/UProceduralMeshComponent), [Epic dynamic-mesh guide](https://dev.epicgames.com/documentation/unreal-engine/geometry-scripting-users-guide-in-unreal-engine), [RMC docs](https://github.com/TriAxis-Games/RealtimeMeshComponent/blob/master/Docs/index.md), [Epic Nanite fallback details](https://dev.epicgames.com/documentation/unreal-engine/nanite-technical-details).
|
||||
|
||||
## Round two: wider search and revised engineering test
|
||||
|
||||
### What the new local probe measured
|
||||
|
||||
[`Saved/Research/round2_probe.cpp`](Saved/Research/round2_probe.cpp) is an independent C++20 program, not copied production code. It models the *kinds* found in `VoxelNoise.h`, `VoxelDensityOpStack.cpp`, and `VoxelGenerator.cpp`: hashed 3D gradient Perlin with a fixed-point quintic fade, three-octave fBm, ridged noise, one domain warp, upper/lower stratum bounds, a passage-like corridor, a room, and two smooth maxima. The gradient choice and fade shape follow the plugin, but the hash, coordinate scale, parameters, and composed graph differ. The code has a scalar reference and explicit eight-lane AVX2 integer evaluator; each lane writes exactly the same 16-bit value in the measured domain. The present plugin already has **SSE4.1 x4 Perlin batching across octaves**; the new speedup is therefore **against this program's scalar reference**, not against the production plugin's SSE path.
|
||||
|
||||
Build/run from `Saved\Research` on the owner's Ryzen/Windows machine:
|
||||
|
||||
```powershell
|
||||
cmd /c '"E:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat" >nul && cl /nologo /O2 /std:c++20 /EHsc /arch:AVX2 round2_probe.cpp /Fe:round2_probe.exe'
|
||||
.\round2_probe.exe
|
||||
```
|
||||
|
||||
MSVC compiled successfully; the installed setup script emitted a missing-`vswhere.exe` notice. Wall times below include six worker threads and output writes, exclude process startup and verification. Three three-trial runs with the same evaluation code (the second after dead-code cleanup, the third after adding the direct-query measurement) gave:
|
||||
|
||||
| Work | Measured wall time | Meaning |
|
||||
|---|---:|---|
|
||||
| 256³ complete scalar field | 492.0, 496.2, 469.6; 430.3, 456.2, 447.1; 478.6, 471.0, 444.3 ms | 16,777,216 output points. |
|
||||
| Same field, explicit AVX2 | 101.3, 106.0, 92.5; 89.2, 107.5, 94.6; 85.8, 80.3, 82.7 ms | Paired speedups **4.25–5.86×**, zero value mismatches. |
|
||||
| Full value comparison | 0 mismatches in all 16,777,216 points on each trial | One machine and coordinate domain, not cross-vendor proof. |
|
||||
| 200,000 spatially scattered direct field queries | 63.030, 62.785, 62.799 ms | **313.9–315.1 ns/query**, one scalar thread; all checksums equal. A synthetic player-query latency proxy, not a capsule sweep. |
|
||||
| Radius-10 m spherical edit against cached field | 0.865; 0.914; 0.861 ms | Six-worker max-composition over the 81³ bounding cube; excludes rendering and copy. |
|
||||
| Copy entire 256³ 16-bit field | 1.623; 1.422; 1.394 ms | An intentionally wasteful 32 MiB copy; local brick copy-on-write would copy much less. |
|
||||
| Touched 8³ bricks / changed lattice sites | 714 / 94,843 | Exact for the synthetic sphere at a brick/tile boundary; geometry and edit content affect changed count. |
|
||||
|
||||
**Inference, not measured production latency:** 17,373,979 ring points divided by the observed AVX2 throughput give **83.2–111.3 ms** for this operator mix. Even the slowest scalar trial extrapolates to ~514 ms; a 0.5 s worker budget is close without AVX2 and leaves no room for a more expensive real composer. The scattered-query result implies ~**635 queries in 0.2 ms** at this synthetic rate, versus **100** at the supplied current ~2 µs/sample. A real player capsule sweep must be bounded and separately timed; this arithmetic does not establish a 0.2 ms sweep. SIMD is one possible factor in the required ~12× reduction, not the entire answer. Probe limitations: the plugin uses float Perlin today, its ridged noise has feedback/squaring absent from this simplified ridge, several operator classes and dynamic world-composer decisions are absent, the program computes all points without brick classification, the six worker threads contend with no UE tasks, and no mesher/upload/collision/VR frame is timed. A full fixed-point replacement would visibly alter worlds, which the owner now permits through a new world version, but its production speed benefit has not been established. Avoid claiming the synthetic 5× is a drop-in speedup. [FastNoise2's fused SIMD graph and published 261.1 M 3D Perlin points/s on a different Intel CPU](https://github.com/Auburn/FastNoise2), [FastNoise2 exact-reproducibility FAQ](https://github.com/Auburn/FastNoise2/wiki/FAQ).
|
||||
|
||||
### New evidence that changes the architecture
|
||||
|
||||
| Source-measured or documented result | Transfer to VoxelForge |
|
||||
|---|---|
|
||||
| Eurographics 2025 **Lipschitz Pruning** reports 0.91–1.61 ms for 256³ discretization on a laptop RTX 4060 for scenes with 53–6,023 nodes, plus 0.72–14.29 ms pruning; its most complex model gained 629× in sphere-tracing against naïve evaluation. | Strong evidence for **region-specific compiled operator programs**, including smooth CSG, not a VoxelForge GPU speed prediction. The method assumes primitives/operators with computable 1-Lipschitz bounds; arbitrary warped scalar density needs derived bounds or a fallback. Its fluid scene with 27,977 close particles needs 125.5 ms pruning and 6.99 ms discretization, demonstrating layout sensitivity. [Paper and Table 1](https://wbrbr.org/publications/LipschitzPruning/documents/LipschitzPruning_submitted_to_EG25.pdf). |
|
||||
| **Accelerating Signed Distance Functions** reports up to three orders of magnitude on its construction-tree scenes, using proxy/LOD nodes. It expressly warns that a discontinuous proxy below a smooth union causes artifacts, and that manual LOD authoring and extra nodes cost memory. | Compile local plans and conservative proxies **with operator-aware legality**; no generic “replace any far branch with a constant.” Preserving the exact zero set near transitions matters for walking and digging. [Paper](https://perso.liris.cnrs.fr/eric.galin/Articles/2025-lod.pdf). |
|
||||
| Keeter's SIGGRAPH 2020 interval method simplifies complex closed-form expressions by region; Knoll et al. compared interval and affine arithmetic for implicit ray tracing. | Both are real alternatives for exotic operators, but the **local** blanket interval attempt worsened 0.588 s to 2.057 s. Use inexpensive analytic primitive influence and final-sign certificates first; profile any tighter affine bound by avoided evaluations per microsecond. [Keeter](https://www.mattkeeter.com/research/mpr/), [Knoll et al.](https://www.vacet.org/publications/Knoll_CGF-implicits-2009.pdf), [local control](Docs/archive/PERF-ATTRIBUTION.md). |
|
||||
| Eurographics 2025 **Fast Sphere Tracing of Procedural Volumetric Noise** reports a detailed 3D procedural-noise scene at **16 ms for 1024² pixels** on an Nvidia 4080 Ti, versus 110 ms naïve; the authors report up to 12× on opaque/constant-transparency scenes using derivative-aware stepping and nested fBm bounds. | Strongest recent case for the analytic-render alternative. Linear scaling from 1.05 M to 4.15 M stereo pixels would be ~**63 ms** on that *different GPU/scene*, before VR lighting; this is a cautionary inference, not a measured Quest 3 figure. Reuse its nested fBm bound idea for local-region culling even if raster meshes remain primary. [Paper](https://diglib.eg.org/bitstream/handle/10.1111/cgf70072/cgf70072.pdf). |
|
||||
| JCGT 2019 reports **15–20%** faster 3D cellular noise traversal than standard Worley on its benchmark (for 16 M queries, one table row falls from 13.69 s to 11.15 s). | Relevant only to the plugin's cellular roughness branch, not its dominant gradient-Perlin calls; do not multiply this into a ring-wide speedup. [JCGT paper](https://jcgt.org/published/0008/01/02/). |
|
||||
| Voxel Plugin 2's own change log claims up to **2×** world generation from skipping cells far from surface, plus up to **40%** VRAM reduction in an experimental Nanite path. Its docs separate collision and render invokers. | Confirms that surface sparsity and collision/render separation are practical in UE, but vendor numbers are not a transferable end-to-end 12×. [Voxel Plugin 2 change log](https://docs.voxelplugin.com/resources/change-log/2.0p8-changelog), [collision design](https://docs.voxelplugin.com/2.0p8%20-%20v2/design-docs/collision-mesh-navmesh). |
|
||||
| FastNoise2 reports **261.1 M** AVX2 3D Perlin points/s on an Intel 7820X; its FAQ says floating SIMD levels can differ and advises strict FP plus a locked SIMD level for exact output. | Fuse whole op graph in registers; do not import float FastNoise2 with automatic SIMD dispatch as the canonical multiplayer field. Our fixed integer benchmark shows a path to parity on one CPU; cross-machine parity still needs testing. [Library and benchmark](https://github.com/Auburn/FastNoise2), [FAQ](https://github.com/Auburn/FastNoise2/wiki/FAQ). |
|
||||
| Epic's UE 5.7 Mover comparison says movement can use a root scene component without a collision primitive and custom movement modes; local UE 5.7 `CharacterMovementComponent.cpp` still makes floor and move sweeps through the engine collision scene. | Mover is the provisional field-aware movement route, preserving sweeps against nonterrain actors. A prioritized cook plus an edit-time CMC field override may be less invasive if the staged-cook and underfoot edit gates both pass. Simply leaving the old CMC capsule on an uncooked mesh does not fulfill “collidable.” Mover remains a migration risk under network prediction. [Epic Mover comparison](https://dev.epicgames.com/documentation/unreal-engine/comparing-mover-and-character-movement-component-in-unreal-engine). |
|
||||
| Epic's RDG documentation supports compute passes, dependency barriers and async compute but notes platform fallback to the graphics pipe. | A GPU edit mesher can be integrated with a render-graph transaction, but “async” does **not** establish same-frame latency or free VR time. [Epic RDG guide](https://dev.epicgames.com/documentation/unreal-engine/render-dependency-graph-in-unreal-engine). |
|
||||
|
||||
### Crater spell: one transaction, up to 64 tiles
|
||||
|
||||
A radius-10 m sphere is **40 lattice intervals** at 25 cm. Its 20 m AABB reaches up to **4 tiles per axis** when centered on a tile boundary, hence **64 full-detail 8 m tiles**; at a tile center it reaches at most 27. The sphere's continuous volume is `4π40³/3 = 268,083` voxel cubes versus `81³ = 531,441` candidate lattice sites. The local exact sphere/AABB enumeration found **714 2 m bricks** touching the ball. Independently contouring each 8-cell brick needs `714×9³ = 520,506` corner-sample reads before sharing; the real distinct set is smaller, and normal or LOD halos can add reads. A naïve re-evaluation at today's 2 µs/sample would cost about **1.04 CPU-seconds** for those corners, so **cache the base field and compose the edit over it**. The measured synthetic cached-field edit took **0.865–0.914 ms**, but no extraction or upload was measured. CPU copy-on-write at touched bricks has a raw 16-bit interior payload of `714×8³×2 = 731,136 bytes`; a full near-field 32 MiB copy was measured at 1.422–1.623 ms. The crater itself is ~one command (center/radius/shape/seed/version), not 95,000 replicated voxels. A pathological marching-cubes upper bound is `714×512×5 = 1.83 million` triangles; at three unshared 16-byte vertices each that is **87.7 MB** of transient output. Realistic counts must be measured, and GPU buffer overflow must fall back to a complete-region visual path, never publish half a crater.
|
||||
|
||||
**Proposed fast path.** Commit an edit in a deterministic order with a monotonically increasing edit sequence. On the CPU, publish a new immutable edit-plan pointer for *all* 714 bricks before movement and render gather for that frame. A canonical signed integer field query composes the crater immediately, so player floor/capsule sweeps cannot see stale support; nonplayer traces use the same fallback until their optional Chaos mesh is ready. Never transfer a removed collision body's velocity or use old cooked floor data after the edit sequence changes. If the dig removes the floor, real gravity begins from the player's pre-edit velocity exactly once; if it adds terrain around the capsule, resolve penetration with a bounded positional correction that does not enter velocity. The entrance-to-exit route is a **protected graph corridor**: preflight the crater against a protected walkable tube and either clip its solid-adding part, carve a replacement bypass, or reject the spell by explicit gameplay rule. For pure excavation, connectivity cannot be reduced, though walking surfaces can be removed, so the route proof must include footholds/clearance, not only air connectivity.
|
||||
|
||||
For visuals, use one GPU edit transaction over the whole affected region. Keep the near lattice field resident on the GPU; upload the compact command, compute the changed bricks and their transition halo in a **single render-graph dependency**, generate canonical marching-cubes triangles with a stable per-cell prefix sum, and swap the entire region's draw descriptors together at a frame boundary. Suppress the old region only when its replacement is drawable; no per-tile apply loop. All visible coarser LOD footprints touched by the spell take the same edit sequence, using the corresponding coarse lattice and transition mesh; an unloaded distant region records the command for generation when it enters view. A local volume-raymarch overlay can cover the transaction while mesh extraction is late, using the same canonical cached field and edit command, with a one-cell overlap and exact depth writes. The overlay must be checked against the meshed boundary and stereo views to rule out cracks, double surfaces, and ghost shadows. **This is an engineering design, not a proved same-frame guarantee:** no UE/GPU measurement was authorized in this research pass. The decisive GPU/VR test must finish edit compute + descriptors + draw in the **first submitted render frame whose simulation includes the command**; an input received after that frame's render submission belongs to the next frame. No affected tile may show a different edit sequence in that frame. [Epic RDG synchronization](https://dev.epicgames.com/documentation/unreal-engine/render-dependency-graph-in-unreal-engine), [GPU Gems 3's 3D GPU marching cubes](https://developer.nvidia.com/gpugems/gpugems3/part-i-geometry/chapter-1-generating-complex-procedural-terrains-using-gpu).
|
||||
|
||||
The current crash mode is especially clear for crater scale: synchronous center-tile remesh followed by up to 63 independently queued neighbors produces a visible wave; disabling old collision and waiting 0.4–1.4 s for cooks creates a falling gap; retaining old BodySetup creates ghost support. The transaction above removes those three states. It does **not** assume that optional Chaos rigid bodies can all get exact crater collision in the same frame; the owner expressly accepts field-query fallback for NPCs, arrows and other physics until their mesh is ready.
|
||||
|
||||
### Serious alternatives, with quantified wins and losses
|
||||
|
||||
| Alternative | Where it could win | Why it is not the primary route now |
|
||||
|---|---|---|
|
||||
| **Whole-world analytic raymarch (radical B)** | No persistent triangle mesh; edit command changes the next render. Keeter and 2025 pruning show that complex trees can be raytraced after regional simplification. | At 1920×1080, **2.07 M rays per eye**, or **4.15 M stereo rays** before shadows/VR supersampling; at 32 field steps this is **133 M queries/frame**. At the measured synthetic AVX2 throughput of ~156–209 M *grid* points/s across six cores, that many CPU queries would take ~0.64–0.85 s; GPU may be far faster. The 2025 procedural-noise paper measures **16 ms at 1024²** on a 4080 Ti; simple stereo-pixel scaling to 4.15 M gives ~63 ms before VR lighting, a scene/hardware inference. Density is not necessarily a distance bound for safe sphere tracing, and a full GPU renderer is a larger rewrite. [Keeter](https://www.mattkeeter.com/research/mpr/), [EG 2025 noise tracing](https://diglib.eg.org/bitstream/handle/10.1111/cgf70072/cgf70072.pdf), [SDF-grid ray tracing, JCGT](https://jcgt.org/published/0011/03/06/). |
|
||||
| **All-GPU integer generation and meshing (C)** | The Eurographics RTX 4060 paper's 256³ discretization at 0.91–1.61 ms shows enormous headroom in a suitable GPU expression, though its data/model differ. Could supply same-frame crater triangles. | Requires explicit 32/64-bit integer arithmetic rules, fixed division/rounding, no unordered atomics for vertex order, deterministic prefix sums, CPU/GPU/AMD/NVIDIA parity hashes, and an UE RHI path. Any float-transcendental or vendor-specific FMA noise breaks the canonical requirement. The paper's pruning and rendering are GPU-only and do not prove the CPU player query. [Lipschitz Pruning](https://wbrbr.org/publications/LipschitzPruning/documents/LipschitzPruning_submitted_to_EG25.pdf), [Khronos float controls](https://github.com/KhronosGroup/Vulkan-Docs/blob/main/proposals/VK_KHR_shader_float_controls2.adoc). |
|
||||
| **Dual contouring / surface nets / adaptive power-diagram contouring** | Potentially fewer vertices and sharper features; JCGT's SurfaceNets work reports better triangle quality than classic MC for segmentation models. A 2025 power-diagram method reconstructs complex SDF geometry from fewer samples. | Generation is ~97% density time in the local attribution, so changing contouring *alone* can save at most the remaining ~3% of that worker window. Adaptive sampling could save more but needs a certified error/feature rule for narrow passages. SIGGRAPH Asia 2024's GPU occupancy dual contouring still describes runtimes of **a few seconds** for its distinct reconstruction problem, not subsecond terrain streaming. Seam dependencies and LOD transition rules become more complex; neither solves cook or atomic edit transactions. Benchmark later if upload dominates after density rewrite. [Dual contouring](https://people.eecs.berkeley.edu/~jrs/meshpapers/JuLosassoSchaeferWarren.pdf), [JCGT SurfaceNets](https://jcgt.org/published/0011/01/03/), [Eurographics 2025 power-diagram contouring](https://onlinelibrary.wiley.com/doi/10.1111/cgf.70037), [SIGGRAPH Asia 2024 occupancy dual contouring](https://occupancy-based-dual-contouring.github.io/), [chunk seam example](https://ngildea.blogspot.com/2014/09/dual-contouring-chunked-terrain.html). |
|
||||
| **SVO/DAG or OpenVDB as source of truth** | Empty-space skipping and sparse memory; GPU SVDAG editing shows large 3D painting can be interactive in a different system. | The world's seed/composer already *is* a compact source. At full 25 cm resolution, the 64 m cube alone has 17.37 M unique samples or **34.7 MB at 16 bits** if fully resident; a global 6 km view volume is vastly larger. DAG sharing degrades with unique noise and edits; keep a sparse cache/hierarchy, not a required full-world DAG. The cited GPU editor's code uses CUDA, so it is no cross-vendor solution. [OpenVDB](https://museth.org/Ken/Publications_files/Museth_TOG13.pdf), [SVDAG editing paper](https://diglib.eg.org/items/ffd7acf6-01f7-4e53-87f8-4cded382c944), [author code](https://github.com/mathijs727/GPU-SVDAG-Editing). |
|
||||
| **Prebake each seasonal world in the shipped game** | If seasons have a finite, known seed/layout set, local NVMe transfer could remove cold density evaluation and deterministic divergence. | For this 64 m cube, even raw 16-bit density is **34.7 MB** before meshes; ~6 km horizontal radius with only an **8 m thick** full-detail slab is `π×6000²×8/(0.25³) ≈ 57.9 billion` voxels, **116 GB raw** per season. That slab is an intentionally generous lower bound for a mountainous/cave world. Sparse compressed meshes could be far smaller, but their disk, decompression, upload, and arbitrary composer/layout variants need an actual content budget. Prebake a few spawn hubs or far-field impostors, not every possible volume. |
|
||||
| **Server-streamed chunk data** | Server could send already generated chunks, avoiding client generation. | A raw 34.7 MB 64 m cube in 1 s requires **278 Mb/s payload** before meshes/protocol, and this contradicts the explicit requirement that only seed, layout and edits replicate. Compression helps bandwidth, but latency/server scaling and the hard protocol rule remain. |
|
||||
| **Far-field impostors, 2.5D clipmaps, Nanite** | Far view can be far cheaper if represented by coarse meshes/impostors; RMC locally contains a runtime Nanite proxy path, and Voxel Plugin 2 reports experimental Nanite memory savings. | An exterior heightfield loses caves/overhangs/islands when used as the whole world. Impostors are acceptable **beyond** interaction range if the world returns to 3D on approach, with a continuous crossfade and occlusion policy. Nanite can help mostly static far render, but Epic's dynamic mesh guidance says `UDynamicMeshComponent` is not Nanite-compatible, and Nanite fallback geometry still serves collision; runtime build/edit/VR costs here are unmeasured. [Geometry clipmaps](https://hhoppe.com/geomclipmap.pdf), [Epic dynamic mesh guidance](https://dev.epicgames.com/documentation/unreal-engine/working-with-meshes-in-unreal-engine), [Nanite fallback](https://dev.epicgames.com/documentation/unreal-engine/nanite-technical-details), [RMC code](https://github.com/TriAxis-Games/RealtimeMeshComponent). |
|
||||
|
||||
### What was searched, and what was ruled out
|
||||
|
||||
The source sweep covered SIGGRAPH closed-form interval rendering and GPU Gems 3 volumetric marching cubes; Eurographics 2025 Lipschitz pruning, derivative-aware procedural-noise tracing, proxy/LOD SDFs and adaptive contouring; older affine-interval ray tracing; JCGT SDF-grid ray tracing, cellular noise and SurfaceNets; HPG/i3D-style GPU mesh shader and SDF work; OpenVDB, SVO, DAG and GPU SVDAG editing; FastNoise2, ISPC, Inigo Quilez's analytic noise-derivative slides and table-free hashed noise; UE 5.7 Mover/CMC source, RMC local source, Epic RDG/Nanite/dynamic mesh documentation; Voxel Plugin 2; and developer material for No Man's Sky, Astroneer, Teardown, Enshrouded, Space Engineers, Vintage Story, Dual Universe, Sebastian Lague, Nick Gildea and 0fps. Quilez's derivative formula is useful for normal or local bound construction, but a sampled derivative is **not** a certified maximum over a brick. The strongest transferable ideas were **region-pruned expression trees, fused SIMD evaluation, sparse near-surface work, direct player field collision, and whole-region render transactions**. No retrieved shipped-game account publishes a directly comparable **cold 64 m, 25 cm, 3D editable, UE/VR, all-visible-and-player-collidable <1 s** measurement; their performance figures must not be presented as proof of this target. [Quilez noise-derivative slides](https://iquilezles.org/articles/function2009/function2009.pdf), [NMS GDC](https://www.gdcvault.com/play/1024265/), [Astroneer developer](https://www.gamedeveloper.com/design/what-i-astroneer-i-s-devs-learned-while-leaving-early-access), [Teardown developer](https://blog.voxagon.se/2018/10/17/from-screen-space-to-voxel-space.html), [Space Engineers source](https://github.com/KeenSoftwareHouse/SpaceEngineers), [Vintage Story developer](https://www.vintagestory.at/blog.html/news/de-jank-and-game-juice-update-1170-pre1-r322/), [0fps smooth voxel terrain](https://0fps.net/2012/07/12/smooth-voxel-terrain-part-2/).
|
||||
|
||||
Specifically ruled out as a **complete** answer: greedy block meshing (different surface model), heightfield-only terrain, unbounded generic interval recursion (failed local control), auto-selected float SIMD noise (vendor/CPU parity risk), CUDA-only edit structures (AMD target), Nanite or another UE mesh component alone (density/cook bottlenecks), and one tile at a time crater updates. Isosurface mesher alternatives, affine arithmetic, full GPU generation, and far impostors remain research branches with explicit benchmark gates. [0fps block meshing](https://0fps.net/2012/06/30/meshing-in-a-minecraft-game/), [FastNoise2 determinism FAQ](https://github.com/Auburn/FastNoise2/wiki/FAQ), [SVDAG editor code](https://github.com/mathijs727/GPU-SVDAG-Editing), [Epic PMC async cooking](https://dev.epicgames.com/documentation/unreal-engine/API/Plugins/ProceduralMeshComponent/UProceduralMeshComponent).
|
||||
|
||||
### Revised gates and migration sequence
|
||||
|
||||
The consolidated seven-step plan above supersedes this round-two sequence. Its decisive change is to **measure the existing collision and RMC apply stages first**, then benchmark a prepared *float* region evaluator against the actual composer before choosing a full integer procedural ABI, Mover or render grouping. Keep the composer/content schema, route graph, multiplayer edit log and old tile evaluator as an oracle while migrating. The final numeric ABI still specifies quantization scale, rounding/saturation, zero tie, interpolation, material ID and stable op ordering; scalar/SSE parity and two-CPU hashes are release gates. A cross-vendor GPU hash is required for integer edit and mesh decisions, but the GPU does not need to reproduce float procedural noise. The round-two standalone probe remains a lower-complexity feasibility bound, not the acceptance test for production latency.
|
||||
|
||||
## What not to do
|
||||
|
||||
- Do not infer a 512-tile deadline from one tile's p95, or from the synthetic 0.1 s classified benchmark. Queueing, bandwidth, upload and physics are separate clocks.
|
||||
- Do not put all 512 Chaos triangle cooks on the **player** readiness path. The observed 0.4–1.4 s interval is submit-to-ready and may be mostly queue delay; prioritize and time the small player-support neighborhood before ruling out cooked steady-terrain collision.
|
||||
- Do not assume a sparse octree/DAG or OpenVDB automatically speeds a procedural query. Their compression and hierarchy help only if the final-field signs/bounds can be computed cheaply; edits and arbitrary smooth operators can make most nodes uncertain.
|
||||
- Do not rely on heightfield clipmaps, GPU float world decisions, unordered GPU atomics, or `sin`/CRT results as a network world ABI.
|
||||
- Do not claim AVX2, dual contouring, Nanite or a different mesh component will erase the observed 97% density cost. Benchmark each in its proper stage.
|
||||
- Do not publish one touched tile while its neighbors still show the old edit; do not suppress collision under a pawn before a consistent replacement exists. Those are the visible ripple, vanish and false-support failure modes in the present implementation.
|
||||
|
||||
The owner has clarified both contracts: collision inside one second applies to the player, while bounded digging and radius-scale spells both require atomic same-frame visuals. Immediate **field-aware edit support** and the transaction-wide visual path are required; Mover versus a prioritized-cook/CMC field-overlay hybrid is decided by the staged tests above. Their deadlines remain unproved until an authorized UE/VR integration gate can be run.
|
||||
@@ -49,19 +49,8 @@ Legend: ✅ verified against code · ◻️ checklist box.
|
||||
> ("nothing is set in stone"). ✅ BUILT & WORKING together with pass 2 (ticked 2026-07-27):
|
||||
> • **Terracing gradient Z-only** (6→2 SDF samples — see the ticked item above).
|
||||
> • **Lerp X-macro** + **BakeRoomFeature dedupe** (both ticked above, bit-identical).
|
||||
> • **T2.b LOD octave drop** — opt-in `UVoxelSettings::LODOctaveDrop` (default 0 = byte-identical);
|
||||
> `VoxelGenLOD::OctaveBias` thread_local set per tile in `GenerateMesh`, per-voxel fractal sites
|
||||
> wrapped in `VoxelGenLOD::Eff(N)`, XY-field noise deliberately excluded. ARCHITECTURE §8.10.
|
||||
> • **T2.c tile component pool** — `TileComponentPool` + `Acquire/ReleaseTileComponent`; unload
|
||||
> parks (RemoveSectionGroup strips geometry+collision, hidden, stays registered), apply pops.
|
||||
> Bonus: `ApplyMeshToTile` reuses the existing `URealtimeMesh` (`GetRealtimeMeshAs`) — the old
|
||||
> unconditional `InitializeRealtimeMesh` allocated + orphaned one mesh UObject PER APPLY.
|
||||
> • **T2.d worker clamp** — `GetMaxConcurrentTasks()` caps the asset budget to logical cores − 2
|
||||
> (the BackgroundNormal priority half had already shipped). **Tier 2 is now COMPLETE** —
|
||||
> T2.a was already done (float SSE `VoxelNoise` core, §8.10).
|
||||
> • **F2 determinism validator** — `AVoxelWorld::ValidateDeterminism` CallInEditor button:
|
||||
> boundary points sampled under two cache-window alignments + a repeat pass; any non-zero
|
||||
> delta = §8.4 regression. The tool that VERIFIES all the "bit-identical" claims above.
|
||||
> • **T2.b / T2.c / T2.d and F2** shipped in the same batch — tracked in `fable-idea.md` (Tier 2
|
||||
> status, F2) and ARCHITECTURE §8.10, not repeated here.
|
||||
|
||||
## Performance
|
||||
- [x] ~~**`VoxelGenerator.cpp` (terrain-op gradient)** — compute once per voxel and reuse.~~
|
||||
@@ -116,6 +105,43 @@ Legend: ✅ verified against code · ◻️ checklist box.
|
||||
two-grid deco redesign (`StreamTier` / `DecorationNearRadiusChunks`).
|
||||
**CORRECTION: `GetStrateChunkZBounds` is NOT dead** — `BuildDesiredTiles` uses it for the
|
||||
strate-aware vertical clamp; struck from the dead list.
|
||||
- [x] **2026-09 cleanup (`cleanup/2026-09`)** — the far sheet ring runtime (`GenerateSheetMesh` + its
|
||||
streaming path) and ~40 unreferenced functions/members/types removed (each proven by a search of
|
||||
`Source/`, `Tools/`, `Config/` and build files; see that branch's `refactor(dead-code)` commit).
|
||||
Reflected (`UPROPERTY`/`UFUNCTION`/`UENUM`…) and enum candidates were NOT removed — see
|
||||
*Owner decisions* below.
|
||||
|
||||
## Owner decisions (2026-09 cleanup)
|
||||
Items left for the owner: unused data that assets, Blueprints, tools or tests may reference by name,
|
||||
and changes that would alter generated terrain.
|
||||
- [x] `UVoxelSettings::bFarSheetRing` / `FarSheetSpanLevels`, `voxel.TestFarSheetRing` and the
|
||||
selector's sheet branch — removed (owner go-ahead); the harness no longer passes the switch and the
|
||||
`sheet_tile` dump field stays for the parity compare.
|
||||
- [x] `UVoxelSettings::CeilingViewMultiplier` / `CeilingBandChunks` / `DensityVolumeMaxTasks` — removed
|
||||
(never read).
|
||||
- [x] **Operator stack as the only density path** (cleanup/2026-09, owner-approved). Every strate —
|
||||
authored, cooked-season (recipe or native) and editor composer candidate — now generates through its
|
||||
operator stack; a degenerate strate is air. Removed: `UVoxelStrateDefinition::bUseOperatorStack`, the
|
||||
archetype `switch` in `GetDensityAt`, `GetSlabDensity` / `GetMazeDensity` / `GetSurfaceDensity` /
|
||||
`SurfaceDensityFromColumn` / `GetVerticalShaftDensity` / `GetFloatingIslandDensity`, the four
|
||||
`Cave Bail Not Op Stack *` stats, the startup opt-in warning, commandlet `-opstack`, and the
|
||||
legacy-vs-stack comparisons in the tests. **Terrain changes for any strate asset that did not
|
||||
already have the opt-in ticked.**
|
||||
- [ ] **No-strate-manager fallback world.** A world whose settings have no season, pool or fixed
|
||||
strates runs `GetDensityAt`'s fallback: default `FStrateGenerationParams` through
|
||||
`GetDensityWithParams` with its direct-caller ("legacy") structural posts. It is the only caller of
|
||||
that post path outside tests/commandlet diagnostics. Decide whether an empty configuration should
|
||||
keep generating generic caves or refuse to generate.
|
||||
- [ ] `UVoxelStrateDefinition::WaterColor`, `Creatures`, `AmbientSound`, `Music`, `MusicVolume` and the
|
||||
`FStrateCreature` / `FStrateAmbientActor` spawn fields (`SpawnChancePerChunk`, `MaxPerStrate`,
|
||||
`SpawnWeight`) — authored data with no consuming system yet (F9 audio, creatures).
|
||||
- [ ] `UVoxelBiomeDefinition::WaterMaterial` — never read; the water plane uses the strate's
|
||||
`WaterMaterial` only.
|
||||
- [ ] Plain C++ enums with no user: `EVoxelFace` (`VoxelTypes.h`), `EVoxelSeasonSelectionReason`
|
||||
(`VoxelSeasonManifest.h`), `EVoxelOpCombine` (`VoxelDensityOp.h` — its comment carries the
|
||||
two-channel sign convention; keep that text if the enum goes).
|
||||
- [ ] `IVoxelHeightOp::MaxDisplacement` / `FVoxelHeightStack::MaxTotalDisplacement` — no caller; kept as
|
||||
the documented hook for a future heightfield `ClassifyBox`.
|
||||
|
||||
## Over-complexity (behavior-preserving splits, optional)
|
||||
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
// Standalone throughput probe, not VoxelForge's generator or an Unreal integration.
|
||||
// Build from a VS 2022 x64 Native Tools prompt:
|
||||
// cl /nologo /O2 /std:c++20 /EHsc /arch:AVX2 brick_kernel.cpp /Fe:brick_kernel.exe
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
constexpr int N = 256, B = 8, NB = N / B;
|
||||
#ifndef VF_WORKERS
|
||||
#define VF_WORKERS 8
|
||||
#endif
|
||||
struct Sphere { int x, y, z, r; };
|
||||
static std::array<Sphere, 48> spheres;
|
||||
static std::vector<int16_t> field(size_t(N) * N * N);
|
||||
static std::vector<uint8_t> reference_sign(size_t(N) * N * N);
|
||||
static volatile uint64_t sink = 0;
|
||||
|
||||
static uint32_t hash3(int x, int y, int z, uint32_t seed) {
|
||||
uint32_t h = seed ^ (uint32_t(x) * 0x9e3779b1u) ^
|
||||
(uint32_t(y) * 0x85ebca77u) ^ (uint32_t(z) * 0xc2b2ae3du);
|
||||
h ^= h >> 16; h *= 0x7feb352du; h ^= h >> 15;
|
||||
h *= 0x846ca68bu; return h ^ (h >> 16);
|
||||
}
|
||||
// Integer lattice noise: fixed shifts and products; no FP/CRT/FMA.
|
||||
static int noise(int x, int y, int z) {
|
||||
int sum = 0;
|
||||
for (int octave = 0; octave != 3; ++octave) {
|
||||
int s = 16 >> octave, ix = x / s, iy = y / s, iz = z / s;
|
||||
int fx = (x % s) * 256 / s, fy = (y % s) * 256 / s, fz = (z % s) * 256 / s;
|
||||
uint32_t seed = 1337u + uint32_t(octave) * 17u;
|
||||
int yz[2];
|
||||
for (int dz = 0; dz != 2; ++dz) {
|
||||
int yy[2];
|
||||
for (int dy = 0; dy != 2; ++dy) {
|
||||
int a = int(hash3(ix, iy + dy, iz + dz, seed) & 255u);
|
||||
int b = int(hash3(ix + 1, iy + dy, iz + dz, seed) & 255u);
|
||||
yy[dy] = a + ((b - a) * fx >> 8);
|
||||
}
|
||||
yz[dz] = yy[0] + ((yy[1] - yy[0]) * fy >> 8);
|
||||
}
|
||||
int v = yz[0] + ((yz[1] - yz[0]) * fz >> 8);
|
||||
sum += (v - 128) >> octave;
|
||||
}
|
||||
return sum; // in [-224, 222], conservatively use +/-224.
|
||||
}
|
||||
static int density(int x, int y, int z) {
|
||||
int d = -10000;
|
||||
for (const Sphere& s : spheres) {
|
||||
int dx=x-s.x, dy=y-s.y, dz=z-s.z;
|
||||
int q=s.r*s.r-dx*dx-dy*dy-dz*dz;
|
||||
d=std::max(d,q);
|
||||
}
|
||||
return d + noise(x,y,z);
|
||||
}
|
||||
static size_t at(int x,int y,int z) { return (size_t(z)*N+y)*N+x; }
|
||||
template<class F> static double run(F&& f) {
|
||||
auto a=std::chrono::steady_clock::now(); f();
|
||||
return std::chrono::duration<double,std::milli>(std::chrono::steady_clock::now()-a).count();
|
||||
}
|
||||
static void parallel_z(auto fn) {
|
||||
std::array<std::thread,VF_WORKERS> t;
|
||||
for (int i=0;i<VF_WORKERS;++i) t[i]=std::thread([=]{
|
||||
fn((i*NB/VF_WORKERS)*B, ((i+1)*NB/VF_WORKERS)*B);
|
||||
});
|
||||
for (auto& x:t) x.join();
|
||||
}
|
||||
int main() {
|
||||
std::printf("workers=%d\n", VF_WORKERS);
|
||||
for(size_t i=0;i<spheres.size();++i) {
|
||||
uint32_t h=hash3(int(i),17,71,12345);
|
||||
spheres[i]={int((h&255u)),int((h>>8)&255u),int((h>>16)&255u), 12+int((h>>24)%16u)};
|
||||
}
|
||||
for(int trial=0;trial<3;++trial) {
|
||||
double full=run([]{parallel_z([](int lo,int hi){
|
||||
for(int z=lo;z<hi;++z) for(int y=0;y<N;++y) for(int x=0;x<N;++x)
|
||||
field[at(x,y,z)]=int16_t(density(x,y,z));
|
||||
});});
|
||||
uint64_t checksum=0; for(size_t i=0;i<field.size();i+=4093) checksum+=uint16_t(field[i]); sink=checksum;
|
||||
std::printf("full trial=%d ms=%.3f checksum=%llu\n",trial,full,(unsigned long long)checksum);
|
||||
}
|
||||
for(size_t i=0;i<field.size();++i) reference_sign[i]=field[i]>0;
|
||||
// Conservative sphere/brick bounds with a +/-224 noise interval.
|
||||
std::array<uint8_t, size_t(NB)*NB*NB> kind{};
|
||||
size_t solid=0,air=0,mixed=0;
|
||||
double classify=run([&]{
|
||||
for(int bz=0;bz<NB;++bz) for(int by=0;by<NB;++by) for(int bx=0;bx<NB;++bx) {
|
||||
int x0=bx*B,y0=by*B,z0=bz*B,x1=x0+B-1,y1=y0+B-1,z1=z0+B-1;
|
||||
bool possiblyAir=false, definitelyAir=false;
|
||||
for(const Sphere& s:spheres) {
|
||||
int dx=std::max({x0-s.x,0,s.x-x1});
|
||||
int dy=std::max({y0-s.y,0,s.y-y1});
|
||||
int dz=std::max({z0-s.z,0,s.z-z1});
|
||||
int near2=dx*dx+dy*dy+dz*dz;
|
||||
int fx=std::max(std::abs(x0-s.x),std::abs(x1-s.x));
|
||||
int fy=std::max(std::abs(y0-s.y),std::abs(y1-s.y));
|
||||
int fz=std::max(std::abs(z0-s.z),std::abs(z1-s.z));
|
||||
int far2=fx*fx+fy*fy+fz*fz;
|
||||
possiblyAir |= (s.r*s.r-near2+224>=0);
|
||||
definitelyAir |= (s.r*s.r-far2-224>0);
|
||||
}
|
||||
uint8_t k=definitelyAir?2:(possiblyAir?1:0);
|
||||
kind[(size_t(bz)*NB+by)*NB+bx]=k;
|
||||
if(k==0)++solid; else if(k==2)++air; else ++mixed;
|
||||
}
|
||||
});
|
||||
std::printf("classify ms=%.3f solid=%zu air=%zu mixed=%zu total=%zu\n",classify,solid,air,mixed,kind.size());
|
||||
for(int trial=0;trial<3;++trial) {
|
||||
double sparse=run([&]{parallel_z([&](int lo,int hi){
|
||||
for(int bz=lo/B;bz<hi/B;++bz) for(int by=0;by<NB;++by) for(int bx=0;bx<NB;++bx) {
|
||||
uint8_t k=kind[(size_t(bz)*NB+by)*NB+bx];
|
||||
for(int z=bz*B;z<(bz+1)*B;++z) for(int y=by*B;y<(by+1)*B;++y)
|
||||
for(int x=bx*B;x<(bx+1)*B;++x)
|
||||
field[at(x,y,z)]= k==1?int16_t(density(x,y,z)):int16_t(k==2?1:-1);
|
||||
}
|
||||
});});
|
||||
uint64_t checksum=0; for(size_t i=0;i<field.size();i+=4093) checksum+=uint16_t(field[i]); sink=checksum;
|
||||
std::printf("sparse trial=%d ms=%.3f checksum=%llu\n",trial,sparse,(unsigned long long)checksum);
|
||||
}
|
||||
size_t mismatches=0;
|
||||
for(size_t i=0;i<field.size();++i) mismatches+=(reference_sign[i]!=(field[i]>0));
|
||||
std::printf("sign mismatches=%zu of %zu\n",mismatches,field.size());
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
# Analytic intervals and packed signs: one-day spike
|
||||
|
||||
Date: 2026-10-02
|
||||
Result: the near-region interval/bit compiler is promising at this corpus size. The experiment does **not** prove the complete realistic visible world can be built in under one second: the near output is smaller than the proposed 60 MB workload, and the far-field generator is a coarse sheet proxy rather than the production cave hierarchy.
|
||||
|
||||
## What was built
|
||||
|
||||
`interval_spike.cpp` is a standalone C++20 program. It generates the same ordered quadric CSG world for both implementations:
|
||||
|
||||
- A point reference evaluates every one of the 17,373,979 near-region lattice nodes, using a 3D AABB index to select possible features.
|
||||
- The interval path intersects the same fixed-point quadrics along all three 67,081-line axis families, sweeps sorted entry/exit events with a 64-bit membership word, composes the ordered edits, and fills packed sign words.
|
||||
- `OR(8 corners) & ~AND(8 corners)` finds active cells 64 at a time. The mesher joins edge crossings around cell faces, uses an exact center sign for ambiguous faces, and emits indexed position/normal buffers. Near-region vertices come from quantized analytic quadric crossings, not edge midpoints.
|
||||
- Sub-cell strata that have no lattice point inside them receive an explicit analytic surface patch. The patches are filtered against the final composed occupancy.
|
||||
- The near cube has 256³ core cells at 25 cm and one node of halo on each side. Its cell region is split into 64 pages. Edge vertices are shared in one keyed buffer and assigned to a deterministic owner page.
|
||||
- A separate far-field stage builds five stacked parent ellipsoid sheets as indexed patches, without generating children.
|
||||
|
||||
The corpus has **372 features** per seed: 18 rooms, 147 overlapping ellipsoid tunnel segments on winding and vertical connections, 40 pillars, 36 stalactites/stalagmites, 42 shelves, 24 floating masses, 12 craters, 40 crowded-junction edits, and 13 thin strata. It uses the current HyperComplex scale as a guide: 26 m room pitch and roughly 0.28–0.37 pitch radius fractions. Five deterministic seeds were measured.
|
||||
|
||||
The crowded cluster has 40 overlapping operations. Across the whole corpus the largest candidate list on a line was 58 shapes, below the 64-membership limit. The largest primitive-interval list was 52. No tested line overflowed the membership word.
|
||||
|
||||
## Toolchain and numeric contract
|
||||
|
||||
The host reports an AMD Ryzen 7 5800X3D with 16 logical processors. The build used the requested VS 2022 `vcvars64.bat` and `cl /std:c++20 /O2 /arch:AVX2 /EHsc`.
|
||||
|
||||
From the plugin root, the tested build command was:
|
||||
|
||||
```bat
|
||||
call "E:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat"
|
||||
cl /nologo /std:c++20 /O2 /arch:AVX2 /EHsc /Fe:Saved\Research\intervals\interval_spike.exe /Fo:Saved\Research\intervals\interval_spike.obj Saved\Research\intervals\interval_spike.cpp
|
||||
```
|
||||
|
||||
Each benchmark process takes `<seed> <workers>` arguments, for example `interval_spike.exe 1 6`, and writes one JSON record to stdout. The final per-process records are `final_seed1_w1.json` through `final_seed5_w8.json` in this directory.
|
||||
|
||||
Shape transforms are Q15 integer matrices; quadric coefficients are rounded once into Q28 per cm². Occupancy points and interval endpoints use signed 1/16 cm integer coordinates. A shape contains a point when its integer quadratic predicate is `<= 0`; the endpoint inclusion and ordered CSG operation rules are fixed. Line endpoints are found by integer bisection over the canonical coordinate grid, and mesh crossings are the midpoints between the last outside and first inside coordinates.
|
||||
|
||||
The sign paths use integer arithmetic only. With this corpus, local coordinates stay within each feature’s conservative AABB; the largest Q28 coefficient is bounded by the 4 cm minimum radius, and the largest possible quadratic terms stay below 10¹⁸, within signed 64-bit range. That makes the sign decision identical on x64 CPUs implementing the same C++ integer rules. The far parent surface root and explicit thin-patch parameterization use floating point for vertex placement, after the sign/classification decision; cross-vendor bitwise mesh identity was not tested. On this 5800X3D, final geometry hashes matched across worker counts and repeated fresh processes.
|
||||
|
||||
## Timings
|
||||
|
||||
Each row summarizes five fresh processes, one per seed. The p95 uses linear interpolation between ordered samples. The interval stage includes solving/composing all three axes **and writing packed sign words**; active-cell masks are timed separately. Meshing is single-threaded in this spike, so worker counts primarily affect interval generation.
|
||||
|
||||
Times are milliseconds, shown as median / p95 / worst.
|
||||
|
||||
| Stage | 1 worker | 6 workers | 8 workers |
|
||||
|---|---:|---:|---:|
|
||||
| Corpus and spatial hierarchy | 0.5 / 0.6 / 0.6 | 0.5 / 0.7 / 0.7 | 0.5 / 0.7 / 0.8 |
|
||||
| Interval solve, compose, and sign words (3 axes) | 154.2 / 167.8 / 170.8 | 48.2 / 52.6 / 52.9 | 43.2 / 49.0 / 50.1 |
|
||||
| Active-cell masks | 2.1 / 3.4 / 3.7 | 2.1 / 2.3 / 2.3 | 2.1 / 2.2 / 2.2 |
|
||||
| Near mesh emission | 188.4 / 197.4 / 198.7 | 191.2 / 229.3 / 238.4 | 192.8 / 219.8 / 223.9 |
|
||||
| Far-field proxy | 8.6 / 8.7 / 8.8 | 8.6 / 8.6 / 8.6 | 8.6 / 8.9 / 9.0 |
|
||||
| **Near CPU total** | **349.3 / 357.9 / 358.1** | **241.1 / 280.3 / 288.3** | **240.2 / 270.9 / 276.7** |
|
||||
| Near plus far proxy | 357.9 / 366.5 / 366.7 | 249.7 / 288.9 / 296.9 | 248.8 / 279.5 / 285.3 |
|
||||
|
||||
The interval stage fits its 150 ms allocation at 6 and 8 workers, but not at one worker. The near CPU total stays below 430 ms for every measured run. The 6-worker mesh p95 exceeds the proposed 220 ms mesh allocation; the 8-worker p95 is just below it, while its worst run is slightly over.
|
||||
|
||||
## Output and memory
|
||||
|
||||
| Result | Median across five seeds | Range |
|
||||
|---|---:|---:|
|
||||
| Near vertices | 282,404 | 272,704–296,660 |
|
||||
| Near triangles | 563,052 | 543,828–591,512 |
|
||||
| Near mesh buffers | 13,535,700 bytes (13.54 MB) | 13,072,232–14,219,284 bytes |
|
||||
| Near plus far buffers | 19,848,460 bytes (19.85 MB) | 19,384,992–20,532,044 bytes |
|
||||
| Process working set after validation | 45,191,168 bytes (43.1 MiB) | 44,199,936–46,628,864 bytes |
|
||||
|
||||
The proposed 60 MB output workload was not reached. This corpus emitted about 23% of the round-3 estimate of 2.4 million triangles, so the measured mesh time does not establish how a 60 MB result scales.
|
||||
|
||||
## Interval and crossing statistics
|
||||
|
||||
Each axis has 67,081 lines. The table gives the median line p50, the median of the five per-seed line p95 values, and the worst single-line value over all five seeds. The per-seed line p95 uses the sorted index `floor(0.95 * (N - 1))`.
|
||||
|
||||
| Axis | Candidate shapes per line: p50 / p95 / worst | Primitive intervals per line: p50 / p95 / worst | Composed crossings per line: p50 / p95 / worst | Occupancy runs per line: p50 / p95 / worst |
|
||||
|---|---:|---:|---:|---:|
|
||||
| X | 0 / 16 / 44 | 0 / 11 / 40 | 0 / 8 / 20 | 1 / 9 / 21 |
|
||||
| Y | 0 / 17 / 46 | 0 / 12 / 40 | 0 / 10 / 24 | 1 / 9 / 23 |
|
||||
| Z | 1 / 13 / 58 | 0 / 9 / 52 | 0 / 8 / 22 | 1 / 9 / 19 |
|
||||
|
||||
The busiest observed line used 58 candidate slots, leaving six membership bits unused. The long lists are localized; the median line has no candidate shape in X and Y and one in Z. The distributions and maxima were stable across worker counts.
|
||||
|
||||
## Correctness and determinism
|
||||
|
||||
- **Full sign agreement:** all 15 final benchmark processes compared the pointwise reference with all three interval-axis sign paths at every lattice node: zero mismatches.
|
||||
- **Mesh crossings:** zero sign-changing lattice edges lacked an analytic crossing; zero contour-degree faults and zero open cell contours.
|
||||
- **Page ownership:** all 64 near pages passed unique edge-key and owner-page checks. Shared border vertices use one canonical crossing record and quantized position.
|
||||
- **Tangency:** an exact tangent line retained its single-point interval.
|
||||
- **Coincident edits:** coincident carve/add intervals had identical roots, and both operation orders produced their defined last-operation result.
|
||||
- **Negative coordinates:** the full lattice includes negative world X and Z coordinates and passed the point-reference comparison.
|
||||
- **Thin strata:** 13 sub-cell strata were in each corpus. Across the five seeds, 1–10 had no structural lattice point inside; every such feature received explicit surface triangles (288–1,993 triangles per seed). The deliberately isolated 12 cm-thick air seam had no lattice sample and two crossing tags on its central line. The validation gate was also given a deliberately omitted thin surface and rejected it.
|
||||
- **Determinism:** for each of five seeds, the combined geometry hash was identical at 1, 6, and 8 workers. A repeated fresh seed-1/one-worker process also matched. Per-seed hashes: `e5ad62303de5f641`, `17a0d35b9c500445`, `c74544dc1b78ce7b`, `69cef91397829b57`, `afb5e5e8fa54b8a9`.
|
||||
|
||||
## Far-field result and limit
|
||||
|
||||
The proxy selected **47,040 nodes**, **92,150 patches**, **94,090 vertices**, and **184,300 triangles** in **6,312,760 bytes**, with zero descendants. It takes about 8.6 ms. The grid spans ±6 km on each horizontal axis and has five stacked parent layers.
|
||||
|
||||
This is not a proof of the far cave hierarchy. The patches are all on the same 125 m grid; the reported LOD bands are metadata rather than different tessellation levels. The parent shapes are five broad ellipsoid sheets, not the room/tunnel grammar used in the near corpus. The square reaches about 8.5 km at its corners, and no far/near transition surface was built or validated. The production far field is therefore **unproven**.
|
||||
|
||||
## Gate decision
|
||||
|
||||
The near interval/bit core passes the **430 ms timing** portion of the first gate on this 372-feature corpus, and crowded junctions stay below the 64-shape line limit. The output is only 13.1–14.2 MB nearby, however, so the proposed ~60 MB mesh budget was not exercised. The 6-worker mesh p95 also exceeds its 220 ms sub-budget. Thin-feature and shared-boundary checks pass for the tested feature family.
|
||||
|
||||
This is a positive near-core result, not a whole-world pass. Do not treat the under-367 ms near-plus-proxy figure as evidence that the realistic six-kilometre cave world is ready for the Unreal publication/collision test; the far hierarchy and larger output workload remain unproven.
|
||||
|
||||
## Failed and retried builds/runs
|
||||
|
||||
- Build attempt 1 failed because Win32 `min`/`max` macros expanded `std::min`/`std::max` calls. `NOMINMAX` fixed that class of error.
|
||||
- Build attempt 2 then failed on the room-center array dimensions and the Windows `far` macro colliding with a local variable name. Both were corrected; subsequent MSVC builds succeeded.
|
||||
- `vcvars64.bat` printed that `vswhere.exe` was not recognized on each build, but it continued and launched `cl.exe`. The final build succeeded with only unused-parameter and bool-comparison warnings.
|
||||
- The first benchmark launch attempt used PowerShell’s unsupported `Start-Process -PriorityClass` parameter and did not start the executable. The retry launched the process and immediately set its priority to `BelowNormal`.
|
||||
- The first executable run completed but failed validation with 878,033 sign mismatches and no thin crossing tags. The line solver had omitted the shape-center offset when converting the quadratic vertex to an absolute coordinate. After correction, the full sign comparison passed; thin surfaces and explicit contour-integrity assertions were then added.
|
||||
- A final-run preflight initially aborted at a self-imposed 6 GB free-memory threshold; no process started. The machine then had 4.5 GB free while another foreground process used about 12.3 GB. The benchmark working set was about 45 MB, so the final checks were retried below-normal with a 1 GB floor. All three checks passed, followed by all 15 final fresh-process runs.
|
||||
|
||||
All final runs exited 0. No Unreal process was launched, no `Source/` file was modified, and no commit, push, or stash was made.
|
||||
|
||||
## Next step
|
||||
|
||||
Extend the CPU corpus until it emits the intended ~60 MB / 2.4 million triangle workload, with page meshing parallelized deterministically. In parallel, replace the far sheet proxy with actual selected parent room/tunnel/strata shapes, radial horizon clipping, and a shared near/far transition. Re-run the same validation and gates; then decide whether the Unreal publication/collision test is justified.
|
||||
|
||||
## Round 2: bounded blends, QEF mesh, and surface relief
|
||||
|
||||
Date: 2026-10-02. The existing section above records the first-round measurements; this section records the changed field and mesh.
|
||||
|
||||
### What changed
|
||||
|
||||
- The corpus now has **363 shapes**: 18 rooms, 147 anisotropic tunnel segments with changing cross-sections, 40 pillars, 36 stalactite/stalagmite forms, 42 shelves, 24 floating masses, 12 craters, 40 crowded-junction edits, three sparse rock bands, and the isolated audit seam. The 12 repeated thin strata were removed. The three remaining bands have different orientations and are placed at a nearby air lattice node so the interval mesh sees their thin crossings.
|
||||
- Smooth min/max is applied only in bounded overlap regions. At 70 cm blend width, the mask covers a median **3.08% of cell centers** (3.07–3.19% across five seeds) and **1.94% of lattice nodes** (1.94–1.98%). Everywhere else the original exact ordered interval field is used. The banded features keep their hard field so their crossings remain exact.
|
||||
- The near mesh now uses per-cell QEF dual contouring with cached analytic Hermite crossings/normals, face-connectivity component splits, a deterministic ambiguous-face sign, and deterministic page ownership. Page-edge ownership is checked across all 64 pages. The 12 cm audit seam, which no lattice point can see, gets a closed 336-triangle analytic patch.
|
||||
- A deterministic four-band value-noise displacement runs after meshing, with wavelengths **6.4, 3.2, 1.6, and 1.2 m**. The shortest band spans 4.8 voxel cells. The hard displacement bound is **0.60 m**; the five measured seeds reached 0.404–0.439 m. Vertex normals are rebuilt after displacement. No extra volume samples are taken for relief.
|
||||
- `interval_viewer2.exe` renders the blended dual-contour mesh and keeps a separate displaced vertex buffer. Press **R** to switch displacement off/on. The world-space rock color mottling and D3D11 back-buffer PNG capture are part of this standalone viewer. Capture mode closes its own window after writing the screenshots.
|
||||
|
||||
### Timing comparison
|
||||
|
||||
The first-round values below are the medians from the original five-seed report. Round 2 reports the median/p95/worst of five fresh sequential processes per worker count; p95 is linearly interpolated at the 95th percentile. The blend-index row is the AABB overlap raster plus surface-pair filtering. Near total includes the listed stages; far-field time is separate.
|
||||
|
||||
| Stage (ms) | Before medians, 1 / 6 / 8 workers | After, 1 worker med / p95 / worst | After, 6 workers med / p95 / worst | After, 8 workers med / p95 / worst |
|
||||
|---|---:|---:|---:|---:|
|
||||
| Corpus/hierarchy, excluding blend-zone index | 0.5 / 0.5 / 0.5 | 0.96 / 1.03 / 1.04 | 0.96 / 0.97 / 0.97 | 0.96 / 1.08 / 1.10 |
|
||||
| Blend-zone index and mask | — / — / — | 186.68 / 203.10 / 205.61 | 186.07 / 203.27 / 205.51 | 186.97 / 202.43 / 204.94 |
|
||||
| Interval solve, compose, and sign words | 154.2 / 48.2 / 43.2 | 154.88 / 156.72 / 157.12 | 48.89 / 50.47 / 50.65 | 46.10 / 46.97 / 47.16 |
|
||||
| Blend-zone pointwise evaluation and crossings | — / — / — | 203.85 / 225.44 / 229.87 | 200.79 / 205.81 / 205.98 | 201.49 / 208.96 / 209.65 |
|
||||
| Active-cell masks | 2.1 / 2.1 / 2.1 | 2.62 / 3.82 / 3.86 | 2.85 / 4.05 / 4.28 | 3.58 / 4.29 / 4.29 |
|
||||
| Near meshing: old contour emitter / QEF dual contour | 188.4 / 191.2 / 192.8 | 315.62 / 324.11 / 325.76 | 322.14 / 344.69 / 349.24 | 330.38 / 331.55 / 331.57 |
|
||||
| Surface displacement and normal rebuild | — / — / — | 61.43 / 62.29 / 62.45 | 61.04 / 61.80 / 61.91 | 61.26 / 63.41 / 63.69 |
|
||||
| **Near CPU total** | **349.3 / 241.1 / 240.2** | **931.93 / 957.68 / 959.08** | **836.06 / 848.28 / 849.57** | **824.27 / 854.37 / 861.33** |
|
||||
| Far-field proxy | 8.6 / 8.6 / 8.6 | 8.59 / 8.61 / 8.62 | 8.57 / 9.51 / 9.58 | 8.58 / 8.60 / 8.61 |
|
||||
| **Near plus far** | **357.9 / 249.7 / 248.8** | **940.54 / 966.28 / 967.68** | **844.62 / 857.37 / 858.80** | **832.85 / 862.93 / 869.89** |
|
||||
|
||||
That is a **2.67× / 3.47× / 3.43×** near-total cost against round 1 at 1/6/8 workers. Most of the new cost is visible in the table: roughly 187–190 ms to build the overlap mask, 201–204 ms to evaluate blended nodes/rebuild crossings, 316–330 ms for dual contouring, and about 61 ms for relief plus normal rebuild. The 6/8-worker interval speedup remains, but the zone work and mesher are single-threaded here.
|
||||
|
||||
The median output across the five seeds is **265,508 near vertices**, **531,164 triangles**, and **12,747,440 near-buffer bytes**. Median working sets were 69.6 / 70.0 / 70.4 MB at 1 / 6 / 8 workers. The proposed 60 MB mesh workload is still not exercised.
|
||||
|
||||
### Correctness, topology, and repeatability
|
||||
|
||||
- All 15 final processes (seeds 1–5 at 1, 6, and 8 workers) exited 0. Each compared the interval/blend signs against the new pointwise blended-field reference at every lattice node: **zero mismatches** and zero blend-boundary sign disagreements. All page ownership checks passed, with zero missing analytic crossings, zero cell-contour faults, and zero open cell contours.
|
||||
- The three lattice-snapped bands are present in the structural mesh. The 12 cm audit seam has no lattice sample and is retained by its closed 336-triangle patch. The thin-surface omission check still rejects a deliberately omitted patch.
|
||||
- The edge-manifold diagnostic reports **zero boundary edges** on all five seeds, but **3–7 overused edge groups** remain. The JSON `non_manifold_edges_zero` field is therefore false. These are localized dual-contour singularities around ambiguous/multiple-crossing cells; they are visible in the diagnostics and were not waived as a zero count. The page-ownership/sign/thin-feature validation result is reported separately from this remaining topology count.
|
||||
- The QEF clamp counter is 141,729–155,046 across seeds. It increments per clamped coordinate axis, not per unique vertex. There are also 1,654–2,035 retained near-zero-area triangles; keeping them preserves edge connectivity after QEF clamping. These facts, plus the remaining overused edges, mean the mesher still needs topology work before treating the output as production collision geometry.
|
||||
- For each seed, raw mesh, displaced mesh, and combined hashes match at all three worker counts. A fresh repeated seed-1/one-worker process also matched: mesh `20d4d80f439c17f6`, displaced mesh `0592d64cb4f269ca`, combined `fe846e12af9fef67`. Per-seed combined hashes: `fe846e12af9fef67`, `8f0994fe2295a34e`, `ee13abc933f14a3f`, `129dcc0c9bac3b3e`, `66361d9daff2b1a6`.
|
||||
|
||||
### Screenshots and visual judgement
|
||||
|
||||
These PNGs were captured from the viewer’s own D3D11 swap-chain back buffer, at 1280×800:
|
||||
|
||||
| View | Capture |
|
||||
|---|---|
|
||||
| Inside the first chamber, relief on | [`chamber.png`](views/chamber.png) |
|
||||
| Same camera, relief off | [`chamber_relief_off.png`](views/chamber_relief_off.png) |
|
||||
| Open point in the crowded junction | [`junction.png`](views/junction.png) |
|
||||
| Close to the chamber wall | [`wall.png`](views/wall.png) |
|
||||
|
||||
Compared with the reject image, the repeated thin slab stack is gone, the color has rock-like variation, and many primitive junctions read as rounded transitions. The relief-on chamber is visibly less smooth than the relief-off view. The result is still well short of the reference cave: the close-wall view shows an obvious rounded ellipsoid, the chamber has broad flat-looking facets, and the material lacks the target’s rich rock texture, crystals, and water. I would describe this as a measurable improvement over the rejection, not a finished natural-rock look.
|
||||
|
||||
### Failed and retried Round 2 attempts
|
||||
|
||||
- Two early compile invocations passed the `vcvars64.bat` path with broken shell quoting, so `cl.exe` never started. An intermediate C++ build also failed because `near` was used as an identifier; it was renamed. The first D3D11 viewer link failed with unresolved `CoInitializeEx`/`CoUninitialize`/`CoCreateInstance`; linking `ole32.lib` fixed it. A later corpus-snap build failed once because `eval_quad` was used before declaration; a forward declaration fixed it. The corrected builds succeeded. MSVC continued to print that `vswhere.exe` was unavailable from `vcvars64.bat`, but it still launched `cl.exe`.
|
||||
- `round2_probe3` through `round2_probe12` were intermediate dual-contour runs that exited on validation failures while the Hermite cache, component topology, degenerate triangles, and thin patches were being corrected. At probe 12, missing crossings had reached zero, but the mesh still reported about 3.7k non-manifold edges. Skipping the zero-area triangles produced thousands of boundary edges; a one-QEF-vertex-per-cell experiment made the overused-edge count worse (798), so both approaches were reverted. The final five-seed check has zero boundary edges and 3–7 overused groups.
|
||||
- The first two D3D capture runs failed at WIC pixel-format selection (`0x88982f80`, unsupported RGBA); they left zero-byte PNGs. The capture path now swaps the D3D11 RGBA readback into WIC BGRA and writes all four non-empty images above. The capture-mode viewer closed itself; no previously open viewer was touched.
|
||||
- MSVC continued to print that `vswhere.exe` was unavailable from `vcvars64.bat`, but all final builds completed. All 15 final benchmark runs and the repeated determinism run exited 0. No Unreal build or launch was done, no plugin `Source/` file changed, and nothing was committed, pushed, or stashed.
|
||||
|
||||
### Round 2 conclusion
|
||||
|
||||
Smooth joins and broad relief improve the rejected world, with only about 3.1% of cell centers in blend zones. The price is substantial: the near build rises from 241 ms to 836 ms at six workers and from 240 ms to 824 ms at eight workers. QEF clamping and the remaining 3–7 overused edges also keep this from being production-ready geometry. The viewer and measurements are ready for owner review; the screenshots show honestly that more corpus shaping/material work is needed to approach the reference.
|
||||
@@ -0,0 +1 @@
|
||||
{"seed":1,"workers":1,"shape_count":372,"near_cells":16777216,"near_nodes":17373979,"membership_limit":64,"overflow":false,"ms":{"corpus_hierarchy":0.497,"interval_solve_compose_and_sign_words_3_axes":153.430,"active_cell_masks":1.978,"near_mesh_emission":172.709,"far_field":8.577,"validation_outside_timing":242.817,"near_cpu_total":328.614,"near_plus_far_total":337.191},"near":{"active_cells":272986,"vertices":275646,"triangles":549585,"bytes":13211864,"hash":"ff39dc05fa0b59e0","pages":64,"index_ranges":67,"page_border_vertices":14777,"ambiguous_faces":3870,"multi_cross_edges":416,"thin_detected":13,"thin_hidden_lattice":3,"thin_meshed":3,"explicit_thin_triangles":639},"interval_stats":{"x":{"lines":67081,"candidate_total":181692,"candidate_p50":0,"candidate_p95":16,"candidate_max":44,"interval_total":110023,"interval_p50":0,"interval_p95":11,"interval_max":40,"crossing_total":88770,"crossing_p50":0,"crossing_p95":8,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":21},"y":{"lines":67081,"candidate_total":193718,"candidate_p50":0,"candidate_p95":18,"candidate_max":45,"interval_total":111420,"interval_p50":0,"interval_p95":12,"interval_max":40,"crossing_total":91616,"crossing_p50":0,"crossing_p95":8,"crossing_max":18,"runs_p50":1,"runs_p95":9,"runs_max":19},"z":{"lines":67081,"candidate_total":206488,"candidate_p50":0,"candidate_p95":13,"candidate_max":58,"interval_total":120012,"interval_p50":0,"interval_p95":9,"interval_max":52,"crossing_total":109308,"crossing_p50":0,"crossing_p95":8,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":19}},"far":{"nodes":47040,"patches":92150,"vertices":94090,"triangles":184300,"bytes":6312760,"descendants":0,"lod_nodes":[240,880,3680,15040,11360,15840],"hash":"ab4a669002ce586d"},"validation":{"signs_ok":true,"mismatches":0,"negative_coordinates":true,"page_ownership_and_shared_edges":true,"tangency":true,"coincident_edits":true,"thin_surface_present":true,"omit_thin_surface_rejected":true,"thin_hidden_in_lattice":3,"audit_thin_crossing_events":2},"output_bytes_total":19524624,"combined_hash":"e5ad62303de5f641","working_set_bytes":44474368,"validation_pass":true}
|
||||
@@ -0,0 +1 @@
|
||||
{"seed":1,"workers":1,"shape_count":372,"near_cells":16777216,"near_nodes":17373979,"membership_limit":64,"overflow":false,"ms":{"corpus_hierarchy":0.493,"interval_solve_compose_and_sign_words_3_axes":151.928,"active_cell_masks":2.082,"near_mesh_emission":178.676,"far_field":8.558,"validation_outside_timing":241.893,"near_cpu_total":333.179,"near_plus_far_total":341.737},"near":{"active_cells":272986,"vertices":275646,"triangles":549585,"bytes":13211864,"hash":"ff39dc05fa0b59e0","pages":64,"index_ranges":67,"page_border_vertices":14777,"ambiguous_faces":3870,"multi_cross_edges":416,"thin_detected":13,"thin_hidden_lattice":3,"thin_meshed":3,"explicit_thin_triangles":639},"interval_stats":{"x":{"lines":67081,"candidate_total":181692,"candidate_p50":0,"candidate_p95":16,"candidate_max":44,"interval_total":110023,"interval_p50":0,"interval_p95":11,"interval_max":40,"crossing_total":88770,"crossing_p50":0,"crossing_p95":8,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":21},"y":{"lines":67081,"candidate_total":193718,"candidate_p50":0,"candidate_p95":18,"candidate_max":45,"interval_total":111420,"interval_p50":0,"interval_p95":12,"interval_max":40,"crossing_total":91616,"crossing_p50":0,"crossing_p95":8,"crossing_max":18,"runs_p50":1,"runs_p95":9,"runs_max":19},"z":{"lines":67081,"candidate_total":206488,"candidate_p50":0,"candidate_p95":13,"candidate_max":58,"interval_total":120012,"interval_p50":0,"interval_p95":9,"interval_max":52,"crossing_total":109308,"crossing_p50":0,"crossing_p95":8,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":19}},"far":{"nodes":47040,"patches":92150,"vertices":94090,"triangles":184300,"bytes":6312760,"descendants":0,"lod_nodes":[240,880,3680,15040,11360,15840],"hash":"ab4a669002ce586d"},"validation":{"signs_ok":true,"mismatches":0,"negative_coordinates":true,"page_ownership_and_shared_edges":true,"tangency":true,"coincident_edits":true,"thin_surface_present":true,"omit_thin_surface_rejected":true,"thin_hidden_in_lattice":3,"audit_thin_crossing_events":2},"output_bytes_total":19524624,"combined_hash":"e5ad62303de5f641","working_set_bytes":44490752,"validation_pass":true}
|
||||
@@ -0,0 +1 @@
|
||||
{"seed":1,"workers":6,"shape_count":372,"near_cells":16777216,"near_nodes":17373979,"membership_limit":64,"overflow":false,"ms":{"corpus_hierarchy":0.725,"interval_solve_compose_and_sign_words_3_axes":72.450,"active_cell_masks":3.939,"near_mesh_emission":254.653,"far_field":8.601,"validation_outside_timing":320.588,"near_cpu_total":331.767,"near_plus_far_total":340.367},"near":{"active_cells":272986,"vertices":275646,"triangles":549585,"bytes":13211864,"hash":"ff39dc05fa0b59e0","pages":64,"index_ranges":67,"page_border_vertices":14777,"ambiguous_faces":3870,"multi_cross_edges":416,"thin_detected":13,"thin_hidden_lattice":3,"thin_meshed":3,"explicit_thin_triangles":639},"interval_stats":{"x":{"lines":67081,"candidate_total":181692,"candidate_p50":0,"candidate_p95":16,"candidate_max":44,"interval_total":110023,"interval_p50":0,"interval_p95":11,"interval_max":40,"crossing_total":88770,"crossing_p50":0,"crossing_p95":8,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":21},"y":{"lines":67081,"candidate_total":193718,"candidate_p50":0,"candidate_p95":18,"candidate_max":45,"interval_total":111420,"interval_p50":0,"interval_p95":12,"interval_max":40,"crossing_total":91616,"crossing_p50":0,"crossing_p95":8,"crossing_max":18,"runs_p50":1,"runs_p95":9,"runs_max":19},"z":{"lines":67081,"candidate_total":206488,"candidate_p50":0,"candidate_p95":13,"candidate_max":58,"interval_total":120012,"interval_p50":0,"interval_p95":9,"interval_max":52,"crossing_total":109308,"crossing_p50":0,"crossing_p95":8,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":19}},"far":{"nodes":47040,"patches":92150,"vertices":94090,"triangles":184300,"bytes":6312760,"descendants":0,"lod_nodes":[240,880,3680,15040,11360,15840],"hash":"ab4a669002ce586d"},"validation":{"signs_ok":true,"mismatches":0,"negative_coordinates":true,"page_ownership_and_shared_edges":true,"tangency":true,"coincident_edits":true,"thin_surface_present":true,"omit_thin_surface_rejected":true,"thin_hidden_in_lattice":3,"audit_thin_crossing_events":2},"output_bytes_total":19524624,"combined_hash":"e5ad62303de5f641","working_set_bytes":44904448,"validation_pass":true}
|
||||
@@ -0,0 +1 @@
|
||||
{"seed":1,"workers":8,"shape_count":372,"near_cells":16777216,"near_nodes":17373979,"membership_limit":64,"overflow":false,"ms":{"corpus_hierarchy":0.501,"interval_solve_compose_and_sign_words_3_axes":49.895,"active_cell_masks":2.151,"near_mesh_emission":183.250,"far_field":9.053,"validation_outside_timing":243.429,"near_cpu_total":235.797,"near_plus_far_total":244.850},"near":{"active_cells":272986,"vertices":275646,"triangles":549585,"bytes":13211864,"hash":"ff39dc05fa0b59e0","pages":64,"index_ranges":67,"page_border_vertices":14777,"ambiguous_faces":3870,"multi_cross_edges":416,"thin_detected":13,"thin_hidden_lattice":3,"thin_meshed":3,"explicit_thin_triangles":639},"interval_stats":{"x":{"lines":67081,"candidate_total":181692,"candidate_p50":0,"candidate_p95":16,"candidate_max":44,"interval_total":110023,"interval_p50":0,"interval_p95":11,"interval_max":40,"crossing_total":88770,"crossing_p50":0,"crossing_p95":8,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":21},"y":{"lines":67081,"candidate_total":193718,"candidate_p50":0,"candidate_p95":18,"candidate_max":45,"interval_total":111420,"interval_p50":0,"interval_p95":12,"interval_max":40,"crossing_total":91616,"crossing_p50":0,"crossing_p95":8,"crossing_max":18,"runs_p50":1,"runs_p95":9,"runs_max":19},"z":{"lines":67081,"candidate_total":206488,"candidate_p50":0,"candidate_p95":13,"candidate_max":58,"interval_total":120012,"interval_p50":0,"interval_p95":9,"interval_max":52,"crossing_total":109308,"crossing_p50":0,"crossing_p95":8,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":19}},"far":{"nodes":47040,"patches":92150,"vertices":94090,"triangles":184300,"bytes":6312760,"descendants":0,"lod_nodes":[240,880,3680,15040,11360,15840],"hash":"ab4a669002ce586d"},"validation":{"signs_ok":true,"mismatches":0,"negative_coordinates":true,"page_ownership_and_shared_edges":true,"tangency":true,"coincident_edits":true,"thin_surface_present":true,"omit_thin_surface_rejected":true,"thin_hidden_in_lattice":3,"audit_thin_crossing_events":2},"output_bytes_total":19524624,"combined_hash":"e5ad62303de5f641","working_set_bytes":45068288,"validation_pass":true}
|
||||
@@ -0,0 +1 @@
|
||||
{"seed":2,"workers":1,"shape_count":372,"near_cells":16777216,"near_nodes":17373979,"membership_limit":64,"overflow":false,"ms":{"corpus_hierarchy":0.505,"interval_solve_compose_and_sign_words_3_axes":165.156,"active_cell_masks":2.084,"near_mesh_emission":184.966,"far_field":8.577,"validation_outside_timing":246.244,"near_cpu_total":352.711,"near_plus_far_total":361.288},"near":{"active_cells":283161,"vertices":286734,"triangles":569817,"bytes":13720900,"hash":"dab38d009700f1f0","pages":64,"index_ranges":74,"page_border_vertices":15582,"ambiguous_faces":2854,"multi_cross_edges":536,"thin_detected":13,"thin_hidden_lattice":10,"thin_meshed":10,"explicit_thin_triangles":1993},"interval_stats":{"x":{"lines":67081,"candidate_total":187058,"candidate_p50":0,"candidate_p95":17,"candidate_max":43,"interval_total":111999,"interval_p50":0,"interval_p95":11,"interval_max":40,"crossing_total":89658,"crossing_p50":0,"crossing_p95":8,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":21},"y":{"lines":67081,"candidate_total":199365,"candidate_p50":0,"candidate_p95":18,"candidate_max":44,"interval_total":120586,"interval_p50":0,"interval_p95":12,"interval_max":39,"crossing_total":104152,"crossing_p50":0,"crossing_p95":10,"crossing_max":22,"runs_p50":1,"runs_p95":11,"runs_max":19},"z":{"lines":67081,"candidate_total":204987,"candidate_p50":1,"candidate_p95":13,"candidate_max":56,"interval_total":124121,"interval_p50":0,"interval_p95":10,"interval_max":48,"crossing_total":112170,"crossing_p50":0,"crossing_p95":8,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":19}},"far":{"nodes":47040,"patches":92150,"vertices":94090,"triangles":184300,"bytes":6312760,"descendants":0,"lod_nodes":[240,880,3680,15040,11360,15840],"hash":"0293ad0bb4aab1f8"},"validation":{"signs_ok":true,"mismatches":0,"negative_coordinates":true,"page_ownership_and_shared_edges":true,"tangency":true,"coincident_edits":true,"thin_surface_present":true,"omit_thin_surface_rejected":true,"thin_hidden_in_lattice":10,"audit_thin_crossing_events":2},"output_bytes_total":20033660,"combined_hash":"17a0d35b9c500445","working_set_bytes":45158400,"validation_pass":true}
|
||||
@@ -0,0 +1 @@
|
||||
{"seed":2,"workers":6,"shape_count":372,"near_cells":16777216,"near_nodes":17373979,"membership_limit":64,"overflow":false,"ms":{"corpus_hierarchy":0.499,"interval_solve_compose_and_sign_words_3_axes":51.718,"active_cell_masks":2.512,"near_mesh_emission":191.036,"far_field":8.609,"validation_outside_timing":269.096,"near_cpu_total":245.766,"near_plus_far_total":254.375},"near":{"active_cells":283161,"vertices":286734,"triangles":569817,"bytes":13720900,"hash":"dab38d009700f1f0","pages":64,"index_ranges":74,"page_border_vertices":15582,"ambiguous_faces":2854,"multi_cross_edges":536,"thin_detected":13,"thin_hidden_lattice":10,"thin_meshed":10,"explicit_thin_triangles":1993},"interval_stats":{"x":{"lines":67081,"candidate_total":187058,"candidate_p50":0,"candidate_p95":17,"candidate_max":43,"interval_total":111999,"interval_p50":0,"interval_p95":11,"interval_max":40,"crossing_total":89658,"crossing_p50":0,"crossing_p95":8,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":21},"y":{"lines":67081,"candidate_total":199365,"candidate_p50":0,"candidate_p95":18,"candidate_max":44,"interval_total":120586,"interval_p50":0,"interval_p95":12,"interval_max":39,"crossing_total":104152,"crossing_p50":0,"crossing_p95":10,"crossing_max":22,"runs_p50":1,"runs_p95":11,"runs_max":19},"z":{"lines":67081,"candidate_total":204987,"candidate_p50":1,"candidate_p95":13,"candidate_max":56,"interval_total":124121,"interval_p50":0,"interval_p95":10,"interval_max":48,"crossing_total":112170,"crossing_p50":0,"crossing_p95":8,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":19}},"far":{"nodes":47040,"patches":92150,"vertices":94090,"triangles":184300,"bytes":6312760,"descendants":0,"lod_nodes":[240,880,3680,15040,11360,15840],"hash":"0293ad0bb4aab1f8"},"validation":{"signs_ok":true,"mismatches":0,"negative_coordinates":true,"page_ownership_and_shared_edges":true,"tangency":true,"coincident_edits":true,"thin_surface_present":true,"omit_thin_surface_rejected":true,"thin_hidden_in_lattice":10,"audit_thin_crossing_events":2},"output_bytes_total":20033660,"combined_hash":"17a0d35b9c500445","working_set_bytes":45715456,"validation_pass":true}
|
||||
@@ -0,0 +1 @@
|
||||
{"seed":2,"workers":8,"shape_count":372,"near_cells":16777216,"near_nodes":17373979,"membership_limit":64,"overflow":false,"ms":{"corpus_hierarchy":0.496,"interval_solve_compose_and_sign_words_3_axes":52.960,"active_cell_masks":2.198,"near_mesh_emission":191.148,"far_field":8.569,"validation_outside_timing":246.765,"near_cpu_total":246.802,"near_plus_far_total":255.372},"near":{"active_cells":283161,"vertices":286734,"triangles":569817,"bytes":13720900,"hash":"dab38d009700f1f0","pages":64,"index_ranges":74,"page_border_vertices":15582,"ambiguous_faces":2854,"multi_cross_edges":536,"thin_detected":13,"thin_hidden_lattice":10,"thin_meshed":10,"explicit_thin_triangles":1993},"interval_stats":{"x":{"lines":67081,"candidate_total":187058,"candidate_p50":0,"candidate_p95":17,"candidate_max":43,"interval_total":111999,"interval_p50":0,"interval_p95":11,"interval_max":40,"crossing_total":89658,"crossing_p50":0,"crossing_p95":8,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":21},"y":{"lines":67081,"candidate_total":199365,"candidate_p50":0,"candidate_p95":18,"candidate_max":44,"interval_total":120586,"interval_p50":0,"interval_p95":12,"interval_max":39,"crossing_total":104152,"crossing_p50":0,"crossing_p95":10,"crossing_max":22,"runs_p50":1,"runs_p95":11,"runs_max":19},"z":{"lines":67081,"candidate_total":204987,"candidate_p50":1,"candidate_p95":13,"candidate_max":56,"interval_total":124121,"interval_p50":0,"interval_p95":10,"interval_max":48,"crossing_total":112170,"crossing_p50":0,"crossing_p95":8,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":19}},"far":{"nodes":47040,"patches":92150,"vertices":94090,"triangles":184300,"bytes":6312760,"descendants":0,"lod_nodes":[240,880,3680,15040,11360,15840],"hash":"0293ad0bb4aab1f8"},"validation":{"signs_ok":true,"mismatches":0,"negative_coordinates":true,"page_ownership_and_shared_edges":true,"tangency":true,"coincident_edits":true,"thin_surface_present":true,"omit_thin_surface_rejected":true,"thin_hidden_in_lattice":10,"audit_thin_crossing_events":2},"output_bytes_total":20033660,"combined_hash":"17a0d35b9c500445","working_set_bytes":45731840,"validation_pass":true}
|
||||
@@ -0,0 +1 @@
|
||||
{"seed":3,"workers":1,"shape_count":372,"near_cells":16777216,"near_nodes":17373979,"membership_limit":64,"overflow":false,"ms":{"corpus_hierarchy":0.508,"interval_solve_compose_and_sign_words_3_axes":155.158,"active_cell_masks":3.366,"near_mesh_emission":198.934,"far_field":8.589,"validation_outside_timing":264.253,"near_cpu_total":357.966,"near_plus_far_total":366.555},"near":{"active_cells":292580,"vertices":296660,"triangles":591512,"bytes":14219284,"hash":"600f405efd49ce29","pages":64,"index_ranges":65,"page_border_vertices":15757,"ambiguous_faces":7772,"multi_cross_edges":264,"thin_detected":13,"thin_hidden_lattice":1,"thin_meshed":1,"explicit_thin_triangles":288},"interval_stats":{"x":{"lines":67081,"candidate_total":190357,"candidate_p50":0,"candidate_p95":16,"candidate_max":42,"interval_total":112011,"interval_p50":0,"interval_p95":11,"interval_max":37,"crossing_total":95212,"crossing_p50":0,"crossing_p95":10,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":21},"y":{"lines":67081,"candidate_total":194781,"candidate_p50":0,"candidate_p95":17,"candidate_max":46,"interval_total":112410,"interval_p50":0,"interval_p95":12,"interval_max":40,"crossing_total":93860,"crossing_p50":0,"crossing_p95":10,"crossing_max":18,"runs_p50":1,"runs_p95":9,"runs_max":19},"z":{"lines":67081,"candidate_total":215639,"candidate_p50":1,"candidate_p95":13,"candidate_max":57,"interval_total":124874,"interval_p50":0,"interval_p95":9,"interval_max":49,"crossing_total":118734,"crossing_p50":0,"crossing_p95":8,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":19}},"far":{"nodes":47040,"patches":92150,"vertices":94090,"triangles":184300,"bytes":6312760,"descendants":0,"lod_nodes":[240,880,3680,15040,11360,15840],"hash":"dbc331a49aa1719e"},"validation":{"signs_ok":true,"mismatches":0,"negative_coordinates":true,"page_ownership_and_shared_edges":true,"tangency":true,"coincident_edits":true,"thin_surface_present":true,"omit_thin_surface_rejected":true,"thin_hidden_in_lattice":1,"audit_thin_crossing_events":2},"output_bytes_total":20532044,"combined_hash":"c74544dc1b78ce7b","working_set_bytes":45920256,"validation_pass":true}
|
||||
@@ -0,0 +1 @@
|
||||
{"seed":3,"workers":6,"shape_count":372,"near_cells":16777216,"near_nodes":17373979,"membership_limit":64,"overflow":false,"ms":{"corpus_hierarchy":0.518,"interval_solve_compose_and_sign_words_3_axes":51.474,"active_cell_masks":2.204,"near_mesh_emission":203.409,"far_field":8.579,"validation_outside_timing":247.255,"near_cpu_total":257.606,"near_plus_far_total":266.185},"near":{"active_cells":292580,"vertices":296660,"triangles":591512,"bytes":14219284,"hash":"600f405efd49ce29","pages":64,"index_ranges":65,"page_border_vertices":15757,"ambiguous_faces":7772,"multi_cross_edges":264,"thin_detected":13,"thin_hidden_lattice":1,"thin_meshed":1,"explicit_thin_triangles":288},"interval_stats":{"x":{"lines":67081,"candidate_total":190357,"candidate_p50":0,"candidate_p95":16,"candidate_max":42,"interval_total":112011,"interval_p50":0,"interval_p95":11,"interval_max":37,"crossing_total":95212,"crossing_p50":0,"crossing_p95":10,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":21},"y":{"lines":67081,"candidate_total":194781,"candidate_p50":0,"candidate_p95":17,"candidate_max":46,"interval_total":112410,"interval_p50":0,"interval_p95":12,"interval_max":40,"crossing_total":93860,"crossing_p50":0,"crossing_p95":10,"crossing_max":18,"runs_p50":1,"runs_p95":9,"runs_max":19},"z":{"lines":67081,"candidate_total":215639,"candidate_p50":1,"candidate_p95":13,"candidate_max":57,"interval_total":124874,"interval_p50":0,"interval_p95":9,"interval_max":49,"crossing_total":118734,"crossing_p50":0,"crossing_p95":8,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":19}},"far":{"nodes":47040,"patches":92150,"vertices":94090,"triangles":184300,"bytes":6312760,"descendants":0,"lod_nodes":[240,880,3680,15040,11360,15840],"hash":"dbc331a49aa1719e"},"validation":{"signs_ok":true,"mismatches":0,"negative_coordinates":true,"page_ownership_and_shared_edges":true,"tangency":true,"coincident_edits":true,"thin_surface_present":true,"omit_thin_surface_rejected":true,"thin_hidden_in_lattice":1,"audit_thin_crossing_events":2},"output_bytes_total":20532044,"combined_hash":"c74544dc1b78ce7b","working_set_bytes":46493696,"validation_pass":true}
|
||||
@@ -0,0 +1 @@
|
||||
{"seed":3,"workers":8,"shape_count":372,"near_cells":16777216,"near_nodes":17373979,"membership_limit":64,"overflow":false,"ms":{"corpus_hierarchy":0.499,"interval_solve_compose_and_sign_words_3_axes":43.016,"active_cell_masks":2.446,"near_mesh_emission":240.567,"far_field":8.808,"validation_outside_timing":267.854,"near_cpu_total":286.528,"near_plus_far_total":295.336},"near":{"active_cells":292580,"vertices":296660,"triangles":591512,"bytes":14219284,"hash":"600f405efd49ce29","pages":64,"index_ranges":65,"page_border_vertices":15757,"ambiguous_faces":7772,"multi_cross_edges":264,"thin_detected":13,"thin_hidden_lattice":1,"thin_meshed":1,"explicit_thin_triangles":288},"interval_stats":{"x":{"lines":67081,"candidate_total":190357,"candidate_p50":0,"candidate_p95":16,"candidate_max":42,"interval_total":112011,"interval_p50":0,"interval_p95":11,"interval_max":37,"crossing_total":95212,"crossing_p50":0,"crossing_p95":10,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":21},"y":{"lines":67081,"candidate_total":194781,"candidate_p50":0,"candidate_p95":17,"candidate_max":46,"interval_total":112410,"interval_p50":0,"interval_p95":12,"interval_max":40,"crossing_total":93860,"crossing_p50":0,"crossing_p95":10,"crossing_max":18,"runs_p50":1,"runs_p95":9,"runs_max":19},"z":{"lines":67081,"candidate_total":215639,"candidate_p50":1,"candidate_p95":13,"candidate_max":57,"interval_total":124874,"interval_p50":0,"interval_p95":9,"interval_max":49,"crossing_total":118734,"crossing_p50":0,"crossing_p95":8,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":19}},"far":{"nodes":47040,"patches":92150,"vertices":94090,"triangles":184300,"bytes":6312760,"descendants":0,"lod_nodes":[240,880,3680,15040,11360,15840],"hash":"dbc331a49aa1719e"},"validation":{"signs_ok":true,"mismatches":0,"negative_coordinates":true,"page_ownership_and_shared_edges":true,"tangency":true,"coincident_edits":true,"thin_surface_present":true,"omit_thin_surface_rejected":true,"thin_hidden_in_lattice":1,"audit_thin_crossing_events":2},"output_bytes_total":20532044,"combined_hash":"c74544dc1b78ce7b","working_set_bytes":46301184,"validation_pass":true}
|
||||
@@ -0,0 +1 @@
|
||||
{"seed":4,"workers":1,"shape_count":372,"near_cells":16777216,"near_nodes":17373979,"membership_limit":64,"overflow":false,"ms":{"corpus_hierarchy":0.485,"interval_solve_compose_and_sign_words_3_axes":153.462,"active_cell_masks":2.555,"near_mesh_emission":189.354,"far_field":8.573,"validation_outside_timing":242.167,"near_cpu_total":345.856,"near_plus_far_total":354.429},"near":{"active_cells":279309,"vertices":282404,"triangles":563052,"bytes":13535700,"hash":"4ed64f01c5df738b","pages":64,"index_ranges":69,"page_border_vertices":13975,"ambiguous_faces":3838,"multi_cross_edges":680,"thin_detected":13,"thin_hidden_lattice":5,"thin_meshed":5,"explicit_thin_triangles":1120},"interval_stats":{"x":{"lines":67081,"candidate_total":181609,"candidate_p50":0,"candidate_p95":16,"candidate_max":41,"interval_total":109240,"interval_p50":0,"interval_p95":11,"interval_max":37,"crossing_total":88640,"crossing_p50":0,"crossing_p95":8,"crossing_max":18,"runs_p50":1,"runs_p95":9,"runs_max":17},"y":{"lines":67081,"candidate_total":191723,"candidate_p50":0,"candidate_p95":17,"candidate_max":42,"interval_total":114274,"interval_p50":0,"interval_p95":12,"interval_max":39,"crossing_total":98854,"crossing_p50":0,"crossing_p95":10,"crossing_max":24,"runs_p50":1,"runs_p95":9,"runs_max":23},"z":{"lines":67081,"candidate_total":201505,"candidate_p50":0,"candidate_p95":13,"candidate_max":56,"interval_total":124143,"interval_p50":0,"interval_p95":10,"interval_max":50,"crossing_total":113480,"crossing_p50":0,"crossing_p95":8,"crossing_max":22,"runs_p50":1,"runs_p95":9,"runs_max":17}},"far":{"nodes":47040,"patches":92150,"vertices":94090,"triangles":184300,"bytes":6312760,"descendants":0,"lod_nodes":[240,880,3680,15040,11360,15840],"hash":"75f729c8e51da37e"},"validation":{"signs_ok":true,"mismatches":0,"negative_coordinates":true,"page_ownership_and_shared_edges":true,"tangency":true,"coincident_edits":true,"thin_surface_present":true,"omit_thin_surface_rejected":true,"thin_hidden_in_lattice":5,"audit_thin_crossing_events":2},"output_bytes_total":19848460,"combined_hash":"69cef91397829b57","working_set_bytes":45002752,"validation_pass":true}
|
||||
@@ -0,0 +1 @@
|
||||
{"seed":4,"workers":6,"shape_count":372,"near_cells":16777216,"near_nodes":17373979,"membership_limit":64,"overflow":false,"ms":{"corpus_hierarchy":0.478,"interval_solve_compose_and_sign_words_3_axes":51.348,"active_cell_masks":2.415,"near_mesh_emission":187.195,"far_field":8.576,"validation_outside_timing":243.163,"near_cpu_total":241.436,"near_plus_far_total":250.012},"near":{"active_cells":279309,"vertices":282404,"triangles":563052,"bytes":13535700,"hash":"4ed64f01c5df738b","pages":64,"index_ranges":69,"page_border_vertices":13975,"ambiguous_faces":3838,"multi_cross_edges":680,"thin_detected":13,"thin_hidden_lattice":5,"thin_meshed":5,"explicit_thin_triangles":1120},"interval_stats":{"x":{"lines":67081,"candidate_total":181609,"candidate_p50":0,"candidate_p95":16,"candidate_max":41,"interval_total":109240,"interval_p50":0,"interval_p95":11,"interval_max":37,"crossing_total":88640,"crossing_p50":0,"crossing_p95":8,"crossing_max":18,"runs_p50":1,"runs_p95":9,"runs_max":17},"y":{"lines":67081,"candidate_total":191723,"candidate_p50":0,"candidate_p95":17,"candidate_max":42,"interval_total":114274,"interval_p50":0,"interval_p95":12,"interval_max":39,"crossing_total":98854,"crossing_p50":0,"crossing_p95":10,"crossing_max":24,"runs_p50":1,"runs_p95":9,"runs_max":23},"z":{"lines":67081,"candidate_total":201505,"candidate_p50":0,"candidate_p95":13,"candidate_max":56,"interval_total":124143,"interval_p50":0,"interval_p95":10,"interval_max":50,"crossing_total":113480,"crossing_p50":0,"crossing_p95":8,"crossing_max":22,"runs_p50":1,"runs_p95":9,"runs_max":17}},"far":{"nodes":47040,"patches":92150,"vertices":94090,"triangles":184300,"bytes":6312760,"descendants":0,"lod_nodes":[240,880,3680,15040,11360,15840],"hash":"75f729c8e51da37e"},"validation":{"signs_ok":true,"mismatches":0,"negative_coordinates":true,"page_ownership_and_shared_edges":true,"tangency":true,"coincident_edits":true,"thin_surface_present":true,"omit_thin_surface_rejected":true,"thin_hidden_in_lattice":5,"audit_thin_crossing_events":2},"output_bytes_total":19848460,"combined_hash":"69cef91397829b57","working_set_bytes":45342720,"validation_pass":true}
|
||||
@@ -0,0 +1 @@
|
||||
{"seed":4,"workers":8,"shape_count":372,"near_cells":16777216,"near_nodes":17373979,"membership_limit":64,"overflow":false,"ms":{"corpus_hierarchy":0.485,"interval_solve_compose_and_sign_words_3_axes":40.148,"active_cell_masks":2.266,"near_mesh_emission":188.109,"far_field":8.564,"validation_outside_timing":242.086,"near_cpu_total":231.009,"near_plus_far_total":239.573},"near":{"active_cells":279309,"vertices":282404,"triangles":563052,"bytes":13535700,"hash":"4ed64f01c5df738b","pages":64,"index_ranges":69,"page_border_vertices":13975,"ambiguous_faces":3838,"multi_cross_edges":680,"thin_detected":13,"thin_hidden_lattice":5,"thin_meshed":5,"explicit_thin_triangles":1120},"interval_stats":{"x":{"lines":67081,"candidate_total":181609,"candidate_p50":0,"candidate_p95":16,"candidate_max":41,"interval_total":109240,"interval_p50":0,"interval_p95":11,"interval_max":37,"crossing_total":88640,"crossing_p50":0,"crossing_p95":8,"crossing_max":18,"runs_p50":1,"runs_p95":9,"runs_max":17},"y":{"lines":67081,"candidate_total":191723,"candidate_p50":0,"candidate_p95":17,"candidate_max":42,"interval_total":114274,"interval_p50":0,"interval_p95":12,"interval_max":39,"crossing_total":98854,"crossing_p50":0,"crossing_p95":10,"crossing_max":24,"runs_p50":1,"runs_p95":9,"runs_max":23},"z":{"lines":67081,"candidate_total":201505,"candidate_p50":0,"candidate_p95":13,"candidate_max":56,"interval_total":124143,"interval_p50":0,"interval_p95":10,"interval_max":50,"crossing_total":113480,"crossing_p50":0,"crossing_p95":8,"crossing_max":22,"runs_p50":1,"runs_p95":9,"runs_max":17}},"far":{"nodes":47040,"patches":92150,"vertices":94090,"triangles":184300,"bytes":6312760,"descendants":0,"lod_nodes":[240,880,3680,15040,11360,15840],"hash":"75f729c8e51da37e"},"validation":{"signs_ok":true,"mismatches":0,"negative_coordinates":true,"page_ownership_and_shared_edges":true,"tangency":true,"coincident_edits":true,"thin_surface_present":true,"omit_thin_surface_rejected":true,"thin_hidden_in_lattice":5,"audit_thin_crossing_events":2},"output_bytes_total":19848460,"combined_hash":"69cef91397829b57","working_set_bytes":45486080,"validation_pass":true}
|
||||
@@ -0,0 +1 @@
|
||||
{"seed":5,"workers":1,"shape_count":372,"near_cells":16777216,"near_nodes":17373979,"membership_limit":64,"overflow":false,"ms":{"corpus_hierarchy":0.470,"interval_solve_compose_and_sign_words_3_axes":172.633,"active_cell_masks":3.120,"near_mesh_emission":184.374,"far_field":8.605,"validation_outside_timing":244.536,"near_cpu_total":360.596,"near_plus_far_total":369.201},"near":{"active_cells":269847,"vertices":272704,"triangles":543828,"bytes":13072232,"hash":"885750d27c475ba9","pages":64,"index_ranges":70,"page_border_vertices":12496,"ambiguous_faces":2638,"multi_cross_edges":976,"thin_detected":13,"thin_hidden_lattice":6,"thin_meshed":6,"explicit_thin_triangles":1282},"interval_stats":{"x":{"lines":67081,"candidate_total":186280,"candidate_p50":0,"candidate_p95":17,"candidate_max":44,"interval_total":111929,"interval_p50":0,"interval_p95":11,"interval_max":38,"crossing_total":89242,"crossing_p50":0,"crossing_p95":8,"crossing_max":18,"runs_p50":1,"runs_p95":9,"runs_max":19},"y":{"lines":67081,"candidate_total":192484,"candidate_p50":0,"candidate_p95":17,"candidate_max":44,"interval_total":111223,"interval_p50":0,"interval_p95":12,"interval_max":36,"crossing_total":90196,"crossing_p50":0,"crossing_p95":8,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":19},"z":{"lines":67081,"candidate_total":214972,"candidate_p50":1,"candidate_p95":13,"candidate_max":56,"interval_total":122709,"interval_p50":0,"interval_p95":9,"interval_max":49,"crossing_total":111168,"crossing_p50":0,"crossing_p95":8,"crossing_max":18,"runs_p50":1,"runs_p95":9,"runs_max":19}},"far":{"nodes":47040,"patches":92150,"vertices":94090,"triangles":184300,"bytes":6312760,"descendants":0,"lod_nodes":[240,880,3680,15040,11360,15840],"hash":"c100350769615273"},"validation":{"signs_ok":true,"mismatches":0,"negative_coordinates":true,"page_ownership_and_shared_edges":true,"tangency":true,"coincident_edits":true,"thin_surface_present":true,"omit_thin_surface_rejected":true,"thin_hidden_in_lattice":6,"audit_thin_crossing_events":2},"output_bytes_total":19384992,"combined_hash":"afb5e5e8fa54b8a9","working_set_bytes":44163072,"validation_pass":true}
|
||||
@@ -0,0 +1 @@
|
||||
{"seed":5,"workers":6,"shape_count":372,"near_cells":16777216,"near_nodes":17373979,"membership_limit":64,"overflow":false,"ms":{"corpus_hierarchy":0.468,"interval_solve_compose_and_sign_words_3_axes":52.449,"active_cell_masks":2.217,"near_mesh_emission":184.168,"far_field":8.708,"validation_outside_timing":248.390,"near_cpu_total":239.302,"near_plus_far_total":248.010},"near":{"active_cells":269847,"vertices":272704,"triangles":543828,"bytes":13072232,"hash":"885750d27c475ba9","pages":64,"index_ranges":70,"page_border_vertices":12496,"ambiguous_faces":2638,"multi_cross_edges":976,"thin_detected":13,"thin_hidden_lattice":6,"thin_meshed":6,"explicit_thin_triangles":1282},"interval_stats":{"x":{"lines":67081,"candidate_total":186280,"candidate_p50":0,"candidate_p95":17,"candidate_max":44,"interval_total":111929,"interval_p50":0,"interval_p95":11,"interval_max":38,"crossing_total":89242,"crossing_p50":0,"crossing_p95":8,"crossing_max":18,"runs_p50":1,"runs_p95":9,"runs_max":19},"y":{"lines":67081,"candidate_total":192484,"candidate_p50":0,"candidate_p95":17,"candidate_max":44,"interval_total":111223,"interval_p50":0,"interval_p95":12,"interval_max":36,"crossing_total":90196,"crossing_p50":0,"crossing_p95":8,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":19},"z":{"lines":67081,"candidate_total":214972,"candidate_p50":1,"candidate_p95":13,"candidate_max":56,"interval_total":122709,"interval_p50":0,"interval_p95":9,"interval_max":49,"crossing_total":111168,"crossing_p50":0,"crossing_p95":8,"crossing_max":18,"runs_p50":1,"runs_p95":9,"runs_max":19}},"far":{"nodes":47040,"patches":92150,"vertices":94090,"triangles":184300,"bytes":6312760,"descendants":0,"lod_nodes":[240,880,3680,15040,11360,15840],"hash":"c100350769615273"},"validation":{"signs_ok":true,"mismatches":0,"negative_coordinates":true,"page_ownership_and_shared_edges":true,"tangency":true,"coincident_edits":true,"thin_surface_present":true,"omit_thin_surface_rejected":true,"thin_hidden_in_lattice":6,"audit_thin_crossing_events":2},"output_bytes_total":19384992,"combined_hash":"afb5e5e8fa54b8a9","working_set_bytes":44736512,"validation_pass":true}
|
||||
@@ -0,0 +1 @@
|
||||
{"seed":5,"workers":8,"shape_count":372,"near_cells":16777216,"near_nodes":17373979,"membership_limit":64,"overflow":false,"ms":{"corpus_hierarchy":0.471,"interval_solve_compose_and_sign_words_3_axes":43.064,"active_cell_masks":1.906,"near_mesh_emission":195.702,"far_field":8.607,"validation_outside_timing":264.699,"near_cpu_total":241.142,"near_plus_far_total":249.750},"near":{"active_cells":269847,"vertices":272704,"triangles":543828,"bytes":13072232,"hash":"885750d27c475ba9","pages":64,"index_ranges":70,"page_border_vertices":12496,"ambiguous_faces":2638,"multi_cross_edges":976,"thin_detected":13,"thin_hidden_lattice":6,"thin_meshed":6,"explicit_thin_triangles":1282},"interval_stats":{"x":{"lines":67081,"candidate_total":186280,"candidate_p50":0,"candidate_p95":17,"candidate_max":44,"interval_total":111929,"interval_p50":0,"interval_p95":11,"interval_max":38,"crossing_total":89242,"crossing_p50":0,"crossing_p95":8,"crossing_max":18,"runs_p50":1,"runs_p95":9,"runs_max":19},"y":{"lines":67081,"candidate_total":192484,"candidate_p50":0,"candidate_p95":17,"candidate_max":44,"interval_total":111223,"interval_p50":0,"interval_p95":12,"interval_max":36,"crossing_total":90196,"crossing_p50":0,"crossing_p95":8,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":19},"z":{"lines":67081,"candidate_total":214972,"candidate_p50":1,"candidate_p95":13,"candidate_max":56,"interval_total":122709,"interval_p50":0,"interval_p95":9,"interval_max":49,"crossing_total":111168,"crossing_p50":0,"crossing_p95":8,"crossing_max":18,"runs_p50":1,"runs_p95":9,"runs_max":19}},"far":{"nodes":47040,"patches":92150,"vertices":94090,"triangles":184300,"bytes":6312760,"descendants":0,"lod_nodes":[240,880,3680,15040,11360,15840],"hash":"c100350769615273"},"validation":{"signs_ok":true,"mismatches":0,"negative_coordinates":true,"page_ownership_and_shared_edges":true,"tangency":true,"coincident_edits":true,"thin_surface_present":true,"omit_thin_surface_rejected":true,"thin_hidden_in_lattice":6,"audit_thin_crossing_events":2},"output_bytes_total":19384992,"combined_hash":"afb5e5e8fa54b8a9","working_set_bytes":44937216,"validation_pass":true}
|
||||
@@ -0,0 +1,138 @@
|
||||
#define main interval_spike_benchmark_main
|
||||
#include "../interval_spike.cpp"
|
||||
#undef main
|
||||
|
||||
#include <d3d11.h>
|
||||
#include <d3dcompiler.h>
|
||||
#include <dxgi.h>
|
||||
#include <wincodec.h>
|
||||
#include <windowsx.h>
|
||||
#include <shellapi.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#pragma comment(lib, "d3d11.lib")
|
||||
#pragma comment(lib, "dxgi.lib")
|
||||
#pragma comment(lib, "d3dcompiler.lib")
|
||||
#pragma comment(lib, "shell32.lib")
|
||||
#pragma comment(lib, "user32.lib")
|
||||
#pragma comment(lib, "windowscodecs.lib")
|
||||
#pragma comment(lib, "ole32.lib")
|
||||
|
||||
namespace {
|
||||
struct V3 { float x,y,z; };
|
||||
V3 add(V3 a,V3 b){return {a.x+b.x,a.y+b.y,a.z+b.z};}
|
||||
V3 sub(V3 a,V3 b){return {a.x-b.x,a.y-b.y,a.z-b.z};}
|
||||
V3 mul(V3 a,float s){return {a.x*s,a.y*s,a.z*s};}
|
||||
float dot(V3 a,V3 b){return a.x*b.x+a.y*b.y+a.z*b.z;}
|
||||
V3 cross(V3 a,V3 b){return {a.y*b.z-a.z*b.y,a.z*b.x-a.x*b.z,a.x*b.y-a.y*b.x};}
|
||||
V3 norm(V3 a){float n=std::sqrt(std::max(1e-20f,dot(a,a)));return mul(a,1.0f/n);}
|
||||
struct M4 { float m[4][4]{}; };
|
||||
M4 identity(){M4 a{};for(int i=0;i<4;++i)a.m[i][i]=1;return a;}
|
||||
M4 perspective(float fov,float aspect,float zn,float zf){M4 a{};float y=1/std::tan(fov*.5f);a.m[0][0]=y/aspect;a.m[1][1]=y;a.m[2][2]=zf/(zf-zn);a.m[2][3]=1;a.m[3][2]=-zn*zf/(zf-zn);return a;}
|
||||
M4 view_matrix(V3 p,V3 f,V3 r,V3 u){M4 a=identity();a.m[0][0]=r.x;a.m[1][0]=r.y;a.m[2][0]=r.z;a.m[3][0]=-dot(r,p);a.m[0][1]=u.x;a.m[1][1]=u.y;a.m[2][1]=u.z;a.m[3][1]=-dot(u,p);a.m[0][2]=f.x;a.m[1][2]=f.y;a.m[2][2]=f.z;a.m[3][2]=-dot(f,p);return a;}
|
||||
M4 transpose(M4 a){M4 b{};for(int i=0;i<4;++i)for(int j=0;j<4;++j)b.m[i][j]=a.m[j][i];return b;}
|
||||
struct GPUVertex { float p[3],n[3]; };
|
||||
struct CB { M4 viewProj; float camera[3],pad; float light[3],fogStart; float fogEnd,ambient,pad2[2]; };
|
||||
struct App {
|
||||
HWND hwnd{}; ID3D11Device* dev{}; ID3D11DeviceContext* ctx{}; IDXGISwapChain* swap{}; ID3D11RenderTargetView* rtv{}; ID3D11DepthStencilView* dsv{};
|
||||
ID3D11VertexShader* vs{}; ID3D11PixelShader* ps{}; ID3D11InputLayout* layout{}; ID3D11Buffer* cb{}; ID3D11RasterizerState* solid{}; ID3D11RasterizerState* wire{};
|
||||
ID3D11Buffer* nearVB{}; ID3D11Buffer* nearReliefVB{}; ID3D11Buffer* nearIB{}; ID3D11Buffer* farVB{}; ID3D11Buffer* farIB{}; UINT nearIndices{},farIndices{};
|
||||
uint64_t seed=1;int workers=6;double generationMs{};bool showNear=true,showFar=true,wireframe=false,captured=false,showDisplacement=true,captureFailed=false;float yaw=0,pitch=0,speed=8;V3 pos{};POINT last{};int width=1280,height=800;Clock::time_point prev=Clock::now();double frameMs=0;bool keys[256]{};std::filesystem::path captureTarget;
|
||||
void release_mesh(){if(nearVB)nearVB->Release();if(nearReliefVB)nearReliefVB->Release();if(nearIB)nearIB->Release();if(farVB)farVB->Release();if(farIB)farIB->Release();nearVB=nearReliefVB=nearIB=farVB=farIB=nullptr;nearIndices=farIndices=0;}
|
||||
~App(){release_mesh();if(solid)solid->Release();if(wire)wire->Release();if(cb)cb->Release();if(layout)layout->Release();if(vs)vs->Release();if(ps)ps->Release();if(rtv)rtv->Release();if(dsv)dsv->Release();if(swap)swap->Release();if(ctx)ctx->Release();if(dev)dev->Release();}
|
||||
bool make_buffer(ID3D11Buffer** out,const void* data,size_t bytes,UINT bind){D3D11_BUFFER_DESC d{};d.ByteWidth=static_cast<UINT>(bytes);d.Usage=D3D11_USAGE_DEFAULT;d.BindFlags=bind;D3D11_SUBRESOURCE_DATA s{};s.pSysMem=data;return SUCCEEDED(dev->CreateBuffer(&d,&s,out));}
|
||||
bool generate(){
|
||||
auto begin=Clock::now(); Corpus corpus;build_corpus(corpus,seed);AxisData axes[3];AxisStats stats[3];bool overflow=false;
|
||||
for(int a=0;a<3;++a)axes[a]=solve_axis(corpus,a,workers,stats[a],overflow);
|
||||
BlendStats blendStats{};apply_blend_zones(corpus,axes,stats,blendStats);
|
||||
std::vector<uint64_t> active;active_cell_masks(axes[2],active);MeshOutput nearMesh=emit_dual_contour_mesh(corpus,axes,axes[2],active);FarOutput farMesh=build_far_world(seed);
|
||||
std::vector<Vertex> reliefVertices;double maxDisplacement=0;apply_surface_displacement(nearMesh,reliefVertices,maxDisplacement);
|
||||
generationMs=ms(begin,Clock::now());release_mesh();
|
||||
std::vector<GPUVertex> nv;nv.reserve(nearMesh.vertices.size());for(const auto& v:nearMesh.vertices)nv.push_back({{v.v.x,v.v.y,v.v.z},{v.v.nx,v.v.ny,v.v.nz}});
|
||||
std::vector<GPUVertex> rv;rv.reserve(reliefVertices.size());for(const auto& v:reliefVertices)rv.push_back({{v.x,v.y,v.z},{v.nx,v.ny,v.nz}});
|
||||
std::vector<GPUVertex> fv;fv.reserve(farMesh.vertices.size());for(const auto& v:farMesh.vertices)fv.push_back({{v.x,v.y,v.z},{v.nx,v.ny,v.nz}});
|
||||
if(!nv.empty()&&(!make_buffer(&nearVB,nv.data(),nv.size()*sizeof(GPUVertex),D3D11_BIND_VERTEX_BUFFER)||!make_buffer(&nearReliefVB,rv.data(),rv.size()*sizeof(GPUVertex),D3D11_BIND_VERTEX_BUFFER)||!make_buffer(&nearIB,nearMesh.indices.data(),nearMesh.indices.size()*sizeof(uint32_t),D3D11_BIND_INDEX_BUFFER)))return false;
|
||||
if(!fv.empty()&&(!make_buffer(&farVB,fv.data(),fv.size()*sizeof(GPUVertex),D3D11_BIND_VERTEX_BUFFER)||!make_buffer(&farIB,farMesh.indices.data(),farMesh.indices.size()*sizeof(uint32_t),D3D11_BIND_INDEX_BUFFER)))return false;
|
||||
nearIndices=static_cast<UINT>(nearMesh.indices.size());farIndices=static_cast<UINT>(farMesh.indices.size());
|
||||
// The first generated room is centred near local (-13, -13, -8.5) metres.
|
||||
// These offsets match its deterministic seed-1 center and keep the camera in its air volume.
|
||||
Rng rng(seed ^ 0x0A57A20261002ull);int lx=-1300+rng.range(-170,170),ly=-1300+rng.range(-170,170),lz=-850+rng.range(-120,120);
|
||||
pos={kWorldOriginCm[0]*.01f+lx*.01f,kWorldOriginCm[1]*.01f+ly*.01f,kWorldOriginCm[2]*.01f+lz*.01f};yaw=0;pitch=0;
|
||||
return true;
|
||||
}
|
||||
bool init_device(){
|
||||
DXGI_SWAP_CHAIN_DESC sd{};sd.BufferCount=1;sd.BufferDesc.Format=DXGI_FORMAT_R8G8B8A8_UNORM;sd.BufferUsage=DXGI_USAGE_RENDER_TARGET_OUTPUT;sd.OutputWindow=hwnd;sd.SampleDesc.Count=1;sd.Windowed=TRUE;sd.SwapEffect=DXGI_SWAP_EFFECT_DISCARD;
|
||||
UINT flags=0;
|
||||
#if defined(_DEBUG)
|
||||
flags|=D3D11_CREATE_DEVICE_DEBUG;
|
||||
#endif
|
||||
D3D_FEATURE_LEVEL fl{};if(FAILED(D3D11CreateDeviceAndSwapChain(nullptr,D3D_DRIVER_TYPE_HARDWARE,nullptr,flags,nullptr,0,D3D11_SDK_VERSION,&sd,&swap,&dev,&fl,&ctx)))return false;
|
||||
const char* shader="cbuffer C:register(b0){float4x4 VP;float3 Camera;float Pad;float3 LightDir;float FogStart;float FogEnd;float Ambient;float2 Pad2;} struct I{float3 p:POSITION;float3 n:NORMAL;};struct O{float4 p:SV_POSITION;float3 world:TEXCOORD0;float3 n:NORMAL;};O VS(I i){O o;o.p=mul(float4(i.p,1),VP);o.world=i.p;o.n=i.n;return o;}float hash3(float3 p){return frac(sin(dot(p,float3(127.1,311.7,74.7)))*43758.5453);}float noise3(float3 p){float3 i=floor(p),f=frac(p);f=f*f*(3-2*f);float a=lerp(hash3(i),hash3(i+float3(1,0,0)),f.x);float b=lerp(hash3(i+float3(0,1,0)),hash3(i+float3(1,1,0)),f.x);float c=lerp(hash3(i+float3(0,0,1)),hash3(i+float3(1,0,1)),f.x);float d=lerp(hash3(i+float3(0,1,1)),hash3(i+float3(1,1,1)),f.x);return lerp(lerp(a,b,f.y),lerp(c,d,f.y),f.z);}float4 PS(O i):SV_TARGET{float3 N=normalize(i.n);float3 L=normalize(-LightDir);float diffuse=saturate(dot(N,L))*0.72;float3 toCam=Camera-i.world;float dist=length(toCam);float head=saturate(dot(N,normalize(toCam)))*0.85/(1+dist*0.018);float light=Ambient+diffuse+head;float mottling=0.68*noise3(i.world*0.28)+0.32*noise3(i.world*1.0);float shade=0.45+mottling;float3 rock=float3(0.46,0.39,0.30)*shade*light;float fog=saturate((dist-FogStart)/(FogEnd-FogStart));return float4(lerp(rock,float3(0.075,0.095,0.12),fog),1);} ";
|
||||
ID3DBlob *vb=nullptr,*pb=nullptr,*err=nullptr;if(FAILED(D3DCompile(shader,strlen(shader),nullptr,nullptr,nullptr,"VS","vs_4_0",0,0,&vb,&err)))return false;if(FAILED(D3DCompile(shader,strlen(shader),nullptr,nullptr,nullptr,"PS","ps_4_0",0,0,&pb,&err))){if(vb)vb->Release();return false;}
|
||||
HRESULT hr=dev->CreateVertexShader(vb->GetBufferPointer(),vb->GetBufferSize(),nullptr,&vs);if(SUCCEEDED(hr))hr=dev->CreatePixelShader(pb->GetBufferPointer(),pb->GetBufferSize(),nullptr,&ps);
|
||||
D3D11_INPUT_ELEMENT_DESC il[]={{"POSITION",0,DXGI_FORMAT_R32G32B32_FLOAT,0,0,D3D11_INPUT_PER_VERTEX_DATA,0},{"NORMAL",0,DXGI_FORMAT_R32G32B32_FLOAT,0,12,D3D11_INPUT_PER_VERTEX_DATA,0}};if(SUCCEEDED(hr))hr=dev->CreateInputLayout(il,2,vb->GetBufferPointer(),vb->GetBufferSize(),&layout);vb->Release();pb->Release();if(FAILED(hr))return false;
|
||||
D3D11_BUFFER_DESC cbd{};cbd.ByteWidth=sizeof(CB);cbd.Usage=D3D11_USAGE_DEFAULT;cbd.BindFlags=D3D11_BIND_CONSTANT_BUFFER;if(FAILED(dev->CreateBuffer(&cbd,nullptr,&cb)))return false;
|
||||
D3D11_RASTERIZER_DESC rd{};rd.FillMode=D3D11_FILL_SOLID;rd.CullMode=D3D11_CULL_NONE;rd.DepthClipEnable=TRUE;if(FAILED(dev->CreateRasterizerState(&rd,&solid)))return false;rd.FillMode=D3D11_FILL_WIREFRAME;if(FAILED(dev->CreateRasterizerState(&rd,&wire)))return false;
|
||||
return resize(width,height);
|
||||
}
|
||||
bool resize(int w,int h){if(!dev||w<1||h<1)return true;width=w;height=h;if(rtv){rtv->Release();rtv=nullptr;}if(dsv){dsv->Release();dsv=nullptr;}if(FAILED(swap->ResizeBuffers(0,w,h,DXGI_FORMAT_UNKNOWN,0)))return false;ID3D11Texture2D* back{};if(FAILED(swap->GetBuffer(0,__uuidof(ID3D11Texture2D),reinterpret_cast<void**>(&back))))return false;HRESULT hr=dev->CreateRenderTargetView(back,nullptr,&rtv);back->Release();if(FAILED(hr))return false;D3D11_TEXTURE2D_DESC dd{};dd.Width=w;dd.Height=h;dd.MipLevels=1;dd.ArraySize=1;dd.Format=DXGI_FORMAT_D24_UNORM_S8_UINT;dd.SampleDesc.Count=1;dd.BindFlags=D3D11_BIND_DEPTH_STENCIL;ID3D11Texture2D* depth{};if(FAILED(dev->CreateTexture2D(&dd,nullptr,&depth)))return false;hr=dev->CreateDepthStencilView(depth,nullptr,&dsv);depth->Release();return SUCCEEDED(hr);}
|
||||
bool save_backbuffer_png(const std::filesystem::path& path){
|
||||
ID3D11Texture2D* back{};ID3D11Texture2D* staging{};IWICImagingFactory* factory{};IWICStream* stream{};IWICBitmapEncoder* encoder{};IWICBitmapFrameEncode* frame{};IPropertyBag2* props{};D3D11_TEXTURE2D_DESC desc{};D3D11_MAPPED_SUBRESOURCE mapped{};bool ok=false;HRESULT hr=S_OK;const char* stage="get swapchain buffer";
|
||||
if(FAILED(hr=swap->GetBuffer(0,__uuidof(ID3D11Texture2D),reinterpret_cast<void**>(&back))))goto done;
|
||||
back->GetDesc(&desc);desc.Usage=D3D11_USAGE_STAGING;desc.BindFlags=0;desc.CPUAccessFlags=D3D11_CPU_ACCESS_READ;desc.MiscFlags=0;
|
||||
stage="create staging texture";if(FAILED(hr=dev->CreateTexture2D(&desc,nullptr,&staging)))goto done;
|
||||
ctx->CopyResource(staging,back);
|
||||
stage="map staging texture";if(FAILED(hr=ctx->Map(staging,0,D3D11_MAP_READ,0,&mapped)))goto done;
|
||||
{
|
||||
const UINT stride=desc.Width*4;std::vector<BYTE> pixels(static_cast<size_t>(stride)*desc.Height);
|
||||
for(UINT y=0;y<desc.Height;++y){const BYTE* src=static_cast<const BYTE*>(mapped.pData)+static_cast<size_t>(y)*mapped.RowPitch;BYTE* dst=pixels.data()+static_cast<size_t>(y)*stride;for(UINT x=0;x<desc.Width;++x){dst[x*4]=src[x*4+2];dst[x*4+1]=src[x*4+1];dst[x*4+2]=src[x*4];dst[x*4+3]=src[x*4+3];}}
|
||||
ctx->Unmap(staging,0);
|
||||
stage="create WIC factory";if(FAILED(hr=CoCreateInstance(CLSID_WICImagingFactory,nullptr,CLSCTX_INPROC_SERVER,IID_PPV_ARGS(&factory))))goto done;
|
||||
stage="create WIC stream";if(FAILED(hr=factory->CreateStream(&stream)))goto done;
|
||||
stage="open PNG path";if(FAILED(hr=stream->InitializeFromFilename(path.c_str(),GENERIC_WRITE)))goto done;
|
||||
stage="create PNG encoder";if(FAILED(hr=factory->CreateEncoder(GUID_ContainerFormatPng,nullptr,&encoder)))goto done;
|
||||
stage="initialize PNG encoder";if(FAILED(hr=encoder->Initialize(stream,WICBitmapEncoderNoCache)))goto done;
|
||||
stage="create PNG frame";if(FAILED(hr=encoder->CreateNewFrame(&frame,&props)))goto done;
|
||||
stage="initialize PNG frame";if(FAILED(hr=frame->Initialize(props)))goto done;
|
||||
stage="set PNG size";if(FAILED(hr=frame->SetSize(desc.Width,desc.Height)))goto done;
|
||||
{WICPixelFormatGUID format=GUID_WICPixelFormat32bppBGRA;stage="set PNG pixel format";hr=frame->SetPixelFormat(&format);if(FAILED(hr)||format!=GUID_WICPixelFormat32bppBGRA){if(SUCCEEDED(hr))hr=WINCODEC_ERR_UNSUPPORTEDPIXELFORMAT;goto done;}}
|
||||
stage="write PNG pixels";if(FAILED(hr=frame->WritePixels(desc.Height,stride,static_cast<UINT>(pixels.size()),pixels.data())))goto done;
|
||||
stage="commit PNG";if(FAILED(hr=frame->Commit())||FAILED(hr=encoder->Commit()))goto done;ok=true;
|
||||
}
|
||||
done:
|
||||
if(!ok)std::fprintf(stderr,"Capture failed at %s (HRESULT 0x%08lx)\n",stage,static_cast<unsigned long>(hr));
|
||||
if(staging)staging->Release();if(back)back->Release();if(frame)frame->Release();if(props)props->Release();if(encoder)encoder->Release();if(stream)stream->Release();if(factory)factory->Release();return ok;
|
||||
}
|
||||
V3 forward(){return norm({std::sin(yaw)*std::cos(pitch),std::cos(yaw)*std::cos(pitch),std::sin(pitch)});}
|
||||
void title(){char b[256];snprintf(b,sizeof(b),"Interval spike | %.2f ms/frame | %u tris | relief %s | (%.1f, %.1f, %.1f) m | gen %.1f ms | seed %llu",frameMs,((showNear?nearIndices:0)+(showFar?farIndices:0))/3,showDisplacement?"ON":"OFF",pos.x,pos.y,pos.z,generationMs,(unsigned long long)seed);SetWindowTextA(hwnd,b);}
|
||||
void draw(){auto now=Clock::now();float dt=std::clamp(std::chrono::duration<float>(now-prev).count(),0.0f,.1f);prev=now;frameMs=dt*1000;
|
||||
V3 f=forward(),r=norm(cross(f,{0,0,1})),u=norm(cross(r,f));float step=speed*dt*(keys[VK_SHIFT]?4.0f:1.0f);if(keys['W'])pos=add(pos,mul(f,step));if(keys['S'])pos=sub(pos,mul(f,step));if(keys['A'])pos=sub(pos,mul(r,step));if(keys['D'])pos=add(pos,mul(r,step));if(keys[VK_SPACE]||keys['E'])pos.z+=step;if(keys[VK_CONTROL]||keys['Q'])pos.z-=step;
|
||||
float clear[]={.075f,.095f,.12f,1};ctx->OMSetRenderTargets(1,&rtv,dsv);ctx->ClearRenderTargetView(rtv,clear);ctx->ClearDepthStencilView(dsv,D3D11_CLEAR_DEPTH,1,0);D3D11_VIEWPORT vp{0,0,(float)width,(float)height,0,1};ctx->RSSetViewports(1,&vp);ctx->RSSetState(wireframe?wire:solid);ctx->IASetInputLayout(layout);ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
|
||||
CB c{};M4 v=view_matrix(pos,f,r,u);M4 p=perspective(1.0472f,(float)width/height,.05f,20000); // compose view and projection for row-vector shader
|
||||
M4 out{};for(int i=0;i<4;++i)for(int j=0;j<4;++j)for(int k=0;k<4;++k)out.m[i][j]+=v.m[i][k]*p.m[k][j];c.viewProj=transpose(out);c.camera[0]=pos.x;c.camera[1]=pos.y;c.camera[2]=pos.z;c.light[0]=-.45f;c.light[1]=-.8f;c.light[2]=-.35f;c.fogStart=80;c.fogEnd=1200;c.ambient=.28f;ctx->UpdateSubresource(cb,0,nullptr,&c,0,0);ctx->VSSetConstantBuffers(0,1,&cb);ctx->PSSetConstantBuffers(0,1,&cb);ctx->VSSetShader(vs,nullptr,0);ctx->PSSetShader(ps,nullptr,0);
|
||||
auto mesh=[&](ID3D11Buffer* vb,ID3D11Buffer* ib,UINT count){if(!vb||!ib||!count)return;UINT stride=sizeof(GPUVertex),offset=0;ctx->IASetVertexBuffers(0,1,&vb,&stride,&offset);ctx->IASetIndexBuffer(ib,DXGI_FORMAT_R32_UINT,0);ctx->DrawIndexed(count,0,0);};if(showNear)mesh(showDisplacement?nearReliefVB:nearVB,nearIB,nearIndices);if(showFar)mesh(farVB,farIB,farIndices);if(!captureTarget.empty()){if(save_backbuffer_png(captureTarget))std::printf("Captured %ls\n",captureTarget.c_str());else{std::fprintf(stderr,"Capture failed: %ls\n",captureTarget.c_str());captureFailed=true;}captureTarget.clear();}swap->Present(1,0);title();}
|
||||
void capture(bool on){captured=on;if(on){SetCapture(hwnd);ShowCursor(FALSE);GetCursorPos(&last);POINT c{width/2,height/2};ClientToScreen(hwnd,&c);SetCursorPos(c.x,c.y);}else{ReleaseCapture();ShowCursor(TRUE);}}
|
||||
};
|
||||
App* g{};
|
||||
LRESULT CALLBACK wndproc(HWND h,UINT m,WPARAM w,LPARAM l){if(!g)return DefWindowProc(h,m,w,l);switch(m){case WM_SIZE:if(w!=SIZE_MINIMIZED)g->resize(LOWORD(l),HIWORD(l));return 0;case WM_KEYDOWN:if(w<256)g->keys[w]=true;if(w==VK_ESCAPE){if(g->captured)g->capture(false);else DestroyWindow(h);}if(w=='F')g->wireframe=!g->wireframe;if(w=='R')g->showDisplacement=!g->showDisplacement;if(w=='N'||w=='P'){g->seed+=(w=='N'?1:static_cast<uint64_t>(-1));if(!g->generate())MessageBoxA(h,"Could not create mesh buffers.","Viewer error",MB_ICONERROR);}if(w=='M')g->showNear=!g->showNear;if(w=='B')g->showFar=!g->showFar;return 0;case WM_KEYUP:if(w<256)g->keys[w]=false;return 0;case WM_RBUTTONDOWN:g->capture(true);return 0;case WM_RBUTTONUP:g->capture(false);return 0;case WM_MOUSEMOVE:if(g->captured){POINT c{g->width/2,g->height/2};ClientToScreen(h,&c);POINT p{GET_X_LPARAM(l),GET_Y_LPARAM(l)};POINT sc=p;ClientToScreen(h,&sc);int dx=sc.x-c.x,dy=sc.y-c.y;if(dx||dy){g->yaw+=dx*.0035f;g->pitch=std::clamp(g->pitch-dy*.0035f,-1.55f,1.55f);SetCursorPos(c.x,c.y);}}return 0;case WM_MOUSEWHEEL:g->speed=std::clamp(g->speed*(GET_WHEEL_DELTA_WPARAM(w)>0?1.25f:.8f),.25f,1000.0f);return 0;case WM_DESTROY:PostQuitMessage(0);return 0;}return DefWindowProc(h,m,w,l);}
|
||||
}
|
||||
|
||||
int WINAPI WinMain(HINSTANCE instance,HINSTANCE,LPSTR,int){
|
||||
int argc=0;LPWSTR* argv=CommandLineToArgvW(GetCommandLineW(),&argc);uint64_t seed=1;int workers=6,positional=0;std::filesystem::path captureDir;bool captureMode=false;
|
||||
if(argv)for(int i=1;i<argc;++i){if(std::wcscmp(argv[i],L"--capture-dir")==0&&i+1<argc){captureDir=argv[++i];captureMode=true;}else if(std::wcscmp(argv[i],L"--seed")==0&&i+1<argc)seed=_wcstoui64(argv[++i],nullptr,10);else if(std::wcscmp(argv[i],L"--workers")==0&&i+1<argc)workers=std::clamp(_wtoi(argv[++i]),1,8);else if(positional==0){seed=_wcstoui64(argv[i],nullptr,10);++positional;}else if(positional==1){workers=std::clamp(_wtoi(argv[i]),1,8);++positional;}}
|
||||
if(FAILED(CoInitializeEx(nullptr,COINIT_MULTITHREADED))){if(argv)LocalFree(argv);return 1;}
|
||||
AllocConsole();FILE* stream{};freopen_s(&stream,"CONOUT$","w",stdout);std::printf("Controls: hold right mouse button to look; WASD move; Space/Ctrl (E/Q) up/down; Shift faster; wheel speed; F wireframe; R relief toggle; M near; B far; N/P next/previous seed; Esc quit.\n");
|
||||
App app;g=&app;app.seed=seed;app.workers=workers;WNDCLASSA wc{};wc.lpfnWndProc=wndproc;wc.hInstance=instance;wc.lpszClassName="IntervalSpikeViewer2";wc.hCursor=LoadCursor(nullptr,IDC_ARROW);RegisterClassA(&wc);RECT rc{0,0,app.width,app.height};AdjustWindowRect(&rc,WS_OVERLAPPEDWINDOW,FALSE);app.hwnd=CreateWindowA(wc.lpszClassName,"Interval spike viewer 2",WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,rc.right-rc.left,rc.bottom-rc.top,nullptr,nullptr,instance,nullptr);if(!app.hwnd||!app.init_device()){MessageBoxA(nullptr,"Direct3D 11 initialization failed.","Viewer error",MB_ICONERROR);CoUninitialize();if(argv)LocalFree(argv);return 1;}ShowWindow(app.hwnd,SW_SHOW);UpdateWindow(app.hwnd);if(!app.generate()){MessageBoxA(app.hwnd,"World generation or GPU buffer creation failed.","Viewer error",MB_ICONERROR);CoUninitialize();if(argv)LocalFree(argv);return 2;}
|
||||
if(captureMode){
|
||||
std::error_code ec;std::filesystem::create_directories(captureDir,ec);if(ec){std::fprintf(stderr,"Could not create capture directory: %ls\n",captureDir.c_str());CoUninitialize();if(argv)LocalFree(argv);return 3;}
|
||||
auto capture=[&](const wchar_t* name){app.captureTarget=captureDir/name;app.draw();};
|
||||
capture(L"chamber.png");
|
||||
app.showDisplacement=false;capture(L"chamber_relief_off.png");app.showDisplacement=true;
|
||||
app.pos={kWorldOriginCm[0]*.01f-2.5f,kWorldOriginCm[1]*.01f-.75f,kWorldOriginCm[2]*.01f-3.5f};app.yaw=0;app.pitch=0;capture(L"junction.png");
|
||||
Rng rng(seed ^ 0x0A57A20261002ull);int lx=-1300+rng.range(-170,170),ly=-1300+rng.range(-170,170),lz=-850+rng.range(-120,120);
|
||||
app.pos={kWorldOriginCm[0]*.01f+(lx+500)*.01f,kWorldOriginCm[1]*.01f+ly*.01f,kWorldOriginCm[2]*.01f+lz*.01f};app.yaw=1.5707963f;app.pitch=0;capture(L"wall.png");
|
||||
const bool ok=!app.captureFailed;DestroyWindow(app.hwnd);g=nullptr;CoUninitialize();if(argv)LocalFree(argv);return ok?0:4;
|
||||
}
|
||||
if(argv)LocalFree(argv);MSG msg{};while(msg.message!=WM_QUIT){if(PeekMessage(&msg,nullptr,0,0,PM_REMOVE)){TranslateMessage(&msg);DispatchMessage(&msg);}else app.draw();}CoUninitialize();return 0;
|
||||
}
|
||||
|
After Width: | Height: | Size: 418 KiB |
|
After Width: | Height: | Size: 406 KiB |
|
After Width: | Height: | Size: 340 KiB |
|
After Width: | Height: | Size: 429 KiB |
|
After Width: | Height: | Size: 498 KiB |
@@ -0,0 +1,31 @@
|
||||
Machine: Ryzen 7 5800X3D, Windows, MSVC 2022, 2026-09-29.
|
||||
Command from Saved/Research:
|
||||
cmd /c '"E:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat" >nul && cl /nologo /O2 /std:c++20 /EHsc /arch:AVX2 round2_probe.cpp /Fe:round2_probe.exe'
|
||||
.\round2_probe.exe
|
||||
|
||||
First run:
|
||||
full trial=0 scalar_ms=492.007 avx2_ms=101.294 speedup=4.857 mismatch=0
|
||||
full trial=1 scalar_ms=496.238 avx2_ms=106.015 speedup=4.681 mismatch=0
|
||||
full trial=2 scalar_ms=469.590 avx2_ms=92.511 speedup=5.076 mismatch=0
|
||||
crater radius_vox=40 touched_bricks=714 tile_AABB_max=64 samples_AABB=531441
|
||||
crater copy_ms=1.623 edit_ms=0.865 changed_lattice=94843 copy_bytes=33554432
|
||||
|
||||
Second run after dead-code cleanup (same evaluation code):
|
||||
full trial=0 scalar_ms=430.286 avx2_ms=89.209 speedup=4.823 mismatch=0
|
||||
full trial=1 scalar_ms=456.202 avx2_ms=107.464 speedup=4.245 mismatch=0
|
||||
full trial=2 scalar_ms=447.117 avx2_ms=94.584 speedup=4.727 mismatch=0
|
||||
crater radius_vox=40 touched_bricks=714 tile_AABB_max=64 samples_AABB=531441
|
||||
crater copy_ms=1.422 edit_ms=0.914 changed_lattice=94843 copy_bytes=33554432
|
||||
|
||||
Third run after adding a scattered direct-query measurement (same field evaluation code):
|
||||
full trial=0 scalar_ms=478.627 avx2_ms=85.763 speedup=5.581 mismatch=0
|
||||
full trial=1 scalar_ms=471.015 avx2_ms=80.318 speedup=5.864 mismatch=0
|
||||
full trial=2 scalar_ms=444.320 avx2_ms=82.728 speedup=5.371 mismatch=0
|
||||
random_query trial=0 count=200000 ms=63.030 ns_per_query=315.1 checksum=-1501231
|
||||
random_query trial=1 count=200000 ms=62.785 ns_per_query=313.9 checksum=-1501231
|
||||
random_query trial=2 count=200000 ms=62.799 ns_per_query=314.0 checksum=-1501231
|
||||
crater radius_vox=40 touched_bricks=714 tile_AABB_max=64 samples_AABB=531441
|
||||
crater copy_ms=1.394 edit_ms=0.861 changed_lattice=94843 copy_bytes=33554432
|
||||
|
||||
Both MSVC builds succeeded. The vcvars script emitted a missing-vswhere.exe notice.
|
||||
Synthetic workload; no Unreal build/launch or GPU benchmark.
|
||||
@@ -0,0 +1,125 @@
|
||||
// Standalone research probe. Synthetic operator composition inspired by VoxelForge;
|
||||
// NOT extracted production code. Compile with MSVC /O2 /arch:AVX2 /std:c++20.
|
||||
#include <immintrin.h>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
constexpr int N=256, B=8, W=6;
|
||||
using V=__m256i;
|
||||
static std::vector<int16_t> scalarField(size_t(N)*N*N), vectorField(size_t(N)*N*N);
|
||||
alignas(32) static int32_t fadeTable[256];
|
||||
static inline uint32_t hashS(uint32_t x,uint32_t y,uint32_t z,uint32_t seed){
|
||||
uint32_t h=seed^(x*0x9e3779b1u)^(y*0x85ebca77u)^(z*0xc2b2ae3du);
|
||||
h^=h>>16; h*=0x7feb352du; h^=h>>15; h*=0x846ca68bu; return h^(h>>16);
|
||||
}
|
||||
static inline V hashV(V x,V y,V z,V seed){
|
||||
V h=_mm256_xor_si256(seed,_mm256_xor_si256(_mm256_mullo_epi32(x,_mm256_set1_epi32(0x9e3779b1u)),
|
||||
_mm256_xor_si256(_mm256_mullo_epi32(y,_mm256_set1_epi32(0x85ebca77u)),_mm256_mullo_epi32(z,_mm256_set1_epi32(0xc2b2ae3du)))));
|
||||
h=_mm256_xor_si256(h,_mm256_srli_epi32(h,16)); h=_mm256_mullo_epi32(h,_mm256_set1_epi32(0x7feb352du));
|
||||
h=_mm256_xor_si256(h,_mm256_srli_epi32(h,15)); h=_mm256_mullo_epi32(h,_mm256_set1_epi32(0x846ca68bu));
|
||||
return _mm256_xor_si256(h,_mm256_srli_epi32(h,16));
|
||||
}
|
||||
static inline int lerpS(int a,int b,int t){return a+((b-a)*t>>8);}
|
||||
static inline V lerpV(V a,V b,V t){return _mm256_add_epi32(a,_mm256_srai_epi32(_mm256_mullo_epi32(_mm256_sub_epi32(b,a),t),8));}
|
||||
static inline int gradS(uint32_t h,int x,int y,int z){
|
||||
h&=15;int u=(h&8)?y:x;int v=(h&12)==0?y:((h&13)==12?x:z);
|
||||
return ((h&1)?-u:u)+((h&2)?-v:v);
|
||||
}
|
||||
static inline V selectV(V mask,V a,V b){return _mm256_blendv_epi8(b,a,mask);}
|
||||
static inline V gradV(V h,V x,V y,V z){
|
||||
V zero=_mm256_setzero_si256(); h=_mm256_and_si256(h,_mm256_set1_epi32(15));
|
||||
V u=selectV(_mm256_cmpeq_epi32(_mm256_and_si256(h,_mm256_set1_epi32(8)),zero),x,y);
|
||||
V isY=_mm256_cmpeq_epi32(_mm256_and_si256(h,_mm256_set1_epi32(12)),zero);
|
||||
V isX=_mm256_cmpeq_epi32(_mm256_and_si256(h,_mm256_set1_epi32(13)),_mm256_set1_epi32(12));
|
||||
V v=selectV(isY,y,selectV(isX,x,z));
|
||||
V negU=_mm256_cmpeq_epi32(_mm256_and_si256(h,_mm256_set1_epi32(1)),_mm256_set1_epi32(1));
|
||||
V negV=_mm256_cmpeq_epi32(_mm256_and_si256(h,_mm256_set1_epi32(2)),_mm256_set1_epi32(2));
|
||||
return _mm256_add_epi32(_mm256_sub_epi32(_mm256_xor_si256(u,negU),negU),_mm256_sub_epi32(_mm256_xor_si256(v,negV),negV));
|
||||
}
|
||||
static inline int noiseS(int x,int y,int z,int shift,int seed){
|
||||
int ix=x>>shift,iy=y>>shift,iz=z>>shift,fx=(x&((1<<shift)-1))<<(8-shift),fy=(y&((1<<shift)-1))<<(8-shift),fz=(z&((1<<shift)-1))<<(8-shift);
|
||||
int ux=fadeTable[fx],uy=fadeTable[fy],uz=fadeTable[fz];
|
||||
int yz[2]; for(int dz=0;dz<2;++dz){int yy[2];for(int dy=0;dy<2;++dy){
|
||||
int a=gradS(hashS(ix,iy+dy,iz+dz,seed),fx,fy-dy*256,fz-dz*256);
|
||||
int b=gradS(hashS(ix+1,iy+dy,iz+dz,seed),fx-256,fy-dy*256,fz-dz*256);
|
||||
yy[dy]=lerpS(a,b,ux);}
|
||||
yz[dz]=lerpS(yy[0],yy[1],uy);} return lerpS(yz[0],yz[1],uz)/4;
|
||||
}
|
||||
static inline V noiseV(V x,V y,V z,int shift,int seed){
|
||||
V mask=_mm256_set1_epi32((1<<shift)-1), one=_mm256_set1_epi32(1);
|
||||
V ix=_mm256_srai_epi32(x,shift),iy=_mm256_srai_epi32(y,shift),iz=_mm256_srai_epi32(z,shift);
|
||||
V fx=_mm256_slli_epi32(_mm256_and_si256(x,mask),8-shift),fy=_mm256_slli_epi32(_mm256_and_si256(y,mask),8-shift),fz=_mm256_slli_epi32(_mm256_and_si256(z,mask),8-shift);
|
||||
V ux=_mm256_i32gather_epi32(fadeTable,fx,4),uy=_mm256_i32gather_epi32(fadeTable,fy,4),uz=_mm256_i32gather_epi32(fadeTable,fz,4);
|
||||
V yz[2]; for(int dz=0;dz<2;++dz){V yy[2];for(int dy=0;dy<2;++dy){
|
||||
V y1=_mm256_add_epi32(iy,_mm256_set1_epi32(dy)),z1=_mm256_add_epi32(iz,_mm256_set1_epi32(dz));
|
||||
V yyoff=_mm256_sub_epi32(fy,_mm256_set1_epi32(dy*256)),zzoff=_mm256_sub_epi32(fz,_mm256_set1_epi32(dz*256));
|
||||
V a=gradV(hashV(ix,y1,z1,_mm256_set1_epi32(seed)),fx,yyoff,zzoff);
|
||||
V b=gradV(hashV(_mm256_add_epi32(ix,one),y1,z1,_mm256_set1_epi32(seed)),_mm256_sub_epi32(fx,_mm256_set1_epi32(256)),yyoff,zzoff);
|
||||
yy[dy]=lerpV(a,b,ux);}yz[dz]=lerpV(yy[0],yy[1],uy);}
|
||||
V raw=lerpV(yz[0],yz[1],uz);V bias=_mm256_and_si256(_mm256_srai_epi32(raw,31),_mm256_set1_epi32(3));
|
||||
return _mm256_srai_epi32(_mm256_add_epi32(raw,bias),2);
|
||||
}
|
||||
static inline int smoothMaxS(int a,int b){int h=std::max(0,64-std::abs(a-b));return std::max(a,b)+(h*h>>8);}
|
||||
static inline V smoothMaxV(V a,V b){V d=_mm256_sub_epi32(a,b);V h=_mm256_max_epi32(_mm256_setzero_si256(),_mm256_sub_epi32(_mm256_set1_epi32(64),_mm256_abs_epi32(d)));
|
||||
return _mm256_add_epi32(_mm256_max_epi32(a,b),_mm256_srai_epi32(_mm256_mullo_epi32(h,h),8));}
|
||||
static inline V evalV(V x,V y,V z){
|
||||
V c=_mm256_set1_epi32(1024), warp=_mm256_srai_epi32(noiseV(_mm256_add_epi32(x,c),_mm256_add_epi32(y,c),_mm256_add_epi32(z,c),4,17),4);
|
||||
V nx=_mm256_add_epi32(_mm256_add_epi32(x,c),warp),ny=_mm256_sub_epi32(_mm256_add_epi32(y,c),warp),nz=_mm256_add_epi32(z,c);
|
||||
V fb=_mm256_add_epi32(noiseV(nx,ny,nz,4,100),_mm256_add_epi32(_mm256_srai_epi32(noiseV(nx,ny,nz,3,101),1),_mm256_srai_epi32(noiseV(nx,ny,nz,2,102),2)));
|
||||
V ridge=_mm256_sub_epi32(_mm256_set1_epi32(128),_mm256_abs_epi32(noiseV(nx,ny,nz,3,202)));
|
||||
// Scalar signed division truncates toward zero; AVX2 shift rounds negative down.
|
||||
// Correct sign before shift to make the two exact.
|
||||
auto divPow2=[](V a,int shift){V bias=_mm256_and_si256(_mm256_srai_epi32(a,31),_mm256_set1_epi32((1<<shift)-1));return _mm256_srai_epi32(_mm256_add_epi32(a,bias),shift);};
|
||||
// Exact truncation toward zero matches scalar division by powers of two.
|
||||
V floor=_mm256_add_epi32(_mm256_sub_epi32(z,_mm256_set1_epi32(84)),divPow2(fb,2));
|
||||
V ceil=_mm256_add_epi32(_mm256_sub_epi32(_mm256_set1_epi32(170),z),divPow2(ridge,2));
|
||||
V stratum=_mm256_min_epi32(floor,ceil),dy=_mm256_sub_epi32(y,_mm256_set1_epi32(128)),dz=_mm256_sub_epi32(z,_mm256_set1_epi32(128));
|
||||
V dy2=_mm256_mullo_epi32(dy,dy),dz2=_mm256_mullo_epi32(dz,dz),dx=_mm256_sub_epi32(x,_mm256_set1_epi32(128));
|
||||
V passage=divPow2(_mm256_sub_epi32(_mm256_set1_epi32(18*18),_mm256_add_epi32(dy2,dz2)),3);
|
||||
V room=divPow2(_mm256_sub_epi32(_mm256_set1_epi32(35*35),_mm256_add_epi32(_mm256_mullo_epi32(dx,dx),_mm256_add_epi32(dy2,dz2))),4);
|
||||
return smoothMaxV(stratum,smoothMaxV(passage,room));
|
||||
}
|
||||
static inline int evalS(int x,int y,int z){
|
||||
int warp=noiseS(x+1024,y+1024,z+1024,4,17)>>4; int nx=x+1024+warp,ny=y+1024-warp,nz=z+1024;
|
||||
int fb=noiseS(nx,ny,nz,4,100)+(noiseS(nx,ny,nz,3,101)>>1)+(noiseS(nx,ny,nz,2,102)>>2);
|
||||
int ridge=128-std::abs(noiseS(nx,ny,nz,3,202));
|
||||
int stratum=std::min(z-84+fb/4,170-z+ridge/4);
|
||||
int dy=y-128,dz=z-128,dx=x-128;
|
||||
int passage=(18*18-dy*dy-dz*dz)/8,room=(35*35-dx*dx-dy*dy-dz*dz)/16;
|
||||
return smoothMaxS(stratum,smoothMaxS(passage,room));
|
||||
}
|
||||
static inline size_t idx(int x,int y,int z){return(size_t(z)*N+y)*N+x;}
|
||||
template<class F> static double ms(F&& f){auto t=std::chrono::steady_clock::now();f();return std::chrono::duration<double,std::milli>(std::chrono::steady_clock::now()-t).count();}
|
||||
template<class F> static void parallel(F fn){std::array<std::thread,W> threads;for(int k=0;k<W;++k)threads[k]=std::thread([=]{fn(k*N/W,(k+1)*N/W);});for(auto& t:threads)t.join();}
|
||||
static void fillScalar(){parallel([](int a,int b){for(int z=a;z<b;++z)for(int y=0;y<N;++y)for(int x=0;x<N;++x)scalarField[idx(x,y,z)]=(int16_t)evalS(x,y,z);});}
|
||||
static void fillVector(){parallel([](int a,int b){for(int z=a;z<b;++z)for(int y=0;y<N;++y)for(int x=0;x<N;x+=8){
|
||||
V xx=_mm256_add_epi32(_mm256_set1_epi32(x),_mm256_setr_epi32(0,1,2,3,4,5,6,7)); V d=evalV(xx,_mm256_set1_epi32(y),_mm256_set1_epi32(z));
|
||||
alignas(32) int32_t values[8];_mm256_store_si256((V*)values,d);for(int j=0;j<8;++j)vectorField[idx(x+j,y,z)]=(int16_t)values[j];}});}
|
||||
static inline int dist2box(int a,int lo,int hi){int d=std::max({lo-a,0,a-hi});return d*d;}
|
||||
int main(){
|
||||
for(int t=0;t<256;++t){int64_t q=t;int64_t q2=q*q,q3=q2*q,q4=q3*q,q5=q4*q;
|
||||
fadeTable[t]=int32_t((6*q5-15*q4*256+10*q3*256*256)/(256LL*256*256*256));}
|
||||
for(int t=0;t<3;++t){double s=ms(fillScalar),v=ms(fillVector);size_t mismatch=0;for(size_t i=0;i<scalarField.size();++i)mismatch+=scalarField[i]!=vectorField[i];
|
||||
std::printf("full trial=%d scalar_ms=%.3f avx2_ms=%.3f speedup=%.3f mismatch=%zu\n",t,s,v,s/v,mismatch);}
|
||||
struct Point{int x,y,z;};std::vector<Point> points(200000);uint32_t rng=0x12345678;
|
||||
for(auto& p:points){rng^=rng<<13;rng^=rng>>17;rng^=rng<<5;p.x=int(rng&255);
|
||||
rng^=rng<<13;rng^=rng>>17;rng^=rng<<5;p.y=int(rng&255);
|
||||
rng^=rng<<13;rng^=rng>>17;rng^=rng<<5;p.z=int(rng&255);}
|
||||
for(int t=0;t<3;++t){int64_t checksum=0;double q=ms([&]{for(const auto& p:points)checksum+=evalS(p.x,p.y,p.z);});
|
||||
std::printf("random_query trial=%d count=%zu ms=%.3f ns_per_query=%.1f checksum=%lld\n",t,points.size(),q,q*1e6/points.size(),(long long)checksum);}
|
||||
// Radius 10m at 25cm grid, center on tile boundary. Count exact AABB touched bricks.
|
||||
const int cx=128,cy=128,cz=128,r=40;int touched=0;for(int bz=0;bz<N/B;++bz)for(int by=0;by<N/B;++by)for(int bx=0;bx<N/B;++bx){
|
||||
int x0=bx*B,x1=x0+B-1,y0=by*B,y1=y0+B-1,z0=bz*B,z1=z0+B-1;
|
||||
if(dist2box(cx,x0,x1)+dist2box(cy,y0,y1)+dist2box(cz,z0,z1)<=r*r)++touched;}
|
||||
std::printf("crater radius_vox=%d touched_bricks=%d tile_AABB_max=%d samples_AABB=%d\n",r,touched,64,81*81*81);
|
||||
// One logical transaction: versioned edit, canonical max composition, copy-on-write buffer.
|
||||
std::vector<int16_t> next(vectorField.size());double copy=ms([&]{next=vectorField;});size_t changed=0;double edit=ms([&]{parallel([&](int za,int zb){for(int z=std::max(za,cz-r);z<std::min(zb,cz+r+1);++z)for(int y=cy-r;y<=cy+r;++y)for(int x=cx-r;x<=cx+r;++x){
|
||||
int dx=x-cx,dy=y-cy,dz=z-cz,q=r*r-dx*dx-dy*dy-dz*dz;
|
||||
if(q>=0){int d=std::max<int>(vectorField[idx(x,y,z)],q/16);next[idx(x,y,z)]=(int16_t)d;}}});});
|
||||
for(int z=cz-r;z<=cz+r;++z)for(int y=cy-r;y<=cy+r;++y)for(int x=cx-r;x<=cx+r;++x)changed+=next[idx(x,y,z)]!=vectorField[idx(x,y,z)];
|
||||
std::printf("crater copy_ms=%.3f edit_ms=%.3f changed_lattice=%zu copy_bytes=%zu\n",copy,edit,changed,vectorField.size()*sizeof(int16_t));
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
# Step 2d launch ledger
|
||||
|
||||
All Unreal processes were launched through `Tools/VoxelForgeTest.ps1` and ran serially. The
|
||||
guarded launches used below-normal priority, the shared persistent DDC where recorded,
|
||||
`-MaxParallelActions=4`, a prelaunch 6 GiB memory check, an 8 GiB per-process limit, and a
|
||||
30-minute timeout. The retained ledgers show **no crash reporters, no process above 8 GiB, and
|
||||
no timeout**. Exit 255 below means automation assertions failed; it was not a native crash.
|
||||
|
||||
## Benchmark attempts
|
||||
|
||||
| Label | Build | Unreal process | Result |
|
||||
|---|---|---|---|
|
||||
| `step2d_rebuild_01` | UBT PID 25780, exit 6, peak 2.71 GiB | Not launched | Compile failed on the `ApplicationContextMask` automation flag; corrected in the next source pass. |
|
||||
| `step2d_rebuild_02` | UBT PID 19420, passed, peak 1.57 GiB | PID 13996, exit 2, peak 1.83 GiB | 3.834 s optimized ring; brick/sample skipping was too low for the speed gate. |
|
||||
| `step2d_rebuild_03` | Passed; PID not retained in the compact log | PID 15480, exit 2, peak 1.82 GiB | 3.918 s; detail spectrum failed. The smoothing negative control worked; the proof negative control did not. |
|
||||
| `step2d_rebuild_04` | Not launched | Not launched | Harness rejected a relative `-Out` path before Unreal startup. |
|
||||
| `step2d_rebuild_05` | UBT PID 23904, passed | PID 24708, exit 2, peak 1.82 GiB | Scratch reuse gave 233.27 density mismatches and hash/boundary failures; scratch ownership was corrected. |
|
||||
| `step2d_rebuild_06` | UBT PID 7540, passed | PID 25828, exit 2, peak 1.82 GiB | Detail, smoothing, boundary, and hash checks passed, but 3.70 s and 1.46% sample skipping missed the speed gate. |
|
||||
| `step2d_rebuild_07` | UBT PID 23800, passed | PID 5208, exit 2, peak 1.81 GiB | 3.66 s; the cached XY macro did not improve the timing. |
|
||||
| `step2d_rebuild_08` | UBT PID 25304, passed | PID 7108, exit 2, peak 1.81 GiB | 2.62 s; 22.20% of bricks and 21.18% of samples skipped, still over target. |
|
||||
| `step2d_rebuild_09` | UBT PID 25612, passed, peak 1.51 GiB | PID 24256, exit 0, peak 1.82 GiB | Superficially under 600 ms, but invalid as evidence: classifier gap/default/chunk semantics had not yet been fixed. Excluded. |
|
||||
| `step2d_harness_10` | UBT PID 2544, passed, peak 1.49 GiB | PID 23204, exit 0, peak 1.84 GiB | 503.026 ms and hash/detail/boundary checks passed. Superseded because its deliberately undersized-bound control did not catch a wrong sign. |
|
||||
| `step2d_harness_11` | UBT PID 2900, passed, peak 1.50 GiB | PID 10688, exit 0, peak 1.83 GiB | 497.518 ms; the scale-0 versus scale-4 undersized-bound control caught 35,847 wrong-sign samples. |
|
||||
| `step2d_harness_12` | UBT PID 13868, passed, peak 1.87 GiB | PID 16872, exit 0, peak 1.81 GiB | Final dynamically scheduled run: 308.177 ms median, all hashes and research gates passed. |
|
||||
|
||||
## Functional runs
|
||||
|
||||
| Label | Build | Unreal process | Result |
|
||||
|---|---|---|---|
|
||||
| `step2d_functional_01` | UBT PID 21184, passed | PID 3444, exit 255, peak 3.26 GiB | Base connectivity passed; refinement failed on an endpoint self-query. The launcher detected a write under `C:\Users\Jahni\AppData\Local\UnrealEngine`; later runs used isolated per-run local data. |
|
||||
| `step2d_functional_02` | UBT PID 11844, passed, peak 1.87 GiB | PID 5048, exit 255, peak 3.13 GiB | Same endpoint-snap failure; isolated LocalAppData was clean. |
|
||||
| `step2d_functional_03` | UBT PID 2060, passed, peak 1.90 GiB | PID 25648, exit 255, peak 3.19 GiB | Origin probe compared raw density 8 with a coarse center at -3.534534 near Z=-1012. |
|
||||
| `step2d_functional_04` | Failed launch; retained PID 23996, stage split not retained | Exit 255; peak not retained | Range-normalization fix did not resolve the origin probe. No crash was recorded. |
|
||||
| `step2d_redefined_seeds_01` | Build passed; PID not retained | PID 21888, exit 255 | Initial multi-seed endpoint-snap expectation was stricter than the route's full-density segment check; expectation was revised. |
|
||||
| `step2d_functional_05` | UBT PID 17000, passed | PID 7540, exit 255 | 12/16 fitted VerticalShafts seeds passed. |
|
||||
| `step2d_functional_06` | Build passed; PID not retained | PID 21948, exit 255, peak 3.43 GiB | 12/16; seed 1337's physical arrival/departure audit found 13 bad samples of 15. |
|
||||
| `step2d_redefined_seeds_02` | UBT PID 14672, exit 0, peak 1.63 GiB | PID 2220, exit 0, peak 2.61 GiB | Passed an earlier three-seed traversal version. |
|
||||
| `step2d_functional_07` | Build/Unreal stage PID 15712 in retained summary | Exit 255, peak 3.42 GiB | 12/16; no native crash. |
|
||||
| Relative-output-path prelaunch attempt | Not launched | Not launched | Harness stopped before Unreal startup; corrected to an absolute output directory. |
|
||||
| `step2d_functional_08` | UBT PID 12596, passed, peak 1.87 GiB | PID 20624, exit 255, peak 3.42 GiB | 13/16; origin path passed, three measurement limits remained. |
|
||||
| `step2d_functional_09` | UBT PID 10848, passed, peak 1.50 GiB | PID 16764, exit 255, peak 3.46 GiB | 13/16 after the cache-key fix. |
|
||||
| `step2d_functional_10` | UBT PID 24704, passed, peak 1.44 GiB | PID 7512, exit 255, peak 3.56 GiB | 13/16 after connector-radius changes. |
|
||||
| `step2d_functional_11` | UBT PID 17088, exit 6, peak 1.96 GiB | Not launched | Compile error C4456 from a duplicated `XYCount`; fixed before the next build. |
|
||||
| `step2d_functional_12` | UBT PID 4760, passed, peak 1.95 GiB | PID 26520, exit 255, peak 3.49 GiB | 13/16 after explicit bridges. |
|
||||
| `step2d_functional_13` | UBT PID 19416, passed, peak 1.50 GiB | PID 26444, exit 255, peak 3.50 GiB | 13/16; diagnostics showed 16 retries and both endpoints in the same large component. |
|
||||
| `step2d_functional_14` | UBT PID 3156, passed, peak 1.50 GiB | PID 17892, exit 255, peak 3.49 GiB | 13/16 after widening the landing bridge. |
|
||||
| `step2d_functional_15` | UBT PID 5648, passed, peak 1.49 GiB | PID 23788, exit 255, peak 3.53 GiB | 14/16 after adding the vertical cavern band. |
|
||||
| `step2d_functional_16` | UBT PID 20028, passed, peak 1.13 GiB | PID 4916, exit 255, peak 3.60 GiB | Controlled-wall assumptions and the 14/16 refinement audit both failed. |
|
||||
| `step2d_functional_17` | UBT PID 14952, passed, peak 1.50 GiB | PID 2604, exit 0, peak 2.69 GiB | Three-seed traversal test passed: 21 landing brackets and 18 routes. |
|
||||
| `step2d_functional_18` | UBT PID 10496, passed, peak 1.49 GiB | PID 25636, exit 0, peak 2.70 GiB | Passage-open-space test passed. |
|
||||
| `step2d_functional_19` | UBT PID 22588, passed, peak 1.48 GiB | PID 7316, exit 255, peak 3.71 GiB | Controlled wall returned conservative unknown after zero retries; refinement remained 14/16. |
|
||||
| `step2d_functional_20` | UBT PID 14076, passed, peak 1.88 GiB | PID 23120, exit 255, peak 3.60 GiB | Wall samples were -2/-102/-2, so the chosen location was not an air/solid/air control; refinement remained 14/16. |
|
||||
| `step2d_functional_21` | UBT PID 8224, passed, peak 1.58 GiB | PID 25652, exit 255, peak 3.67 GiB | `StrateConnectivity` passed; refinement remained 14/16 because the fallback had been placed in a skipped diagnostic branch. |
|
||||
| `step2d_functional_22` | UBT PID 26528, passed, peak 1.62 GiB | PID 24396, exit 0, peak 3.68 GiB | Both required connectivity tests passed; the fitted audit directly confirmed 16/16 routes at step 2. |
|
||||
| `step2d_functional_23` | Build not requested | PID 15176, exit 0, peak 2.44 GiB | `PassageLandsInOpenSpace` passed. |
|
||||
| `step2d_functional_24` | Build not requested | PID 26336, exit 0, peak 2.42 GiB | `RedefinedFieldTraversalSeeds` passed across three seeds. |
|
||||
|
||||
The manual `Win32_OperatingSystem` memory probe was denied once before a launch; no Unreal process
|
||||
was started by that probe. The harness's own native memory preflight succeeded on subsequent
|
||||
launches. No files outside the workspace were deleted.
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
@@ -0,0 +1,131 @@
|
||||
# Rebuild step 2d research report
|
||||
|
||||
Date: 2026-09-30
|
||||
Branch: `rebuild/step2d-redefined`
|
||||
Scope: `Plugins/VoxelForge`
|
||||
|
||||
## Result
|
||||
|
||||
The editor-only research field passes the 259³, 25 cm ring target on six workers. Its final
|
||||
median is **308.177 ms** across three runs (295.325–317.807 ms), below both the 600 ms step-2
|
||||
bar and the 350 ms step-3 target. The old field measured 8,900.965 ms median on the same ring.
|
||||
Preparation, including the 135.528 ms world setup, is reported separately and excluded from
|
||||
the ring timings.
|
||||
|
||||
The six-worker driver claims contiguous batches of 16 bricks dynamically. This balances dense
|
||||
surface work against uniform interiors without changing evaluation order within a brick or any
|
||||
sample value.
|
||||
|
||||
The old and new density fields intentionally differ. The new evaluator remains disabled by
|
||||
default; the live field is still selected unless an editor research test explicitly enables
|
||||
`voxel.ResearchRedefinedDensityField`.
|
||||
|
||||
## Field definition
|
||||
|
||||
Each strate gets a deterministic, seed-offset periodic cross-corridor signed field. Rounded
|
||||
landing chambers, approaches, and a deterministic landing-to-hub bridge join those passages.
|
||||
Authored passage carving and floor geometry remain active. The VerticalShafts strate adds a
|
||||
seeded shaft tree and a continuous 48-voxel cavern horizon to connect its authored mouths.
|
||||
|
||||
Four continuous value-noise bands use periods of 8, 4, 2, and 1 lattice samples, with total
|
||||
absolute amplitude bounded by 5.25 density units. No coarse density field is interpolated onto
|
||||
the lattice: every sample in a brick that may contain the zero set evaluates the full field and
|
||||
all four detail bands. Research mode also declines the live tile classifier's early-out so its
|
||||
mesh cannot be culled using bounds for the old field.
|
||||
|
||||
## Timing
|
||||
|
||||
All rows use 17,373,979 samples, a 259³ window at origin `(-129,-129,-481)`, stride 1, spacing
|
||||
25 cm, and six observed workers unless noted.
|
||||
|
||||
| Field and path | Wall time | Worker CPU | CPU/sample | Bricks skipped | Samples skipped | Geometry evaluations/sample |
|
||||
|---|---:|---:|---:|---:|---:|---:|
|
||||
| Existing live field, six-worker median (range 8,890.120–8,923.020 ms) | 8,900.965 ms | 37.70 s | 2,170.09 ns | 0% | 0% | 6.734 |
|
||||
| Redefined field, all points evaluated, six workers | 467.212 ms | 2.313 s | 133.10 ns | 0% | 0% | 1.000 |
|
||||
| Redefined field, sound 8³ proofs, six-worker median (range 295.325–317.807 ms) | **308.177 ms** | 1.41 s | 80.94 ns | 60.54% | 63.06% | 0.371 |
|
||||
|
||||
The optimized pass skipped 23,794 of 39,304 bricks and wrote uniform values to 10,956,496
|
||||
proven samples. It evaluated full density at the remaining 6,417,483 samples. The 0.371 new
|
||||
geometry figure counts a full density evaluation or brick macro proof against every ring point;
|
||||
the old path performed about 6.734 room/tunnel/passage primitive evaluations per point. Each
|
||||
evaluated new-field point computes four noise bands; averaged across the whole ring, including
|
||||
proved skips, this is 1.477 noise-band evaluations per sample.
|
||||
|
||||
The final run's accumulated worker-phase timers were 1.79 s for full density evaluation, 0.04 s
|
||||
for uniform fills, and 0.01 s for brick proofs. Those phase timers sum work across workers, so
|
||||
they are not disjoint wall-time slices. The longest worker took 307.76 ms; measured parallel
|
||||
overhead after that was 0.42 ms. The remaining wall time is in full-detail evaluation of the
|
||||
6.42 million nonuniform samples.
|
||||
|
||||
## Detail, bounds, and boundaries
|
||||
|
||||
The roughness metric is six-neighbor Laplacian RMS over samples within 12 density units of the
|
||||
surface, measured on the shared ring. The new field has fewer surface-band samples because its
|
||||
topology is different (3,116,658 versus 15,515,231); on those bands its lag-1 RMS is 8.978 versus
|
||||
6.248 for the old field, and lag-2 RMS is 9.169 versus 8.646. A deliberately smoothed new-field
|
||||
control (detail scale 0) falls to 1.315 and 1.933. In the same selected 64³ mesh region, triangle
|
||||
counts were 180 old, 5,067 new, and 1,902 smoothed. The metric and mesh both detected the
|
||||
smoothing control. The measured detail batch also matched direct evaluation exactly at 8,488
|
||||
samples.
|
||||
|
||||
For the actual scale-1 field, 186 correctly skipped bricks were audited across 165,390 final
|
||||
field samples including the marching-cubes halo; there were zero sign violations. The bound
|
||||
includes the full 5.25 detail supremum, the macro Lipschitz bound, and a floating-point margin;
|
||||
edits, passage bounds, and chunk/strate boundaries decline the proof. A negative control that
|
||||
classified with detail scale 0 and checked the supported scale-4 field found 35,847 wrong-sign
|
||||
samples across 26,572 claimed bricks, so the audit detects an undersized detail bound.
|
||||
|
||||
Near-surface gradient-jump RMS was 3.420 at 8³ brick boundaries versus 8.694 in brick interiors,
|
||||
3.423 at chunk boundaries, and 3.225 at 128-sample macro-cell boundaries versus 10.036 in those
|
||||
cell interiors. These checks found no boundary spike consistent with stair-stepping. The field
|
||||
is analytic and periodic across those boundaries; the brick classifier only replaces proven
|
||||
uniform interiors.
|
||||
|
||||
## Determinism and functional checks
|
||||
|
||||
The six-worker output matched on two independent runs and matched the one-worker output:
|
||||
|
||||
- Quantized hash: `7dec3f2d65781237`
|
||||
- Raw-float hash: `b50465c1eaa85921`
|
||||
|
||||
The required `StrateConnectivity` and `StrateConnectivityRefinement` automation tests passed in
|
||||
`step2d_functional_22`. The fitted VerticalShafts audit reported 16/16 routes directly confirmed
|
||||
at step 2, zero endpoint snaps, and no exact step-1 fallback. Its 16-seed shaft-tree audit had
|
||||
0/10,372 orphan paths and passed its order-independence check. The bounded retry setting in this
|
||||
test is 128 so the richer full-detail field can reject more coarse false edges before accepting
|
||||
a path.
|
||||
|
||||
`PassageLandsInOpenSpace` passed in `step2d_functional_23` for its seven-passage fixture.
|
||||
`RedefinedFieldTraversalSeeds` passed in `step2d_functional_24`: over three seeds, 21 authored
|
||||
passage landings bracketed open air and 18 arrival-to-exit walking routes were checked; six
|
||||
routes used a resolved coarse endpoint only after a full-density segment check.
|
||||
|
||||
## Slices
|
||||
|
||||
Each image places the old field on the left and the redefined field on the right. Light is air,
|
||||
dark is solid, and cyan marks the near-zero surface.
|
||||
|
||||
- [Horizontal slice through the ring center](</E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2d/step2d_rebuild_12/slice_xy_center.png>)
|
||||
- [Vertical slice at Y=0](</E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2d/step2d_rebuild_12/slice_xz_y0.png>)
|
||||
- [Vertical slice at X=0](</E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2d/step2d_rebuild_12/slice_yz_x0.png>)
|
||||
- [Vertical slice through an authored passage](</E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2d/step2d_rebuild_12/slice_xz_passage.png>)
|
||||
|
||||
Machine-readable measurements are in [step2d_report.json](</E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2d/step2d_rebuild_12/step2d_report.json>).
|
||||
|
||||
## Scaling to 12.5 cm and compile risk
|
||||
|
||||
At 12.5 cm, double macro lengths and broad/medium noise periods in lattice units to preserve
|
||||
their world-space shape: the current noise periods 8/4/2/1 become 16/8/4/2 samples. Keep or add
|
||||
a 1-sample fine band so detail stays at least as rich as the 25 cm version. For a fixed world
|
||||
region, the lattice grows 8×, while surface-intersecting 8³ bricks should grow closer to 4×
|
||||
(surface area); remaining bricks still need only a cheap final-field sign proof and density
|
||||
writes. This scaling model is reasoned from the geometry and has not yet been timed at 12.5 cm.
|
||||
|
||||
The UE 5.7 Win64 Development build passed with `-ForceHeaderGeneration` and
|
||||
`-MaxParallelActions=4`. No UCLASS/UPROPERTY layout changed, so no generated-class layout risk
|
||||
was introduced. The sensitive code is the locally bounded landing-floor derivative and the
|
||||
supremum used by `TryClassifyBrick`; future field terms must update those bounds and keep the
|
||||
proof audit. The live path and ARCHITECTURE §8.10 were left unchanged when research mode is off.
|
||||
No `.uasset` file was edited. Nothing was committed, pushed, or stashed.
|
||||
|
||||
See the exhaustive [launch ledger](</E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2d/step2d_launch_ledger.md>) for every benchmark, test run, failure, and prelaunch rejection.
|
||||
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 9.5 KiB |
|
After Width: | Height: | Size: 8.2 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 9.7 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 8.0 KiB |
|
After Width: | Height: | Size: 7.8 KiB |
@@ -0,0 +1,815 @@
|
||||
{
|
||||
"status": "pass",
|
||||
"pass": true,
|
||||
"worst_profile_meets_600ms": true,
|
||||
"seed": 0,
|
||||
"ring_dimension": 259,
|
||||
"ring_samples": 17373979,
|
||||
"workers_requested": 6,
|
||||
"world_prepare_ms_excluded": 134.9404975771904,
|
||||
"old_live_ring_ms": 9188.3964985609055,
|
||||
"old_live_ring_raw_float_hash": "cf1e3a48b3bc1bda",
|
||||
"field_definition": "Seeded jittered XY chamber cells form a deterministic origin-rooted spanning tree; selected loops, horizontal tunnels and full-strate shafts open the network. Each chamber can add pillars, annular ledges, paired stalactites/stalagmites, floating rock masses and a bowl floor with an explicit water level. Passage pads and anchor tunnels join the authored inter-strate route to the generated network.",
|
||||
"parameter_space": "Eight named profiles can be assigned per strate from a stable seed hash (-1 mode), or forced for measurement. Each profile supplies nominal room, tunnel, shaft, feature-probability and basin values. Per-cell hashes deterministically roll room jitter and feature presence. Profile minima and maxima are recorded below; room height is clamped to the strate span, room radii to 0.20-0.72/0.80 of pitch, loop probability to 0-0.8, probabilities and water level to 0-1, and basin depth to 0-48 voxels.",
|
||||
"parameter_ranges_across_eight_profiles":
|
||||
{
|
||||
"room_pitch_voxels":
|
||||
{
|
||||
"minimum_across_profiles": 96,
|
||||
"maximum_across_profiles": 176
|
||||
},
|
||||
"room_radius_fraction_min":
|
||||
{
|
||||
"minimum_across_profiles": 0.5,
|
||||
"maximum_across_profiles": 0.62000000476837158
|
||||
},
|
||||
"room_radius_fraction_max":
|
||||
{
|
||||
"minimum_across_profiles": 0.62000000476837158,
|
||||
"maximum_across_profiles": 0.75999999046325684
|
||||
},
|
||||
"room_height_voxels":
|
||||
{
|
||||
"minimum_across_profiles": 216,
|
||||
"maximum_across_profiles": 340
|
||||
},
|
||||
"floor_relief_voxels":
|
||||
{
|
||||
"minimum_across_profiles": 8,
|
||||
"maximum_across_profiles": 20
|
||||
},
|
||||
"tunnel_radius_voxels":
|
||||
{
|
||||
"minimum_across_profiles": 20,
|
||||
"maximum_across_profiles": 40
|
||||
},
|
||||
"tunnel_half_height_voxels":
|
||||
{
|
||||
"minimum_across_profiles": 22,
|
||||
"maximum_across_profiles": 36
|
||||
},
|
||||
"loop_probability":
|
||||
{
|
||||
"minimum_across_profiles": 0.11999999731779099,
|
||||
"maximum_across_profiles": 0.40000000596046448
|
||||
},
|
||||
"vertical_shaft_probability":
|
||||
{
|
||||
"minimum_across_profiles": 0.029999999329447746,
|
||||
"maximum_across_profiles": 0.92000001668930054
|
||||
},
|
||||
"shaft_radius_voxels":
|
||||
{
|
||||
"minimum_across_profiles": 14,
|
||||
"maximum_across_profiles": 28
|
||||
},
|
||||
"pillar_probability":
|
||||
{
|
||||
"minimum_across_profiles": 0.039999999105930328,
|
||||
"maximum_across_profiles": 0.72000002861022949
|
||||
},
|
||||
"ledge_probability":
|
||||
{
|
||||
"minimum_across_profiles": 0.079999998211860657,
|
||||
"maximum_across_profiles": 0.8399999737739563
|
||||
},
|
||||
"floating_mass_probability":
|
||||
{
|
||||
"minimum_across_profiles": 0.019999999552965164,
|
||||
"maximum_across_profiles": 0.72000002861022949
|
||||
},
|
||||
"stalactite_stalagmite_probability":
|
||||
{
|
||||
"minimum_across_profiles": 0.11999999731779099,
|
||||
"maximum_across_profiles": 0.75
|
||||
},
|
||||
"basin_probability":
|
||||
{
|
||||
"minimum_across_profiles": 0,
|
||||
"maximum_across_profiles": 0.85000002384185791
|
||||
},
|
||||
"basin_depth_voxels":
|
||||
{
|
||||
"minimum_across_profiles": 0,
|
||||
"maximum_across_profiles": 48
|
||||
},
|
||||
"water_level_fraction":
|
||||
{
|
||||
"minimum_across_profiles": 0.30000001192092896,
|
||||
"maximum_across_profiles": 0.5
|
||||
}
|
||||
},
|
||||
"resolution_contract": "One exact field sample per 25 cm lattice point. The four existing 8/4/2/1-sample detail bands are evaluated for every potentially surface-affecting point; no coarse field is interpolated onto the lattice. 4-cubed step 2e skips require the final macro field Lipschitz interval plus the full 5.25-unit detail supremum and marching-cubes halo.",
|
||||
"finer_lattice_scaling": "For 12.5 cm voxels, double room pitch, room height, tunnel/feature dimensions and the broad-to-medium noise periods in sample units to preserve meter-scale shapes. Keep the 1-sample detail band or add a finer octave. The lattice grows 8x for a fixed world volume; surface bricks grow roughly with area while certified interior bricks remain cheap. This scale has not been timed.",
|
||||
"variety_metric_method": "Open fraction uses every ring sample. Surface area estimates sign-changing lattice edges times 0.25 m squared. Nominal chamber-volume p10/p50/p90 values use the uniform per-cell radius roll and room height as circular-cylinder volumes. Tunnel-neck share and connected-component sizes use a 1 m downsampled six-neighbor occupancy grid. Verticality is the mean longest contiguous open run per XY column, with open-column coverage reported separately.",
|
||||
"water_geometry_limit": "Basin profiles generate the bowl floor and water-level parameter; this density field does not render or simulate water.",
|
||||
"worst_six_worker_ring_median_ms": 570.03279775381088,
|
||||
"target_aim_ms": 350,
|
||||
"hardest_profile": "HyperComplex",
|
||||
"profiles": [
|
||||
{
|
||||
"profile_index": 0,
|
||||
"name": "OpenVaults",
|
||||
"parameters":
|
||||
{
|
||||
"room_pitch_voxels": 176,
|
||||
"room_radius_fraction_min": 0.62000000476837158,
|
||||
"room_radius_fraction_max": 0.75999999046325684,
|
||||
"room_height_voxels": 340,
|
||||
"floor_relief_voxels": 12,
|
||||
"tunnel_radius_voxels": 40,
|
||||
"tunnel_half_height_voxels": 36,
|
||||
"loop_probability": 0.34999999403953552,
|
||||
"vertical_shaft_probability": 0.14000000059604645,
|
||||
"shaft_radius_voxels": 22,
|
||||
"pillar_probability": 0.039999999105930328,
|
||||
"ledge_probability": 0.11999999731779099,
|
||||
"floating_mass_probability": 0.079999998211860657,
|
||||
"stalactite_stalagmite_probability": 0.15000000596046448,
|
||||
"basin_probability": 0,
|
||||
"basin_depth_voxels": 0,
|
||||
"water_level_fraction": 0.5
|
||||
},
|
||||
"nominal_chamber_volume_quantiles_m3":
|
||||
{
|
||||
"p10_m3": 207803.41085068439,
|
||||
"p50_m3": 246134.408195325,
|
||||
"p90_m3": 287707.90649226471
|
||||
},
|
||||
"six_worker_ring_median_ms": 404.54889833927155,
|
||||
"six_worker_ring_trials_ms": [
|
||||
403.37140113115311,
|
||||
404.54889833927155,
|
||||
422.8769987821579
|
||||
],
|
||||
"open_air_fraction": 0.97343389214410814,
|
||||
"estimated_surface_area_m2": 33145,
|
||||
"estimated_surface_area_per_volume_m_inv": 0.12209523218601795,
|
||||
"vertical_air_adjacency_share": 0.33298452070631224,
|
||||
"vertical_air_column_share": 1,
|
||||
"mean_longest_vertical_air_run_voxels": 241.88837375709963,
|
||||
"longest_vertical_air_run_voxels": 259,
|
||||
"one_meter_tunnel_neck_share": 0.014380675966669787,
|
||||
"one_meter_connected_components": 22,
|
||||
"largest_component_coarse_m3": 267001,
|
||||
"components_1_to_8_coarse_m3": 21,
|
||||
"components_9_to_64_coarse_m3": 0,
|
||||
"components_65_to_512_coarse_m3": 0,
|
||||
"components_over_512_coarse_m3": 1,
|
||||
"skipped_bricks": 213327,
|
||||
"skipped_sample_fraction": 0.75428248186555302,
|
||||
"evaluated_samples": 4269091,
|
||||
"worker_cpu_seconds": 1.609375,
|
||||
"brick_classification_worker_ms": 242.50604957342148,
|
||||
"density_evaluation_worker_ms": 2076.3386078178883,
|
||||
"uniform_fill_worker_ms": 53.428228944540024,
|
||||
"longest_worker_ms": 399.43369850516319,
|
||||
"slice_directory": "E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2e/step2e_profiles_30/set_00_openvaults"
|
||||
},
|
||||
{
|
||||
"profile_index": 1,
|
||||
"name": "TightNetwork",
|
||||
"parameters":
|
||||
{
|
||||
"room_pitch_voxels": 104,
|
||||
"room_radius_fraction_min": 0.51999998092651367,
|
||||
"room_radius_fraction_max": 0.63999998569488525,
|
||||
"room_height_voxels": 256,
|
||||
"floor_relief_voxels": 8,
|
||||
"tunnel_radius_voxels": 20,
|
||||
"tunnel_half_height_voxels": 22,
|
||||
"loop_probability": 0.11999999731779099,
|
||||
"vertical_shaft_probability": 0.029999999329447746,
|
||||
"shaft_radius_voxels": 14,
|
||||
"pillar_probability": 0.059999998658895493,
|
||||
"ledge_probability": 0.079999998211860657,
|
||||
"floating_mass_probability": 0.019999999552965164,
|
||||
"stalactite_stalagmite_probability": 0.11999999731779099,
|
||||
"basin_probability": 0,
|
||||
"basin_depth_voxels": 0,
|
||||
"water_level_fraction": 0.5
|
||||
},
|
||||
"nominal_chamber_volume_quantiles_m3":
|
||||
{
|
||||
"p10_m3": 38468.015011234471,
|
||||
"p50_m3": 45722.767008321418,
|
||||
"p90_m3": 53603.828575098079
|
||||
},
|
||||
"six_worker_ring_median_ms": 561.79890036582947,
|
||||
"six_worker_ring_trials_ms": [
|
||||
553.03480103611946,
|
||||
561.79890036582947,
|
||||
575.50980150699615
|
||||
],
|
||||
"open_air_fraction": 0.89594007221949556,
|
||||
"estimated_surface_area_m2": 92336.125,
|
||||
"estimated_surface_area_per_volume_m_inv": 0.34013578582085313,
|
||||
"vertical_air_adjacency_share": 0.33327139189444377,
|
||||
"vertical_air_column_share": 0.99737630625661511,
|
||||
"mean_longest_vertical_air_run_voxels": 210.41539496300726,
|
||||
"longest_vertical_air_run_voxels": 259,
|
||||
"one_meter_tunnel_neck_share": 0.039870522719675165,
|
||||
"one_meter_connected_components": 109,
|
||||
"largest_component_coarse_m3": 245133,
|
||||
"components_1_to_8_coarse_m3": 106,
|
||||
"components_9_to_64_coarse_m3": 2,
|
||||
"components_65_to_512_coarse_m3": 0,
|
||||
"components_over_512_coarse_m3": 1,
|
||||
"skipped_bricks": 142897,
|
||||
"skipped_sample_fraction": 0.50734353943906574,
|
||||
"evaluated_samples": 8559403,
|
||||
"worker_cpu_seconds": 2.328125,
|
||||
"brick_classification_worker_ms": 241.08080938458443,
|
||||
"density_evaluation_worker_ms": 3046.3547222316265,
|
||||
"uniform_fill_worker_ms": 35.994317382574081,
|
||||
"longest_worker_ms": 557.83749744296074,
|
||||
"slice_directory": "E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2e/step2e_profiles_30/set_01_tightnetwork"
|
||||
},
|
||||
{
|
||||
"profile_index": 2,
|
||||
"name": "ChamberHalls",
|
||||
"parameters":
|
||||
{
|
||||
"room_pitch_voxels": 144,
|
||||
"room_radius_fraction_min": 0.55000001192092896,
|
||||
"room_radius_fraction_max": 0.72000002861022949,
|
||||
"room_height_voxels": 320,
|
||||
"floor_relief_voxels": 18,
|
||||
"tunnel_radius_voxels": 34,
|
||||
"tunnel_half_height_voxels": 32,
|
||||
"loop_probability": 0.2800000011920929,
|
||||
"vertical_shaft_probability": 0.18000000715255737,
|
||||
"shaft_radius_voxels": 22,
|
||||
"pillar_probability": 0.079999998211860657,
|
||||
"ledge_probability": 0.2199999988079071,
|
||||
"floating_mass_probability": 0.10000000149011612,
|
||||
"stalactite_stalagmite_probability": 0.2199999988079071,
|
||||
"basin_probability": 0.05000000074505806,
|
||||
"basin_depth_voxels": 20,
|
||||
"water_level_fraction": 0.40000000596046448
|
||||
},
|
||||
"nominal_chamber_volume_quantiles_m3":
|
||||
{
|
||||
"p10_m3": 104715.5070092809,
|
||||
"p50_m3": 131338.58696522861,
|
||||
"p90_m3": 160973.92909046411
|
||||
},
|
||||
"six_worker_ring_median_ms": 505.73009997606277,
|
||||
"six_worker_ring_trials_ms": [
|
||||
465.90260043740273,
|
||||
505.73009997606277,
|
||||
545.07719725370407
|
||||
],
|
||||
"open_air_fraction": 0.92935711502816942,
|
||||
"estimated_surface_area_m2": 57662.9375,
|
||||
"estimated_surface_area_per_volume_m_inv": 0.21241121564611076,
|
||||
"vertical_air_adjacency_share": 0.33303864616767592,
|
||||
"vertical_air_column_share": 1,
|
||||
"mean_longest_vertical_air_run_voxels": 224.23037819949016,
|
||||
"longest_vertical_air_run_voxels": 259,
|
||||
"one_meter_tunnel_neck_share": 0.026658600207423239,
|
||||
"one_meter_connected_components": 50,
|
||||
"largest_component_coarse_m3": 254470,
|
||||
"components_1_to_8_coarse_m3": 49,
|
||||
"components_9_to_64_coarse_m3": 0,
|
||||
"components_65_to_512_coarse_m3": 0,
|
||||
"components_over_512_coarse_m3": 1,
|
||||
"skipped_bricks": 185011,
|
||||
"skipped_sample_fraction": 0.6521041610560252,
|
||||
"evaluated_samples": 6044335,
|
||||
"worker_cpu_seconds": 1.78125,
|
||||
"brick_classification_worker_ms": 237.52568662166595,
|
||||
"density_evaluation_worker_ms": 2464.1959294676781,
|
||||
"uniform_fill_worker_ms": 46.43239825963974,
|
||||
"longest_worker_ms": 461.93370223045349,
|
||||
"slice_directory": "E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2e/step2e_profiles_30/set_02_chamberhalls"
|
||||
},
|
||||
{
|
||||
"profile_index": 3,
|
||||
"name": "ShaftForest",
|
||||
"parameters":
|
||||
{
|
||||
"room_pitch_voxels": 128,
|
||||
"room_radius_fraction_min": 0.5,
|
||||
"room_radius_fraction_max": 0.62000000476837158,
|
||||
"room_height_voxels": 272,
|
||||
"floor_relief_voxels": 12,
|
||||
"tunnel_radius_voxels": 26,
|
||||
"tunnel_half_height_voxels": 28,
|
||||
"loop_probability": 0.11999999731779099,
|
||||
"vertical_shaft_probability": 0.92000001668930054,
|
||||
"shaft_radius_voxels": 28,
|
||||
"pillar_probability": 0.05000000074505806,
|
||||
"ledge_probability": 0.18000000715255737,
|
||||
"floating_mass_probability": 0.039999999105930328,
|
||||
"stalactite_stalagmite_probability": 0.20000000298023224,
|
||||
"basin_probability": 0,
|
||||
"basin_depth_voxels": 0,
|
||||
"water_level_fraction": 0.5
|
||||
},
|
||||
"nominal_chamber_volume_quantiles_m3":
|
||||
{
|
||||
"p10_m3": 57345.410351032719,
|
||||
"p50_m3": 68601.687643874015,
|
||||
"p90_m3": 80865.989806275087
|
||||
},
|
||||
"six_worker_ring_median_ms": 548.60490188002586,
|
||||
"six_worker_ring_trials_ms": [
|
||||
546.6328002512455,
|
||||
548.60490188002586,
|
||||
578.33020016551018
|
||||
],
|
||||
"open_air_fraction": 0.85949580116333746,
|
||||
"estimated_surface_area_m2": 93586.625,
|
||||
"estimated_surface_area_per_volume_m_inv": 0.34474221477993039,
|
||||
"vertical_air_adjacency_share": 0.33316652784439366,
|
||||
"vertical_air_column_share": 0.99175623499947829,
|
||||
"mean_longest_vertical_air_run_voxels": 202.98173701298703,
|
||||
"longest_vertical_air_run_voxels": 259,
|
||||
"one_meter_tunnel_neck_share": 0.043446839263699355,
|
||||
"one_meter_connected_components": 100,
|
||||
"largest_component_coarse_m3": 235109,
|
||||
"components_1_to_8_coarse_m3": 99,
|
||||
"components_9_to_64_coarse_m3": 0,
|
||||
"components_65_to_512_coarse_m3": 0,
|
||||
"components_over_512_coarse_m3": 1,
|
||||
"skipped_bricks": 149078,
|
||||
"skipped_sample_fraction": 0.52414844060764665,
|
||||
"evaluated_samples": 8267435,
|
||||
"worker_cpu_seconds": 2.09375,
|
||||
"brick_classification_worker_ms": 232.56969451904297,
|
||||
"density_evaluation_worker_ms": 2963.2366858422756,
|
||||
"uniform_fill_worker_ms": 37.121176719665527,
|
||||
"longest_worker_ms": 542.60710254311562,
|
||||
"slice_directory": "E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2e/step2e_profiles_30/set_03_shaftforest"
|
||||
},
|
||||
{
|
||||
"profile_index": 4,
|
||||
"name": "FloatingArchipelago",
|
||||
"parameters":
|
||||
{
|
||||
"room_pitch_voxels": 136,
|
||||
"room_radius_fraction_min": 0.55000001192092896,
|
||||
"room_radius_fraction_max": 0.68000000715255737,
|
||||
"room_height_voxels": 216,
|
||||
"floor_relief_voxels": 10,
|
||||
"tunnel_radius_voxels": 28,
|
||||
"tunnel_half_height_voxels": 28,
|
||||
"loop_probability": 0.15000000596046448,
|
||||
"vertical_shaft_probability": 0.10000000149011612,
|
||||
"shaft_radius_voxels": 16,
|
||||
"pillar_probability": 0.05000000074505806,
|
||||
"ledge_probability": 0.10000000149011612,
|
||||
"floating_mass_probability": 0.72000002861022949,
|
||||
"stalactite_stalagmite_probability": 0.2800000011920929,
|
||||
"basin_probability": 0,
|
||||
"basin_depth_voxels": 0,
|
||||
"water_level_fraction": 0.5
|
||||
},
|
||||
"nominal_chamber_volume_quantiles_m3":
|
||||
{
|
||||
"p10_m3": 62161.040291956291,
|
||||
"p50_m3": 74174.001993180806,
|
||||
"p90_m3": 87247.530713802553
|
||||
},
|
||||
"six_worker_ring_median_ms": 559.36940014362335,
|
||||
"six_worker_ring_trials_ms": [
|
||||
556.77210167050362,
|
||||
559.36940014362335,
|
||||
564.77300077676773
|
||||
],
|
||||
"open_air_fraction": 0.81436451603861149,
|
||||
"estimated_surface_area_m2": 77448.0625,
|
||||
"estimated_surface_area_per_volume_m_inv": 0.285293081107097,
|
||||
"vertical_air_adjacency_share": 0.3339729720652625,
|
||||
"vertical_air_column_share": 0.98258821424844589,
|
||||
"mean_longest_vertical_air_run_voxels": 196.60781636399497,
|
||||
"longest_vertical_air_run_voxels": 248,
|
||||
"one_meter_tunnel_neck_share": 0.034649779794953671,
|
||||
"one_meter_connected_components": 65,
|
||||
"largest_component_coarse_m3": 222906,
|
||||
"components_1_to_8_coarse_m3": 64,
|
||||
"components_9_to_64_coarse_m3": 0,
|
||||
"components_65_to_512_coarse_m3": 0,
|
||||
"components_over_512_coarse_m3": 1,
|
||||
"skipped_bricks": 150891,
|
||||
"skipped_sample_fraction": 0.53115961519235178,
|
||||
"evaluated_samples": 8145623,
|
||||
"worker_cpu_seconds": 2.359375,
|
||||
"brick_classification_worker_ms": 269.87878605723381,
|
||||
"density_evaluation_worker_ms": 2985.2445237338543,
|
||||
"uniform_fill_worker_ms": 37.513568997383118,
|
||||
"longest_worker_ms": 552.79690027236938,
|
||||
"slice_directory": "E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2e/step2e_profiles_30/set_04_floatingarchipelago"
|
||||
},
|
||||
{
|
||||
"profile_index": 5,
|
||||
"name": "PillaredCathedral",
|
||||
"parameters":
|
||||
{
|
||||
"room_pitch_voxels": 160,
|
||||
"room_radius_fraction_min": 0.55000001192092896,
|
||||
"room_radius_fraction_max": 0.72000002861022949,
|
||||
"room_height_voxels": 248,
|
||||
"floor_relief_voxels": 12,
|
||||
"tunnel_radius_voxels": 32,
|
||||
"tunnel_half_height_voxels": 34,
|
||||
"loop_probability": 0.25,
|
||||
"vertical_shaft_probability": 0.079999998211860657,
|
||||
"shaft_radius_voxels": 18,
|
||||
"pillar_probability": 0.72000002861022949,
|
||||
"ledge_probability": 0.30000001192092896,
|
||||
"floating_mass_probability": 0.079999998211860657,
|
||||
"stalactite_stalagmite_probability": 0.44999998807907104,
|
||||
"basin_probability": 0,
|
||||
"basin_depth_voxels": 0,
|
||||
"water_level_fraction": 0.5
|
||||
},
|
||||
"nominal_chamber_volume_quantiles_m3":
|
||||
{
|
||||
"p10_m3": 100190.76287925025,
|
||||
"p50_m3": 125663.46283710146,
|
||||
"p90_m3": 154018.26548778973
|
||||
},
|
||||
"six_worker_ring_median_ms": 500.73060020804405,
|
||||
"six_worker_ring_trials_ms": [
|
||||
499.61259961128235,
|
||||
500.73060020804405,
|
||||
516.76810160279274
|
||||
],
|
||||
"open_air_fraction": 0.92515940073370639,
|
||||
"estimated_surface_area_m2": 67416.5625,
|
||||
"estimated_surface_area_per_volume_m_inv": 0.248340348517746,
|
||||
"vertical_air_adjacency_share": 0.33307025414258157,
|
||||
"vertical_air_column_share": 1,
|
||||
"mean_longest_vertical_air_run_voxels": 220.65106363948064,
|
||||
"longest_vertical_air_run_voxels": 259,
|
||||
"one_meter_tunnel_neck_share": 0.029466147835377512,
|
||||
"one_meter_connected_components": 50,
|
||||
"largest_component_coarse_m3": 253593,
|
||||
"components_1_to_8_coarse_m3": 49,
|
||||
"components_9_to_64_coarse_m3": 0,
|
||||
"components_65_to_512_coarse_m3": 0,
|
||||
"components_over_512_coarse_m3": 1,
|
||||
"skipped_bricks": 173832,
|
||||
"skipped_sample_fraction": 0.61222175990888439,
|
||||
"evaluated_samples": 6737251,
|
||||
"worker_cpu_seconds": 2.0625,
|
||||
"brick_classification_worker_ms": 249.71644952893257,
|
||||
"density_evaluation_worker_ms": 2656.6633693873882,
|
||||
"uniform_fill_worker_ms": 43.705981224775314,
|
||||
"longest_worker_ms": 495.59580162167549,
|
||||
"slice_directory": "E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2e/step2e_profiles_30/set_05_pillaredcathedral"
|
||||
},
|
||||
{
|
||||
"profile_index": 6,
|
||||
"name": "TerracedBasin",
|
||||
"parameters":
|
||||
{
|
||||
"room_pitch_voxels": 136,
|
||||
"room_radius_fraction_min": 0.51999998092651367,
|
||||
"room_radius_fraction_max": 0.64999997615814209,
|
||||
"room_height_voxels": 264,
|
||||
"floor_relief_voxels": 14,
|
||||
"tunnel_radius_voxels": 28,
|
||||
"tunnel_half_height_voxels": 30,
|
||||
"loop_probability": 0.20000000298023224,
|
||||
"vertical_shaft_probability": 0.10000000149011612,
|
||||
"shaft_radius_voxels": 18,
|
||||
"pillar_probability": 0.10000000149011612,
|
||||
"ledge_probability": 0.8399999737739563,
|
||||
"floating_mass_probability": 0.05000000074505806,
|
||||
"stalactite_stalagmite_probability": 0.25,
|
||||
"basin_probability": 0.85000002384185791,
|
||||
"basin_depth_voxels": 48,
|
||||
"water_level_fraction": 0.30000001192092896
|
||||
},
|
||||
"nominal_chamber_volume_quantiles_m3":
|
||||
{
|
||||
"p10_m3": 68093.558179425308,
|
||||
"p50_m3": 82028.230406505769,
|
||||
"p90_m3": 97259.151212849523
|
||||
},
|
||||
"six_worker_ring_median_ms": 570.03279775381088,
|
||||
"six_worker_ring_trials_ms": [
|
||||
564.51970338821411,
|
||||
570.03279775381088,
|
||||
573.95610213279724
|
||||
],
|
||||
"open_air_fraction": 0.83870016189152752,
|
||||
"estimated_surface_area_m2": 94288.5625,
|
||||
"estimated_surface_area_per_volume_m_inv": 0.34732792067954038,
|
||||
"vertical_air_adjacency_share": 0.33312483627697398,
|
||||
"vertical_air_column_share": 0.98552496235893916,
|
||||
"mean_longest_vertical_air_run_voxels": 182.00520344879746,
|
||||
"longest_vertical_air_run_voxels": 259,
|
||||
"one_meter_tunnel_neck_share": 0.048149240524996313,
|
||||
"one_meter_connected_components": 88,
|
||||
"largest_component_coarse_m3": 230451,
|
||||
"components_1_to_8_coarse_m3": 87,
|
||||
"components_9_to_64_coarse_m3": 0,
|
||||
"components_65_to_512_coarse_m3": 0,
|
||||
"components_over_512_coarse_m3": 1,
|
||||
"skipped_bricks": 145055,
|
||||
"skipped_sample_fraction": 0.50965412125800313,
|
||||
"evaluated_samples": 8519259,
|
||||
"worker_cpu_seconds": 2.34375,
|
||||
"brick_classification_worker_ms": 249.46653842926025,
|
||||
"density_evaluation_worker_ms": 3080.0332017242908,
|
||||
"uniform_fill_worker_ms": 36.166064441204071,
|
||||
"longest_worker_ms": 564.91680070757866,
|
||||
"slice_directory": "E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2e/step2e_profiles_30/set_06_terracedbasin"
|
||||
},
|
||||
{
|
||||
"profile_index": 7,
|
||||
"name": "HyperComplex",
|
||||
"parameters":
|
||||
{
|
||||
"room_pitch_voxels": 96,
|
||||
"room_radius_fraction_min": 0.60000002384185791,
|
||||
"room_radius_fraction_max": 0.74000000953674316,
|
||||
"room_height_voxels": 280,
|
||||
"floor_relief_voxels": 20,
|
||||
"tunnel_radius_voxels": 30,
|
||||
"tunnel_half_height_voxels": 28,
|
||||
"loop_probability": 0.40000000596046448,
|
||||
"vertical_shaft_probability": 0.55000001192092896,
|
||||
"shaft_radius_voxels": 20,
|
||||
"pillar_probability": 0.44999998807907104,
|
||||
"ledge_probability": 0.68000000715255737,
|
||||
"floating_mass_probability": 0.5,
|
||||
"stalactite_stalagmite_probability": 0.75,
|
||||
"basin_probability": 0.41999998688697815,
|
||||
"basin_depth_voxels": 28,
|
||||
"water_level_fraction": 0.40000000596046448
|
||||
},
|
||||
"nominal_chamber_volume_quantiles_m3":
|
||||
{
|
||||
"p10_m3": 47753.715763874934,
|
||||
"p50_m3": 56861.724022144372,
|
||||
"p90_m3": 66764.200185109366
|
||||
},
|
||||
"six_worker_ring_median_ms": 559.64479967951775,
|
||||
"six_worker_ring_trials_ms": [
|
||||
520.49140259623528,
|
||||
559.64479967951775,
|
||||
575.4614993929863
|
||||
],
|
||||
"open_air_fraction": 0.94705570900022384,
|
||||
"estimated_surface_area_m2": 61014.1875,
|
||||
"estimated_surface_area_per_volume_m_inv": 0.22475611372616486,
|
||||
"vertical_air_adjacency_share": 0.33306194335010991,
|
||||
"vertical_air_column_share": 1,
|
||||
"mean_longest_vertical_air_run_voxels": 219.53374278856904,
|
||||
"longest_vertical_air_run_voxels": 259,
|
||||
"one_meter_tunnel_neck_share": 0.029053088791938297,
|
||||
"one_meter_connected_components": 65,
|
||||
"largest_component_coarse_m3": 259588,
|
||||
"components_1_to_8_coarse_m3": 62,
|
||||
"components_9_to_64_coarse_m3": 2,
|
||||
"components_65_to_512_coarse_m3": 0,
|
||||
"components_over_512_coarse_m3": 1,
|
||||
"skipped_bricks": 165593,
|
||||
"skipped_sample_fraction": 0.58573939798131447,
|
||||
"evaluated_samples": 7197355,
|
||||
"worker_cpu_seconds": 2.09375,
|
||||
"brick_classification_worker_ms": 262.15906068682671,
|
||||
"density_evaluation_worker_ms": 2771.4953608810902,
|
||||
"uniform_fill_worker_ms": 41.310880333185196,
|
||||
"longest_worker_ms": 516.51959866285324,
|
||||
"slice_directory": "E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2e/step2e_profiles_30/set_07_hypercomplex"
|
||||
}
|
||||
],
|
||||
"slices": [
|
||||
{
|
||||
"profile": "OpenVaults",
|
||||
"file": "E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2e/step2e_profiles_30/set_00_openvaults/slice_xy_center.png",
|
||||
"layout": "today's live field on left; step 2e parameterized field on right; light=air, dark=solid, cyan=near zero"
|
||||
},
|
||||
{
|
||||
"profile": "OpenVaults",
|
||||
"file": "E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2e/step2e_profiles_30/set_00_openvaults/slice_xz_y0.png",
|
||||
"layout": "today's live field on left; step 2e parameterized field on right; light=air, dark=solid, cyan=near zero"
|
||||
},
|
||||
{
|
||||
"profile": "OpenVaults",
|
||||
"file": "E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2e/step2e_profiles_30/set_00_openvaults/slice_yz_x0.png",
|
||||
"layout": "today's live field on left; step 2e parameterized field on right; light=air, dark=solid, cyan=near zero"
|
||||
},
|
||||
{
|
||||
"profile": "OpenVaults",
|
||||
"file": "E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2e/step2e_profiles_30/set_00_openvaults/slice_xz_passage.png",
|
||||
"layout": "today's live field on left; step 2e parameterized field on right; light=air, dark=solid, cyan=near zero"
|
||||
},
|
||||
{
|
||||
"profile": "TightNetwork",
|
||||
"file": "E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2e/step2e_profiles_30/set_01_tightnetwork/slice_xy_center.png",
|
||||
"layout": "today's live field on left; step 2e parameterized field on right; light=air, dark=solid, cyan=near zero"
|
||||
},
|
||||
{
|
||||
"profile": "TightNetwork",
|
||||
"file": "E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2e/step2e_profiles_30/set_01_tightnetwork/slice_xz_y0.png",
|
||||
"layout": "today's live field on left; step 2e parameterized field on right; light=air, dark=solid, cyan=near zero"
|
||||
},
|
||||
{
|
||||
"profile": "TightNetwork",
|
||||
"file": "E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2e/step2e_profiles_30/set_01_tightnetwork/slice_yz_x0.png",
|
||||
"layout": "today's live field on left; step 2e parameterized field on right; light=air, dark=solid, cyan=near zero"
|
||||
},
|
||||
{
|
||||
"profile": "TightNetwork",
|
||||
"file": "E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2e/step2e_profiles_30/set_01_tightnetwork/slice_xz_passage.png",
|
||||
"layout": "today's live field on left; step 2e parameterized field on right; light=air, dark=solid, cyan=near zero"
|
||||
},
|
||||
{
|
||||
"profile": "ChamberHalls",
|
||||
"file": "E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2e/step2e_profiles_30/set_02_chamberhalls/slice_xy_center.png",
|
||||
"layout": "today's live field on left; step 2e parameterized field on right; light=air, dark=solid, cyan=near zero"
|
||||
},
|
||||
{
|
||||
"profile": "ChamberHalls",
|
||||
"file": "E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2e/step2e_profiles_30/set_02_chamberhalls/slice_xz_y0.png",
|
||||
"layout": "today's live field on left; step 2e parameterized field on right; light=air, dark=solid, cyan=near zero"
|
||||
},
|
||||
{
|
||||
"profile": "ChamberHalls",
|
||||
"file": "E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2e/step2e_profiles_30/set_02_chamberhalls/slice_yz_x0.png",
|
||||
"layout": "today's live field on left; step 2e parameterized field on right; light=air, dark=solid, cyan=near zero"
|
||||
},
|
||||
{
|
||||
"profile": "ChamberHalls",
|
||||
"file": "E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2e/step2e_profiles_30/set_02_chamberhalls/slice_xz_passage.png",
|
||||
"layout": "today's live field on left; step 2e parameterized field on right; light=air, dark=solid, cyan=near zero"
|
||||
},
|
||||
{
|
||||
"profile": "ShaftForest",
|
||||
"file": "E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2e/step2e_profiles_30/set_03_shaftforest/slice_xy_center.png",
|
||||
"layout": "today's live field on left; step 2e parameterized field on right; light=air, dark=solid, cyan=near zero"
|
||||
},
|
||||
{
|
||||
"profile": "ShaftForest",
|
||||
"file": "E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2e/step2e_profiles_30/set_03_shaftforest/slice_xz_y0.png",
|
||||
"layout": "today's live field on left; step 2e parameterized field on right; light=air, dark=solid, cyan=near zero"
|
||||
},
|
||||
{
|
||||
"profile": "ShaftForest",
|
||||
"file": "E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2e/step2e_profiles_30/set_03_shaftforest/slice_yz_x0.png",
|
||||
"layout": "today's live field on left; step 2e parameterized field on right; light=air, dark=solid, cyan=near zero"
|
||||
},
|
||||
{
|
||||
"profile": "ShaftForest",
|
||||
"file": "E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2e/step2e_profiles_30/set_03_shaftforest/slice_xz_passage.png",
|
||||
"layout": "today's live field on left; step 2e parameterized field on right; light=air, dark=solid, cyan=near zero"
|
||||
},
|
||||
{
|
||||
"profile": "FloatingArchipelago",
|
||||
"file": "E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2e/step2e_profiles_30/set_04_floatingarchipelago/slice_xy_center.png",
|
||||
"layout": "today's live field on left; step 2e parameterized field on right; light=air, dark=solid, cyan=near zero"
|
||||
},
|
||||
{
|
||||
"profile": "FloatingArchipelago",
|
||||
"file": "E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2e/step2e_profiles_30/set_04_floatingarchipelago/slice_xz_y0.png",
|
||||
"layout": "today's live field on left; step 2e parameterized field on right; light=air, dark=solid, cyan=near zero"
|
||||
},
|
||||
{
|
||||
"profile": "FloatingArchipelago",
|
||||
"file": "E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2e/step2e_profiles_30/set_04_floatingarchipelago/slice_yz_x0.png",
|
||||
"layout": "today's live field on left; step 2e parameterized field on right; light=air, dark=solid, cyan=near zero"
|
||||
},
|
||||
{
|
||||
"profile": "FloatingArchipelago",
|
||||
"file": "E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2e/step2e_profiles_30/set_04_floatingarchipelago/slice_xz_passage.png",
|
||||
"layout": "today's live field on left; step 2e parameterized field on right; light=air, dark=solid, cyan=near zero"
|
||||
},
|
||||
{
|
||||
"profile": "PillaredCathedral",
|
||||
"file": "E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2e/step2e_profiles_30/set_05_pillaredcathedral/slice_xy_center.png",
|
||||
"layout": "today's live field on left; step 2e parameterized field on right; light=air, dark=solid, cyan=near zero"
|
||||
},
|
||||
{
|
||||
"profile": "PillaredCathedral",
|
||||
"file": "E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2e/step2e_profiles_30/set_05_pillaredcathedral/slice_xz_y0.png",
|
||||
"layout": "today's live field on left; step 2e parameterized field on right; light=air, dark=solid, cyan=near zero"
|
||||
},
|
||||
{
|
||||
"profile": "PillaredCathedral",
|
||||
"file": "E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2e/step2e_profiles_30/set_05_pillaredcathedral/slice_yz_x0.png",
|
||||
"layout": "today's live field on left; step 2e parameterized field on right; light=air, dark=solid, cyan=near zero"
|
||||
},
|
||||
{
|
||||
"profile": "PillaredCathedral",
|
||||
"file": "E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2e/step2e_profiles_30/set_05_pillaredcathedral/slice_xz_passage.png",
|
||||
"layout": "today's live field on left; step 2e parameterized field on right; light=air, dark=solid, cyan=near zero"
|
||||
},
|
||||
{
|
||||
"profile": "TerracedBasin",
|
||||
"file": "E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2e/step2e_profiles_30/set_06_terracedbasin/slice_xy_center.png",
|
||||
"layout": "today's live field on left; step 2e parameterized field on right; light=air, dark=solid, cyan=near zero"
|
||||
},
|
||||
{
|
||||
"profile": "TerracedBasin",
|
||||
"file": "E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2e/step2e_profiles_30/set_06_terracedbasin/slice_xz_y0.png",
|
||||
"layout": "today's live field on left; step 2e parameterized field on right; light=air, dark=solid, cyan=near zero"
|
||||
},
|
||||
{
|
||||
"profile": "TerracedBasin",
|
||||
"file": "E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2e/step2e_profiles_30/set_06_terracedbasin/slice_yz_x0.png",
|
||||
"layout": "today's live field on left; step 2e parameterized field on right; light=air, dark=solid, cyan=near zero"
|
||||
},
|
||||
{
|
||||
"profile": "TerracedBasin",
|
||||
"file": "E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2e/step2e_profiles_30/set_06_terracedbasin/slice_xz_passage.png",
|
||||
"layout": "today's live field on left; step 2e parameterized field on right; light=air, dark=solid, cyan=near zero"
|
||||
},
|
||||
{
|
||||
"profile": "HyperComplex",
|
||||
"file": "E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2e/step2e_profiles_30/set_07_hypercomplex/slice_xy_center.png",
|
||||
"layout": "today's live field on left; step 2e parameterized field on right; light=air, dark=solid, cyan=near zero"
|
||||
},
|
||||
{
|
||||
"profile": "HyperComplex",
|
||||
"file": "E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2e/step2e_profiles_30/set_07_hypercomplex/slice_xz_y0.png",
|
||||
"layout": "today's live field on left; step 2e parameterized field on right; light=air, dark=solid, cyan=near zero"
|
||||
},
|
||||
{
|
||||
"profile": "HyperComplex",
|
||||
"file": "E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2e/step2e_profiles_30/set_07_hypercomplex/slice_yz_x0.png",
|
||||
"layout": "today's live field on left; step 2e parameterized field on right; light=air, dark=solid, cyan=near zero"
|
||||
},
|
||||
{
|
||||
"profile": "HyperComplex",
|
||||
"file": "E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2e/step2e_profiles_30/set_07_hypercomplex/slice_xz_passage.png",
|
||||
"layout": "today's live field on left; step 2e parameterized field on right; light=air, dark=solid, cyan=near zero"
|
||||
}
|
||||
],
|
||||
"slice_directory": "E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2e/step2e_profiles_30",
|
||||
"detail_check":
|
||||
{
|
||||
"live_surface_band_samples": 15515231,
|
||||
"new_surface_band_samples": 4880019,
|
||||
"live_curvature_rms_lag1": 6.248069730899255,
|
||||
"new_curvature_rms_lag1": 8.9351827746107304,
|
||||
"smooth_control_curvature_rms_lag1": 1.9552341792313939,
|
||||
"live_curvature_rms_lag2": 8.6459817485493211,
|
||||
"new_curvature_rms_lag2": 9.3960370724645941,
|
||||
"smooth_control_curvature_rms_lag2": 3.2348563389313552,
|
||||
"detail_raw_parity_samples": 8488,
|
||||
"detail_raw_parity_max_error": 0,
|
||||
"detail_at_least_live_spectrum": true,
|
||||
"deliberate_zero_detail_smoothing_detected": true,
|
||||
"direct_vs_brick_detail_parity": true
|
||||
},
|
||||
"boundary_artifact_check":
|
||||
{
|
||||
"brick_boundary_gradient_jump_rms": 4.0223860554078321,
|
||||
"interior_gradient_jump_rms": 3.475720295663749,
|
||||
"chunk_boundary_gradient_jump_rms": 3.3855279003366117,
|
||||
"feature_cell_boundary_gradient_jump_rms": 3.3785618604833521,
|
||||
"feature_cell_interior_gradient_jump_rms": 3.4366715251600706,
|
||||
"boundary_artifact_check_pass": true,
|
||||
"deliberate_seam_samples_mutated": 706718,
|
||||
"deliberate_seam_failure_caught": true
|
||||
},
|
||||
"brick_bound_audit":
|
||||
{
|
||||
"correctly_skipped_bricks_audited": 109,
|
||||
"final_field_samples_checked_including_halo": 91890,
|
||||
"correct_proof_sign_violations": 0,
|
||||
"first_violation_brick_x": 0,
|
||||
"first_violation_brick_y": 0,
|
||||
"first_violation_brick_z": 0,
|
||||
"first_violation_brick_size_x": 0,
|
||||
"first_violation_brick_size_y": 0,
|
||||
"first_violation_brick_size_z": 0,
|
||||
"first_violation_sample_x": 0,
|
||||
"first_violation_sample_y": 0,
|
||||
"first_violation_sample_z": 0,
|
||||
"first_violation_density": 0,
|
||||
"first_violation_center_macro": 0,
|
||||
"first_violation_sample_macro": 0,
|
||||
"first_violation_sample_detail": 0,
|
||||
"first_violation_macro_delta_per_voxel": 0,
|
||||
"first_violation_path_max_gradient": 0,
|
||||
"first_violation_tiny_step_max_gradient": 0,
|
||||
"first_violation_inside_proven_brick": false,
|
||||
"first_violation_claimed_air": false,
|
||||
"undersized_bound_claims_checked": 20328,
|
||||
"wrong_sign_samples_caught_by_undersized_bound": 14547,
|
||||
"sound_supremum_audit_pass": true,
|
||||
"deliberate_undersized_bound_failure_caught": true
|
||||
},
|
||||
"determinism":
|
||||
{
|
||||
"six_worker_run_1_quantized": "77439101d77e7750",
|
||||
"six_worker_run_2_quantized": "77439101d77e7750",
|
||||
"one_worker_quantized": "77439101d77e7750",
|
||||
"six_worker_run_1_raw_float": "65bdd30af60254e1",
|
||||
"six_worker_run_2_raw_float": "65bdd30af60254e1",
|
||||
"one_worker_raw_float": "65bdd30af60254e1",
|
||||
"same_across_two_runs_and_1_vs_6_workers": true,
|
||||
"deliberate_quantized_hash_mismatch_rejected": true
|
||||
},
|
||||
"entrance_exit_walkability_test_added": true,
|
||||
"inter_strate_passage_open_space_test_added": true,
|
||||
"functional_test_filter": "VoxelForge.ParameterizedGeneration.TraversalSeeds"
|
||||
}
|
||||