diff --git a/CODEMAP.md b/CODEMAP.md index 56c616f..133c28b 100644 --- a/CODEMAP.md +++ b/CODEMAP.md @@ -119,9 +119,9 @@ remains `0`, and lateral regions remain gated off. | `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|visual-edit|visual-lod|visual-gap-walk|visual-gap-inject|dig-motion|parity|horizon|horizon-render|determinism|region-eval|step2d|step2e`, optional `-Build`, `-Assets owner|default`, `-Cvars`, absolute `-Out`, `-Label`, `-HorizonLevel`, `-HorizonRenderDistanceChunks`, `-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; `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` measures eight room-network profiles on the 259³ ring, emits variety metrics and live/new slices, and checks detail, boundaries, bounds, 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. | `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. Optional `-exporttilex/y/z` sets an exact L0 tile origin for controlled export/game parity; omitting all three preserves the existing center-derived origin. `-regioneval=1` retains the exact-field step-2c research harness. `-step2d` retains the earlier independent field benchmark; `-step2e` measures eight room-network profiles and writes timing, variety, and side-by-side slice evidence under `Saved/Research/step2e`. | -| `Source/VoxelForgeEditor/Private/VoxelForgeRegionEvalExperiment.*` | Six-worker lattice drivers for step 2c, step 2d, and step 2e. The original path still compares live `GetDensityAt` with the editor chunk program. Research modes assign contiguous batches of 8³ bricks dynamically so surface-heavy work balances across workers. Each skip requires a macro Lipschitz bound plus the complete detail-amplitude supremum; uncertain bricks retain all 25 cm detail samples. Timings split classification, point evaluation, and uniform fill; step 2e hashes compare 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` (step 2e). Step 2e deterministically assigns one of eight parameter profiles per strate, then builds jittered rooms and a root-connected tunnel graph with optional loops and shafts, pillars, ledges, floating masses, stalactites/stalagmites, and basin floors; four bounded world-space value-noise bands, including a one-sample micro band, are evaluated at full resolution. Step 2e proofs include the model Lipschitz bound, passage/refinement fallback, and full detail supremum; a local derivative bound permits sound proofs through landing-floor blends. The public API adds no reflected class layout. | +| `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. Optional `-exporttilex/y/z` sets an exact L0 tile origin for controlled export/game parity; omitting all three preserves the existing center-derived origin. `-regioneval=1` retains the exact-field step-2c research harness. `-step2d` retains the earlier independent field benchmark; `-step2e` measures eight room-network profiles and writes timing, per-profile parameter ranges, nominal chamber-volume quantiles, vertical-run and tunnel metrics, and side-by-side live/new slice evidence under `Saved/Research/step2e`. The report records 12.5 cm scaling guidance and notes that basin water levels are parameters without water rendering or simulation. Its hardest-profile gates include full-detail parity, seam injection, sound and undersized-bound controls, and repeated / 1-vs-6-worker hash checks. | +| `Source/VoxelForgeEditor/Private/VoxelForgeRegionEvalExperiment.*` | Six-worker lattice drivers for step 2c, step 2d, and step 2e. 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; step 2e uses 4³ proof bricks and stages each evaluated brick before copying contiguous X rows, while step 2d retains 8³. Each skip requires a macro Lipschitz bound plus the complete detail-amplitude supremum; uncertain bricks retain all 25 cm detail samples. Timings split classification, point evaluation, and uniform fill; step 2e hashes compare 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` (step 2e). Step 2e deterministically assigns one of eight parameter profiles per strate, then builds jittered rooms and a root-connected tunnel graph with optional loops and shafts, pillars, ledges, floating masses, stalactites/stalagmites, and basin floors. The room evaluator checks the containing cell first, then cardinal and diagonal neighbors, with horizontal/vertical upper-bound rejection before feature work. A padded thread-local generated-cell cache is reused across adjacent proof bricks, and XY-dependent room/tunnel distances are reused through each vertical column while four bounded world-space value-noise bands, including a one-sample micro band, remain evaluated at full resolution. Step 2e proofs include the model Lipschitz interval, passage/refinement fallback, and full detail supremum; a local derivative bound permits sound proofs through landing-floor blends. The basin has a water-level parameter, but this field does not render or simulate water. The public API adds no reflected class layout. | | `Private/Tests/VoxelForgeParameterizedGenerationTest.cpp` | Separate three-seed functional sweep for the step 2e 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. | diff --git a/Saved/Research/step2e/step2e_profiles_30/set_00_openvaults/slice_xy_center.png b/Saved/Research/step2e/step2e_profiles_30/set_00_openvaults/slice_xy_center.png new file mode 100644 index 0000000..0d31646 Binary files /dev/null and b/Saved/Research/step2e/step2e_profiles_30/set_00_openvaults/slice_xy_center.png differ diff --git a/Saved/Research/step2e/step2e_profiles_30/set_00_openvaults/slice_xz_passage.png b/Saved/Research/step2e/step2e_profiles_30/set_00_openvaults/slice_xz_passage.png new file mode 100644 index 0000000..5cbf759 Binary files /dev/null and b/Saved/Research/step2e/step2e_profiles_30/set_00_openvaults/slice_xz_passage.png differ diff --git a/Saved/Research/step2e/step2e_profiles_30/set_00_openvaults/slice_xz_y0.png b/Saved/Research/step2e/step2e_profiles_30/set_00_openvaults/slice_xz_y0.png new file mode 100644 index 0000000..7f8f0df Binary files /dev/null and b/Saved/Research/step2e/step2e_profiles_30/set_00_openvaults/slice_xz_y0.png differ diff --git a/Saved/Research/step2e/step2e_profiles_30/set_00_openvaults/slice_yz_x0.png b/Saved/Research/step2e/step2e_profiles_30/set_00_openvaults/slice_yz_x0.png new file mode 100644 index 0000000..e154e20 Binary files /dev/null and b/Saved/Research/step2e/step2e_profiles_30/set_00_openvaults/slice_yz_x0.png differ diff --git a/Saved/Research/step2e/step2e_profiles_30/set_01_tightnetwork/slice_xy_center.png b/Saved/Research/step2e/step2e_profiles_30/set_01_tightnetwork/slice_xy_center.png new file mode 100644 index 0000000..d7708ad Binary files /dev/null and b/Saved/Research/step2e/step2e_profiles_30/set_01_tightnetwork/slice_xy_center.png differ diff --git a/Saved/Research/step2e/step2e_profiles_30/set_01_tightnetwork/slice_xz_passage.png b/Saved/Research/step2e/step2e_profiles_30/set_01_tightnetwork/slice_xz_passage.png new file mode 100644 index 0000000..7eea752 Binary files /dev/null and b/Saved/Research/step2e/step2e_profiles_30/set_01_tightnetwork/slice_xz_passage.png differ diff --git a/Saved/Research/step2e/step2e_profiles_30/set_01_tightnetwork/slice_xz_y0.png b/Saved/Research/step2e/step2e_profiles_30/set_01_tightnetwork/slice_xz_y0.png new file mode 100644 index 0000000..49bcaba Binary files /dev/null and b/Saved/Research/step2e/step2e_profiles_30/set_01_tightnetwork/slice_xz_y0.png differ diff --git a/Saved/Research/step2e/step2e_profiles_30/set_01_tightnetwork/slice_yz_x0.png b/Saved/Research/step2e/step2e_profiles_30/set_01_tightnetwork/slice_yz_x0.png new file mode 100644 index 0000000..161dbc8 Binary files /dev/null and b/Saved/Research/step2e/step2e_profiles_30/set_01_tightnetwork/slice_yz_x0.png differ diff --git a/Saved/Research/step2e/step2e_profiles_30/set_02_chamberhalls/slice_xy_center.png b/Saved/Research/step2e/step2e_profiles_30/set_02_chamberhalls/slice_xy_center.png new file mode 100644 index 0000000..2422666 Binary files /dev/null and b/Saved/Research/step2e/step2e_profiles_30/set_02_chamberhalls/slice_xy_center.png differ diff --git a/Saved/Research/step2e/step2e_profiles_30/set_02_chamberhalls/slice_xz_passage.png b/Saved/Research/step2e/step2e_profiles_30/set_02_chamberhalls/slice_xz_passage.png new file mode 100644 index 0000000..3831f63 Binary files /dev/null and b/Saved/Research/step2e/step2e_profiles_30/set_02_chamberhalls/slice_xz_passage.png differ diff --git a/Saved/Research/step2e/step2e_profiles_30/set_02_chamberhalls/slice_xz_y0.png b/Saved/Research/step2e/step2e_profiles_30/set_02_chamberhalls/slice_xz_y0.png new file mode 100644 index 0000000..fcca442 Binary files /dev/null and b/Saved/Research/step2e/step2e_profiles_30/set_02_chamberhalls/slice_xz_y0.png differ diff --git a/Saved/Research/step2e/step2e_profiles_30/set_02_chamberhalls/slice_yz_x0.png b/Saved/Research/step2e/step2e_profiles_30/set_02_chamberhalls/slice_yz_x0.png new file mode 100644 index 0000000..56d0db8 Binary files /dev/null and b/Saved/Research/step2e/step2e_profiles_30/set_02_chamberhalls/slice_yz_x0.png differ diff --git a/Saved/Research/step2e/step2e_profiles_30/set_03_shaftforest/slice_xy_center.png b/Saved/Research/step2e/step2e_profiles_30/set_03_shaftforest/slice_xy_center.png new file mode 100644 index 0000000..a65771e Binary files /dev/null and b/Saved/Research/step2e/step2e_profiles_30/set_03_shaftforest/slice_xy_center.png differ diff --git a/Saved/Research/step2e/step2e_profiles_30/set_03_shaftforest/slice_xz_passage.png b/Saved/Research/step2e/step2e_profiles_30/set_03_shaftforest/slice_xz_passage.png new file mode 100644 index 0000000..5b18b6f Binary files /dev/null and b/Saved/Research/step2e/step2e_profiles_30/set_03_shaftforest/slice_xz_passage.png differ diff --git a/Saved/Research/step2e/step2e_profiles_30/set_03_shaftforest/slice_xz_y0.png b/Saved/Research/step2e/step2e_profiles_30/set_03_shaftforest/slice_xz_y0.png new file mode 100644 index 0000000..a9bc557 Binary files /dev/null and b/Saved/Research/step2e/step2e_profiles_30/set_03_shaftforest/slice_xz_y0.png differ diff --git a/Saved/Research/step2e/step2e_profiles_30/set_03_shaftforest/slice_yz_x0.png b/Saved/Research/step2e/step2e_profiles_30/set_03_shaftforest/slice_yz_x0.png new file mode 100644 index 0000000..19f27c1 Binary files /dev/null and b/Saved/Research/step2e/step2e_profiles_30/set_03_shaftforest/slice_yz_x0.png differ diff --git a/Saved/Research/step2e/step2e_profiles_30/set_04_floatingarchipelago/slice_xy_center.png b/Saved/Research/step2e/step2e_profiles_30/set_04_floatingarchipelago/slice_xy_center.png new file mode 100644 index 0000000..07e258c Binary files /dev/null and b/Saved/Research/step2e/step2e_profiles_30/set_04_floatingarchipelago/slice_xy_center.png differ diff --git a/Saved/Research/step2e/step2e_profiles_30/set_04_floatingarchipelago/slice_xz_passage.png b/Saved/Research/step2e/step2e_profiles_30/set_04_floatingarchipelago/slice_xz_passage.png new file mode 100644 index 0000000..7cec3b9 Binary files /dev/null and b/Saved/Research/step2e/step2e_profiles_30/set_04_floatingarchipelago/slice_xz_passage.png differ diff --git a/Saved/Research/step2e/step2e_profiles_30/set_04_floatingarchipelago/slice_xz_y0.png b/Saved/Research/step2e/step2e_profiles_30/set_04_floatingarchipelago/slice_xz_y0.png new file mode 100644 index 0000000..6c2c19a Binary files /dev/null and b/Saved/Research/step2e/step2e_profiles_30/set_04_floatingarchipelago/slice_xz_y0.png differ diff --git a/Saved/Research/step2e/step2e_profiles_30/set_04_floatingarchipelago/slice_yz_x0.png b/Saved/Research/step2e/step2e_profiles_30/set_04_floatingarchipelago/slice_yz_x0.png new file mode 100644 index 0000000..f138d03 Binary files /dev/null and b/Saved/Research/step2e/step2e_profiles_30/set_04_floatingarchipelago/slice_yz_x0.png differ diff --git a/Saved/Research/step2e/step2e_profiles_30/set_05_pillaredcathedral/slice_xy_center.png b/Saved/Research/step2e/step2e_profiles_30/set_05_pillaredcathedral/slice_xy_center.png new file mode 100644 index 0000000..d9a14d0 Binary files /dev/null and b/Saved/Research/step2e/step2e_profiles_30/set_05_pillaredcathedral/slice_xy_center.png differ diff --git a/Saved/Research/step2e/step2e_profiles_30/set_05_pillaredcathedral/slice_xz_passage.png b/Saved/Research/step2e/step2e_profiles_30/set_05_pillaredcathedral/slice_xz_passage.png new file mode 100644 index 0000000..79b4a80 Binary files /dev/null and b/Saved/Research/step2e/step2e_profiles_30/set_05_pillaredcathedral/slice_xz_passage.png differ diff --git a/Saved/Research/step2e/step2e_profiles_30/set_05_pillaredcathedral/slice_xz_y0.png b/Saved/Research/step2e/step2e_profiles_30/set_05_pillaredcathedral/slice_xz_y0.png new file mode 100644 index 0000000..2134bde Binary files /dev/null and b/Saved/Research/step2e/step2e_profiles_30/set_05_pillaredcathedral/slice_xz_y0.png differ diff --git a/Saved/Research/step2e/step2e_profiles_30/set_05_pillaredcathedral/slice_yz_x0.png b/Saved/Research/step2e/step2e_profiles_30/set_05_pillaredcathedral/slice_yz_x0.png new file mode 100644 index 0000000..1ca5d0f Binary files /dev/null and b/Saved/Research/step2e/step2e_profiles_30/set_05_pillaredcathedral/slice_yz_x0.png differ diff --git a/Saved/Research/step2e/step2e_profiles_30/set_06_terracedbasin/slice_xy_center.png b/Saved/Research/step2e/step2e_profiles_30/set_06_terracedbasin/slice_xy_center.png new file mode 100644 index 0000000..5e31efa Binary files /dev/null and b/Saved/Research/step2e/step2e_profiles_30/set_06_terracedbasin/slice_xy_center.png differ diff --git a/Saved/Research/step2e/step2e_profiles_30/set_06_terracedbasin/slice_xz_passage.png b/Saved/Research/step2e/step2e_profiles_30/set_06_terracedbasin/slice_xz_passage.png new file mode 100644 index 0000000..662ec2c Binary files /dev/null and b/Saved/Research/step2e/step2e_profiles_30/set_06_terracedbasin/slice_xz_passage.png differ diff --git a/Saved/Research/step2e/step2e_profiles_30/set_06_terracedbasin/slice_xz_y0.png b/Saved/Research/step2e/step2e_profiles_30/set_06_terracedbasin/slice_xz_y0.png new file mode 100644 index 0000000..bbf6ccf Binary files /dev/null and b/Saved/Research/step2e/step2e_profiles_30/set_06_terracedbasin/slice_xz_y0.png differ diff --git a/Saved/Research/step2e/step2e_profiles_30/set_06_terracedbasin/slice_yz_x0.png b/Saved/Research/step2e/step2e_profiles_30/set_06_terracedbasin/slice_yz_x0.png new file mode 100644 index 0000000..5de58d5 Binary files /dev/null and b/Saved/Research/step2e/step2e_profiles_30/set_06_terracedbasin/slice_yz_x0.png differ diff --git a/Saved/Research/step2e/step2e_profiles_30/set_07_hypercomplex/slice_xy_center.png b/Saved/Research/step2e/step2e_profiles_30/set_07_hypercomplex/slice_xy_center.png new file mode 100644 index 0000000..243ab7b Binary files /dev/null and b/Saved/Research/step2e/step2e_profiles_30/set_07_hypercomplex/slice_xy_center.png differ diff --git a/Saved/Research/step2e/step2e_profiles_30/set_07_hypercomplex/slice_xz_passage.png b/Saved/Research/step2e/step2e_profiles_30/set_07_hypercomplex/slice_xz_passage.png new file mode 100644 index 0000000..4f33b1d Binary files /dev/null and b/Saved/Research/step2e/step2e_profiles_30/set_07_hypercomplex/slice_xz_passage.png differ diff --git a/Saved/Research/step2e/step2e_profiles_30/set_07_hypercomplex/slice_xz_y0.png b/Saved/Research/step2e/step2e_profiles_30/set_07_hypercomplex/slice_xz_y0.png new file mode 100644 index 0000000..aa9a080 Binary files /dev/null and b/Saved/Research/step2e/step2e_profiles_30/set_07_hypercomplex/slice_xz_y0.png differ diff --git a/Saved/Research/step2e/step2e_profiles_30/set_07_hypercomplex/slice_yz_x0.png b/Saved/Research/step2e/step2e_profiles_30/set_07_hypercomplex/slice_yz_x0.png new file mode 100644 index 0000000..24239b3 Binary files /dev/null and b/Saved/Research/step2e/step2e_profiles_30/set_07_hypercomplex/slice_yz_x0.png differ diff --git a/Saved/Research/step2e/step2e_profiles_30/step2e_report.json b/Saved/Research/step2e/step2e_profiles_30/step2e_report.json new file mode 100644 index 0000000..85a0cf3 --- /dev/null +++ b/Saved/Research/step2e/step2e_profiles_30/step2e_report.json @@ -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" +} \ No newline at end of file diff --git a/Source/VoxelForge/Private/VoxelRedefinedDensityField.cpp b/Source/VoxelForge/Private/VoxelRedefinedDensityField.cpp index 1229776..b932874 100644 --- a/Source/VoxelForge/Private/VoxelRedefinedDensityField.cpp +++ b/Source/VoxelForge/Private/VoxelRedefinedDensityField.cpp @@ -55,21 +55,21 @@ namespace }; const FParameterProfile GParameterProfiles[] = { - { TEXT("OpenVaults"), { 176.0f, 0.34f, 0.45f, 340.0f, 12.0f, 40.0f, 36.0f, + { TEXT("OpenVaults"), { 176.0f, 0.62f, 0.76f, 340.0f, 12.0f, 40.0f, 36.0f, 0.35f, 0.14f, 22.0f, 0.04f, 0.12f, 0.08f, 0.15f, 0.00f, 0.0f, 0.50f } }, - { TEXT("TightNetwork"), { 104.0f, 0.28f, 0.42f, 112.0f, 8.0f, 20.0f, 22.0f, + { TEXT("TightNetwork"), { 104.0f, 0.52f, 0.64f, 256.0f, 8.0f, 20.0f, 22.0f, 0.12f, 0.03f, 14.0f, 0.06f, 0.08f, 0.02f, 0.12f, 0.00f, 0.0f, 0.50f } }, - { TEXT("ChamberHalls"), { 144.0f, 0.38f, 0.45f, 320.0f, 18.0f, 34.0f, 32.0f, + { TEXT("ChamberHalls"), { 144.0f, 0.55f, 0.72f, 320.0f, 18.0f, 34.0f, 32.0f, 0.28f, 0.18f, 22.0f, 0.08f, 0.22f, 0.10f, 0.22f, 0.05f, 20.0f, 0.40f } }, - { TEXT("ShaftForest"), { 128.0f, 0.32f, 0.43f, 176.0f, 12.0f, 26.0f, 28.0f, - 0.12f, 0.55f, 20.0f, 0.05f, 0.18f, 0.04f, 0.20f, 0.00f, 0.0f, 0.50f } }, - { TEXT("FloatingArchipelago"), { 136.0f, 0.37f, 0.45f, 192.0f, 10.0f, 28.0f, 28.0f, + { TEXT("ShaftForest"), { 128.0f, 0.50f, 0.62f, 272.0f, 12.0f, 26.0f, 28.0f, + 0.12f, 0.92f, 28.0f, 0.05f, 0.18f, 0.04f, 0.20f, 0.00f, 0.0f, 0.50f } }, + { TEXT("FloatingArchipelago"), { 136.0f, 0.55f, 0.68f, 216.0f, 10.0f, 28.0f, 28.0f, 0.15f, 0.10f, 16.0f, 0.05f, 0.10f, 0.72f, 0.28f, 0.00f, 0.0f, 0.50f } }, - { TEXT("PillaredCathedral"), { 160.0f, 0.38f, 0.45f, 248.0f, 12.0f, 32.0f, 34.0f, + { TEXT("PillaredCathedral"), { 160.0f, 0.55f, 0.72f, 248.0f, 12.0f, 32.0f, 34.0f, 0.25f, 0.08f, 18.0f, 0.72f, 0.30f, 0.08f, 0.45f, 0.00f, 0.0f, 0.50f } }, - { TEXT("TerracedBasin"), { 136.0f, 0.34f, 0.45f, 216.0f, 14.0f, 28.0f, 30.0f, + { TEXT("TerracedBasin"), { 136.0f, 0.52f, 0.65f, 264.0f, 14.0f, 28.0f, 30.0f, 0.20f, 0.10f, 18.0f, 0.10f, 0.84f, 0.05f, 0.25f, 0.85f, 48.0f, 0.30f } }, - { TEXT("HyperComplex"), { 96.0f, 0.30f, 0.45f, 192.0f, 20.0f, 30.0f, 28.0f, + { TEXT("HyperComplex"), { 96.0f, 0.60f, 0.74f, 280.0f, 20.0f, 30.0f, 28.0f, 0.40f, 0.55f, 20.0f, 0.45f, 0.68f, 0.50f, 0.75f, 0.42f, 28.0f, 0.40f } }, }; @@ -82,6 +82,11 @@ namespace float PadRadius = 0.0f; float BlendRadius = LandingFloorBlendVoxels; FVector ApproachEnd = FVector::ZeroVector; + bool bGeneratedAnchorPrepared = false; + FVector GeneratedAnchorStart = FVector::ZeroVector; + FVector GeneratedAnchorDelta = FVector::ZeroVector; + float GeneratedAnchorLengthSquared = 0.0f; + float GeneratedAnchorReach = 0.0f; }; struct FVerticalShaftNode @@ -147,6 +152,8 @@ namespace TArray Landings; }; + void PrepareParameterizedAnchors(FStratePlan& Plan); + struct FThreadPlanCache { const UVoxelStrateManager* Manager = nullptr; @@ -159,6 +166,7 @@ namespace }; thread_local FThreadPlanCache GPlanCache; + thread_local const FStratePlan* GParameterizedRoomCellCachePlan = nullptr; thread_local FThreadShaftEdgeCache GShaftEdgeCache; FORCEINLINE uint32 Mix(uint32 Value) @@ -477,6 +485,7 @@ namespace if (Manager == nullptr) { GPlanCache = FThreadPlanCache(); + GParameterizedRoomCellCachePlan = nullptr; return; } const uint64 Lifetime = Manager->GetCacheLifetimeId(); @@ -492,6 +501,7 @@ namespace } GPlanCache = FThreadPlanCache(); + GParameterizedRoomCellCachePlan = nullptr; GPlanCache.Manager = Manager; GPlanCache.ManagerLifetimeId = Lifetime; GPlanCache.LayoutVersion = Version; @@ -566,10 +576,10 @@ namespace Plan.Generation.RoomPitchVoxels = FMath::Max( Plan.Generation.RoomPitchVoxels, 80.0f); Plan.Generation.MinimumRoomRadiusFraction = FMath::Clamp( - Plan.Generation.MinimumRoomRadiusFraction, 0.20f, 0.41f); + Plan.Generation.MinimumRoomRadiusFraction, 0.20f, 0.72f); Plan.Generation.MaximumRoomRadiusFraction = FMath::Clamp( Plan.Generation.MaximumRoomRadiusFraction, - Plan.Generation.MinimumRoomRadiusFraction, 0.45f); + Plan.Generation.MinimumRoomRadiusFraction, 0.80f); Plan.Generation.LoopProbability = FMath::Clamp( Plan.Generation.LoopProbability, 0.0f, 0.8f); Plan.Generation.VerticalShaftProbability = FMath::Clamp( @@ -718,7 +728,8 @@ namespace Plan.FloorZ = FMath::Clamp( static_cast(FloorSum / FloorCount), Bottom + 16.0f, Top - 72.0f); Plan.CeilingZ = Plan.FloorZ + MinimumCaveHeight; - Plan.GenerationFloorZ = Plan.FloorZ; + // Parameterized chambers keep their profile-defined vertical span. The + // passage landing is connected into that graph by its generated anchor. } } for (FLandingSite& Site : Plan.Landings) @@ -765,6 +776,10 @@ namespace } } } + for (FStratePlan& Plan : GPlanCache.Strates) + { + PrepareParameterizedAnchors(Plan); + } } struct FGeneratedCaveCell @@ -788,6 +803,30 @@ namespace float SpireLength = 0.0f; float SpireRadius = 0.0f; uint8 FeatureFlags = 0; + uint8 OpenTunnelEdges = 0; + }; + + struct FParameterizedColumnContext + { + FGeneratedCaveCell GeneratedCells[9]; + const FGeneratedCaveCell* Cells[9] = {}; + float RoomDistanceSquared[9] = {}; + float RoomRadii[9] = {}; + float RoomHorizontals[9] = {}; + float RoomFloors[9] = {}; + float PillarHorizontals[9] = {}; + float FloatingXYDistanceSquared[9][2] = {}; + float SpireBaseZ[9] = {}; + float SpireTipZ[9] = {}; + float SpireBaseTopZ[9] = {}; + float SpireTipBottomZ[9] = {}; + float TunnelT[4] = {}; + float TunnelHorizontal[4] = {}; + float LandingDistanceSquared[8] = {}; + float LandingDistance[8] = {}; + float LandingBridgeT[8] = {}; + float LandingBridgeHorizontal[8] = {}; + bool bHasLandingXYCache = false; }; constexpr uint8 HasPillarFlag = 1u << 0; @@ -795,6 +834,20 @@ namespace constexpr uint8 HasFloatingFlag = 1u << 2; constexpr uint8 HasSpireFlag = 1u << 3; constexpr uint8 HasBasinFlag = 1u << 4; + constexpr uint8 HasVerticalShaftFlag = 1u << 5; + constexpr uint8 TunnelToNegativeY = 1u << 0; + constexpr uint8 TunnelToPositiveY = 1u << 1; + constexpr uint8 TunnelToNegativeX = 1u << 2; + constexpr uint8 TunnelToPositiveX = 1u << 3; + + thread_local TArray GParameterizedRoomCellCache; + thread_local FIntPoint GParameterizedRoomCellCacheMin = FIntPoint::ZeroValue; + thread_local FIntPoint GParameterizedRoomCellCacheSize = FIntPoint::ZeroValue; + + FIntPoint GeneratedParent(const FStratePlan& Plan, int32 CellX, int32 CellY); + bool IsGeneratedEdgeOpen( + const FStratePlan& Plan, int32 AX, int32 AY, int32 BX, int32 BY); + FORCEINLINE FIntPoint GenerationCellAt(const FStratePlan& Plan, float X, float Y); FORCEINLINE float ParameterRoll(uint32 Hash, uint32 Salt) { @@ -829,6 +882,27 @@ namespace Cell.RadiusX = Pitch * RadiusFraction; Cell.RadiusY = Cell.RadiusX * FMath::Lerp( 0.82f, 0.98f, ParameterRoll(Hash, 0x27C5u)); + if (VoxelHash::ToFloat01(Hash3(CellX, CellY, 0x53, Plan.GenerationSeed)) + < P.VerticalShaftProbability) + { + Cell.FeatureFlags |= HasVerticalShaftFlag; + } + if (IsGeneratedEdgeOpen(Plan, CellX, CellY, CellX, CellY - 1)) + { + Cell.OpenTunnelEdges |= TunnelToNegativeY; + } + if (IsGeneratedEdgeOpen(Plan, CellX, CellY, CellX, CellY + 1)) + { + Cell.OpenTunnelEdges |= TunnelToPositiveY; + } + if (IsGeneratedEdgeOpen(Plan, CellX, CellY, CellX - 1, CellY)) + { + Cell.OpenTunnelEdges |= TunnelToNegativeX; + } + if (IsGeneratedEdgeOpen(Plan, CellX, CellY, CellX + 1, CellY)) + { + Cell.OpenTunnelEdges |= TunnelToPositiveX; + } Cell.FloorZ = Plan.GenerationFloorZ + (ParameterRoll(Hash, 0xF103u) * 2.0f - 1.0f) * P.FloorReliefVoxels; Cell.CeilingZ = Cell.FloorZ + P.RoomHeightVoxels; @@ -880,6 +954,42 @@ namespace return Cell; } + void PrepareParameterizedAnchors(FStratePlan& Plan) + { + const float AnchorReach = Plan.Generation.RoomPitchVoxels * 1.25f + + FMath::Max(32.0f, Plan.Generation.TunnelRadiusVoxels); + for (FLandingSite& Site : Plan.Landings) + { + const FIntPoint LandingCell = GenerationCellAt( + Plan, Site.Position.X, Site.Position.Y); + FGeneratedCaveCell NearestCell; + float BestDistanceSquared = FLT_MAX; + for (int32 CellY = -1; CellY <= 1; ++CellY) + for (int32 CellX = -1; CellX <= 1; ++CellX) + { + const FGeneratedCaveCell Candidate = BuildGeneratedCell( + Plan, LandingCell.X + CellX, LandingCell.Y + CellY); + const float DX = Candidate.CenterX - Site.Position.X; + const float DY = Candidate.CenterY - Site.Position.Y; + const float DistanceSquared = DX * DX + DY * DY; + if (DistanceSquared < BestDistanceSquared) + { + BestDistanceSquared = DistanceSquared; + NearestCell = Candidate; + } + } + Site.GeneratedAnchorStart = FVector( + Site.Position.X, Site.Position.Y, Site.FloorZ + 24.0f); + const FVector AnchorEnd( + NearestCell.CenterX, NearestCell.CenterY, + NearestCell.FloorZ + Plan.Generation.TunnelHalfHeightVoxels); + Site.GeneratedAnchorDelta = AnchorEnd - Site.GeneratedAnchorStart; + Site.GeneratedAnchorLengthSquared = Site.GeneratedAnchorDelta.SizeSquared(); + Site.GeneratedAnchorReach = AnchorReach; + Site.bGeneratedAnchorPrepared = true; + } + } + FORCEINLINE float SmoothMinimum(float A, float B, float K) { const float H = FMath::Max(K - FMath::Abs(A - B), 0.0f) / K; @@ -903,72 +1013,81 @@ namespace } FORCEINLINE float GeneratedRoomCell( - const FStratePlan& Plan, const FGeneratedCaveCell& Cell, - float X, float Y, float Z) + const FGeneratedCaveCell& Cell, + float Z, float Radius, float Horizontal, + const FParameterizedColumnContext& Column, int32 CellIndex) { - const float DX = X - Cell.CenterX; - const float DY = Y - Cell.CenterY; - const float Radius = FMath::Sqrt(DX * DX + DY * DY); - const float Horizontal = Cell.RadiusX - Radius; - float FloorZ = Cell.FloorZ; - if ((Cell.FeatureFlags & HasBasinFlag) != 0) - { - FloorZ += Plan.Generation.BasinDepthVoxels - * FMath::Clamp(Radius / FMath::Max(Cell.RadiusX, 1.0f), 0.0f, 1.0f); - } + const float FloorZ = Column.RoomFloors[CellIndex]; const float Vertical = FMath::Min(Z - FloorZ, Cell.CeilingZ - Z); float Room = SmoothMinimum(Horizontal, Vertical, 8.0f); - if (Room > 0.0f && (Cell.FeatureFlags & HasPillarFlag) != 0) + if ((Cell.FeatureFlags & HasPillarFlag) != 0 && Room > -14.0f) { - const float PX = X - Cell.PillarX; - const float PY = Y - Cell.PillarY; const float Pillar = FMath::Max3( - FMath::Sqrt(PX * PX + PY * PY) - Cell.PillarRadius, + Column.PillarHorizontals[CellIndex], Cell.FloorZ - Z, Z - Cell.CeilingZ); - Room = FMath::Min(Room, Pillar); + if (Pillar < Room) + { + Room = Pillar; + } } - if ((Cell.FeatureFlags & HasLedgeFlag) != 0 - && Horizontal >= -2.0f && FMath::Abs(Z - Cell.LedgeZ) <= 3.0f) + if ((Cell.FeatureFlags & HasLedgeFlag) != 0) { const float Inner = Cell.RadiusX * 0.58f; const float Outer = Cell.RadiusX + 1.0f; const float Ledge = FMath::Max3( Inner - Radius, Radius - Outer, FMath::Abs(Z - Cell.LedgeZ) - 2.5f); - Room = FMath::Min(Room, Ledge); + if (Ledge < Room) + { + Room = Ledge; + } } - if (Room > 0.0f && (Cell.FeatureFlags & HasFloatingFlag) != 0) + if ((Cell.FeatureFlags & HasFloatingFlag) != 0) { for (int32 Blob = 0; Blob < 2; ++Blob) { - const float BX = X - Cell.FloatingX[Blob]; - const float BY = Y - Cell.FloatingY[Blob]; + if (Room <= -Cell.FloatingRadius[Blob]) + { + continue; + } const float BZ = Z - Cell.FloatingZ[Blob]; - const float BlobSolid = FMath::Sqrt(BX * BX + BY * BY + BZ * BZ) + const float BlobSolid = FMath::Sqrt( + Column.FloatingXYDistanceSquared[CellIndex][Blob] + BZ * BZ) - Cell.FloatingRadius[Blob]; - Room = FMath::Min(Room, BlobSolid); + if (BlobSolid < Room) + { + Room = BlobSolid; + } } } - if (Room > 0.0f && (Cell.FeatureFlags & HasSpireFlag) != 0) + if ((Cell.FeatureFlags & HasSpireFlag) != 0 && Room > -14.0f) { - const float BaseZ = Cell.FloorZ + 2.0f; - const float TipZ = FMath::Min(Cell.CeilingZ - 2.0f, BaseZ + Cell.SpireLength); - if (Z >= BaseZ && Z <= TipZ) + const float BaseZ = Column.SpireBaseZ[CellIndex]; + const float TipZ = Column.SpireTipZ[CellIndex]; + if (Z >= BaseZ || BaseZ - Z < Room) { - const float T = (Z - BaseZ) / FMath::Max(TipZ - BaseZ, 1.0f); + const float T = FMath::Clamp( + (Z - BaseZ) / FMath::Max(TipZ - BaseZ, 1.0f), 0.0f, 1.0f); const float RadiusAtZ = FMath::Lerp(Cell.SpireRadius, 1.25f, T); const float Spire = FMath::Max3( Radius - RadiusAtZ, BaseZ - Z, Z - TipZ); - Room = FMath::Min(Room, Spire); + if (Spire < Room) + { + Room = Spire; + } } - const float BaseTop = Cell.CeilingZ - 2.0f; - const float TipBottom = FMath::Max(Cell.FloorZ + 2.0f, BaseTop - Cell.SpireLength); - if (Z >= TipBottom && Z <= BaseTop) + const float BaseTop = Column.SpireBaseTopZ[CellIndex]; + const float TipBottom = Column.SpireTipBottomZ[CellIndex]; + if (Z <= BaseTop || Z - BaseTop < Room) { - const float T = (BaseTop - Z) / FMath::Max(BaseTop - TipBottom, 1.0f); + const float T = FMath::Clamp( + (BaseTop - Z) / FMath::Max(BaseTop - TipBottom, 1.0f), 0.0f, 1.0f); const float RadiusAtZ = FMath::Lerp(Cell.SpireRadius, 1.25f, T); const float Stalactite = FMath::Max3( Radius - RadiusAtZ, Z - BaseTop, TipBottom - Z); - Room = FMath::Min(Room, Stalactite); + if (Stalactite < Room) + { + Room = Stalactite; + } } } return Room; @@ -1018,18 +1137,8 @@ namespace FORCEINLINE float GeneratedTunnelEdge( const FStratePlan& Plan, const FGeneratedCaveCell& A, const FGeneratedCaveCell& B, - float X, float Y, float Z) + float Z, float T, float Horizontal) { - if (!IsGeneratedEdgeOpen(Plan, A.CellX, A.CellY, B.CellX, B.CellY)) - { - return -FLT_MAX; - } - const float T = SegmentParameterXY(X, Y, - FVector(A.CenterX, A.CenterY, 0.0f), FVector(B.CenterX, B.CenterY, 0.0f)); - const float Horizontal = Plan.Generation.TunnelRadiusVoxels - - FMath::Sqrt(DistanceSquaredToSegmentXY( - X, Y, FVector(A.CenterX, A.CenterY, 0.0f), - FVector(B.CenterX, B.CenterY, 0.0f))); const float FloorZ = FMath::Lerp(A.FloorZ, B.FloorZ, T); const float Vertical = FMath::Min( Z - FloorZ, @@ -1037,66 +1146,267 @@ namespace return SmoothMinimum(Horizontal, Vertical, 5.0f); } - float EvaluateParameterizedMacro( - const FStratePlan& Plan, float X, float Y, float Z, float* OutDetailScale) + const FGeneratedCaveCell* FindParameterizedBrickRoomCell( + const FStratePlan& Plan, const FParameterizedBrickContext* Context, + int32 CellX, int32 CellY) + { + if (Context == nullptr || Context->PlanToken != &Plan + || Context->RoomCellCacheToken != GParameterizedRoomCellCache.GetData() + || GParameterizedRoomCellCachePlan != &Plan + || CellX < Context->RoomCellCacheMin.X + || CellY < Context->RoomCellCacheMin.Y + || CellX >= Context->RoomCellCacheMin.X + Context->RoomCellCacheSize.X + || CellY >= Context->RoomCellCacheMin.Y + Context->RoomCellCacheSize.Y) + { + return nullptr; + } + const int32 Index = (CellY - Context->RoomCellCacheMin.Y) + * Context->RoomCellCacheSize.X + CellX - Context->RoomCellCacheMin.X; + return GParameterizedRoomCellCache.IsValidIndex(Index) + ? &GParameterizedRoomCellCache[Index] : nullptr; + } + + void BuildParameterizedColumnContext( + const FStratePlan& Plan, float X, float Y, + const FParameterizedBrickContext* Context, + FParameterizedColumnContext& OutColumn) { const FIntPoint BaseCell = GenerationCellAt(Plan, X, Y); - FGeneratedCaveCell Cells[9]; + const bool bHasRoomCellCache = Context != nullptr + && Context->PlanToken == &Plan + && Context->RoomCellCacheToken == GParameterizedRoomCellCache.GetData() + && GParameterizedRoomCellCachePlan == &Plan; for (int32 CellY = -1; CellY <= 1; ++CellY) for (int32 CellX = -1; CellX <= 1; ++CellX) { const int32 Index = (CellY + 1) * 3 + CellX + 1; - Cells[Index] = BuildGeneratedCell( - Plan, BaseCell.X + CellX, BaseCell.Y + CellY); + const int32 GeneratedX = BaseCell.X + CellX; + const int32 GeneratedY = BaseCell.Y + CellY; + if (bHasRoomCellCache) + { + const int32 CacheX = GeneratedX - Context->RoomCellCacheMin.X; + const int32 CacheY = GeneratedY - Context->RoomCellCacheMin.Y; + if (CacheX >= 0 && CacheY >= 0 + && CacheX < Context->RoomCellCacheSize.X + && CacheY < Context->RoomCellCacheSize.Y) + { + const int32 CacheIndex = CacheY * Context->RoomCellCacheSize.X + CacheX; + if (GParameterizedRoomCellCache.IsValidIndex(CacheIndex)) + { + OutColumn.Cells[Index] = &GParameterizedRoomCellCache[CacheIndex]; + } + } + } + else + { + OutColumn.Cells[Index] = FindParameterizedBrickRoomCell( + Plan, Context, GeneratedX, GeneratedY); + } + if (OutColumn.Cells[Index] == nullptr) + { + OutColumn.GeneratedCells[Index] = BuildGeneratedCell( + Plan, GeneratedX, GeneratedY); + OutColumn.Cells[Index] = &OutColumn.GeneratedCells[Index]; + } + const float DX = X - OutColumn.Cells[Index]->CenterX; + const float DY = Y - OutColumn.Cells[Index]->CenterY; + const float DistanceSquared = DX * DX + DY * DY; + OutColumn.RoomDistanceSquared[Index] = DistanceSquared; + OutColumn.RoomRadii[Index] = FMath::Sqrt(DistanceSquared); + OutColumn.RoomHorizontals[Index] = OutColumn.Cells[Index]->RadiusX + - OutColumn.RoomRadii[Index]; + + const FGeneratedCaveCell& Cell = *OutColumn.Cells[Index]; + OutColumn.RoomFloors[Index] = Cell.FloorZ; + if ((Cell.FeatureFlags & HasBasinFlag) != 0) + { + OutColumn.RoomFloors[Index] += Plan.Generation.BasinDepthVoxels + * FMath::Clamp(OutColumn.RoomRadii[Index] + / FMath::Max(Cell.RadiusX, 1.0f), 0.0f, 1.0f); + } + if ((Cell.FeatureFlags & HasPillarFlag) != 0) + { + const float PX = X - Cell.PillarX; + const float PY = Y - Cell.PillarY; + OutColumn.PillarHorizontals[Index] = FMath::Sqrt(PX * PX + PY * PY) + - Cell.PillarRadius; + } + if ((Cell.FeatureFlags & HasFloatingFlag) != 0) + { + for (int32 Blob = 0; Blob < 2; ++Blob) + { + const float BX = X - Cell.FloatingX[Blob]; + const float BY = Y - Cell.FloatingY[Blob]; + OutColumn.FloatingXYDistanceSquared[Index][Blob] = BX * BX + BY * BY; + } + } + if ((Cell.FeatureFlags & HasSpireFlag) != 0) + { + OutColumn.SpireBaseZ[Index] = Cell.FloorZ + 2.0f; + OutColumn.SpireTipZ[Index] = FMath::Min( + Cell.CeilingZ - 2.0f, + OutColumn.SpireBaseZ[Index] + Cell.SpireLength); + OutColumn.SpireBaseTopZ[Index] = Cell.CeilingZ - 2.0f; + OutColumn.SpireTipBottomZ[Index] = FMath::Max( + Cell.FloorZ + 2.0f, + OutColumn.SpireBaseTopZ[Index] - Cell.SpireLength); + } + } + + const FGeneratedCaveCell& Centre = *OutColumn.Cells[4]; + const int32 EdgeIndices[] = {1, 7, 3, 5}; + const uint8 EdgeFlags[] = { + TunnelToNegativeY, TunnelToPositiveY, + TunnelToNegativeX, TunnelToPositiveX}; + for (int32 EdgeIndex = 0; EdgeIndex < UE_ARRAY_COUNT(EdgeIndices); ++EdgeIndex) + { + OutColumn.TunnelHorizontal[EdgeIndex] = -FLT_MAX; + if ((Centre.OpenTunnelEdges & EdgeFlags[EdgeIndex]) == 0) + { + continue; + } + const FGeneratedCaveCell& Next = *OutColumn.Cells[EdgeIndices[EdgeIndex]]; + const float ABX = Next.CenterX - Centre.CenterX; + const float ABY = Next.CenterY - Centre.CenterY; + const float LengthSquared = ABX * ABX + ABY * ABY; + const float T = LengthSquared > 1.0e-8f + ? FMath::Clamp(((X - Centre.CenterX) * ABX + + (Y - Centre.CenterY) * ABY) / LengthSquared, 0.0f, 1.0f) + : 0.0f; + const float DX = X - (Centre.CenterX + T * ABX); + const float DY = Y - (Centre.CenterY + T * ABY); + OutColumn.TunnelT[EdgeIndex] = T; + OutColumn.TunnelHorizontal[EdgeIndex] = Plan.Generation.TunnelRadiusVoxels + - FMath::Sqrt(DX * DX + DY * DY); + } + + OutColumn.bHasLandingXYCache = Plan.Landings.Num() <= UE_ARRAY_COUNT( + OutColumn.LandingDistanceSquared); + if (OutColumn.bHasLandingXYCache) + { + const FLandingSite* Hub = Plan.LandingHubIndex != INDEX_NONE + ? &Plan.Landings[Plan.LandingHubIndex] : nullptr; + for (int32 SiteIndex = 0; SiteIndex < Plan.Landings.Num(); ++SiteIndex) + { + const FLandingSite& Site = Plan.Landings[SiteIndex]; + const float DX = X - Site.Position.X; + const float DY = Y - Site.Position.Y; + const float DistanceSquared = DX * DX + DY * DY; + OutColumn.LandingDistanceSquared[SiteIndex] = DistanceSquared; + OutColumn.LandingDistance[SiteIndex] = FMath::Sqrt(DistanceSquared); + OutColumn.LandingBridgeT[SiteIndex] = 0.0f; + OutColumn.LandingBridgeHorizontal[SiteIndex] = -FLT_MAX; + if (Hub != nullptr && Plan.Landings.Num() > 1 + && SiteIndex != Plan.LandingHubIndex) + { + OutColumn.LandingBridgeT[SiteIndex] = SegmentParameterXY( + X, Y, Site.Position, Hub->Position); + OutColumn.LandingBridgeHorizontal[SiteIndex] = LandingBridgeRadius + - FMath::Sqrt(DistanceSquaredToSegmentXY( + X, Y, Site.Position, Hub->Position)); + } + } + } + } + + float EvaluateParameterizedMacro( + const FStratePlan& Plan, float X, float Y, float Z, float* OutDetailScale, + const FParameterizedBrickContext* Context = nullptr, + const FParameterizedColumnContext* ColumnContext = nullptr) + { + FParameterizedColumnContext LocalColumn; + if (ColumnContext == nullptr) + { + BuildParameterizedColumnContext(Plan, X, Y, Context, LocalColumn); + ColumnContext = &LocalColumn; } float Macro = -FLT_MAX; - for (const FGeneratedCaveCell& Cell : Cells) - { - const float DX = X - Cell.CenterX; - const float DY = Y - Cell.CenterY; - const float Horizontal = Cell.RadiusX - FMath::Sqrt(DX * DX + DY * DY); - if (Macro > -FLT_MAX * 0.5f && Horizontal < Macro - 6.0f) - { - continue; - } - Macro = SmoothMaximum(Macro, - GeneratedRoomCell(Plan, Cell, X, Y, Z), 6.0f); - } - const FGeneratedCaveCell& Centre = Cells[4]; - const int32 EdgeIndices[] = {1, 7, 3, 5}; - for (const int32 EdgeIndex : EdgeIndices) - { - Macro = SmoothMaximum(Macro, - GeneratedTunnelEdge(Plan, Centre, Cells[EdgeIndex], X, Y, Z), 5.0f); - } - const float Bottom = static_cast(Plan.MinChunkZ * CHUNK_SIZE) + 8.0f; const float Top = static_cast(Plan.MaxChunkZExclusive * CHUNK_SIZE) - 8.0f; - for (const FGeneratedCaveCell& Cell : Cells) + static constexpr int32 CellEvaluationOrder[] = {4, 1, 3, 5, 7, 0, 2, 6, 8}; + for (int32 OrderIndex = 0; + OrderIndex < UE_ARRAY_COUNT(CellEvaluationOrder); ++OrderIndex) { - const uint32 ShaftHash = Hash3( - Cell.CellX, Cell.CellY, 0x53, Plan.GenerationSeed); - if (VoxelHash::ToFloat01(ShaftHash) >= Plan.Generation.VerticalShaftProbability) + const int32 Index = CellEvaluationOrder[OrderIndex]; + const FGeneratedCaveCell& Cell = *ColumnContext->Cells[Index]; + const float DistanceSquared = ColumnContext->RoomDistanceSquared[Index]; + if (Macro > -FLT_MAX * 0.5f) + { + const float MaximumDistance = Cell.RadiusX - Macro + 6.0f; + if (MaximumDistance < 0.0f + || DistanceSquared > FMath::Square(MaximumDistance + 1.0e-3f)) + { + continue; + } + const float VerticalUpperBound = FMath::Min( + Z - ColumnContext->RoomFloors[Index], Cell.CeilingZ - Z); + if (VerticalUpperBound <= Macro - 6.0f) + { + continue; + } + } + Macro = SmoothMaximum(Macro, + GeneratedRoomCell(Cell, Z, + ColumnContext->RoomRadii[Index], + ColumnContext->RoomHorizontals[Index], + *ColumnContext, Index), 6.0f); + } + const FGeneratedCaveCell& Centre = *ColumnContext->Cells[4]; + const int32 EdgeIndices[] = {1, 7, 3, 5}; + const uint8 EdgeFlags[] = { + TunnelToNegativeY, TunnelToPositiveY, + TunnelToNegativeX, TunnelToPositiveX}; + for (int32 EdgeIndex = 0; EdgeIndex < UE_ARRAY_COUNT(EdgeIndices); ++EdgeIndex) + { + if ((Centre.OpenTunnelEdges & EdgeFlags[EdgeIndex]) != 0) + { + Macro = SmoothMaximum(Macro, GeneratedTunnelEdge( + Plan, Centre, *ColumnContext->Cells[EdgeIndices[EdgeIndex]], Z, + ColumnContext->TunnelT[EdgeIndex], + ColumnContext->TunnelHorizontal[EdgeIndex]), 5.0f); + } + } + + for (int32 Index = 0; Index < UE_ARRAY_COUNT(ColumnContext->Cells); ++Index) + { + const FGeneratedCaveCell& Cell = *ColumnContext->Cells[Index]; + if ((Cell.FeatureFlags & HasVerticalShaftFlag) == 0) + { + continue; + } + const float DistanceSquared = ColumnContext->RoomDistanceSquared[Index]; + const float MaximumDistance = Plan.Generation.ShaftRadiusVoxels - Macro + 5.0f; + if (MaximumDistance < 0.0f + || DistanceSquared > FMath::Square(MaximumDistance + 1.0e-3f)) { continue; } - const float DX = X - Cell.CenterX; - const float DY = Y - Cell.CenterY; const float Horizontal = Plan.Generation.ShaftRadiusVoxels - - FMath::Sqrt(DX * DX + DY * DY); + - ColumnContext->RoomRadii[Index]; const float Vertical = FMath::Min(Z - Bottom, Top - Z); Macro = SmoothMaximum(Macro, SmoothMinimum(Horizontal, Vertical, 5.0f), 5.0f); } float DetailFade = 1.0f; - for (const FLandingSite& Site : Plan.Landings) + for (int32 SiteIndex = 0; SiteIndex < Plan.Landings.Num(); ++SiteIndex) { - const float DX = X - Site.Position.X; - const float DY = Y - Site.Position.Y; - const float DistanceSquared = DX * DX + DY * DY; - const float Distance = FMath::Sqrt(DistanceSquared); + const FLandingSite& Site = Plan.Landings[SiteIndex]; + float DistanceSquared = 0.0f; + float Distance = 0.0f; + if (ColumnContext->bHasLandingXYCache) + { + DistanceSquared = ColumnContext->LandingDistanceSquared[SiteIndex]; + Distance = ColumnContext->LandingDistance[SiteIndex]; + } + else + { + const float DX = X - Site.Position.X; + const float DY = Y - Site.Position.Y; + DistanceSquared = DX * DX + DY * DY; + Distance = FMath::Sqrt(DistanceSquared); + } const float RoomAir = FMath::Min( Site.PadRadius - Distance, FMath::Min(Z - Site.FloorZ, Site.FloorZ + MinimumCaveHeight - Z)); @@ -1106,39 +1416,20 @@ namespace DetailFade = 0.0f; } - const float AnchorReach = Plan.Generation.RoomPitchVoxels * 1.25f - + FMath::Max(32.0f, Plan.Generation.TunnelRadiusVoxels); - if (DistanceSquared > FMath::Square(AnchorReach)) + if (!Site.bGeneratedAnchorPrepared + || DistanceSquared > FMath::Square(Site.GeneratedAnchorReach)) { continue; } - const FIntPoint LandingCell = GenerationCellAt(Plan, Site.Position.X, Site.Position.Y); - FGeneratedCaveCell NearestCell; - float BestDistanceSq = FLT_MAX; - for (int32 CellY = -1; CellY <= 1; ++CellY) - for (int32 CellX = -1; CellX <= 1; ++CellX) - { - const FGeneratedCaveCell Candidate = BuildGeneratedCell( - Plan, LandingCell.X + CellX, LandingCell.Y + CellY); - const float CandidateDistanceSq = FMath::Square(Candidate.CenterX - Site.Position.X) - + FMath::Square(Candidate.CenterY - Site.Position.Y); - if (CandidateDistanceSq < BestDistanceSq) - { - BestDistanceSq = CandidateDistanceSq; - NearestCell = Candidate; - } - } - const FVector AnchorA(Site.Position.X, Site.Position.Y, Site.FloorZ + 24.0f); - const FVector AnchorB(NearestCell.CenterX, NearestCell.CenterY, - NearestCell.FloorZ + Plan.Generation.TunnelHalfHeightVoxels); - const FVector Segment = AnchorB - AnchorA; - const float SegmentLengthSq = Segment.SizeSquared(); const FVector Point(X, Y, Z); - const float T = SegmentLengthSq > 1.0e-8f - ? FMath::Clamp(FVector::DotProduct(Point - AnchorA, Segment) / SegmentLengthSq, - 0.0f, 1.0f) : 0.0f; + const float T = Site.GeneratedAnchorLengthSquared > 1.0e-8f + ? FMath::Clamp(FVector::DotProduct( + Point - Site.GeneratedAnchorStart, Site.GeneratedAnchorDelta) + / Site.GeneratedAnchorLengthSquared, 0.0f, 1.0f) : 0.0f; + const FVector AnchorDelta = Point + - (Site.GeneratedAnchorStart + Site.GeneratedAnchorDelta * T); const float Anchor = FMath::Max(32.0f, Plan.Generation.TunnelRadiusVoxels) - - (Point - (AnchorA + Segment * T)).Size(); + - AnchorDelta.Size(); Macro = SmoothMaximum(Macro, Anchor, 5.0f); } @@ -1152,9 +1443,13 @@ namespace continue; } const FLandingSite& Site = Plan.Landings[SiteIndex]; - const float Horizontal = LandingBridgeRadius - FMath::Sqrt( - DistanceSquaredToSegmentXY(X, Y, Site.Position, Hub.Position)); - const float T = SegmentParameterXY(X, Y, Site.Position, Hub.Position); + const float Horizontal = ColumnContext->bHasLandingXYCache + ? ColumnContext->LandingBridgeHorizontal[SiteIndex] + : LandingBridgeRadius - FMath::Sqrt( + DistanceSquaredToSegmentXY(X, Y, Site.Position, Hub.Position)); + const float T = ColumnContext->bHasLandingXYCache + ? ColumnContext->LandingBridgeT[SiteIndex] + : SegmentParameterXY(X, Y, Site.Position, Hub.Position); const float FloorZ = FMath::Lerp(Site.FloorZ, Hub.FloorZ, T); const float Vertical = FMath::Min( Z - FloorZ, FloorZ + MinimumCaveHeight - Z); @@ -1455,6 +1750,10 @@ void ResetThreadCache() { GPlanCache = FThreadPlanCache(); GShaftEdgeCache = FThreadShaftEdgeCache(); + GParameterizedRoomCellCache.Reset(); + GParameterizedRoomCellCacheMin = FIntPoint::ZeroValue; + GParameterizedRoomCellCacheSize = FIntPoint::ZeroValue; + GParameterizedRoomCellCachePlan = nullptr; } static float EvaluateInternal(const UVoxelGenerator& Generator, @@ -1462,7 +1761,8 @@ static float EvaluateInternal(const UVoxelGenerator& Generator, float* OutPreEditDensity, float DetailScale, const FBrickEvaluationContext* Context = nullptr, bool bParameterized = false, - const FParameterizedBrickContext* ParameterizedContext = nullptr) + const FParameterizedBrickContext* ParameterizedContext = nullptr, + const FParameterizedColumnContext* ParameterizedColumnContext = nullptr) { if (Context == nullptr && ParameterizedContext == nullptr) { @@ -1509,7 +1809,8 @@ static float EvaluateInternal(const UVoxelGenerator& Generator, { float DetailFade = 1.0f; const float Macro = bParameterized - ? EvaluateParameterizedMacro(*Plan, WorldX, WorldY, WorldZ, &DetailFade) + ? EvaluateParameterizedMacro(*Plan, WorldX, WorldY, WorldZ, &DetailFade, + ParameterizedContext, ParameterizedColumnContext) : (Context != nullptr && Context->bMacroXYPrecomputed ? EvaluateMacroFromBrick(*Plan, *Context, WorldX, WorldY, WorldZ, &DetailFade) : EvaluateMacro(*Plan, WorldX, WorldY, WorldZ, &DetailFade)); @@ -1635,6 +1936,51 @@ float EvaluateParameterizedWithDetailValue(const UVoxelGenerator& Generator, OutPreEditDensity, DetailScale, nullptr, true, Context); } +void EvaluateParameterizedColumnWithDetailValues( + const UVoxelGenerator& Generator, float WorldX, float WorldY, float WorldZStart, + const float* DetailValues, int32 DetailStride, int32 Count, float* OutValues, + const FParameterizedBrickContext* Context, float DetailScale) +{ + if (DetailValues == nullptr || OutValues == nullptr || Count <= 0 || DetailStride <= 0) + { + return; + } + const FIntVector Chunk( + FMath::FloorToInt(WorldX / CHUNK_SIZE), + FMath::FloorToInt(WorldY / CHUNK_SIZE), + FMath::FloorToInt(WorldZStart / CHUNK_SIZE)); + const FStratePlan* Plan = Context != nullptr && Context->bPlanResolved + ? static_cast(Context->PlanToken) : nullptr; + if (Plan == nullptr) + { + EnsurePlan(Generator); + Plan = FindStrate(Chunk); + } + const int32 EndChunkZ = FMath::FloorToInt( + (WorldZStart + static_cast(Count - 1)) / CHUNK_SIZE); + if (Plan == nullptr || Chunk.Z != EndChunkZ) + { + for (int32 Z = 0; Z < Count; ++Z) + { + OutValues[Z] = EvaluateInternal( + Generator, WorldX, WorldY, WorldZStart + static_cast(Z), + DetailValues[Z * DetailStride], true, nullptr, DetailScale, + nullptr, true, Context); + } + return; + } + + FParameterizedColumnContext ColumnContext; + BuildParameterizedColumnContext(*Plan, WorldX, WorldY, Context, ColumnContext); + for (int32 Z = 0; Z < Count; ++Z) + { + OutValues[Z] = EvaluateInternal( + Generator, WorldX, WorldY, WorldZStart + static_cast(Z), + DetailValues[Z * DetailStride], true, nullptr, DetailScale, + nullptr, true, Context, &ColumnContext); + } +} + void BuildParameterizedBrickContext( const UVoxelGenerator& Generator, FIntVector OriginVoxels, FIntVector Size, FParameterizedBrickContext& OutContext) @@ -1674,6 +2020,58 @@ void BuildParameterizedBrickContext( static_cast(OriginVoxels.Z + Size.Z)); FindPassageCandidates(FBox(Min, Max), OutContext.PassageCandidates); OutContext.bMayContainPassage = !OutContext.PassageCandidates.IsEmpty(); + + if (Plan != nullptr && bStableStrate && !OutContext.bIsGapChunk + && Size.X > 0 && Size.Y > 0) + { + FIntPoint MinimumBaseCell(MAX_int32, MAX_int32); + FIntPoint MaximumBaseCell(MIN_int32, MIN_int32); + const int32 CornerX[] = {OriginVoxels.X, End.X}; + const int32 CornerY[] = {OriginVoxels.Y, End.Y}; + for (const int32 WorldY : CornerY) + for (const int32 WorldX : CornerX) + { + const FIntPoint Cell = GenerationCellAt( + *Plan, static_cast(WorldX), static_cast(WorldY)); + MinimumBaseCell.X = FMath::Min(MinimumBaseCell.X, Cell.X); + MinimumBaseCell.Y = FMath::Min(MinimumBaseCell.Y, Cell.Y); + MaximumBaseCell.X = FMath::Max(MaximumBaseCell.X, Cell.X); + MaximumBaseCell.Y = FMath::Max(MaximumBaseCell.Y, Cell.Y); + } + + const FIntPoint NeededMin = MinimumBaseCell - FIntPoint(1, 1); + const FIntPoint NeededMax = MaximumBaseCell + FIntPoint(1, 1); + const bool bCacheContainsNeededCells = GParameterizedRoomCellCachePlan == Plan + && GParameterizedRoomCellCacheMin.X <= NeededMin.X + && GParameterizedRoomCellCacheMin.Y <= NeededMin.Y + && GParameterizedRoomCellCacheMin.X + GParameterizedRoomCellCacheSize.X - 1 + >= NeededMax.X + && GParameterizedRoomCellCacheMin.Y + GParameterizedRoomCellCacheSize.Y - 1 + >= NeededMax.Y; + if (!bCacheContainsNeededCells) + { + // Keep one extra generated-cell layer beyond the 3x3 query halo so adjacent + // proof bricks reuse their deterministic room and edge rolls. + GParameterizedRoomCellCacheMin = MinimumBaseCell - FIntPoint(2, 2); + GParameterizedRoomCellCacheSize = MaximumBaseCell - MinimumBaseCell + + FIntPoint(5, 5); + const int32 RoomCellCount = GParameterizedRoomCellCacheSize.X + * GParameterizedRoomCellCacheSize.Y; + GParameterizedRoomCellCache.SetNumUninitialized(RoomCellCount); + int32 RoomCellIndex = 0; + for (int32 CellY = 0; CellY < GParameterizedRoomCellCacheSize.Y; ++CellY) + for (int32 CellX = 0; CellX < GParameterizedRoomCellCacheSize.X; ++CellX) + { + GParameterizedRoomCellCache[RoomCellIndex++] = BuildGeneratedCell( + *Plan, GParameterizedRoomCellCacheMin.X + CellX, + GParameterizedRoomCellCacheMin.Y + CellY); + } + GParameterizedRoomCellCachePlan = Plan; + } + OutContext.RoomCellCacheMin = GParameterizedRoomCellCacheMin; + OutContext.RoomCellCacheSize = GParameterizedRoomCellCacheSize; + OutContext.RoomCellCacheToken = GParameterizedRoomCellCache.GetData(); + } } void BuildDetailBrick(const UVoxelGenerator& Generator, FIntVector OriginVoxels, @@ -1879,22 +2277,47 @@ void BuildDetailBrick(const UVoxelGenerator& Generator, FIntVector OriginVoxels, MinCellX + X, MinCellY + Y, MinCellZ + Z, BandSeed); } - for (int32 Z = 0; Z < Size.Z; ++Z) - for (int32 Y = 0; Y < Size.Y; ++Y) + int32 CellX[8]; + int32 CellY[8]; + int32 CellZ[8]; + float FadeX[8]; + float FadeY[8]; + float FadeZ[8]; for (int32 X = 0; X < Size.X; ++X) { const float SX = static_cast(OriginVoxels.X + X) / static_cast(CellSize); + const int32 Cell = FMath::FloorToInt(SX); + CellX[X] = Cell - MinCellX; + FadeX[X] = Fade(SX - static_cast(Cell)); + } + for (int32 Y = 0; Y < Size.Y; ++Y) + { const float SY = static_cast(OriginVoxels.Y + Y) / static_cast(CellSize); + const int32 Cell = FMath::FloorToInt(SY); + CellY[Y] = Cell - MinCellY; + FadeY[Y] = Fade(SY - static_cast(Cell)); + } + for (int32 Z = 0; Z < Size.Z; ++Z) + { const float SZ = static_cast(OriginVoxels.Z + Z) / static_cast(CellSize); - const int32 IX = FMath::FloorToInt(SX) - MinCellX; - const int32 IY = FMath::FloorToInt(SY) - MinCellY; - const int32 IZ = FMath::FloorToInt(SZ) - MinCellZ; - const float AX = Fade(SX - static_cast(FMath::FloorToInt(SX))); - const float AY = Fade(SY - static_cast(FMath::FloorToInt(SY))); - const float AZ = Fade(SZ - static_cast(FMath::FloorToInt(SZ))); + const int32 Cell = FMath::FloorToInt(SZ); + CellZ[Z] = Cell - MinCellZ; + FadeZ[Z] = Fade(SZ - static_cast(Cell)); + } + + for (int32 Z = 0; Z < Size.Z; ++Z) + for (int32 Y = 0; Y < Size.Y; ++Y) + for (int32 X = 0; X < Size.X; ++X) + { + const int32 IX = CellX[X]; + const int32 IY = CellY[Y]; + const int32 IZ = CellZ[Z]; + const float AX = FadeX[X]; + const float AY = FadeY[Y]; + const float AZ = FadeZ[Z]; const int32 Index = (IZ * GridY + IY) * GridX + IX; const float Value = CellSize == 1 ? Grid[Index] diff --git a/Source/VoxelForge/Public/VoxelRedefinedDensityField.h b/Source/VoxelForge/Public/VoxelRedefinedDensityField.h index 217044f..64ea265 100644 --- a/Source/VoxelForge/Public/VoxelRedefinedDensityField.h +++ b/Source/VoxelForge/Public/VoxelRedefinedDensityField.h @@ -66,6 +66,7 @@ namespace VoxelForgeRedefinedDensityField struct FParameterizedBrickContext { const void* PlanToken = nullptr; + const void* RoomCellCacheToken = nullptr; bool bPlanResolved = false; bool bChunkResolved = false; bool bIsGapChunk = false; @@ -74,6 +75,8 @@ namespace VoxelForgeRedefinedDensityField FIntVector BrickOrigin = FIntVector::ZeroValue; FIntVector BrickSize = FIntVector::ZeroValue; FIntVector Chunk = FIntVector::ZeroValue; + FIntPoint RoomCellCacheMin = FIntPoint::ZeroValue; + FIntPoint RoomCellCacheSize = FIntPoint::ZeroValue; TArray PassageCandidates; }; @@ -117,6 +120,12 @@ namespace VoxelForgeRedefinedDensityField float DetailValue, float* OutPreEditDensity = nullptr, float DetailScale = 1.0f, const FParameterizedBrickContext* Context = nullptr); + /** Evaluate a Z column while reusing its XY-dependent room graph data. */ + VOXELFORGE_API void EvaluateParameterizedColumnWithDetailValues( + const UVoxelGenerator& Generator, float WorldX, float WorldY, float WorldZStart, + const float* DetailValues, int32 DetailStride, int32 Count, float* OutValues, + const FParameterizedBrickContext* Context = nullptr, float DetailScale = 1.0f); + VOXELFORGE_API bool TryClassifyParameterizedBrick( const UVoxelGenerator& Generator, FIntVector OriginVoxels, FIntVector Size, bool& bOutAir, float DetailScale = 1.0f, diff --git a/Source/VoxelForgeEditor/Private/VoxelForgeExploreCommandlet.cpp b/Source/VoxelForgeEditor/Private/VoxelForgeExploreCommandlet.cpp index 7b83163..e286eac 100644 --- a/Source/VoxelForgeEditor/Private/VoxelForgeExploreCommandlet.cpp +++ b/Source/VoxelForgeEditor/Private/VoxelForgeExploreCommandlet.cpp @@ -2545,6 +2545,18 @@ struct FStep2dBrickProofAudit uint64 CorrectlySkippedBricksAudited = 0; uint64 CorrectProofSamplesChecked = 0; uint64 CorrectProofViolations = 0; + FIntVector FirstViolationBrickOrigin = FIntVector::ZeroValue; + FIntVector FirstViolationBrickSize = FIntVector::ZeroValue; + FIntVector FirstViolationSample = FIntVector::ZeroValue; + float FirstViolationDensity = 0.0f; + float FirstViolationCenterMacro = 0.0f; + float FirstViolationSampleMacro = 0.0f; + float FirstViolationSampleDetail = 0.0f; + float FirstViolationMacroDeltaPerVoxel = 0.0f; + float FirstViolationPathMaxGradient = 0.0f; + float FirstViolationTinyStepMaxGradient = 0.0f; + bool bFirstViolationWasInsideBrick = false; + bool bFirstViolationClaimedAir = false; uint64 DeliberatelyUndersizedBoundClaimsChecked = 0; uint64 DeliberateBoundDisagreements = 0; }; @@ -2608,6 +2620,88 @@ FStep2dBrickProofAudit AuditStep2dBrickProofs( ++Result.CorrectProofSamplesChecked; if ((Density > 0.0f) != bProvenAir) { + if (Result.CorrectProofViolations == 0) + { + Result.FirstViolationBrickOrigin = BrickOrigin; + Result.FirstViolationBrickSize = Size; + Result.FirstViolationSample = BrickOrigin + FIntVector(X, Y, Z); + Result.FirstViolationDensity = Density; + const FVector ProofMin( + static_cast(BrickOrigin.X - 1), + static_cast(BrickOrigin.Y - 1), + static_cast(BrickOrigin.Z - 1)); + const FVector ProofMax( + static_cast(BrickOrigin.X + Size.X), + static_cast(BrickOrigin.Y + Size.Y), + static_cast(BrickOrigin.Z + Size.Z)); + const FVector ProofCentre = (ProofMin + ProofMax) * 0.5f; + const FVector SamplePoint( + static_cast(BrickOrigin.X + X), + static_cast(BrickOrigin.Y + Y), + static_cast(BrickOrigin.Z + Z)); + Result.FirstViolationCenterMacro = bParameterized + ? VoxelForgeRedefinedDensityField::EvaluateParameterized( + Generator, ProofCentre.X, ProofCentre.Y, ProofCentre.Z, + nullptr, 0.0f) + : VoxelForgeRedefinedDensityField::Evaluate( + Generator, ProofCentre.X, ProofCentre.Y, ProofCentre.Z, + nullptr, 0.0f); + Result.FirstViolationSampleMacro = bParameterized + ? VoxelForgeRedefinedDensityField::EvaluateParameterized( + Generator, SamplePoint.X, SamplePoint.Y, SamplePoint.Z, + nullptr, 0.0f) + : VoxelForgeRedefinedDensityField::Evaluate( + Generator, SamplePoint.X, SamplePoint.Y, SamplePoint.Z, + nullptr, 0.0f); + Result.FirstViolationSampleDetail = Density + - Result.FirstViolationSampleMacro; + const float CentreDistance = (SamplePoint - ProofCentre).Size(); + Result.FirstViolationMacroDeltaPerVoxel = CentreDistance > 1.0e-6f + ? FMath::Abs(Result.FirstViolationCenterMacro + - Result.FirstViolationSampleMacro) / CentreDistance + : 0.0f; + if (CentreDistance > 1.0e-6f) + { + constexpr int32 PathSteps = 32; + float PreviousMacro = Result.FirstViolationSampleMacro; + for (int32 PathStep = 1; PathStep <= PathSteps; ++PathStep) + { + const float Alpha = static_cast(PathStep) / PathSteps; + const FVector Point = FMath::Lerp( + SamplePoint, ProofCentre, Alpha); + const float PathMacro = bParameterized + ? VoxelForgeRedefinedDensityField::EvaluateParameterized( + Generator, Point.X, Point.Y, Point.Z, nullptr, 0.0f) + : VoxelForgeRedefinedDensityField::Evaluate( + Generator, Point.X, Point.Y, Point.Z, nullptr, 0.0f); + Result.FirstViolationPathMaxGradient = FMath::Max( + Result.FirstViolationPathMaxGradient, + FMath::Abs(PathMacro - PreviousMacro) + * PathSteps / CentreDistance); + PreviousMacro = PathMacro; + } + } + constexpr float TinyStep = 0.01f; + for (int32 Axis = 0; Axis < 3; ++Axis) + for (int32 SignIndex = 0; SignIndex < 2; ++SignIndex) + { + const int32 Sign = SignIndex == 0 ? -1 : 1; + FVector Point = SamplePoint; + Point[Axis] += static_cast(Sign) * TinyStep; + const float NeighborMacro = bParameterized + ? VoxelForgeRedefinedDensityField::EvaluateParameterized( + Generator, Point.X, Point.Y, Point.Z, nullptr, 0.0f) + : VoxelForgeRedefinedDensityField::Evaluate( + Generator, Point.X, Point.Y, Point.Z, nullptr, 0.0f); + Result.FirstViolationTinyStepMaxGradient = FMath::Max( + Result.FirstViolationTinyStepMaxGradient, + FMath::Abs(NeighborMacro - Result.FirstViolationSampleMacro) + / TinyStep); + } + Result.bFirstViolationWasInsideBrick = X >= 0 && X < Size.X + && Y >= 0 && Y < Size.Y && Z >= 0 && Z < Size.Z; + Result.bFirstViolationClaimedAir = bProvenAir; + } ++Result.CorrectProofViolations; } } @@ -2805,6 +2899,17 @@ FStep2dBoundaryStats ComputeStep2dBoundaryStats( return Result; } +bool Step2eBoundaryStatsPass(const FStep2dBoundaryStats& Stats) +{ + return Stats.BrickBoundarySamples > 0 + && Stats.InteriorSamples > 0 + && Stats.ChunkBoundarySamples > 0 + && Stats.FeatureCellBoundarySamples > 0 + && Stats.BrickJumpRms <= Stats.InteriorJumpRms * 2.0 + 0.25 + && Stats.ChunkBoundaryJumpRms <= Stats.InteriorJumpRms * 2.0 + 0.25 + && Stats.FeatureCellJumpRms <= Stats.FeatureCellInteriorJumpRms * 2.0 + 0.25; +} + FColor Step2dDensityColor(float Density) { if (FMath::Abs(Density) < 1.0f) @@ -2868,6 +2973,9 @@ struct FStep2eVarietyMetrics double SurfaceAreaSquareMeters = 0.0; double SurfaceAreaPerVolume = 0.0; double VerticalAdjacencyShare = 0.0; + double VerticalAirColumnShare = 0.0; + double MeanLongestVerticalAirRunVoxels = 0.0; + int32 LongestVerticalAirRunVoxels = 0; double TunnelNeckShare = 0.0; int32 ConnectedComponentsAtOneMeter = 0; int64 LargestComponentCubicMeters = 0; @@ -2886,6 +2994,11 @@ FStep2eVarietyMetrics ComputeStep2eVarietyMetrics( uint64 SurfaceEdgeCount = 0; uint64 HorizontalAdjacencies = 0; uint64 VerticalAdjacencies = 0; + const int32 ColumnCount = Dimension * Dimension; + TArray CurrentVerticalRun; + CurrentVerticalRun.SetNumZeroed(ColumnCount); + TArray LongestVerticalRun; + LongestVerticalRun.SetNumZeroed(ColumnCount); for (int32 Z = 0; Z < Dimension; ++Z) for (int32 Y = 0; Y < Dimension; ++Y) for (int32 X = 0; X < Dimension; ++X) @@ -2893,6 +3006,10 @@ FStep2eVarietyMetrics ComputeStep2eVarietyMetrics( const int32 Index = (Z * Dimension + Y) * Dimension + X; const bool bAir = Density[Index] > 0.0f; AirCount += bAir ? 1 : 0; + const int32 ColumnIndex = Y * Dimension + X; + CurrentVerticalRun[ColumnIndex] = bAir ? CurrentVerticalRun[ColumnIndex] + 1 : 0; + LongestVerticalRun[ColumnIndex] = FMath::Max( + LongestVerticalRun[ColumnIndex], CurrentVerticalRun[ColumnIndex]); if (X + 1 < Dimension) { const bool bNextAir = Density[Index + 1] > 0.0f; @@ -2922,6 +3039,22 @@ FStep2eVarietyMetrics ComputeStep2eVarietyMetrics( const uint64 AllAdjacencies = HorizontalAdjacencies + VerticalAdjacencies; Result.VerticalAdjacencyShare = AllAdjacencies > 0 ? static_cast(VerticalAdjacencies) / AllAdjacencies : 0.0; + uint64 ColumnsWithAir = 0; + uint64 TotalLongestRunVoxels = 0; + for (const int32 LongestRun : LongestVerticalRun) + { + if (LongestRun > 0) + { + ++ColumnsWithAir; + TotalLongestRunVoxels += static_cast(LongestRun); + Result.LongestVerticalAirRunVoxels = FMath::Max( + Result.LongestVerticalAirRunVoxels, LongestRun); + } + } + Result.VerticalAirColumnShare = ColumnCount > 0 + ? static_cast(ColumnsWithAir) / ColumnCount : 0.0; + Result.MeanLongestVerticalAirRunVoxels = ColumnsWithAir > 0 + ? static_cast(TotalLongestRunVoxels) / ColumnsWithAir : 0.0; constexpr int32 CoarseStep = 4; const int32 CoarseDimension = (Dimension - 1) / CoarseStep + 1; @@ -3056,6 +3189,90 @@ TSharedRef Step2eParametersJson( return Json; } +TSharedRef Step2eChamberVolumeQuantilesJson( + const VoxelForgeRedefinedDensityField::FGenerationParameters& P) +{ + TSharedRef Json = MakeShared(); + constexpr double Pi = 3.14159265358979323846; + constexpr double CubicMetersPerVoxel = 0.25 * 0.25 * 0.25; + const double Quantiles[] = {0.10, 0.50, 0.90}; + const TCHAR* Names[] = {TEXT("p10_m3"), TEXT("p50_m3"), TEXT("p90_m3")}; + for (int32 Index = 0; Index < UE_ARRAY_COUNT(Quantiles); ++Index) + { + const double RadiusFraction = FMath::Lerp( + static_cast(P.MinimumRoomRadiusFraction), + static_cast(P.MaximumRoomRadiusFraction), Quantiles[Index]); + const double RadiusVoxels = RadiusFraction * P.RoomPitchVoxels; + const double VolumeCubicVoxels = Pi * RadiusVoxels * RadiusVoxels + * P.RoomHeightVoxels; + Json->SetNumberField(Names[Index], VolumeCubicVoxels * CubicMetersPerVoxel); + } + return Json; +} + +TSharedRef Step2eParameterRangesJson() +{ + using FParameters = VoxelForgeRedefinedDensityField::FGenerationParameters; + struct FParameterRange + { + const TCHAR* Name; + float FParameters::*Value; + }; + static const FParameterRange Fields[] = { + {TEXT("room_pitch_voxels"), &FParameters::RoomPitchVoxels}, + {TEXT("room_radius_fraction_min"), &FParameters::MinimumRoomRadiusFraction}, + {TEXT("room_radius_fraction_max"), &FParameters::MaximumRoomRadiusFraction}, + {TEXT("room_height_voxels"), &FParameters::RoomHeightVoxels}, + {TEXT("floor_relief_voxels"), &FParameters::FloorReliefVoxels}, + {TEXT("tunnel_radius_voxels"), &FParameters::TunnelRadiusVoxels}, + {TEXT("tunnel_half_height_voxels"), &FParameters::TunnelHalfHeightVoxels}, + {TEXT("loop_probability"), &FParameters::LoopProbability}, + {TEXT("vertical_shaft_probability"), &FParameters::VerticalShaftProbability}, + {TEXT("shaft_radius_voxels"), &FParameters::ShaftRadiusVoxels}, + {TEXT("pillar_probability"), &FParameters::PillarProbability}, + {TEXT("ledge_probability"), &FParameters::LedgeProbability}, + {TEXT("floating_mass_probability"), &FParameters::FloatingMassProbability}, + {TEXT("stalactite_stalagmite_probability"), &FParameters::SpireProbability}, + {TEXT("basin_probability"), &FParameters::BasinProbability}, + {TEXT("basin_depth_voxels"), &FParameters::BasinDepthVoxels}, + {TEXT("water_level_fraction"), &FParameters::WaterLevelFraction}, + }; + + TSharedRef Result = MakeShared(); + for (const FParameterRange& Field : Fields) + { + float Minimum = FLT_MAX; + float Maximum = -FLT_MAX; + for (int32 ProfileIndex = 0; + ProfileIndex < VoxelForgeRedefinedDensityField::GetParameterSetCount(); + ++ProfileIndex) + { + const TCHAR* Name = nullptr; + FParameters Parameters; + if (!VoxelForgeRedefinedDensityField::GetParameterSetInfo( + ProfileIndex, Name, Parameters)) + { + continue; + } + const float Value = Parameters.*(Field.Value); + Minimum = FMath::Min(Minimum, Value); + Maximum = FMath::Max(Maximum, Value); + } + TSharedRef Bounds = MakeShared(); + Bounds->SetNumberField(TEXT("minimum_across_profiles"), Minimum); + Bounds->SetNumberField(TEXT("maximum_across_profiles"), Maximum); + Result->SetObjectField(Field.Name, Bounds); + } + return Result; +} + +bool Step2eSameWorkerRun( + const VoxelForgeRegionEval::FTiming& A, + const VoxelForgeRegionEval::FTiming& B) +{ + return A.QuantizedHash == B.QuantizedHash && A.RawFloatHash == B.RawFloatHash; +} + TSharedRef Step2dTimingJson(const VoxelForgeRegionEval::FTiming& Timing) { TSharedRef Json = MakeShared(); @@ -3653,6 +3870,8 @@ int32 RunStep2e(const FString& Params) Row->SetNumberField(TEXT("profile_index"), Profile); Row->SetStringField(TEXT("name"), ProfileName); Row->SetObjectField(TEXT("parameters"), Step2eParametersJson(ProfileParameters)); + Row->SetObjectField(TEXT("nominal_chamber_volume_quantiles_m3"), + Step2eChamberVolumeQuantilesJson(ProfileParameters)); Row->SetNumberField(TEXT("six_worker_ring_median_ms"), MedianMs); TArray> TrialValues; TrialValues.Add(MakeShared(Times[0])); @@ -3664,6 +3883,11 @@ int32 RunStep2e(const FString& Params) Row->SetNumberField(TEXT("estimated_surface_area_per_volume_m_inv"), Variety.SurfaceAreaPerVolume); Row->SetNumberField(TEXT("vertical_air_adjacency_share"), Variety.VerticalAdjacencyShare); + Row->SetNumberField(TEXT("vertical_air_column_share"), Variety.VerticalAirColumnShare); + Row->SetNumberField(TEXT("mean_longest_vertical_air_run_voxels"), + Variety.MeanLongestVerticalAirRunVoxels); + Row->SetNumberField(TEXT("longest_vertical_air_run_voxels"), + Variety.LongestVerticalAirRunVoxels); Row->SetNumberField(TEXT("one_meter_tunnel_neck_share"), Variety.TunnelNeckShare); Row->SetNumberField(TEXT("one_meter_connected_components"), Variety.ConnectedComponentsAtOneMeter); @@ -3675,6 +3899,17 @@ int32 RunStep2e(const FString& Params) Row->SetNumberField(TEXT("skipped_bricks"), static_cast(LastTiming.SkippedBricks)); Row->SetNumberField(TEXT("skipped_sample_fraction"), LastTiming.SampleCount > 0 ? static_cast(LastTiming.SkippedSamples) / LastTiming.SampleCount : 0.0); + Row->SetNumberField(TEXT("evaluated_samples"), + static_cast(LastTiming.EvaluatedSamples)); + Row->SetNumberField(TEXT("worker_cpu_seconds"), LastTiming.WorkerCpuSeconds); + Row->SetNumberField(TEXT("brick_classification_worker_ms"), + LastTiming.BrickClassificationSeconds * 1000.0); + Row->SetNumberField(TEXT("density_evaluation_worker_ms"), + LastTiming.DensityEvaluationSeconds * 1000.0); + Row->SetNumberField(TEXT("uniform_fill_worker_ms"), + LastTiming.UniformFillSeconds * 1000.0); + Row->SetNumberField(TEXT("longest_worker_ms"), + LastTiming.LongestWorkerWallSeconds * 1000.0); FString SafeName(ProfileName); SafeName.ToLowerInline(); @@ -3778,26 +4013,41 @@ int32 RunStep2e(const FString& Params) const FStep2dBoundaryStats BoundaryStats = ComputeStep2dBoundaryStats( HardestFullDensity, Dimension, Origin, FMath::RoundToInt(HardestParameters.RoomPitchVoxels)); + TArray SeamFailureDensity = HardestFullDensity; + int64 DeliberateSeamSamplesMutated = 0; + for (int32 Z = 1; Z < Dimension - 1; ++Z) + for (int32 Y = 1; Y < Dimension - 1; ++Y) + for (int32 X = 1; X < Dimension - 1; ++X) + { + const int32 Index = (Z * Dimension + Y) * Dimension + X; + if (((Origin.X + X) % 8) == 0 && FMath::Abs(HardestFullDensity[Index]) <= 12.0f) + { + SeamFailureDensity[Index + 1] += 64.0f; + ++DeliberateSeamSamplesMutated; + } + } + const FStep2dBoundaryStats SeamFailureStats = ComputeStep2dBoundaryStats( + SeamFailureDensity, Dimension, Origin, + FMath::RoundToInt(HardestParameters.RoomPitchVoxels)); const FStep2dBrickProofAudit ProofAudit = AuditStep2dBrickProofs( *World.Generator, Origin, Dimension, true); const uint64 HardestHash = VoxelForgeRegionEval::HashQuantized(HardestFullDensity); const uint64 RepeatHash = VoxelForgeRegionEval::HashQuantized(RepeatFullDensity); const uint64 OneWorkerHash = VoxelForgeRegionEval::HashQuantized(OneWorkerDensity); const bool bDeterminism = HardestHash == RepeatHash && HardestHash == OneWorkerHash - && HardestFullTiming.RawFloatHash == RepeatTiming.RawFloatHash - && HardestFullTiming.RawFloatHash == OneWorkerTiming.RawFloatHash; + && Step2eSameWorkerRun(HardestFullTiming, RepeatTiming) + && Step2eSameWorkerRun(HardestFullTiming, OneWorkerTiming); + VoxelForgeRegionEval::FTiming DeliberateMismatch = HardestFullTiming; + DeliberateMismatch.QuantizedHash ^= 1u; + const bool bDeliberateDeterminismMismatchCaught = + !Step2eSameWorkerRun(HardestFullTiming, DeliberateMismatch); const bool bDetailAtLeastLive = NewSpectrum.RMSLag1 >= OldSpectrum.RMSLag1 && NewSpectrum.RMSLag2 >= OldSpectrum.RMSLag2; const bool bSmoothingControlCaught = SmoothSpectrum.RMSLag1 < NewSpectrum.RMSLag1 * 0.80 || SmoothSpectrum.RMSLag2 < NewSpectrum.RMSLag2 * 0.80; - const bool bBoundaryPass = BoundaryStats.BrickBoundarySamples > 0 - && BoundaryStats.InteriorSamples > 0 - && BoundaryStats.ChunkBoundarySamples > 0 - && BoundaryStats.FeatureCellBoundarySamples > 0 - && BoundaryStats.BrickJumpRms <= BoundaryStats.InteriorJumpRms * 2.0 + 0.25 - && BoundaryStats.ChunkBoundaryJumpRms <= BoundaryStats.InteriorJumpRms * 2.0 + 0.25 - && BoundaryStats.FeatureCellJumpRms - <= BoundaryStats.FeatureCellInteriorJumpRms * 2.0 + 0.25; + const bool bBoundaryPass = Step2eBoundaryStatsPass(BoundaryStats); + const bool bDeliberateSeamFailureCaught = DeliberateSeamSamplesMutated > 0 + && !Step2eBoundaryStatsPass(SeamFailureStats); const bool bBoundAudit = ProofAudit.CorrectlySkippedBricksAudited > 0 && ProofAudit.CorrectProofSamplesChecked > 0 && ProofAudit.CorrectProofViolations == 0; @@ -3810,6 +4060,7 @@ int32 RunStep2e(const FString& Params) const bool bSpeedPass = Workers == 6 && bProfilePasses && WorstMedianMs <= 600.0; const bool bPassed = bSpeedPass && bDeterminism && bDetailAtLeastLive && bSmoothingControlCaught && bDetailParity && bBoundaryPass + && bDeliberateSeamFailureCaught && bDeliberateDeterminismMismatchCaught && bBoundAudit && bUndersizedBoundCaught && HardestFullTiming.NonFiniteSamples == 0; @@ -3832,13 +4083,19 @@ int32 RunStep2e(const FString& Params) "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.")); Report->SetStringField(TEXT("parameter_space"), - TEXT("Eight named profiles can be assigned per strate from a seed hash (-1 mode), or " - "forced for measurement. Each profile rolls cell placement and feature choices from " - "the world seed and strate index; see parameters and documented ranges in the report.")); + TEXT("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.")); + Report->SetObjectField(TEXT("parameter_ranges_across_eight_profiles"), + Step2eParameterRangesJson()); Report->SetStringField(TEXT("resolution_contract"), TEXT("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. 8-cubed skips require the final macro field " + "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.")); Report->SetStringField(TEXT("finer_lattice_scaling"), TEXT("For 12.5 cm voxels, double room pitch, room height, tunnel/feature dimensions and " @@ -3848,9 +4105,11 @@ int32 RunStep2e(const FString& Params) "remain cheap. This scale has not been timed.")); Report->SetStringField(TEXT("variety_metric_method"), TEXT("Open fraction uses every ring sample. Surface area estimates sign-changing lattice " - "edges times 0.25 m squared. Chamber-size bins, connected components and tunnel-neck " - "share use a 1 m downsampled six-neighbor occupancy grid; verticality is the share of " - "open 1-cell adjacencies that point vertically.")); + "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.")); Report->SetStringField(TEXT("water_geometry_limit"), TEXT("Basin profiles generate the bowl floor and water-level parameter; this density field " "does not render or simulate water.")); @@ -3884,6 +4143,10 @@ int32 RunStep2e(const FString& Params) Boundary->SetNumberField(TEXT("feature_cell_boundary_gradient_jump_rms"), BoundaryStats.FeatureCellJumpRms); Boundary->SetNumberField(TEXT("feature_cell_interior_gradient_jump_rms"), BoundaryStats.FeatureCellInteriorJumpRms); Boundary->SetBoolField(TEXT("boundary_artifact_check_pass"), bBoundaryPass); + Boundary->SetNumberField(TEXT("deliberate_seam_samples_mutated"), + static_cast(DeliberateSeamSamplesMutated)); + Boundary->SetBoolField(TEXT("deliberate_seam_failure_caught"), + bDeliberateSeamFailureCaught); Report->SetObjectField(TEXT("boundary_artifact_check"), Boundary); TSharedRef Proofs = MakeShared(); @@ -3893,6 +4156,32 @@ int32 RunStep2e(const FString& Params) static_cast(ProofAudit.CorrectProofSamplesChecked)); Proofs->SetNumberField(TEXT("correct_proof_sign_violations"), static_cast(ProofAudit.CorrectProofViolations)); + Proofs->SetNumberField(TEXT("first_violation_brick_x"), ProofAudit.FirstViolationBrickOrigin.X); + Proofs->SetNumberField(TEXT("first_violation_brick_y"), ProofAudit.FirstViolationBrickOrigin.Y); + Proofs->SetNumberField(TEXT("first_violation_brick_z"), ProofAudit.FirstViolationBrickOrigin.Z); + Proofs->SetNumberField(TEXT("first_violation_brick_size_x"), ProofAudit.FirstViolationBrickSize.X); + Proofs->SetNumberField(TEXT("first_violation_brick_size_y"), ProofAudit.FirstViolationBrickSize.Y); + Proofs->SetNumberField(TEXT("first_violation_brick_size_z"), ProofAudit.FirstViolationBrickSize.Z); + Proofs->SetNumberField(TEXT("first_violation_sample_x"), ProofAudit.FirstViolationSample.X); + Proofs->SetNumberField(TEXT("first_violation_sample_y"), ProofAudit.FirstViolationSample.Y); + Proofs->SetNumberField(TEXT("first_violation_sample_z"), ProofAudit.FirstViolationSample.Z); + Proofs->SetNumberField(TEXT("first_violation_density"), ProofAudit.FirstViolationDensity); + Proofs->SetNumberField(TEXT("first_violation_center_macro"), + ProofAudit.FirstViolationCenterMacro); + Proofs->SetNumberField(TEXT("first_violation_sample_macro"), + ProofAudit.FirstViolationSampleMacro); + Proofs->SetNumberField(TEXT("first_violation_sample_detail"), + ProofAudit.FirstViolationSampleDetail); + Proofs->SetNumberField(TEXT("first_violation_macro_delta_per_voxel"), + ProofAudit.FirstViolationMacroDeltaPerVoxel); + Proofs->SetNumberField(TEXT("first_violation_path_max_gradient"), + ProofAudit.FirstViolationPathMaxGradient); + Proofs->SetNumberField(TEXT("first_violation_tiny_step_max_gradient"), + ProofAudit.FirstViolationTinyStepMaxGradient); + Proofs->SetBoolField(TEXT("first_violation_inside_proven_brick"), + ProofAudit.bFirstViolationWasInsideBrick); + Proofs->SetBoolField(TEXT("first_violation_claimed_air"), + ProofAudit.bFirstViolationClaimedAir); Proofs->SetNumberField(TEXT("undersized_bound_claims_checked"), static_cast(ProofAudit.DeliberatelyUndersizedBoundClaimsChecked)); Proofs->SetNumberField(TEXT("wrong_sign_samples_caught_by_undersized_bound"), @@ -3915,6 +4204,8 @@ int32 RunStep2e(const FString& Params) Determinism->SetStringField(TEXT("one_worker_raw_float"), FString::Printf( TEXT("%016llx"), static_cast(OneWorkerTiming.RawFloatHash))); Determinism->SetBoolField(TEXT("same_across_two_runs_and_1_vs_6_workers"), bDeterminism); + Determinism->SetBoolField(TEXT("deliberate_quantized_hash_mismatch_rejected"), + bDeliberateDeterminismMismatchCaught); Report->SetObjectField(TEXT("determinism"), Determinism); Report->SetBoolField(TEXT("entrance_exit_walkability_test_added"), true); Report->SetBoolField(TEXT("inter_strate_passage_open_space_test_added"), true); diff --git a/Source/VoxelForgeEditor/Private/VoxelForgeRegionEvalExperiment.cpp b/Source/VoxelForgeEditor/Private/VoxelForgeRegionEvalExperiment.cpp index 6fe4a99..efebfe0 100644 --- a/Source/VoxelForgeEditor/Private/VoxelForgeRegionEvalExperiment.cpp +++ b/Source/VoxelForgeEditor/Private/VoxelForgeRegionEvalExperiment.cpp @@ -181,27 +181,30 @@ namespace VoxelForgeRegionEval TArray Bricks; const FIntVector Last = OriginVoxels + FIntVector(Dimension - 1, Dimension - 1, Dimension - 1); + const FIntVector ProofBrickSize = bParameterized + ? FIntVector(4, 4, 4) : FIntVector(8, 8, 8); const FIntVector FirstBrick( - FMath::FloorToInt(static_cast(OriginVoxels.X) / 8.0f), - FMath::FloorToInt(static_cast(OriginVoxels.Y) / 8.0f), - FMath::FloorToInt(static_cast(OriginVoxels.Z) / 8.0f)); + FMath::FloorToInt(static_cast(OriginVoxels.X) / ProofBrickSize.X), + FMath::FloorToInt(static_cast(OriginVoxels.Y) / ProofBrickSize.Y), + FMath::FloorToInt(static_cast(OriginVoxels.Z) / ProofBrickSize.Z)); const FIntVector LastBrick( - FMath::FloorToInt(static_cast(Last.X) / 8.0f), - FMath::FloorToInt(static_cast(Last.Y) / 8.0f), - FMath::FloorToInt(static_cast(Last.Z) / 8.0f)); + FMath::FloorToInt(static_cast(Last.X) / ProofBrickSize.X), + FMath::FloorToInt(static_cast(Last.Y) / ProofBrickSize.Y), + FMath::FloorToInt(static_cast(Last.Z) / ProofBrickSize.Z)); Bricks.Reserve((LastBrick.X - FirstBrick.X + 1) * (LastBrick.Y - FirstBrick.Y + 1) * (LastBrick.Z - FirstBrick.Z + 1)); for (int32 BZ = FirstBrick.Z; BZ <= LastBrick.Z; ++BZ) for (int32 BY = FirstBrick.Y; BY <= LastBrick.Y; ++BY) for (int32 BX = FirstBrick.X; BX <= LastBrick.X; ++BX) { - const FIntVector BrickWorldMin(BX * 8, BY * 8, BZ * 8); + const FIntVector BrickWorldMin( + BX * ProofBrickSize.X, BY * ProofBrickSize.Y, BZ * ProofBrickSize.Z); FResearchBrick& Brick = Bricks.AddDefaulted_GetRef(); Brick.Origin = FIntVector( FMath::Max(OriginVoxels.X, BrickWorldMin.X), FMath::Max(OriginVoxels.Y, BrickWorldMin.Y), FMath::Max(OriginVoxels.Z, BrickWorldMin.Z)); - const FIntVector BrickEnd = BrickWorldMin + FIntVector(8, 8, 8); + const FIntVector BrickEnd = BrickWorldMin + ProofBrickSize; const FIntVector RingEnd = OriginVoxels + FIntVector(Dimension, Dimension, Dimension); const FIntVector End( @@ -315,28 +318,62 @@ namespace VoxelForgeRegionEval VoxelForgeRedefinedDensityField::BuildParameterizedBrickContext( Generator, Brick.Origin, Brick.Size, ParameterizedBrickContext); } - for (int32 Z = 0; Z < Brick.Size.Z; ++Z) - for (int32 Y = 0; Y < Brick.Size.Y; ++Y) - for (int32 X = 0; X < Brick.Size.X; ++X) + if (bParameterized) { - const int32 WorldX = Brick.Origin.X + X; - const int32 WorldY = Brick.Origin.Y + Y; - const int32 WorldZ = Brick.Origin.Z + Z; - const int64 LatticeIndex = LinearIndex( - Brick.Lattice.X + X, Brick.Lattice.Y + Y, - Brick.Lattice.Z + Z, Dimension); - const float DetailValue = BrickDetail[ - (Z * Brick.Size.Y + Y) * Brick.Size.X + X]; - OutDensities[static_cast(LatticeIndex)] = bParameterized - ? VoxelForgeRedefinedDensityField::EvaluateParameterizedWithDetailValue( - Generator, static_cast(WorldX), static_cast(WorldY), - static_cast(WorldZ), DetailValue, nullptr, DetailScale, - &ParameterizedBrickContext) - : VoxelForgeRedefinedDensityField::EvaluateWithDetailValue( - Generator, static_cast(WorldX), static_cast(WorldY), - static_cast(WorldZ), DetailValue, - nullptr, DetailScale, &BrickContext); - ++WorkerEvaluated[WorkerIndex]; + const int32 DetailStride = Brick.Size.X * Brick.Size.Y; + float ColumnValues[8] = {}; + float ParameterizedBrickValues[8 * 8 * 8]; + for (int32 Y = 0; Y < Brick.Size.Y; ++Y) + for (int32 X = 0; X < Brick.Size.X; ++X) + { + const int32 DetailBase = Y * Brick.Size.X + X; + VoxelForgeRedefinedDensityField::EvaluateParameterizedColumnWithDetailValues( + Generator, static_cast(Brick.Origin.X + X), + static_cast(Brick.Origin.Y + Y), + static_cast(Brick.Origin.Z), + BrickDetail.GetData() + DetailBase, DetailStride, + Brick.Size.Z, ColumnValues, + &ParameterizedBrickContext, DetailScale); + for (int32 Z = 0; Z < Brick.Size.Z; ++Z) + { + ParameterizedBrickValues[(Z * Brick.Size.Y + Y) + * Brick.Size.X + X] = ColumnValues[Z]; + } + } + for (int32 Z = 0; Z < Brick.Size.Z; ++Z) + for (int32 Y = 0; Y < Brick.Size.Y; ++Y) + { + const int64 LatticeIndex = LinearIndex( + Brick.Lattice.X, Brick.Lattice.Y + Y, + Brick.Lattice.Z + Z, Dimension); + const int32 LocalIndex = (Z * Brick.Size.Y + Y) * Brick.Size.X; + FMemory::Memcpy(OutDensities.GetData() + LatticeIndex, + ParameterizedBrickValues + LocalIndex, + static_cast(Brick.Size.X) * sizeof(float)); + } + WorkerEvaluated[WorkerIndex] += static_cast(Brick.SampleCount); + } + else + { + for (int32 Z = 0; Z < Brick.Size.Z; ++Z) + for (int32 Y = 0; Y < Brick.Size.Y; ++Y) + for (int32 X = 0; X < Brick.Size.X; ++X) + { + const int32 WorldX = Brick.Origin.X + X; + const int32 WorldY = Brick.Origin.Y + Y; + const int32 WorldZ = Brick.Origin.Z + Z; + const int64 LatticeIndex = LinearIndex( + Brick.Lattice.X + X, Brick.Lattice.Y + Y, + Brick.Lattice.Z + Z, Dimension); + const float DetailValue = BrickDetail[ + (Z * Brick.Size.Y + Y) * Brick.Size.X + X]; + OutDensities[static_cast(LatticeIndex)] = + VoxelForgeRedefinedDensityField::EvaluateWithDetailValue( + Generator, static_cast(WorldX), static_cast(WorldY), + static_cast(WorldZ), DetailValue, + nullptr, DetailScale, &BrickContext); + ++WorkerEvaluated[WorkerIndex]; + } } WorkerEvaluate[WorkerIndex] += FPlatformTime::Seconds() - EvaluationStart; }