research(step2d): a simpler field is fast (81 ns/sample) -- but it is not the owner's world; NOT a pass

Editor-only, default off (voxel.ResearchRedefinedDensityField). The redefined field is a seeded,
periodic cross-corridor pattern per strate plus the authored passages, landings and a shaft tree,
with four value-noise detail bands at every near-surface 25 cm sample and sound 8^3 brick proofs.
259^3 ring, six workers: 308 ms median (467 ms without skips) vs 8.9 s for the live field.
Detail, boundary-artefact, determinism (1 vs 6 workers, two runs) and bound-audit checks pass.

Why it is not a pass: the slices show a mostly-solid field with a few straight corridors, where the
owner's world is mostly open space; rooms, the tunnel network, composer recipes and archetypes are
gone. Much of the speed comes from 60% of bricks being proven solid in that simpler world.
What it does show: detail at full resolution plus brick skipping costs ~80-130 ns/sample; the
cost of the live field is how rooms/tunnels/passages are represented, not the detail or the lattice.

The two walkability tests were redirected to the research field in the working tree; that change
is NOT committed (saved as scratchpad step2d-test-redirect.patch) so they keep testing the live world.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-09-30 09:06:25 +02:00
1 parent d6e021a80b
commit 78641da0bb
15 files changed
+3077 -6

No files matched your search

+5 -3
View File
@@ -117,10 +117,12 @@ remains `0`, and lateral regions remain gated off.
|------|------| |------|------|
| `../../VoxelForge.uplugin` | Plugin manifest. Runtime module `VoxelForge` plus the Editor-only `VoxelForgeEditor` commandlet module. Beta. | | `../../VoxelForge.uplugin` | Plugin manifest. Runtime module `VoxelForge` plus the Editor-only `VoxelForgeEditor` commandlet module. Beta. |
| `VoxelForge.Build.cs` | Deps: Core, CoreUObject, Engine, **GameplayTags**, **RealtimeMeshComponent**. | | `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`, 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, reporting whole-ring setup/candidate counters; `-RegionHashProbe -RegionWorkers N -RegionReferenceHash HASH` checks a full-ring hash at another partition count. `-SampleDensityStacks` captures profiles, and `-StackSampleIntervalUs` sets the sampling interval. | `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`, 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` measures the independent full-detail research field, sound 8³ brick proofs, detail/gradient controls, slices, and 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). | | `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` runs the default-off step-2 research harness: full-ring and five-seed Q16 comparisons, repeat hashes, shrunk-reach falsification, cold timings, full-ring setup/candidate counters, counted live fallbacks, and separate before/after stack captures when `-SampleDensityStacks` is set. `-RegionHashProbe` runs one full-ring chunk evaluation and compares its raw float hash with a reference from another worker count. | | `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` runs the independent field benchmark without old-world equality gates and writes timing, detail, boundary, determinism, triangle, and side-by-side slice evidence under `Saved/Research/step2d`. |
| `Source/VoxelForgeEditor/Private/VoxelForgeRegionEvalExperiment.*` | Lattice driver with six workers by default for the commandlet-only region experiment. The live path calls `GetDensityAt` per point; the prepared path assigns each 32³ chunk once and calls editor-only `EvaluateDensityChunk`, which seeds one block evaluation and runs canonical final-field writers across that chunk. The prepared room/SDF cache window is aligned to that chunk; 8-voxel SDF candidate bins stay editor-only and default off. Unsupported routes use counted scalar fallbacks. Full-ring output reports room/SDF cache, active-op-list, and broad-phase/index builds; general-path entries and fallbacks; candidate visits/evaluations per point; and fresh sampled attribution. The full-ring hash probe supports 1–32 fixed partitions to check determinism across worker counts. | | `Source/VoxelForgeEditor/Private/VoxelForgeRegionEvalExperiment.*` | Six-worker lattice drivers for step 2c and step 2d. The original path still compares live `GetDensityAt` with the editor chunk program. Step 2d evaluates the independent field and assigns contiguous batches of 8³ bricks dynamically so surface-heavy work balances across workers. It first proves bricks uniform using a macro Lipschitz bound plus the full detail-amplitude bound; uncertain bricks retain all 25 cm detail samples. Timings split classification, point evaluation, and uniform fill; hashes compare workers 1 and 6. |
| `Public/VoxelRedefinedDensityField.h` / `Private/VoxelRedefinedDensityField.cpp` | Default-off `WITH_EDITOR` research field, selected by `voxel.ResearchRedefinedDensityField`. A seeded connected room/corridor lattice joins authored flat passage landings; four bounded world-space value-noise bands, including a one-sample micro band, are evaluated at full resolution. Skip proofs refuse edits, passage bounds, strate boundaries, and uncertain bricks; a local derivative bound permits sound proofs through landing-floor blends. The public API adds no reflected class layout. |
| `Private/Tests/VoxelForgeRedefinedDensityFieldTest.cpp` | Three-seed functional sweep for the research field: authored passage endpoints must bracket open air and solid floor, and each tested strate must have a walkable arrival-to-exit route. |
| `Public/VoxelGeometryHash.h` | Header-only geometry digest shared by commandlet export and runtime tile-hash dump; hashes mesh counts and the vertex/normal/UV/color/triangle arrays with the historical CRC order. | | `Public/VoxelGeometryHash.h` | Header-only geometry digest shared by commandlet export and runtime tile-hash dump; hashes mesh counts and the vertex/normal/UV/color/triangle arrays with the historical CRC order. |
| `Public/VoxelStats.h` / `Private/VoxelStats.cpp` | `stat VoxelForge` DWORD counters for tile classification, skipping, meshing, operator-stack verdicts, and cave-bail diagnosis. Each `Cave Bail *` counter names one guard; `Cave Bail No Stack` counts slots with no buildable stack (no definition, or a degenerate strate). | | `Public/VoxelStats.h` / `Private/VoxelStats.cpp` | `stat VoxelForge` DWORD counters for tile classification, skipping, meshing, operator-stack verdicts, and cave-bail diagnosis. Each `Cave Bail *` counter names one guard; `Cave Bail No Stack` counts slots with no buildable stack (no definition, or a degenerate strate). |
@@ -0,0 +1,60 @@
# Step 2d launch ledger
All Unreal processes were launched through `Tools/VoxelForgeTest.ps1` and ran serially. The
guarded launches used below-normal priority, the shared persistent DDC where recorded,
`-MaxParallelActions=4`, a prelaunch 6 GiB memory check, an 8 GiB per-process limit, and a
30-minute timeout. The retained ledgers show **no crash reporters, no process above 8 GiB, and
no timeout**. Exit 255 below means automation assertions failed; it was not a native crash.
## Benchmark attempts
| Label | Build | Unreal process | Result |
|---|---|---|---|
| `step2d_rebuild_01` | UBT PID 25780, exit 6, peak 2.71 GiB | Not launched | Compile failed on the `ApplicationContextMask` automation flag; corrected in the next source pass. |
| `step2d_rebuild_02` | UBT PID 19420, passed, peak 1.57 GiB | PID 13996, exit 2, peak 1.83 GiB | 3.834 s optimized ring; brick/sample skipping was too low for the speed gate. |
| `step2d_rebuild_03` | Passed; PID not retained in the compact log | PID 15480, exit 2, peak 1.82 GiB | 3.918 s; detail spectrum failed. The smoothing negative control worked; the proof negative control did not. |
| `step2d_rebuild_04` | Not launched | Not launched | Harness rejected a relative `-Out` path before Unreal startup. |
| `step2d_rebuild_05` | UBT PID 23904, passed | PID 24708, exit 2, peak 1.82 GiB | Scratch reuse gave 233.27 density mismatches and hash/boundary failures; scratch ownership was corrected. |
| `step2d_rebuild_06` | UBT PID 7540, passed | PID 25828, exit 2, peak 1.82 GiB | Detail, smoothing, boundary, and hash checks passed, but 3.70 s and 1.46% sample skipping missed the speed gate. |
| `step2d_rebuild_07` | UBT PID 23800, passed | PID 5208, exit 2, peak 1.81 GiB | 3.66 s; the cached XY macro did not improve the timing. |
| `step2d_rebuild_08` | UBT PID 25304, passed | PID 7108, exit 2, peak 1.81 GiB | 2.62 s; 22.20% of bricks and 21.18% of samples skipped, still over target. |
| `step2d_rebuild_09` | UBT PID 25612, passed, peak 1.51 GiB | PID 24256, exit 0, peak 1.82 GiB | Superficially under 600 ms, but invalid as evidence: classifier gap/default/chunk semantics had not yet been fixed. Excluded. |
| `step2d_harness_10` | UBT PID 2544, passed, peak 1.49 GiB | PID 23204, exit 0, peak 1.84 GiB | 503.026 ms and hash/detail/boundary checks passed. Superseded because its deliberately undersized-bound control did not catch a wrong sign. |
| `step2d_harness_11` | UBT PID 2900, passed, peak 1.50 GiB | PID 10688, exit 0, peak 1.83 GiB | 497.518 ms; the scale-0 versus scale-4 undersized-bound control caught 35,847 wrong-sign samples. |
| `step2d_harness_12` | UBT PID 13868, passed, peak 1.87 GiB | PID 16872, exit 0, peak 1.81 GiB | Final dynamically scheduled run: 308.177 ms median, all hashes and research gates passed. |
## Functional runs
| Label | Build | Unreal process | Result |
|---|---|---|---|
| `step2d_functional_01` | UBT PID 21184, passed | PID 3444, exit 255, peak 3.26 GiB | Base connectivity passed; refinement failed on an endpoint self-query. The launcher detected a write under `C:\Users\Jahni\AppData\Local\UnrealEngine`; later runs used isolated per-run local data. |
| `step2d_functional_02` | UBT PID 11844, passed, peak 1.87 GiB | PID 5048, exit 255, peak 3.13 GiB | Same endpoint-snap failure; isolated LocalAppData was clean. |
| `step2d_functional_03` | UBT PID 2060, passed, peak 1.90 GiB | PID 25648, exit 255, peak 3.19 GiB | Origin probe compared raw density 8 with a coarse center at -3.534534 near Z=-1012. |
| `step2d_functional_04` | Failed launch; retained PID 23996, stage split not retained | Exit 255; peak not retained | Range-normalization fix did not resolve the origin probe. No crash was recorded. |
| `step2d_redefined_seeds_01` | Build passed; PID not retained | PID 21888, exit 255 | Initial multi-seed endpoint-snap expectation was stricter than the route's full-density segment check; expectation was revised. |
| `step2d_functional_05` | UBT PID 17000, passed | PID 7540, exit 255 | 12/16 fitted VerticalShafts seeds passed. |
| `step2d_functional_06` | Build passed; PID not retained | PID 21948, exit 255, peak 3.43 GiB | 12/16; seed 1337's physical arrival/departure audit found 13 bad samples of 15. |
| `step2d_redefined_seeds_02` | UBT PID 14672, exit 0, peak 1.63 GiB | PID 2220, exit 0, peak 2.61 GiB | Passed an earlier three-seed traversal version. |
| `step2d_functional_07` | Build/Unreal stage PID 15712 in retained summary | Exit 255, peak 3.42 GiB | 12/16; no native crash. |
| Relative-output-path prelaunch attempt | Not launched | Not launched | Harness stopped before Unreal startup; corrected to an absolute output directory. |
| `step2d_functional_08` | UBT PID 12596, passed, peak 1.87 GiB | PID 20624, exit 255, peak 3.42 GiB | 13/16; origin path passed, three measurement limits remained. |
| `step2d_functional_09` | UBT PID 10848, passed, peak 1.50 GiB | PID 16764, exit 255, peak 3.46 GiB | 13/16 after the cache-key fix. |
| `step2d_functional_10` | UBT PID 24704, passed, peak 1.44 GiB | PID 7512, exit 255, peak 3.56 GiB | 13/16 after connector-radius changes. |
| `step2d_functional_11` | UBT PID 17088, exit 6, peak 1.96 GiB | Not launched | Compile error C4456 from a duplicated `XYCount`; fixed before the next build. |
| `step2d_functional_12` | UBT PID 4760, passed, peak 1.95 GiB | PID 26520, exit 255, peak 3.49 GiB | 13/16 after explicit bridges. |
| `step2d_functional_13` | UBT PID 19416, passed, peak 1.50 GiB | PID 26444, exit 255, peak 3.50 GiB | 13/16; diagnostics showed 16 retries and both endpoints in the same large component. |
| `step2d_functional_14` | UBT PID 3156, passed, peak 1.50 GiB | PID 17892, exit 255, peak 3.49 GiB | 13/16 after widening the landing bridge. |
| `step2d_functional_15` | UBT PID 5648, passed, peak 1.49 GiB | PID 23788, exit 255, peak 3.53 GiB | 14/16 after adding the vertical cavern band. |
| `step2d_functional_16` | UBT PID 20028, passed, peak 1.13 GiB | PID 4916, exit 255, peak 3.60 GiB | Controlled-wall assumptions and the 14/16 refinement audit both failed. |
| `step2d_functional_17` | UBT PID 14952, passed, peak 1.50 GiB | PID 2604, exit 0, peak 2.69 GiB | Three-seed traversal test passed: 21 landing brackets and 18 routes. |
| `step2d_functional_18` | UBT PID 10496, passed, peak 1.49 GiB | PID 25636, exit 0, peak 2.70 GiB | Passage-open-space test passed. |
| `step2d_functional_19` | UBT PID 22588, passed, peak 1.48 GiB | PID 7316, exit 255, peak 3.71 GiB | Controlled wall returned conservative unknown after zero retries; refinement remained 14/16. |
| `step2d_functional_20` | UBT PID 14076, passed, peak 1.88 GiB | PID 23120, exit 255, peak 3.60 GiB | Wall samples were -2/-102/-2, so the chosen location was not an air/solid/air control; refinement remained 14/16. |
| `step2d_functional_21` | UBT PID 8224, passed, peak 1.58 GiB | PID 25652, exit 255, peak 3.67 GiB | `StrateConnectivity` passed; refinement remained 14/16 because the fallback had been placed in a skipped diagnostic branch. |
| `step2d_functional_22` | UBT PID 26528, passed, peak 1.62 GiB | PID 24396, exit 0, peak 3.68 GiB | Both required connectivity tests passed; the fitted audit directly confirmed 16/16 routes at step 2. |
| `step2d_functional_23` | Build not requested | PID 15176, exit 0, peak 2.44 GiB | `PassageLandsInOpenSpace` passed. |
| `step2d_functional_24` | Build not requested | PID 26336, exit 0, peak 2.42 GiB | `RedefinedFieldTraversalSeeds` passed across three seeds. |
The manual `Win32_OperatingSystem` memory probe was denied once before a launch; no Unreal process
was started by that probe. The harness's own native memory preflight succeeded on subsequent
launches. No files outside the workspace were deleted.
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

+131
View File
@@ -0,0 +1,131 @@
# Rebuild step 2d research report
Date: 2026-09-30
Branch: `rebuild/step2d-redefined`
Scope: `Plugins/VoxelForge`
## Result
The editor-only research field passes the 259³, 25 cm ring target on six workers. Its final
median is **308.177 ms** across three runs (295.325–317.807 ms), below both the 600 ms step-2
bar and the 350 ms step-3 target. The old field measured 8,900.965 ms median on the same ring.
Preparation, including the 135.528 ms world setup, is reported separately and excluded from
the ring timings.
The six-worker driver claims contiguous batches of 16 bricks dynamically. This balances dense
surface work against uniform interiors without changing evaluation order within a brick or any
sample value.
The old and new density fields intentionally differ. The new evaluator remains disabled by
default; the live field is still selected unless an editor research test explicitly enables
`voxel.ResearchRedefinedDensityField`.
## Field definition
Each strate gets a deterministic, seed-offset periodic cross-corridor signed field. Rounded
landing chambers, approaches, and a deterministic landing-to-hub bridge join those passages.
Authored passage carving and floor geometry remain active. The VerticalShafts strate adds a
seeded shaft tree and a continuous 48-voxel cavern horizon to connect its authored mouths.
Four continuous value-noise bands use periods of 8, 4, 2, and 1 lattice samples, with total
absolute amplitude bounded by 5.25 density units. No coarse density field is interpolated onto
the lattice: every sample in a brick that may contain the zero set evaluates the full field and
all four detail bands. Research mode also declines the live tile classifier's early-out so its
mesh cannot be culled using bounds for the old field.
## Timing
All rows use 17,373,979 samples, a 259³ window at origin `(-129,-129,-481)`, stride 1, spacing
25 cm, and six observed workers unless noted.
| Field and path | Wall time | Worker CPU | CPU/sample | Bricks skipped | Samples skipped | Geometry evaluations/sample |
|---|---:|---:|---:|---:|---:|---:|
| Existing live field, six-worker median (range 8,890.120–8,923.020 ms) | 8,900.965 ms | 37.70 s | 2,170.09 ns | 0% | 0% | 6.734 |
| Redefined field, all points evaluated, six workers | 467.212 ms | 2.313 s | 133.10 ns | 0% | 0% | 1.000 |
| Redefined field, sound 8³ proofs, six-worker median (range 295.325–317.807 ms) | **308.177 ms** | 1.41 s | 80.94 ns | 60.54% | 63.06% | 0.371 |
The optimized pass skipped 23,794 of 39,304 bricks and wrote uniform values to 10,956,496
proven samples. It evaluated full density at the remaining 6,417,483 samples. The 0.371 new
geometry figure counts a full density evaluation or brick macro proof against every ring point;
the old path performed about 6.734 room/tunnel/passage primitive evaluations per point. Each
evaluated new-field point computes four noise bands; averaged across the whole ring, including
proved skips, this is 1.477 noise-band evaluations per sample.
The final run's accumulated worker-phase timers were 1.79 s for full density evaluation, 0.04 s
for uniform fills, and 0.01 s for brick proofs. Those phase timers sum work across workers, so
they are not disjoint wall-time slices. The longest worker took 307.76 ms; measured parallel
overhead after that was 0.42 ms. The remaining wall time is in full-detail evaluation of the
6.42 million nonuniform samples.
## Detail, bounds, and boundaries
The roughness metric is six-neighbor Laplacian RMS over samples within 12 density units of the
surface, measured on the shared ring. The new field has fewer surface-band samples because its
topology is different (3,116,658 versus 15,515,231); on those bands its lag-1 RMS is 8.978 versus
6.248 for the old field, and lag-2 RMS is 9.169 versus 8.646. A deliberately smoothed new-field
control (detail scale 0) falls to 1.315 and 1.933. In the same selected 64³ mesh region, triangle
counts were 180 old, 5,067 new, and 1,902 smoothed. The metric and mesh both detected the
smoothing control. The measured detail batch also matched direct evaluation exactly at 8,488
samples.
For the actual scale-1 field, 186 correctly skipped bricks were audited across 165,390 final
field samples including the marching-cubes halo; there were zero sign violations. The bound
includes the full 5.25 detail supremum, the macro Lipschitz bound, and a floating-point margin;
edits, passage bounds, and chunk/strate boundaries decline the proof. A negative control that
classified with detail scale 0 and checked the supported scale-4 field found 35,847 wrong-sign
samples across 26,572 claimed bricks, so the audit detects an undersized detail bound.
Near-surface gradient-jump RMS was 3.420 at 8³ brick boundaries versus 8.694 in brick interiors,
3.423 at chunk boundaries, and 3.225 at 128-sample macro-cell boundaries versus 10.036 in those
cell interiors. These checks found no boundary spike consistent with stair-stepping. The field
is analytic and periodic across those boundaries; the brick classifier only replaces proven
uniform interiors.
## Determinism and functional checks
The six-worker output matched on two independent runs and matched the one-worker output:
- Quantized hash: `7dec3f2d65781237`
- Raw-float hash: `b50465c1eaa85921`
The required `StrateConnectivity` and `StrateConnectivityRefinement` automation tests passed in
`step2d_functional_22`. The fitted VerticalShafts audit reported 16/16 routes directly confirmed
at step 2, zero endpoint snaps, and no exact step-1 fallback. Its 16-seed shaft-tree audit had
0/10,372 orphan paths and passed its order-independence check. The bounded retry setting in this
test is 128 so the richer full-detail field can reject more coarse false edges before accepting
a path.
`PassageLandsInOpenSpace` passed in `step2d_functional_23` for its seven-passage fixture.
`RedefinedFieldTraversalSeeds` passed in `step2d_functional_24`: over three seeds, 21 authored
passage landings bracketed open air and 18 arrival-to-exit walking routes were checked; six
routes used a resolved coarse endpoint only after a full-density segment check.
## Slices
Each image places the old field on the left and the redefined field on the right. Light is air,
dark is solid, and cyan marks the near-zero surface.
- [Horizontal slice through the ring center](</E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2d/step2d_rebuild_12/slice_xy_center.png>)
- [Vertical slice at Y=0](</E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2d/step2d_rebuild_12/slice_xz_y0.png>)
- [Vertical slice at X=0](</E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2d/step2d_rebuild_12/slice_yz_x0.png>)
- [Vertical slice through an authored passage](</E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2d/step2d_rebuild_12/slice_xz_passage.png>)
Machine-readable measurements are in [step2d_report.json](</E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2d/step2d_rebuild_12/step2d_report.json>).
## Scaling to 12.5 cm and compile risk
At 12.5 cm, double macro lengths and broad/medium noise periods in lattice units to preserve
their world-space shape: the current noise periods 8/4/2/1 become 16/8/4/2 samples. Keep or add
a 1-sample fine band so detail stays at least as rich as the 25 cm version. For a fixed world
region, the lattice grows 8×, while surface-intersecting 8³ bricks should grow closer to 4×
(surface area); remaining bricks still need only a cheap final-field sign proof and density
writes. This scaling model is reasoned from the geometry and has not yet been timed at 12.5 cm.
The UE 5.7 Win64 Development build passed with `-ForceHeaderGeneration` and
`-MaxParallelActions=4`. No UCLASS/UPROPERTY layout changed, so no generated-class layout risk
was introduced. The sensitive code is the locally bounded landing-floor derivative and the
supremum used by `TryClassifyBrick`; future field terms must update those bounds and keep the
proof audit. The live path and ARCHITECTURE §8.10 were left unchanged when research mode is off.
No `.uasset` file was edited. Nothing was committed, pushed, or stashed.
See the exhaustive [launch ledger](</E:/Projet Unreal/VoxelM/Plugins/VoxelForge/Saved/Research/step2d/step2d_launch_ledger.md>) for every benchmark, test run, failure, and prelaunch rejection.
@@ -0,0 +1,146 @@
#if WITH_DEV_AUTOMATION_TESTS
#include "Misc/AutomationTest.h"
#include "VoxelForgeTestFixture.h"
#include "VoxelRedefinedDensityField.h"
#include "VoxelStrateMeasure.h"
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
FVoxelForgeRedefinedDensityFieldTraversalSeedsTest,
"VoxelForge.Research.RedefinedFieldTraversalSeeds",
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
bool FVoxelForgeRedefinedDensityFieldTraversalSeedsTest::RunTest(const FString& Parameters)
{
(void)Parameters;
struct FRestoreResearchField
{
bool bWasEnabled = VoxelForgeRedefinedDensityField::IsEnabled();
float PreviousScale = VoxelForgeRedefinedDensityField::GetDetailScale();
~FRestoreResearchField()
{
VoxelForgeRedefinedDensityField::SetDetailScale(PreviousScale);
VoxelForgeRedefinedDensityField::SetEnabled(bWasEnabled);
}
} Restore;
VoxelForgeRedefinedDensityField::SetEnabled(true);
VoxelForgeRedefinedDensityField::SetDetailScale(1.0f);
constexpr int32 Seeds[] = {1337, 12345, 20260930};
int32 CheckedLandings = 0;
int32 CheckedRoutes = 0;
int32 RoutesWithResolvedEndpoints = 0;
for (const int32 Seed : Seeds)
{
VoxelForgeTest::FTestWorld World;
World.Build(Seed, /*InGapChunks=*/2, /*InStrateHeightInChunks=*/4);
if (!World.IsValid())
{
AddError(FString::Printf(
TEXT("Seed %d: test world did not build: %s"), Seed, *World.WhyInvalid()));
return false;
}
const TArray<FStrateSlot>& Layout = World.StrateManager->GetLayout();
const TArray<FVoxelPassage>& Passages = World.StrateManager->GetPassages();
for (const FVoxelPassage& Passage : Passages)
{
if (Passage.UpperStrateIndex == Passage.LowerStrateIndex)
{
continue;
}
const FVector UpperAir = Passage.UpperLanding.StandingPoint;
const FVector LowerAir = Passage.LowerLanding.StandingPoint;
const float UpperDensity = World.Generator->GetDensityAt(
UpperAir.X, UpperAir.Y, UpperAir.Z);
const float UpperFooting = World.Generator->GetDensityAt(
UpperAir.X, UpperAir.Y, Passage.UpperLanding.FloorZ - 0.75f);
const float LowerDensity = World.Generator->GetDensityAt(
LowerAir.X, LowerAir.Y, LowerAir.Z);
const float LowerFooting = World.Generator->GetDensityAt(
LowerAir.X, LowerAir.Y, Passage.LowerLanding.FloorZ - 0.75f);
++CheckedLandings;
if (!FMath::IsFinite(UpperDensity) || !FMath::IsFinite(UpperFooting)
|| !FMath::IsFinite(LowerDensity) || !FMath::IsFinite(LowerFooting)
|| UpperDensity <= 0.0f || LowerDensity <= 0.0f
|| UpperFooting >= 0.0f || LowerFooting >= 0.0f)
{
AddError(FString::Printf(
TEXT("Seed %d: passage %d landing failed the final-field air/floor bracket "
"(upper %.4f/%.4f, lower %.4f/%.4f)."),
Seed, CheckedLandings - 1, UpperDensity, UpperFooting,
LowerDensity, LowerFooting));
}
}
for (int32 StrateIndex = 0; StrateIndex < Layout.Num(); ++StrateIndex)
{
FVector Arrival = FVector::ZeroVector;
FVector Departure = FVector::ZeroVector;
bool bHasArrival = false;
bool bHasDeparture = false;
for (const FVoxelPassage& Passage : Passages)
{
if (Passage.UpperStrateIndex == Passage.LowerStrateIndex)
{
continue;
}
if (Passage.LowerStrateIndex == StrateIndex && !bHasArrival)
{
Arrival = Passage.LowerLanding.StandingPoint;
bHasArrival = true;
}
if (Passage.UpperStrateIndex == StrateIndex && !bHasDeparture)
{
Departure = Passage.UpperLanding.StandingPoint;
bHasDeparture = true;
}
}
if (!bHasArrival || !bHasDeparture)
{
continue;
}
FVoxelStrateMeasureSettings Settings;
Settings.SampleStep = 4;
Settings.RadiusInVoxels = 192;
Settings.CenterXY = FVector2D(
(Arrival.X + Departure.X) * 0.5f,
(Arrival.Y + Departure.Y) * 0.5f);
Settings.MaxCells = 4000000;
Settings.HeadroomCells = 2;
const FVoxelConnectivityDiagnostics Route = VF_DiagnoseConnectivity(
*World.Generator, *World.StrateManager, StrateIndex,
Arrival, Departure, Settings);
++CheckedRoutes;
RoutesWithResolvedEndpoints += Route.bStartSnapped || Route.bGoalSnapped;
if (!Route.bValid || Route.Result != EVoxelConnectivityResult::Connected)
{
AddError(FString::Printf(
TEXT("Seed %d strate %d (%s): arrival-to-exit walking route was %s "
"(result=%d, valid=%s, start resolved=%s, goal resolved=%s)."),
Seed, StrateIndex,
Layout[StrateIndex].Definition
? *Layout[StrateIndex].Definition->GetName() : TEXT("no definition"),
Route.Result == EVoxelConnectivityResult::Connected
? TEXT("connected") : TEXT("not connected"),
static_cast<int32>(Route.Result),
Route.bValid ? TEXT("yes") : TEXT("no"),
Route.bStartSnapped ? TEXT("yes") : TEXT("no"),
Route.bGoalSnapped ? TEXT("yes") : TEXT("no")));
}
}
}
TestTrue(TEXT("Several seeds exercised passage landings"), CheckedLandings >= 6);
TestTrue(TEXT("Several seeds exercised arrival-to-exit walking"), CheckedRoutes >= 6);
AddInfo(FString::Printf(
TEXT("Redefined field functional sweep: %d authored passage landings bracket open air "
"over %d seeds; %d arrival-to-exit walking routes checked; %d used a resolved "
"coarse endpoint after the full-density segment check."),
CheckedLandings, UE_ARRAY_COUNT(Seeds), CheckedRoutes, RoutesWithResolvedEndpoints));
return CheckedLandings >= 6 && CheckedRoutes >= 6;
}
#endif // WITH_DEV_AUTOMATION_TESTS
@@ -17,6 +17,7 @@
#include "VoxelDensityAblation.h" // fingerprinted development-only stage measurements #include "VoxelDensityAblation.h" // fingerprinted development-only stage measurements
#include "VoxelDensityPrimitives.h" // spine / seals / passage — shared with the operator stack #include "VoxelDensityPrimitives.h" // spine / seals / passage — shared with the operator stack
#include "VoxelDensityOpStack.h" // the per-strate operator stack #include "VoxelDensityOpStack.h" // the per-strate operator stack
#include "VoxelRedefinedDensityField.h" // default-off editor research evaluator
#include "VoxelHeightOp.h" // IVoxelBiomeField — the adapter below implements it #include "VoxelHeightOp.h" // IVoxelBiomeField — the adapter below implements it
#include "VoxelStats.h" #include "VoxelStats.h"
#include "VoxelDensityProfile.h" #include "VoxelDensityProfile.h"
@@ -2815,6 +2816,19 @@ bool UVoxelGenerator::EvaluateDensityChunk(
float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ, float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ,
float* OutPreEditDensity) const float* OutPreEditDensity) const
{ {
#if WITH_EDITOR
// Step 2d is an editor-only, default-off side path. It does not alter the live evaluator's
// state or caches; the field is selected only for an explicit research/test run.
if (VoxelForgeRedefinedDensityField::IsEnabled())
{
VoxelPassageGeometry::ResetVerticalShaftConnectorAirMarker();
const float ResearchDensity = VoxelForgeRedefinedDensityField::Evaluate(
*this, WorldX, WorldY, WorldZ, OutPreEditDensity,
VoxelForgeRedefinedDensityField::GetDetailScale());
VoxelPassageGeometry::ResetVerticalShaftConnectorAirMarker();
return ResearchDensity;
}
#endif
// Soundness mode: for a sample in a block whose core or passage-carving reach bit is clear, // Soundness mode: for a sample in a block whose core or passage-carving reach bit is clear,
// evaluate the complete canonical field once with the bypass bit set. The normal call then // evaluate the complete canonical field once with the bypass bit set. The normal call then
// takes the fast path below and compares the final MC field after the boundary seal and diff // takes the fast path below and compares the final MC field after the boundary seal and diff
@@ -6298,6 +6312,16 @@ EVoxelTileClass UVoxelGenerator::ClassifyTile(const FIntVector& OriginVoxels, in
VoxelDensityProfile::AddCounter( VoxelDensityProfile::AddCounter(
VoxelDensityProfile::ECounter::TileClassifyCalls); VoxelDensityProfile::ECounter::TileClassifyCalls);
#if WITH_EDITOR
// The ordinary tile classifier proves the live field's interval bounds. While the separate
// research field is selected, decline those proofs until its own complete final-field interval
// contract is wired into streaming; a conservative Mixed verdict keeps the test mesh complete.
if (VoxelForgeRedefinedDensityField::IsEnabled())
{
return EVoxelTileClass::Mixed;
}
#endif
const EVoxelTileClass Verdict = ClassifyTileUncached( const EVoxelTileClass Verdict = ClassifyTileUncached(
OriginVoxels, NormalizedStep, NormalizedCellsPerAxis, OutStats); OriginVoxels, NormalizedStep, NormalizedCellsPerAxis, OutStats);
if (Verdict == EVoxelTileClass::AllSolid) if (Verdict == EVoxelTileClass::AllSolid)
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,90 @@
#pragma once
#include "CoreMinimal.h"
class UVoxelGenerator;
#if WITH_EDITOR
/**
* Step 2d editor research field. It is intentionally separate from the gameplay evaluator and
* is selected only by the default-off voxel.ResearchRedefinedDensityField console variable.
*
* The field is a connected, archetype-sized cave lattice with an authored walkable floor at each
* passage landing. Its macro shape is evaluated directly in world space; bounded, continuous
* hash-value noise is added at every full-resolution sample. The brick classifier is permitted to
* skip only when a Lipschitz bound on the macro field plus the exact maximum detail amplitude
* proves that the final sign is uniform throughout the brick and its marching-cubes halo.
*/
namespace VoxelForgeRedefinedDensityField
{
/** Per-worker, per-brick state. The opaque plan pointer is valid until the layout changes. */
struct FBrickEvaluationContext
{
const void* PlanToken = nullptr;
bool bPlanResolved = false;
bool bMayContainPassage = true;
bool bChunkResolved = false;
bool bIsGapChunk = false;
bool bHasDiffMods = false;
bool bMacroXYPrecomputed = false;
FIntVector BrickOrigin = FIntVector::ZeroValue;
FIntVector BrickSize = FIntVector::ZeroValue;
FIntVector Chunk = FIntVector::ZeroValue;
TArray<float> FloorByXY;
TArray<float> GridAirByXY;
TArray<float> DetailFadeByXY;
TArray<float> LandingHorizontalByXY;
TArray<float> LandingApproachByXY;
TArray<float> LandingBridgeHorizontalByXY;
TArray<float> LandingBridgeFloorByXY;
TArray<int32> PassageCandidates;
};
enum class EBrickFallbackReason : uint8
{
None,
UnsupportedState,
ChunkOrStrateBoundary,
PassageBounds,
MixedMacroInterval
};
struct FBrickCounters
{
uint64 Tested = 0;
uint64 Skipped = 0;
uint64 EvaluatedSamples = 0;
};
VOXELFORGE_API bool IsEnabled();
VOXELFORGE_API void SetEnabled(bool bEnabled);
VOXELFORGE_API float GetDetailScale();
VOXELFORGE_API void SetDetailScale(float Scale);
/** Evaluate the new field, including existing passage carving and player diffs. */
VOXELFORGE_API float Evaluate(const UVoxelGenerator& Generator, float WorldX, float WorldY,
float WorldZ, float* OutPreEditDensity = nullptr,
float DetailScale = 1.0f);
/** Build mathematically identical multi-scale noise for one integer-lattice brick. */
VOXELFORGE_API void BuildDetailBrick(const UVoxelGenerator& Generator,
FIntVector OriginVoxels, FIntVector Size, float DetailScale,
TArray<float>& OutDetail, FBrickEvaluationContext* OutContext = nullptr);
/** Evaluate with a detail value produced by BuildDetailBrick. */
VOXELFORGE_API float EvaluateWithDetailValue(const UVoxelGenerator& Generator,
float WorldX, float WorldY, float WorldZ, float DetailValue,
float* OutPreEditDensity = nullptr, float DetailScale = 1.0f,
const FBrickEvaluationContext* Context = nullptr);
/** Prove a world-aligned box has one final density sign; false means evaluate every point. */
VOXELFORGE_API bool TryClassifyBrick(
const UVoxelGenerator& Generator, FIntVector OriginVoxels, FIntVector Size,
bool& bOutAir, float DetailScale = 1.0f,
bool* bOutEvaluatedMacroCentre = nullptr,
EBrickFallbackReason* OutFallbackReason = nullptr);
/** Reset this thread's immutable plan cache; used by deterministic editor experiments. */
VOXELFORGE_API void ResetThreadCache();
}
#endif
@@ -34,6 +34,7 @@
#include "VoxelGenerator.h" #include "VoxelGenerator.h"
#include "VoxelGeometryHash.h" #include "VoxelGeometryHash.h"
#include "VoxelMarchingCubesMesher.h" #include "VoxelMarchingCubesMesher.h"
#include "VoxelRedefinedDensityField.h"
#include "VoxelNoise.h" #include "VoxelNoise.h"
#include "VoxelPassageGeometry.h" #include "VoxelPassageGeometry.h"
#include "VoxelSettings.h" #include "VoxelSettings.h"
@@ -1509,6 +1510,9 @@ struct FRegionEvalStackSamplerSession
} }
}; };
bool SavePng(const FString& FilePath, const TArray<FColor>& Pixels,
int32 Width, int32 Height, FString& OutError);
int32 FindRoomGraphRecipeIndex(int32 Seed) int32 FindRoomGraphRecipeIndex(int32 Seed)
{ {
for (int32 Index = 0; Index < 64; ++Index) for (int32 Index = 0; Index < 64; ++Index)
@@ -2511,6 +2515,829 @@ int32 RunRegionEval(const FString& Params)
return bPassed ? 0 : 2; return bPassed ? 0 : 2;
} }
struct FStep2dSurfaceSpectrum
{
uint64 SurfaceBandSamples = 0;
double RMSLag1 = 0.0;
double RMSLag2 = 0.0;
double RMSLag4 = 0.0;
};
struct FStep2dBoundaryStats
{
uint64 BrickBoundarySamples = 0;
uint64 InteriorSamples = 0;
uint64 ChunkBoundarySamples = 0;
uint64 FeatureCellBoundarySamples = 0;
uint64 FeatureCellInteriorSamples = 0;
double BrickJumpRms = 0.0;
double InteriorJumpRms = 0.0;
double ChunkBoundaryJumpRms = 0.0;
double FeatureCellJumpRms = 0.0;
double FeatureCellInteriorJumpRms = 0.0;
double BrickJumpMax = 0.0;
double ChunkBoundaryJumpMax = 0.0;
double FeatureCellJumpMax = 0.0;
};
struct FStep2dBrickProofAudit
{
uint64 CorrectlySkippedBricksAudited = 0;
uint64 CorrectProofSamplesChecked = 0;
uint64 CorrectProofViolations = 0;
uint64 DeliberatelyUndersizedBoundClaimsChecked = 0;
uint64 DeliberateBoundDisagreements = 0;
};
FStep2dBrickProofAudit AuditStep2dBrickProofs(
const UVoxelGenerator& Generator,
FIntVector Origin,
int32 Dimension)
{
FStep2dBrickProofAudit Result;
const FIntVector Last = Origin + FIntVector(Dimension - 1, Dimension - 1, Dimension - 1);
const FIntVector FirstBrick(
FMath::FloorToInt(static_cast<float>(Origin.X) / 8.0f),
FMath::FloorToInt(static_cast<float>(Origin.Y) / 8.0f),
FMath::FloorToInt(static_cast<float>(Origin.Z) / 8.0f));
const FIntVector LastBrick(
FMath::FloorToInt(static_cast<float>(Last.X) / 8.0f),
FMath::FloorToInt(static_cast<float>(Last.Y) / 8.0f),
FMath::FloorToInt(static_cast<float>(Last.Z) / 8.0f));
uint64 CorrectSkipOrdinal = 0;
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 BrickOrigin(
FMath::Max(Origin.X, BrickWorldMin.X),
FMath::Max(Origin.Y, BrickWorldMin.Y),
FMath::Max(Origin.Z, BrickWorldMin.Z));
const FIntVector End(
FMath::Min(BrickWorldMin.X + 8, Origin.X + Dimension),
FMath::Min(BrickWorldMin.Y + 8, Origin.Y + Dimension),
FMath::Min(BrickWorldMin.Z + 8, Origin.Z + Dimension));
const FIntVector Size = End - BrickOrigin;
bool bProvenAir = false;
if (VoxelForgeRedefinedDensityField::TryClassifyBrick(
Generator, BrickOrigin, Size, bProvenAir, 1.0f))
{
const bool bAuditThisSkip = (CorrectSkipOrdinal++ % 128u) == 0u;
if (bAuditThisSkip)
{
++Result.CorrectlySkippedBricksAudited;
for (int32 Z = -1; Z <= Size.Z; ++Z)
for (int32 Y = -1; Y <= Size.Y; ++Y)
for (int32 X = -1; X <= Size.X; ++X)
{
const float Density = VoxelForgeRedefinedDensityField::Evaluate(
Generator,
static_cast<float>(BrickOrigin.X + X),
static_cast<float>(BrickOrigin.Y + Y),
static_cast<float>(BrickOrigin.Z + Z), nullptr, 1.0f);
++Result.CorrectProofSamplesChecked;
if ((Density > 0.0f) != bProvenAir)
{
++Result.CorrectProofViolations;
}
}
}
}
// Negative control: classify while omitting all detail, then check the supported
// maximum detail amplitude. A valid supremum must reject at least some of these
// sign claims; otherwise this audit would not demonstrate that it can catch a bad bound.
bool bDeliberatelyClaimedAir = false;
if (VoxelForgeRedefinedDensityField::TryClassifyBrick(
Generator, BrickOrigin, Size, bDeliberatelyClaimedAir, 0.0f))
{
++Result.DeliberatelyUndersizedBoundClaimsChecked;
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 float MaximumDetailDensity = VoxelForgeRedefinedDensityField::Evaluate(
Generator,
static_cast<float>(BrickOrigin.X + X),
static_cast<float>(BrickOrigin.Y + Y),
static_cast<float>(BrickOrigin.Z + Z),
nullptr,
4.0f);
if ((MaximumDetailDensity > 0.0f) != bDeliberatelyClaimedAir)
{
++Result.DeliberateBoundDisagreements;
}
}
}
}
return Result;
}
FStep2dSurfaceSpectrum ComputeStep2dSpectrum(const TArray<float>& Density, int32 Dimension)
{
FStep2dSurfaceSpectrum Result;
double SumSquares[3] = {0.0, 0.0, 0.0};
uint64 Counts[3] = {0, 0, 0};
const int32 Lags[] = {1, 2, 4};
for (int32 Z = 4; Z < Dimension - 4; ++Z)
for (int32 Y = 4; Y < Dimension - 4; ++Y)
for (int32 X = 4; X < Dimension - 4; ++X)
{
const int32 CenterIndex = (Z * Dimension + Y) * Dimension + X;
const float Center = Density[CenterIndex];
if (!FMath::IsFinite(Center) || FMath::Abs(Center) > 12.0f)
{
continue;
}
++Result.SurfaceBandSamples;
for (int32 LagIndex = 0; LagIndex < UE_ARRAY_COUNT(Lags); ++LagIndex)
{
const int32 Lag = Lags[LagIndex];
const float Laplacian =
Density[CenterIndex - Lag] + Density[CenterIndex + Lag]
+ Density[CenterIndex - Lag * Dimension]
+ Density[CenterIndex + Lag * Dimension]
+ Density[CenterIndex - Lag * Dimension * Dimension]
+ Density[CenterIndex + Lag * Dimension * Dimension]
- 6.0f * Center;
SumSquares[LagIndex] += static_cast<double>(Laplacian) * Laplacian;
++Counts[LagIndex];
}
}
Result.RMSLag1 = Counts[0] > 0 ? FMath::Sqrt(SumSquares[0] / Counts[0]) : 0.0;
Result.RMSLag2 = Counts[1] > 0 ? FMath::Sqrt(SumSquares[1] / Counts[1]) : 0.0;
Result.RMSLag4 = Counts[2] > 0 ? FMath::Sqrt(SumSquares[2] / Counts[2]) : 0.0;
return Result;
}
FIntVector SelectStep2dMeshOrigin(
const TArray<float>& OldDensity,
const TArray<float>& NewDensity,
FIntVector RingOrigin,
int32 Dimension,
bool& bOutContainsBothIsoBands)
{
bOutContainsBothIsoBands = false;
float BestCombinedBand = FLT_MAX;
FIntVector BestSample(Dimension / 2, Dimension / 2, Dimension / 2);
for (int32 Z = 32; Z < Dimension - 32; ++Z)
for (int32 Y = 32; Y < Dimension - 32; ++Y)
for (int32 X = 32; X < Dimension - 32; ++X)
{
const int32 Index = (Z * Dimension + Y) * Dimension + X;
const float OldAbs = FMath::Abs(OldDensity[Index]);
const float NewAbs = FMath::Abs(NewDensity[Index]);
if (OldAbs <= 8.0f && NewAbs <= 8.0f)
{
const float Combined = OldAbs + NewAbs;
if (Combined < BestCombinedBand)
{
BestCombinedBand = Combined;
BestSample = FIntVector(X, Y, Z);
bOutContainsBothIsoBands = true;
}
}
}
if (!bOutContainsBothIsoBands)
{
float BestNewBand = FLT_MAX;
for (int32 Z = 32; Z < Dimension - 32; ++Z)
for (int32 Y = 32; Y < Dimension - 32; ++Y)
for (int32 X = 32; X < Dimension - 32; ++X)
{
const int32 Index = (Z * Dimension + Y) * Dimension + X;
const float NewAbs = FMath::Abs(NewDensity[Index]);
if (NewAbs < BestNewBand)
{
BestNewBand = NewAbs;
BestSample = FIntVector(X, Y, Z);
}
}
}
return RingOrigin + BestSample - FIntVector(32, 32, 32);
}
FStep2dBoundaryStats ComputeStep2dBoundaryStats(
const TArray<float>& Density, int32 Dimension, FIntVector Origin, int32 FeatureCellSpacing)
{
FStep2dBoundaryStats Result;
double BrickSquares = 0.0;
double InteriorSquares = 0.0;
double ChunkSquares = 0.0;
double FeatureSquares = 0.0;
double FeatureInteriorSquares = 0.0;
const int32 CellSpacing = FMath::Max(FeatureCellSpacing, 1);
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 CenterIndex = (Z * Dimension + Y) * Dimension + X;
const int32 Stride[3] = {1, Dimension, Dimension * Dimension};
const int32 World[3] = {Origin.X + X, Origin.Y + Y, Origin.Z + Z};
for (int32 Axis = 0; Axis < 3; ++Axis)
{
const double LeftGradient = Density[CenterIndex] - Density[CenterIndex - Stride[Axis]];
const double RightGradient = Density[CenterIndex + Stride[Axis]] - Density[CenterIndex];
const double Jump = FMath::Abs(RightGradient - LeftGradient);
const bool bBrickBoundary = (World[Axis] % 8) == 0;
const bool bChunkBoundary = (World[Axis] % CHUNK_SIZE) == 0 && Axis < 2;
const bool bFeatureBoundary = ((World[Axis] + CellSpacing / 2) % CellSpacing) == 0;
if (FMath::Abs(Density[CenterIndex]) <= 12.0f)
{
if (bBrickBoundary)
{
++Result.BrickBoundarySamples;
BrickSquares += Jump * Jump;
Result.BrickJumpMax = FMath::Max(Result.BrickJumpMax, Jump);
}
else
{
++Result.InteriorSamples;
InteriorSquares += Jump * Jump;
}
if (bChunkBoundary)
{
++Result.ChunkBoundarySamples;
ChunkSquares += Jump * Jump;
Result.ChunkBoundaryJumpMax = FMath::Max(Result.ChunkBoundaryJumpMax, Jump);
}
}
if (bFeatureBoundary)
{
++Result.FeatureCellBoundarySamples;
FeatureSquares += Jump * Jump;
}
else
{
++Result.FeatureCellInteriorSamples;
FeatureInteriorSquares += Jump * Jump;
}
if (bFeatureBoundary)
Result.FeatureCellJumpMax = FMath::Max(Result.FeatureCellJumpMax, Jump);
}
}
Result.BrickJumpRms = Result.BrickBoundarySamples > 0
? FMath::Sqrt(BrickSquares / Result.BrickBoundarySamples) : 0.0;
Result.InteriorJumpRms = Result.InteriorSamples > 0
? FMath::Sqrt(InteriorSquares / Result.InteriorSamples) : 0.0;
Result.ChunkBoundaryJumpRms = Result.ChunkBoundarySamples > 0
? FMath::Sqrt(ChunkSquares / Result.ChunkBoundarySamples) : 0.0;
Result.FeatureCellJumpRms = Result.FeatureCellBoundarySamples > 0
? FMath::Sqrt(FeatureSquares / Result.FeatureCellBoundarySamples) : 0.0;
Result.FeatureCellInteriorJumpRms = Result.FeatureCellInteriorSamples > 0
? FMath::Sqrt(FeatureInteriorSquares / Result.FeatureCellInteriorSamples) : 0.0;
return Result;
}
FColor Step2dDensityColor(float Density)
{
if (FMath::Abs(Density) < 1.0f)
{
return FColor(56, 184, 206, 255);
}
return Density >= 0.0f
? FColor(226, 220, 202, 255)
: FColor(22, 30, 45, 255);
}
void SaveStep2dSideBySideSlice(
const FString& Path,
const TArray<float>& OldDensity,
const TArray<float>& NewDensity,
int32 Dimension,
TCHAR Plane,
int32 FixedIndex,
FString& OutError)
{
const int32 Width = Dimension * 2;
const int32 Height = Dimension;
TArray<FColor> Pixels;
Pixels.SetNumUninitialized(Width * Height);
for (int32 Row = 0; Row < Height; ++Row)
for (int32 Column = 0; Column < Dimension; ++Column)
{
int32 OldIndex = 0;
int32 NewIndex = 0;
if (Plane == TEXT('h'))
{
const int32 X = Column;
const int32 Y = Dimension - 1 - Row;
const int32 Z = FixedIndex;
OldIndex = (Z * Dimension + Y) * Dimension + X;
}
else if (Plane == TEXT('x'))
{
const int32 X = Column;
const int32 Y = FixedIndex;
const int32 Z = Dimension - 1 - Row;
OldIndex = (Z * Dimension + Y) * Dimension + X;
}
else
{
const int32 X = FixedIndex;
const int32 Y = Column;
const int32 Z = Dimension - 1 - Row;
OldIndex = (Z * Dimension + Y) * Dimension + X;
}
NewIndex = OldIndex;
Pixels[Row * Width + Column] = Step2dDensityColor(OldDensity[OldIndex]);
Pixels[Row * Width + Dimension + Column] = Step2dDensityColor(NewDensity[NewIndex]);
}
SavePng(Path, Pixels, Width, Height, OutError);
}
TSharedRef<FJsonObject> Step2dTimingJson(const VoxelForgeRegionEval::FTiming& Timing)
{
TSharedRef<FJsonObject> Json = MakeShared<FJsonObject>();
Json->SetNumberField(TEXT("workers_requested"), Timing.Workers);
Json->SetNumberField(TEXT("workers_observed"), Timing.ObservedWorkerThreads);
Json->SetNumberField(TEXT("lattice_samples"), static_cast<double>(Timing.SampleCount));
Json->SetNumberField(TEXT("density_samples_evaluated"), static_cast<double>(Timing.EvaluatedSamples));
Json->SetNumberField(TEXT("samples_skipped_by_brick_proof"), static_cast<double>(Timing.SkippedSamples));
Json->SetNumberField(TEXT("bricks_tested"), static_cast<double>(Timing.BrickCount));
Json->SetNumberField(TEXT("bricks_skipped"), static_cast<double>(Timing.SkippedBricks));
Json->SetNumberField(TEXT("share_of_bricks_skipped"), Timing.BrickCount > 0
? static_cast<double>(Timing.SkippedBricks) / static_cast<double>(Timing.BrickCount) : 0.0);
Json->SetNumberField(TEXT("share_of_lattice_samples_skipped"), Timing.SampleCount > 0
? static_cast<double>(Timing.SkippedSamples) / static_cast<double>(Timing.SampleCount) : 0.0);
Json->SetNumberField(TEXT("wall_ms"), Timing.WallSeconds * 1000.0);
Json->SetNumberField(TEXT("worker_cpu_seconds"), Timing.WorkerCpuSeconds);
Json->SetNumberField(TEXT("worker_cpu_ns_per_lattice_sample"), Timing.NanosecondsPerSample);
Json->SetNumberField(TEXT("brick_classification_worker_seconds"), Timing.BrickClassificationSeconds);
Json->SetNumberField(TEXT("density_evaluation_worker_seconds"), Timing.DensityEvaluationSeconds);
Json->SetNumberField(TEXT("uniform_fill_worker_seconds"), Timing.UniformFillSeconds);
Json->SetNumberField(TEXT("longest_worker_wall_ms"), Timing.LongestWorkerWallSeconds * 1000.0);
Json->SetNumberField(TEXT("parallel_overhead_wall_ms"), FMath::Max(
0.0, Timing.WallSeconds - Timing.LongestWorkerWallSeconds) * 1000.0);
Json->SetNumberField(TEXT("brick_classification_calls"), static_cast<double>(Timing.MacroBrickChecks));
Json->SetNumberField(TEXT("macro_center_evaluations"), static_cast<double>(Timing.MacroCentreEvaluations));
Json->SetNumberField(TEXT("unsupported_brick_proofs"), static_cast<double>(Timing.UnsupportedBrickProofs));
Json->SetNumberField(TEXT("chunk_or_strate_boundary_proofs"),
static_cast<double>(Timing.ChunkBoundaryBrickProofs));
Json->SetNumberField(TEXT("passage_bound_proofs"), static_cast<double>(Timing.PassageBoundBrickProofs));
Json->SetNumberField(TEXT("mixed_macro_interval_proofs"), static_cast<double>(Timing.MixedMacroBrickProofs));
Json->SetNumberField(TEXT("room_sdf_evaluations"), static_cast<double>(Timing.RoomEvaluations));
Json->SetNumberField(TEXT("tunnel_sdf_evaluations"), static_cast<double>(Timing.TunnelEvaluations));
Json->SetNumberField(TEXT("tunnel_core_evaluations"), static_cast<double>(Timing.TunnelCoreEvaluations));
Json->SetNumberField(TEXT("passage_sdf_evaluations"), static_cast<double>(Timing.PassageEvaluations));
Json->SetNumberField(TEXT("geometry_evaluations_per_lattice_sample"), Timing.CandidateEvaluationsPerSample);
Json->SetNumberField(TEXT("quantized_hash"), static_cast<double>(Timing.QuantizedHash));
Json->SetNumberField(TEXT("raw_float_hash"), static_cast<double>(Timing.RawFloatHash));
return Json;
}
int32 RunStep2d(const FString& Params)
{
FExploreArguments Arguments;
FString Error;
if (!ParseArguments(Params, Arguments, Error))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2d] argument error: %s"), *Error);
return 2;
}
int32 Seed = 1337;
FParse::Value(*Params, TEXT("seed="), Seed);
int32 Workers = VoxelForgeRegionEval::WorkerCount;
FParse::Value(*Params, TEXT("regionworkers="), Workers);
Workers = FMath::Clamp(Workers, 1, 32);
FString ResearchOut;
FParse::Value(*Params, TEXT("researchout="), ResearchOut);
if (ResearchOut.IsEmpty())
{
ResearchOut = FPaths::Combine(Arguments.OutDirectory, TEXT("Research/step2d"));
}
ResearchOut = FPaths::ConvertRelativePathToFull(ResearchOut);
IFileManager::Get().MakeDirectory(*ResearchOut, true);
IFileManager::Get().MakeDirectory(*Arguments.OutDirectory, true);
FExploreWorld World;
int32 RecipeIndex = INDEX_NONE;
uint32 RecipeHash = 0;
int32 EditChunkCount = 0;
const double PrepareStartSeconds = FPlatformTime::Seconds();
if (!PrepareRegionEvalWorld(World, Arguments, Seed,
RecipeIndex, RecipeHash, EditChunkCount, Error))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2d] world preparation failed: %s"), *Error);
return 2;
}
const double WorldPrepareSeconds = FPlatformTime::Seconds() - PrepareStartSeconds;
if (World.DiffLayer != nullptr)
{
World.DiffLayer->Clear();
}
const int32 Dimension = VoxelForgeRegionEval::RingDimension;
const int32 CenterZ = (World.TargetTopWorldZ + World.TargetBottomWorldZ) / 2;
const FIntVector Origin(-129, -129, CenterZ - 129);
TArray<VoxelForgeRegionEval::FTiming> OldTrials;
TArray<VoxelForgeRegionEval::FTiming> NewTrials;
TArray<float> OldDensities;
TArray<float> NewDensities;
TArray<float> NewRepeatDensities;
TArray<float> NewSkippedDensities;
TArray<float> SmoothDensities;
TArray<float> SingleWorkerDensities;
VoxelForgeRedefinedDensityField::SetEnabled(false);
VoxelForgeRedefinedDensityField::SetDetailScale(1.0f);
for (int32 Trial = 0; Trial < 3; ++Trial)
{
VoxelForgeRegionEval::FTiming Timing;
if (!VoxelForgeRegionEval::Evaluate(
*World.Generator, Origin, Dimension, nullptr, true, false, 1.0f,
&OldDensities, Timing, false, Workers, false))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2d] old field timing pass %d failed."), Trial + 1);
return 2;
}
OldTrials.Add(Timing);
}
// This fourth old-field pass carries counters; its wall time is deliberately excluded from
// the performance row so instrumentation does not contaminate the measured baseline.
VoxelForgeRegionEval::FTiming OldCounters;
if (!VoxelForgeRegionEval::Evaluate(
*World.Generator, Origin, Dimension, nullptr, true, false, 1.0f,
nullptr, OldCounters, false, Workers, true))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2d] old field counter pass failed."));
return 2;
}
OldCounters.WallSeconds = OldTrials.Last().WallSeconds;
OldCounters.WorkerCpuSeconds = OldTrials.Last().WorkerCpuSeconds;
OldCounters.NanosecondsPerSample = OldTrials.Last().NanosecondsPerSample;
for (int32 Trial = 0; Trial < 3; ++Trial)
{
VoxelForgeRegionEval::FTiming Timing;
if (!VoxelForgeRegionEval::EvaluateRedefined(
*World.Generator, Origin, Dimension, Workers, 1.0f,
NewSkippedDensities, Timing, true))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2d] redefined 6-worker pass %d failed."), Trial + 1);
return 2;
}
NewTrials.Add(Timing);
}
VoxelForgeRegionEval::FTiming OneWorkerTiming;
if (!VoxelForgeRegionEval::EvaluateRedefined(
*World.Generator, Origin, Dimension, 1, 1.0f,
SingleWorkerDensities, OneWorkerTiming, false))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2d] 1-worker determinism pass failed."));
return 2;
}
VoxelForgeRegionEval::FTiming NewFullSixWorkerTiming;
if (!VoxelForgeRegionEval::EvaluateRedefined(
*World.Generator, Origin, Dimension, Workers, 1.0f,
NewDensities, NewFullSixWorkerTiming, false))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2d] full-detail six-worker evidence pass failed."));
return 2;
}
VoxelForgeRegionEval::FTiming NewRepeatTiming;
if (!VoxelForgeRegionEval::EvaluateRedefined(
*World.Generator, Origin, Dimension, Workers, 1.0f,
NewRepeatDensities, NewRepeatTiming, false))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2d] repeated full-detail hash pass failed."));
return 2;
}
VoxelForgeRegionEval::FTiming SmoothTiming;
if (!VoxelForgeRegionEval::EvaluateRedefined(
*World.Generator, Origin, Dimension, Workers, 0.0f,
SmoothDensities, SmoothTiming, false))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2d] deliberate smoothing control failed."));
return 2;
}
int64 DetailBatchParitySamples = 0;
double DetailBatchMaxAbsoluteError = 0.0;
const int64 TotalLatticeSamples = static_cast<int64>(Dimension) * Dimension * Dimension;
for (int64 Index = 0; Index < TotalLatticeSamples; Index += 2047)
{
const int32 X = static_cast<int32>(Index % Dimension);
const int32 Y = static_cast<int32>((Index / Dimension) % Dimension);
const int32 Z = static_cast<int32>(Index / (static_cast<int64>(Dimension) * Dimension));
const float RawDetailField = VoxelForgeRedefinedDensityField::Evaluate(
*World.Generator, static_cast<float>(Origin.X + X),
static_cast<float>(Origin.Y + Y), static_cast<float>(Origin.Z + Z), nullptr, 1.0f);
DetailBatchMaxAbsoluteError = FMath::Max(
DetailBatchMaxAbsoluteError,
static_cast<double>(FMath::Abs(RawDetailField - NewDensities[static_cast<int32>(Index)])));
++DetailBatchParitySamples;
}
const bool bDetailBatchMatchesRaw = DetailBatchMaxAbsoluteError <= 1.0e-5;
const FStep2dSurfaceSpectrum OldSpectrum = ComputeStep2dSpectrum(OldDensities, Dimension);
const FStep2dSurfaceSpectrum NewSpectrum = ComputeStep2dSpectrum(NewDensities, Dimension);
const FStep2dSurfaceSpectrum SmoothSpectrum = ComputeStep2dSpectrum(SmoothDensities, Dimension);
const FStep2dBrickProofAudit BrickProofAudit = AuditStep2dBrickProofs(
*World.Generator, Origin, Dimension);
const int32 FeatureCellSpacing = World.Definitions.IsValidIndex(Arguments.Slot)
&& World.Definitions[Arguments.Slot].IsValid()
&& World.Definitions[Arguments.Slot]->GeneratorType == ECaveGeneratorType::Maze
? 96 : 128;
const FStep2dBoundaryStats BoundaryStats = ComputeStep2dBoundaryStats(
NewDensities, Dimension, Origin, FeatureCellSpacing);
const bool bHashesAgree = OldDensities.Num() == Dimension * Dimension * Dimension
&& VoxelForgeRegionEval::HashQuantized(NewDensities)
== VoxelForgeRegionEval::HashQuantized(SingleWorkerDensities)
&& VoxelForgeRegionEval::HashQuantized(NewDensities)
== VoxelForgeRegionEval::HashQuantized(NewRepeatDensities)
&& NewFullSixWorkerTiming.RawFloatHash == OneWorkerTiming.RawFloatHash
&& NewFullSixWorkerTiming.RawFloatHash == NewRepeatTiming.RawFloatHash;
VoxelForgeRedefinedDensityField::SetEnabled(false);
VoxelForgeRedefinedDensityField::SetDetailScale(1.0f);
bool bMeshContainsBothIsoBands = false;
const FIntVector MeshOrigin = SelectStep2dMeshOrigin(
OldDensities, NewDensities, Origin, Dimension, bMeshContainsBothIsoBands);
const FVoxelMeshData OldMesh = World.Mesher->GenerateMesh(MeshOrigin, 1, 64);
const int32 OldTriangleCount = FMath::Max(0, OldMesh.Triangles.Num() / 3 - OldMesh.NumSkirtTriangles);
VoxelForgeRedefinedDensityField::SetEnabled(true);
const FVoxelMeshData NewMesh = World.Mesher->GenerateMesh(MeshOrigin, 1, 64);
const int32 NewTriangleCount = FMath::Max(0, NewMesh.Triangles.Num() / 3 - NewMesh.NumSkirtTriangles);
VoxelForgeRedefinedDensityField::SetDetailScale(0.0f);
const FVoxelMeshData SmoothMesh = World.Mesher->GenerateMesh(MeshOrigin, 1, 64);
const int32 SmoothTriangleCount = FMath::Max(0, SmoothMesh.Triangles.Num() / 3 - SmoothMesh.NumSkirtTriangles);
VoxelForgeRedefinedDensityField::SetDetailScale(1.0f);
VoxelForgeRedefinedDensityField::SetEnabled(false);
const FString ImageNames[] = {
TEXT("slice_xy_center.png"), TEXT("slice_xz_y0.png"),
TEXT("slice_yz_x0.png"), TEXT("slice_xz_passage.png")};
FString SliceError;
SaveStep2dSideBySideSlice(
FPaths::Combine(ResearchOut, ImageNames[0]), OldDensities, NewDensities,
Dimension, TEXT('h'), Dimension / 2, SliceError);
SaveStep2dSideBySideSlice(
FPaths::Combine(ResearchOut, ImageNames[1]), OldDensities, NewDensities,
Dimension, TEXT('x'), Dimension / 2, SliceError);
SaveStep2dSideBySideSlice(
FPaths::Combine(ResearchOut, ImageNames[2]), OldDensities, NewDensities,
Dimension, TEXT('y'), Dimension / 2, SliceError);
int32 PassageIndex = Dimension / 2;
if (World.Manager != nullptr && !World.Manager->GetPassages().IsEmpty())
{
const FVoxelPassage& Passage = World.Manager->GetPassages()[0];
PassageIndex = FMath::Clamp(FMath::RoundToInt(Passage.LowerLanding.StandingPoint.Y)
- Origin.Y, 0, Dimension - 1);
}
SaveStep2dSideBySideSlice(
FPaths::Combine(ResearchOut, ImageNames[3]), OldDensities, NewDensities,
Dimension, TEXT('x'), PassageIndex, SliceError);
if (!SliceError.IsEmpty())
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2d] slice image write failed: %s"), *SliceError);
return 2;
}
TArray<double> OldWallTrials;
TArray<double> NewWallTrials;
for (const auto& Timing : OldTrials) OldWallTrials.Add(Timing.WallSeconds);
for (const auto& Timing : NewTrials) NewWallTrials.Add(Timing.WallSeconds);
OldWallTrials.Sort();
NewWallTrials.Sort();
const double OldMedianWall = OldWallTrials[OldWallTrials.Num() / 2];
const double NewMedianWall = NewWallTrials[NewWallTrials.Num() / 2];
const double OldGeometryEvaluations = static_cast<double>(OldCounters.RoomEvaluations
+ OldCounters.TunnelEvaluations + OldCounters.TunnelCoreEvaluations
+ OldCounters.PassageEvaluations);
const double OldGeometryPerSample = OldCounters.SampleCount > 0
? OldGeometryEvaluations / static_cast<double>(OldCounters.SampleCount) : 0.0;
const double NewMacroPerSample = NewTrials.Last().SampleCount > 0
? static_cast<double>(NewTrials.Last().MacroCentreEvaluations
+ NewTrials.Last().EvaluatedSamples)
/ static_cast<double>(NewTrials.Last().SampleCount) : 0.0;
const bool bDetailAtLeastOld = NewSpectrum.RMSLag1 >= OldSpectrum.RMSLag1
&& NewSpectrum.RMSLag2 >= OldSpectrum.RMSLag2;
const bool bSmoothControlDetected = SmoothSpectrum.RMSLag1 < NewSpectrum.RMSLag1 * 0.80
|| SmoothSpectrum.RMSLag2 < NewSpectrum.RMSLag2 * 0.80;
const bool bBoundaryStatsSound = 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 bCorrectProofsSound = BrickProofAudit.CorrectlySkippedBricksAudited > 0
&& BrickProofAudit.CorrectProofSamplesChecked > 0
&& BrickProofAudit.CorrectProofViolations == 0;
const bool bUndersizedBoundCaught = BrickProofAudit.DeliberateBoundDisagreements > 0;
const bool bSmoothWouldFallBelowOldSpectrum = SmoothSpectrum.RMSLag1 < OldSpectrum.RMSLag1
|| SmoothSpectrum.RMSLag2 < OldSpectrum.RMSLag2;
const bool bSpeedPass = NewMedianWall <= 0.600;
const bool bPassed = bSpeedPass && bHashesAgree && bDetailAtLeastOld
&& bSmoothControlDetected
&& bBoundaryStatsSound && bCorrectProofsSound
&& bUndersizedBoundCaught
&& bDetailBatchMatchesRaw
&& NewTrials.Last().NonFiniteSamples == 0;
TSharedRef<FJsonObject> Report = MakeShared<FJsonObject>();
Report->SetStringField(TEXT("status"), bPassed ? TEXT("pass") : TEXT("fail"));
Report->SetBoolField(TEXT("pass"), bPassed);
Report->SetBoolField(TEXT("speed_pass_600ms"), bSpeedPass);
Report->SetNumberField(TEXT("seed"), Seed);
Report->SetNumberField(TEXT("ring_dimension"), Dimension);
Report->SetNumberField(TEXT("ring_samples"), static_cast<double>(Dimension) * Dimension * Dimension);
Report->SetNumberField(TEXT("workers_requested"), Workers);
Report->SetNumberField(TEXT("world_prepare_ms_excluded"), WorldPrepareSeconds * 1000.0);
Report->SetNumberField(TEXT("world_prepare_recipe_index"), RecipeIndex);
Report->SetNumberField(TEXT("world_prepare_recipe_hash"), RecipeHash);
Report->SetNumberField(TEXT("cleared_player_edit_chunks"), EditChunkCount);
TSharedRef<FJsonObject> Window = MakeShared<FJsonObject>();
Window->SetNumberField(TEXT("origin_x_voxels"), Origin.X);
Window->SetNumberField(TEXT("origin_y_voxels"), Origin.Y);
Window->SetNumberField(TEXT("origin_z_voxels"), Origin.Z);
Window->SetNumberField(TEXT("samples_per_axis"), Dimension);
Window->SetNumberField(TEXT("sample_stride_voxels"), 1);
Window->SetNumberField(TEXT("sample_spacing_cm"), 25.0);
Report->SetObjectField(TEXT("full_resolution_window"), Window);
Report->SetStringField(TEXT("field_definition"),
TEXT("A seed-offset periodic cross-corridor signed field per strate, joined with rounded "
"landing chambers and existing authored passage carving. Four continuous hash-value "
"noise bands (8, 4, 2, and 1 lattice point) with total bounded amplitude 5.25 are "
"evaluated at every full-resolution sample that can affect the zero set."));
Report->SetStringField(TEXT("resolution_contract"),
TEXT("The lattice remains one sample per 25 cm. No coarse density result is interpolated "
"onto it: every potentially surface-affecting point evaluates all four detail bands. "
"Brick skips require a final-field sign proof including the full noise supremum, a "
"floating-point safety margin, and the one-sample marching-cubes halo."));
Report->SetStringField(TEXT("finer_lattice_scaling"),
TEXT("At 12.5 cm, scale macro lengths and the broad/medium noise cell sizes in lattice "
"units by 2 to preserve their world-space shapes; keep or add a 1-2 sample fine octave "
"to retain and extend detail. With fixed 8^3 sample bricks, samples grow 8x but "
"surface-intersecting bricks grow approximately with area (about 4x); the rest are "
"sign-proven and require only density writes. Full-resolution detail remains per "
"potentially surface-affecting point."));
Report->SetNumberField(TEXT("old_macro_geometry_evaluations_per_point"), OldGeometryPerSample);
Report->SetNumberField(TEXT("new_macro_evaluations_per_point_including_brick_proofs"), NewMacroPerSample);
Report->SetNumberField(TEXT("new_noise_value_octaves_per_evaluated_point"), 4.0);
Report->SetNumberField(TEXT("new_noise_octave_evaluations_per_full_ring_point"),
static_cast<double>(NewTrials.Last().EvaluatedSamples) * 4.0
/ static_cast<double>(NewTrials.Last().SampleCount));
Report->SetNumberField(TEXT("old_reported_noise_share_percent_from_step2c"), 2.6);
Report->SetNumberField(TEXT("old_six_worker_median_ms"), OldMedianWall * 1000.0);
Report->SetNumberField(TEXT("new_six_worker_median_ms"), NewMedianWall * 1000.0);
const auto WorkerRemainderSeconds = [](const VoxelForgeRegionEval::FTiming& Timing)
{
return FMath::Max(0.0, Timing.WorkerCpuSeconds
- Timing.BrickClassificationSeconds - Timing.DensityEvaluationSeconds
- Timing.UniformFillSeconds);
};
Report->SetNumberField(TEXT("new_other_worker_cpu_seconds"),
WorkerRemainderSeconds(NewTrials.Last()));
Report->SetNumberField(TEXT("new_other_worker_cpu_ns_per_point"),
WorkerRemainderSeconds(NewTrials.Last()) * 1.0e9
/ static_cast<double>(NewTrials.Last().SampleCount));
Report->SetNumberField(TEXT("new_speedup_vs_old"), NewMedianWall > 0.0
? OldMedianWall / NewMedianWall : 0.0);
TArray<TSharedPtr<FJsonValue>> OldRows;
TArray<TSharedPtr<FJsonValue>> NewRows;
for (const auto& Timing : OldTrials)
{
OldRows.Add(MakeShared<FJsonValueObject>(Step2dTimingJson(Timing)));
}
for (const auto& Timing : NewTrials)
{
NewRows.Add(MakeShared<FJsonValueObject>(Step2dTimingJson(Timing)));
}
Report->SetArrayField(TEXT("old_field_trials"), OldRows);
Report->SetArrayField(TEXT("new_field_trials"), NewRows);
Report->SetObjectField(TEXT("old_full_ring_counters"), Step2dTimingJson(OldCounters));
Report->SetObjectField(TEXT("new_one_worker_determinism_pass"), Step2dTimingJson(OneWorkerTiming));
Report->SetObjectField(TEXT("new_full_detail_six_worker_evidence_pass"),
Step2dTimingJson(NewFullSixWorkerTiming));
Report->SetObjectField(TEXT("new_repeated_full_detail_six_worker_hash_pass"),
Step2dTimingJson(NewRepeatTiming));
Report->SetObjectField(TEXT("smooth_control_full_detail_pass"), Step2dTimingJson(SmoothTiming));
TSharedRef<FJsonObject> Hashes = MakeShared<FJsonObject>();
Hashes->SetStringField(TEXT("six_worker_run_1_quantized"), FString::Printf(
TEXT("%016llx"), static_cast<unsigned long long>(NewFullSixWorkerTiming.QuantizedHash)));
Hashes->SetStringField(TEXT("six_worker_run_2_quantized"), FString::Printf(
TEXT("%016llx"), static_cast<unsigned long long>(NewRepeatTiming.QuantizedHash)));
Hashes->SetStringField(TEXT("one_worker_quantized"), FString::Printf(
TEXT("%016llx"), static_cast<unsigned long long>(OneWorkerTiming.QuantizedHash)));
Hashes->SetStringField(TEXT("six_worker_run_1_raw_float"), FString::Printf(
TEXT("%016llx"), static_cast<unsigned long long>(NewFullSixWorkerTiming.RawFloatHash)));
Hashes->SetStringField(TEXT("six_worker_run_2_raw_float"), FString::Printf(
TEXT("%016llx"), static_cast<unsigned long long>(NewRepeatTiming.RawFloatHash)));
Hashes->SetStringField(TEXT("one_worker_raw_float"), FString::Printf(
TEXT("%016llx"), static_cast<unsigned long long>(OneWorkerTiming.RawFloatHash)));
Hashes->SetBoolField(TEXT("same_across_two_runs_and_1_vs_6_workers"), bHashesAgree);
Report->SetObjectField(TEXT("determinism"), Hashes);
TSharedRef<FJsonObject> Detail = MakeShared<FJsonObject>();
Detail->SetNumberField(TEXT("old_surface_band_samples"), static_cast<double>(OldSpectrum.SurfaceBandSamples));
Detail->SetNumberField(TEXT("new_surface_band_samples"), static_cast<double>(NewSpectrum.SurfaceBandSamples));
Detail->SetNumberField(TEXT("old_curvature_rms_lag1"), OldSpectrum.RMSLag1);
Detail->SetNumberField(TEXT("new_curvature_rms_lag1"), NewSpectrum.RMSLag1);
Detail->SetNumberField(TEXT("smoothed_control_curvature_rms_lag1"), SmoothSpectrum.RMSLag1);
Detail->SetNumberField(TEXT("old_curvature_rms_lag2"), OldSpectrum.RMSLag2);
Detail->SetNumberField(TEXT("new_curvature_rms_lag2"), NewSpectrum.RMSLag2);
Detail->SetNumberField(TEXT("smoothed_control_curvature_rms_lag2"), SmoothSpectrum.RMSLag2);
Detail->SetNumberField(TEXT("new_curvature_rms_lag4"), NewSpectrum.RMSLag4);
Detail->SetNumberField(TEXT("smoothed_control_curvature_rms_lag4"), SmoothSpectrum.RMSLag4);
Detail->SetNumberField(TEXT("old_surface_triangles_64cubed"), OldTriangleCount);
Detail->SetNumberField(TEXT("new_surface_triangles_64cubed"), NewTriangleCount);
Detail->SetNumberField(TEXT("smoothed_control_surface_triangles_64cubed"), SmoothTriangleCount);
Detail->SetNumberField(TEXT("mesh_region_origin_x_voxels"), MeshOrigin.X);
Detail->SetNumberField(TEXT("mesh_region_origin_y_voxels"), MeshOrigin.Y);
Detail->SetNumberField(TEXT("mesh_region_origin_z_voxels"), MeshOrigin.Z);
Detail->SetBoolField(TEXT("mesh_region_contains_old_and_new_iso_bands"),
bMeshContainsBothIsoBands);
Detail->SetBoolField(TEXT("new_detail_at_least_old_spectrum"), bDetailAtLeastOld);
Detail->SetNumberField(TEXT("detail_batch_raw_parity_samples"), DetailBatchParitySamples);
Detail->SetNumberField(TEXT("detail_batch_max_absolute_error"), DetailBatchMaxAbsoluteError);
Detail->SetBoolField(TEXT("detail_batch_matches_direct_field"), bDetailBatchMatchesRaw);
Detail->SetBoolField(TEXT("deliberate_smoothing_control_detected"), bSmoothControlDetected);
Detail->SetBoolField(TEXT("smoothed_control_below_old_spectrum"),
bSmoothWouldFallBelowOldSpectrum);
Report->SetObjectField(TEXT("detail_preservation"), Detail);
TSharedRef<FJsonObject> Boundaries = MakeShared<FJsonObject>();
Boundaries->SetNumberField(TEXT("brick_size_voxels"), 8);
Boundaries->SetNumberField(TEXT("feature_cell_spacing_voxels"), FeatureCellSpacing);
Boundaries->SetNumberField(TEXT("chunk_boundary_gradient_jump_rms_xy_near_surface"),
BoundaryStats.ChunkBoundaryJumpRms);
Boundaries->SetNumberField(TEXT("chunk_boundary_gradient_jump_max_xy_near_surface"),
BoundaryStats.ChunkBoundaryJumpMax);
Boundaries->SetNumberField(TEXT("brick_boundary_gradient_jump_rms"), BoundaryStats.BrickJumpRms);
Boundaries->SetNumberField(TEXT("interior_gradient_jump_rms"), BoundaryStats.InteriorJumpRms);
Boundaries->SetNumberField(TEXT("feature_cell_boundary_gradient_jump_rms"), BoundaryStats.FeatureCellJumpRms);
Boundaries->SetNumberField(TEXT("feature_cell_interior_gradient_jump_rms"),
BoundaryStats.FeatureCellInteriorJumpRms);
Boundaries->SetNumberField(TEXT("brick_boundary_samples_near_surface"),
static_cast<double>(BoundaryStats.BrickBoundarySamples));
Boundaries->SetNumberField(TEXT("chunk_boundary_samples_near_surface_xy"),
static_cast<double>(BoundaryStats.ChunkBoundarySamples));
Boundaries->SetNumberField(TEXT("macro_period_boundary_samples"),
static_cast<double>(BoundaryStats.FeatureCellBoundarySamples));
Boundaries->SetNumberField(TEXT("brick_boundary_gradient_jump_max"), BoundaryStats.BrickJumpMax);
Boundaries->SetNumberField(TEXT("feature_cell_boundary_gradient_jump_max"), BoundaryStats.FeatureCellJumpMax);
Boundaries->SetBoolField(TEXT("boundary_check_pass"), bBoundaryStatsSound);
Report->SetObjectField(TEXT("boundary_artifact_check"), Boundaries);
TSharedRef<FJsonObject> Proofs = MakeShared<FJsonObject>();
Proofs->SetNumberField(TEXT("correctly_skipped_bricks_audited"),
static_cast<double>(BrickProofAudit.CorrectlySkippedBricksAudited));
Proofs->SetNumberField(TEXT("final_field_samples_checked_including_halo"),
static_cast<double>(BrickProofAudit.CorrectProofSamplesChecked));
Proofs->SetNumberField(TEXT("correct_proof_sign_violations"),
static_cast<double>(BrickProofAudit.CorrectProofViolations));
Proofs->SetNumberField(TEXT("deliberately_undersized_detail_bound_claims_checked"),
static_cast<double>(BrickProofAudit.DeliberatelyUndersizedBoundClaimsChecked));
Proofs->SetNumberField(TEXT("wrong_sign_samples_from_undersized_bound"),
static_cast<double>(BrickProofAudit.DeliberateBoundDisagreements));
Proofs->SetNumberField(TEXT("deliberate_control_classifier_detail_scale"), 0.0);
Proofs->SetNumberField(TEXT("deliberate_control_actual_detail_scale"), 4.0);
Proofs->SetBoolField(TEXT("sound_bound_audit_pass"), bCorrectProofsSound);
Proofs->SetBoolField(TEXT("deliberate_undersized_bound_control_caught"), bUndersizedBoundCaught);
Report->SetObjectField(TEXT("brick_bound_audit"), Proofs);
TArray<TSharedPtr<FJsonValue>> Slices;
for (const FString& Name : ImageNames)
{
TSharedRef<FJsonObject> Slice = MakeShared<FJsonObject>();
Slice->SetStringField(TEXT("file"), FPaths::Combine(ResearchOut, Name));
Slice->SetStringField(TEXT("layout"), TEXT("old field on left; redefined field on right; light=air, dark=solid, cyan=near zero"));
Slices.Add(MakeShared<FJsonValueObject>(Slice));
}
Report->SetArrayField(TEXT("slices"), Slices);
FString JsonText;
const TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&JsonText);
const FString ReportPath = FPaths::Combine(ResearchOut, TEXT("step2d_report.json"));
if (!FJsonSerializer::Serialize(Report, Writer)
|| !FFileHelper::SaveStringToFile(
JsonText, *ReportPath, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM)
|| !FFileHelper::SaveStringToFile(
JsonText, *FPaths::Combine(Arguments.OutDirectory, TEXT("step2d_report.json")),
FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeStep2d] could not write report '%s'."), *ReportPath);
return 2;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeStep2d] pass=%s speed600=%s old_ms=%.3f new_ms=%.3f skip=%.1f%% "
"roughness(old/new/smooth)=%.4f/%.4f/%.4f hashes=%s report=%s"),
bPassed ? TEXT("yes") : TEXT("no"), bSpeedPass ? TEXT("yes") : TEXT("no"),
OldMedianWall * 1000.0, NewMedianWall * 1000.0,
NewTrials.Last().SampleCount > 0
? 100.0 * static_cast<double>(NewTrials.Last().SkippedSamples)
/ static_cast<double>(NewTrials.Last().SampleCount) : 0.0,
OldSpectrum.RMSLag1, NewSpectrum.RMSLag1, SmoothSpectrum.RMSLag1,
bHashesAgree ? TEXT("same") : TEXT("DIFFERENT"), *ReportPath);
return bPassed ? 0 : 2;
}
class FGeneratorDensitySampler final : public IVoxelStrateDensitySampler class FGeneratorDensitySampler final : public IVoxelStrateDensitySampler
{ {
public: public:
@@ -9648,6 +10475,10 @@ int32 RunBatch(const FString& Params)
int32 UVoxelForgeExploreCommandlet::Main(const FString& Params) int32 UVoxelForgeExploreCommandlet::Main(const FString& Params)
{ {
if (FParse::Param(*Params, TEXT("step2d")))
{
return RunStep2d(Params);
}
const bool bRegionEvalSwitch = FParse::Param(*Params, TEXT("regioneval")); const bool bRegionEvalSwitch = FParse::Param(*Params, TEXT("regioneval"));
int32 RegionEvalFlag = 0; int32 RegionEvalFlag = 0;
const bool bRegionEvalValue = FParse::Value(*Params, TEXT("regioneval="), RegionEvalFlag); const bool bRegionEvalValue = FParse::Value(*Params, TEXT("regioneval="), RegionEvalFlag);
@@ -5,9 +5,12 @@
#include "VoxelCaveMorphology.h" #include "VoxelCaveMorphology.h"
#include "VoxelDensityProfile.h" #include "VoxelDensityProfile.h"
#include "VoxelGenerator.h" #include "VoxelGenerator.h"
#include "VoxelRedefinedDensityField.h"
#include "VoxelStackSampler.h" #include "VoxelStackSampler.h"
#include "HAL/PlatformTLS.h" #include "HAL/PlatformTLS.h"
#include <atomic>
#if PLATFORM_WINDOWS #if PLATFORM_WINDOWS
#include "Windows/AllowWindowsPlatformTypes.h" #include "Windows/AllowWindowsPlatformTypes.h"
THIRD_PARTY_INCLUDES_START THIRD_PARTY_INCLUDES_START
@@ -92,6 +95,14 @@ namespace VoxelForgeRegionEval
int32 LatticeZ = 0; int32 LatticeZ = 0;
TArray<FRegionChunkSample> RequestedSamples; TArray<FRegionChunkSample> RequestedSamples;
}; };
struct FResearchBrick
{
FIntVector Origin = FIntVector::ZeroValue;
FIntVector Size = FIntVector::ZeroValue;
FIntVector Lattice = FIntVector::ZeroValue;
int32 SampleCount = 0;
};
} }
int64 LinearIndex(int32 X, int32 Y, int32 Z, int32 Dimension) int64 LinearIndex(int32 X, int32 Y, int32 Z, int32 Dimension)
@@ -138,6 +149,245 @@ namespace VoxelForgeRegionEval
return Hash; return Hash;
} }
bool EvaluateRedefined(
const UVoxelGenerator& Generator,
FIntVector OriginVoxels,
int32 Dimension,
int32 RequestedWorkerCount,
float DetailScale,
TArray<float>& OutDensities,
FTiming& OutTiming,
bool bEnableBrickSkips)
{
OutTiming = FTiming();
OutDensities.Reset();
if (Dimension <= 0 || Dimension > 1024 || !FMath::IsFinite(DetailScale))
{
return false;
}
const int32 NumWorkers = FMath::Clamp(RequestedWorkerCount, 1, 32);
const int64 TotalSamples = LatticeCount(Dimension);
if (TotalSamples > MAX_int32)
{
return false;
}
const double StartSeconds = FPlatformTime::Seconds();
OutTiming.Workers = NumWorkers;
OutTiming.SampleCount = TotalSamples;
OutDensities.SetNumUninitialized(static_cast<int32>(TotalSamples));
TArray<FResearchBrick> Bricks;
const FIntVector Last = OriginVoxels
+ FIntVector(Dimension - 1, Dimension - 1, Dimension - 1);
const FIntVector FirstBrick(
FMath::FloorToInt(static_cast<float>(OriginVoxels.X) / 8.0f),
FMath::FloorToInt(static_cast<float>(OriginVoxels.Y) / 8.0f),
FMath::FloorToInt(static_cast<float>(OriginVoxels.Z) / 8.0f));
const FIntVector LastBrick(
FMath::FloorToInt(static_cast<float>(Last.X) / 8.0f),
FMath::FloorToInt(static_cast<float>(Last.Y) / 8.0f),
FMath::FloorToInt(static_cast<float>(Last.Z) / 8.0f));
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);
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 RingEnd = OriginVoxels
+ FIntVector(Dimension, Dimension, Dimension);
const FIntVector End(
FMath::Min(BrickEnd.X, RingEnd.X),
FMath::Min(BrickEnd.Y, RingEnd.Y),
FMath::Min(BrickEnd.Z, RingEnd.Z));
Brick.Size = End - Brick.Origin;
Brick.Lattice = Brick.Origin - OriginVoxels;
Brick.SampleCount = Brick.Size.X * Brick.Size.Y * Brick.Size.Z;
}
TArray<double> WorkerWall;
TArray<double> WorkerCpu;
TArray<double> WorkerClassify;
TArray<double> WorkerEvaluate;
TArray<double> WorkerFill;
TArray<uint32> WorkerThreadIds;
TArray<uint64> WorkerEvaluated;
TArray<uint64> WorkerSkippedBricks;
TArray<uint64> WorkerSkippedSamples;
TArray<uint64> WorkerMacroChecks;
TArray<uint64> WorkerMacroCentreEvaluations;
TArray<uint64> WorkerFallbackReasons;
WorkerWall.SetNumZeroed(NumWorkers);
WorkerCpu.SetNumZeroed(NumWorkers);
WorkerClassify.SetNumZeroed(NumWorkers);
WorkerEvaluate.SetNumZeroed(NumWorkers);
WorkerFill.SetNumZeroed(NumWorkers);
WorkerThreadIds.SetNumZeroed(NumWorkers);
WorkerEvaluated.SetNumZeroed(NumWorkers);
WorkerSkippedBricks.SetNumZeroed(NumWorkers);
WorkerSkippedSamples.SetNumZeroed(NumWorkers);
WorkerMacroChecks.SetNumZeroed(NumWorkers);
WorkerMacroCentreEvaluations.SetNumZeroed(NumWorkers);
WorkerFallbackReasons.SetNumZeroed(NumWorkers * 5);
// Surface bricks carry more work than uniform interior bricks. Equal brick counts do
// not balance that work, so claim small contiguous batches dynamically. Each brick
// writes a disjoint output range and still evaluates the same exact field.
std::atomic<int32> NextBrickIndex{0};
constexpr int32 BrickBatchSize = 16;
ParallelFor(NumWorkers, [&](int32 WorkerIndex)
{
WorkerThreadIds[WorkerIndex] = FPlatformTLS::GetCurrentThreadId();
const double WorkerStart = FPlatformTime::Seconds();
double CpuStart = 0.0;
const bool bHaveCpuStart = GetCurrentThreadCpuSeconds(CpuStart);
TArray<float> BrickDetail;
VoxelForgeRedefinedDensityField::FBrickEvaluationContext BrickContext;
for (;;)
{
const int32 BatchBegin = NextBrickIndex.fetch_add(
BrickBatchSize, std::memory_order_relaxed);
if (BatchBegin >= Bricks.Num())
{
break;
}
const int32 BatchEnd = FMath::Min(BatchBegin + BrickBatchSize, Bricks.Num());
for (int32 BrickIndex = BatchBegin; BrickIndex < BatchEnd; ++BrickIndex)
{
const FResearchBrick& Brick = Bricks[BrickIndex];
bool bUniformAir = false;
bool bEvaluatedMacroCentre = false;
VoxelForgeRedefinedDensityField::EBrickFallbackReason FallbackReason =
VoxelForgeRedefinedDensityField::EBrickFallbackReason::UnsupportedState;
++WorkerMacroChecks[WorkerIndex];
const double ClassifyStart = FPlatformTime::Seconds();
const bool bCanSkip = bEnableBrickSkips
&& VoxelForgeRedefinedDensityField::TryClassifyBrick(
Generator, Brick.Origin, Brick.Size, bUniformAir, DetailScale,
&bEvaluatedMacroCentre, &FallbackReason);
WorkerClassify[WorkerIndex] += FPlatformTime::Seconds() - ClassifyStart;
WorkerMacroCentreEvaluations[WorkerIndex] += bEvaluatedMacroCentre ? 1u : 0u;
if (bEnableBrickSkips && !bCanSkip)
{
const int32 ReasonIndex = static_cast<uint8>(FallbackReason);
if (ReasonIndex < 5)
{
++WorkerFallbackReasons[WorkerIndex * 5 + ReasonIndex];
}
}
if (bCanSkip)
{
++WorkerSkippedBricks[WorkerIndex];
WorkerSkippedSamples[WorkerIndex] += static_cast<uint64>(Brick.SampleCount);
const float UniformDensity = bUniformAir ? 64.0f : -64.0f;
const double FillStart = FPlatformTime::Seconds();
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 int64 LatticeIndex = LinearIndex(
Brick.Lattice.X + X, Brick.Lattice.Y + Y,
Brick.Lattice.Z + Z, Dimension);
OutDensities[static_cast<int32>(LatticeIndex)] = UniformDensity;
}
WorkerFill[WorkerIndex] += FPlatformTime::Seconds() - FillStart;
continue;
}
const double EvaluationStart = FPlatformTime::Seconds();
VoxelForgeRedefinedDensityField::BuildDetailBrick(
Generator, Brick.Origin, Brick.Size, DetailScale, BrickDetail,
&BrickContext);
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);
OutDensities[static_cast<int32>(LatticeIndex)] =
VoxelForgeRedefinedDensityField::EvaluateWithDetailValue(
Generator, static_cast<float>(WorldX), static_cast<float>(WorldY),
static_cast<float>(WorldZ),
BrickDetail[(Z * Brick.Size.Y + Y) * Brick.Size.X + X],
nullptr, DetailScale, &BrickContext);
++WorkerEvaluated[WorkerIndex];
}
WorkerEvaluate[WorkerIndex] += FPlatformTime::Seconds() - EvaluationStart;
}
}
WorkerWall[WorkerIndex] = FPlatformTime::Seconds() - WorkerStart;
double CpuEnd = 0.0;
if (bHaveCpuStart && GetCurrentThreadCpuSeconds(CpuEnd))
{
WorkerCpu[WorkerIndex] = FMath::Max(0.0, CpuEnd - CpuStart);
}
else
{
WorkerCpu[WorkerIndex] = WorkerWall[WorkerIndex];
}
}, EParallelForFlags::None);
OutTiming.WallSeconds = FPlatformTime::Seconds() - StartSeconds;
OutTiming.bRegionProgram = true;
OutTiming.BrickCount = static_cast<uint64>(Bricks.Num());
OutTiming.ObservedWorkerThreads = 0;
for (int32 Worker = 0; Worker < NumWorkers; ++Worker)
{
OutTiming.WorkerCpuSeconds += WorkerCpu[Worker];
OutTiming.BrickClassificationSeconds += WorkerClassify[Worker];
OutTiming.DensityEvaluationSeconds += WorkerEvaluate[Worker];
OutTiming.UniformFillSeconds += WorkerFill[Worker];
OutTiming.LongestWorkerWallSeconds = FMath::Max(
OutTiming.LongestWorkerWallSeconds, WorkerWall[Worker]);
OutTiming.EvaluatedSamples += static_cast<int64>(WorkerEvaluated[Worker]);
OutTiming.SkippedBricks += WorkerSkippedBricks[Worker];
OutTiming.SkippedSamples += WorkerSkippedSamples[Worker];
OutTiming.MacroBrickChecks += WorkerMacroChecks[Worker];
OutTiming.MacroCentreEvaluations += WorkerMacroCentreEvaluations[Worker];
OutTiming.UnsupportedBrickProofs += WorkerFallbackReasons[Worker * 5
+ static_cast<uint8>(VoxelForgeRedefinedDensityField::EBrickFallbackReason::UnsupportedState)];
OutTiming.ChunkBoundaryBrickProofs += WorkerFallbackReasons[Worker * 5
+ static_cast<uint8>(VoxelForgeRedefinedDensityField::EBrickFallbackReason::ChunkOrStrateBoundary)];
OutTiming.PassageBoundBrickProofs += WorkerFallbackReasons[Worker * 5
+ static_cast<uint8>(VoxelForgeRedefinedDensityField::EBrickFallbackReason::PassageBounds)];
OutTiming.MixedMacroBrickProofs += WorkerFallbackReasons[Worker * 5
+ static_cast<uint8>(VoxelForgeRedefinedDensityField::EBrickFallbackReason::MixedMacroInterval)];
bool bPreviouslyObserved = false;
for (int32 Previous = 0; Previous < Worker; ++Previous)
{
bPreviouslyObserved |= WorkerThreadIds[Previous] == WorkerThreadIds[Worker];
}
OutTiming.ObservedWorkerThreads += bPreviouslyObserved ? 0 : 1;
}
OutTiming.NanosecondsPerSample = TotalSamples > 0
? OutTiming.WorkerCpuSeconds * 1.0e9 / static_cast<double>(TotalSamples)
: 0.0;
OutTiming.CandidateEvaluationsPerSample = TotalSamples > 0
? static_cast<double>(OutTiming.EvaluatedSamples
+ static_cast<int64>(OutTiming.MacroCentreEvaluations))
/ static_cast<double>(TotalSamples)
: 0.0;
for (const float Density : OutDensities)
{
OutTiming.NonFiniteSamples += FMath::IsFinite(Density) ? 0u : 1u;
}
OutTiming.QuantizedHash = HashQuantized(OutDensities);
OutTiming.RawFloatHash = HashRawFloats(OutDensities);
return OutTiming.EvaluatedSamples + static_cast<int64>(OutTiming.SkippedSamples)
== TotalSamples && OutTiming.NonFiniteSamples == 0;
}
bool Evaluate( bool Evaluate(
const UVoxelGenerator& Generator, const UVoxelGenerator& Generator,
FIntVector OriginVoxels, FIntVector OriginVoxels,
@@ -26,6 +26,9 @@ namespace VoxelForgeRegionEval
double GeneralPathEntriesPerSample = 0.0; double GeneralPathEntriesPerSample = 0.0;
double WallSeconds = 0.0; double WallSeconds = 0.0;
double WorkerCpuSeconds = 0.0; double WorkerCpuSeconds = 0.0;
double BrickClassificationSeconds = 0.0;
double DensityEvaluationSeconds = 0.0;
double UniformFillSeconds = 0.0;
double NanosecondsPerSample = 0.0; double NanosecondsPerSample = 0.0;
bool bWorkerCpuFromOsThreadTimes = false; bool bWorkerCpuFromOsThreadTimes = false;
bool bRegionProgram = false; bool bRegionProgram = false;
@@ -34,6 +37,16 @@ namespace VoxelForgeRegionEval
uint64 RawFloatHash = 0; uint64 RawFloatHash = 0;
uint64 SaturatedSamples = 0; uint64 SaturatedSamples = 0;
uint64 NonFiniteSamples = 0; uint64 NonFiniteSamples = 0;
uint64 BrickCount = 0;
uint64 SkippedBricks = 0;
uint64 SkippedSamples = 0;
uint64 MacroBrickChecks = 0;
uint64 MacroCentreEvaluations = 0;
uint64 UnsupportedBrickProofs = 0;
uint64 ChunkBoundaryBrickProofs = 0;
uint64 PassageBoundBrickProofs = 0;
uint64 MixedMacroBrickProofs = 0;
double LongestWorkerWallSeconds = 0.0;
uint64 SdfCacheBuilds = 0; uint64 SdfCacheBuilds = 0;
uint64 ActiveOpListBuilds = 0; uint64 ActiveOpListBuilds = 0;
uint64 BroadPhaseIndexBuilds = 0; uint64 BroadPhaseIndexBuilds = 0;
@@ -122,6 +135,17 @@ namespace VoxelForgeRegionEval
int32 RequestedWorkerCount = WorkerCount, int32 RequestedWorkerCount = WorkerCount,
bool bCollectWholeRunCounters = false); bool bCollectWholeRunCounters = false);
/** Full-resolution step 2d field pass with sound world-aligned 8^3 brick proofs. */
bool EvaluateRedefined(
const UVoxelGenerator& Generator,
FIntVector OriginVoxels,
int32 Dimension,
int32 RequestedWorkerCount,
float DetailScale,
TArray<float>& OutDensities,
FTiming& OutTiming,
bool bEnableBrickSkips = true);
/** Count exact room/tunnel/core/passage candidate visits with instrumentation enabled. */ /** Count exact room/tunnel/core/passage candidate visits with instrumentation enabled. */
FCandidateVisits CountCandidates( FCandidateVisits CountCandidates(
const UVoxelGenerator& Generator, const UVoxelGenerator& Generator,
+29 -3
View File
@@ -1,6 +1,6 @@
[CmdletBinding()] [CmdletBinding()]
param( param(
[ValidateSet('canonical', 'owner', 'probe', 'perf', 'visual-edit', 'visual-lod', 'visual-gap-walk', 'visual-gap-inject', 'dig-motion', 'tests', 'surface-fall', 'crossing', 'gate-stress', 'parity', 'horizon', 'horizon-render', 'determinism', 'symbolize-stacks', 'region-eval')] [ValidateSet('canonical', 'owner', 'probe', 'perf', 'visual-edit', 'visual-lod', 'visual-gap-walk', 'visual-gap-inject', 'dig-motion', 'tests', 'surface-fall', 'crossing', 'gate-stress', 'parity', 'horizon', 'horizon-render', 'determinism', 'symbolize-stacks', 'region-eval', 'step2d')]
[string]$Scenario = 'canonical', [string]$Scenario = 'canonical',
[switch]$Build, [switch]$Build,
[switch]$EnableRender, [switch]$EnableRender,
@@ -575,7 +575,7 @@ function Sync-OwnerAssets {
$null = Copy-Item -LiteralPath $source -Destination $target -Force -PassThru $null = Copy-Item -LiteralPath $source -Destination $target -Force -PassThru
[void]$rows.Add([ordered]@{ [void]$rows.Add([ordered]@{
name = $assetName name = $assetName
used_by_scenario = $Scenario -in @('owner', 'probe', 'parity', 'tests', 'surface-fall', 'crossing', 'gate-stress', 'visual-edit', 'visual-lod', 'visual-gap-walk', 'visual-gap-inject', 'dig-motion', 'horizon', 'horizon-render', 'determinism', 'region-eval') used_by_scenario = $Scenario -in @('owner', 'probe', 'parity', 'tests', 'surface-fall', 'crossing', 'gate-stress', 'visual-edit', 'visual-lod', 'visual-gap-walk', 'visual-gap-inject', 'dig-motion', 'horizon', 'horizon-render', 'determinism', 'region-eval', 'step2d')
source_path = $source source_path = $source
source_sha256 = Get-Sha256 $source source_sha256 = Get-Sha256 $source
staged_path = $target staged_path = $target
@@ -623,7 +623,7 @@ function Get-AssetEvidence {
$target = Join-Path $HostRoot "Content\VoxelForge\$assetName.uasset" $target = Join-Path $HostRoot "Content\VoxelForge\$assetName.uasset"
[void]$rows.Add([ordered]@{ [void]$rows.Add([ordered]@{
name = $assetName name = $assetName
used_by_scenario = $Scenario -in @('owner', 'probe', 'parity', 'tests', 'surface-fall', 'crossing', 'gate-stress', 'visual-edit', 'visual-lod', 'visual-gap-walk', 'visual-gap-inject', 'dig-motion', 'horizon', 'horizon-render', 'determinism', 'region-eval') used_by_scenario = $Scenario -in @('owner', 'probe', 'parity', 'tests', 'surface-fall', 'crossing', 'gate-stress', 'visual-edit', 'visual-lod', 'visual-gap-walk', 'visual-gap-inject', 'dig-motion', 'horizon', 'horizon-render', 'determinism', 'region-eval', 'step2d')
source_path = $source source_path = $source
source_sha256 = Get-Sha256 $source source_sha256 = Get-Sha256 $source
staged_path = $target staged_path = $target
@@ -1473,6 +1473,32 @@ try {
} }
} }
} }
'step2d' {
$step2dLog = Join-Path $RunRoot 'Logs\step2d.log'
$args = New-CommonArguments 'commandlet' $step2dLog (Merge-Cvars $baseCvars)
$args += New-CommandletArguments $commandletOut 'walk,export' ($Assets -eq 'owner') $false 128
$researchOut = Join-Path $PluginRoot "Saved\Research\step2d\$Label"
$args += @('-step2d', '-regionworkers=6', ('-researchout="{0}"' -f $researchOut))
$commandletLedger = Invoke-UnrealLaunch 'step2d-commandlet' $args `
$step2dLog (Join-Path $RunRoot 'Logs\step2d.stdout.log') `
(Join-Path $RunRoot 'Logs\step2d.stderr.log')
$step2dReportPath = Join-Path $researchOut 'step2d_report.json'
$step2dReport = Read-JsonFile $step2dReportPath
if ($null -eq $step2dReport) {
$CommandletMetrics = [ordered]@{ status = 'missing'; output = $researchOut; report = $step2dReportPath }
Add-Failure "step2d did not produce $step2dReportPath"
} else {
$CommandletMetrics = [ordered]@{
status = if ((Get-JsonValue $step2dReport 'pass' $false) -and $commandletLedger.status -eq 'passed') { 'passed' } else { 'failed' }
output = $researchOut
report = $step2dReportPath
result = $step2dReport
}
if (-not (Get-JsonValue $step2dReport 'pass' $false)) {
Add-Failure 'step2d speed, detail, boundary, or determinism gate failed'
}
}
}
'symbolize-stacks' { 'symbolize-stacks' {
if (([string]::IsNullOrWhiteSpace($StackSamplesPath)) -or -not (Test-Path -LiteralPath $StackSamplesPath -PathType Leaf)) { if (([string]::IsNullOrWhiteSpace($StackSamplesPath)) -or -not (Test-Path -LiteralPath $StackSamplesPath -PathType Leaf)) {
throw "-StackSamplesPath must name an existing raw sampler CSV." throw "-StackSamplesPath must name an existing raw sampler CSV."