perf(generation): exact worm block skip with a proven 3D Perlin bound -- measured NEUTRAL
The sampler put the worm carve's first noise call (VoxelGenerator.cpp:3985, FusedWorm) at 19.4% of LOD0. The worm only carves where N1 + N2 < T with N2 >= 0, so where N1 >= T it contributes nothing, bit for bit. A block proven to have N1 >= T everywhere can skip both noise calls exactly. - The Lipschitz bound for this Perlin3D implementation: L = (15/4) * sqrt(3) * 1.25 = 8.118988160, in scaled-N1 units, with a 1e-3 float-rounding margin. Adversarial sampling found a worst ratio of 3.187 (0.39 L). The existing 2D floor-relief bound (8.5) is a component bound of a different field and was confirmed not to transfer. - A soundness test that can fail (VoxelForgeWormBlockSkipTest): 6,476 proved blocks, 355,945 exact lattice samples, 0 violations. A deliberately halved L proves a bad block; the full L rejects it. - voxel.WormBlockSkip, default 1. Field exact: export OBJ byte-identical (b3e5f4c3..9b377), 841/841 keyed tiles identical to NextHeadStaticTrace_20260913, capability 20,830 / 10,909. RESULT, stated plainly: no measurable speed gain. The skip rate is 4.07% of worm-eligible LOD0 samples and 0% at LOD1-4, because the bound is ~2.5x looser than the worst observed ratio at the block size used. Paired on-vs-off, two interleaved runs each: static worker +2.15%, generation p50 +1.8%; moving worker -1.42%, request p50 -2.5%. Within noise. The next round turns the default OFF. The proven bound and its test are kept for a tighter skip later (smaller or hierarchical blocks, or a per-cell bound). Run ledger (from Codex's report): the first build failed on an ambiguous test overload and was fixed; two timing runs are void because the orchestrator stopped the session so the owner could use the PC; the eight valid timing runs, the sampler run and the diagnostics runs completed. No new crash folders. PERF-SAMPLED.md now lists all eight earlier-round UECC crash records. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
This commit is contained in:
1 parent
5489b5e311
commit
26b34765d5
9 files changed
+987
-24
No files matched your search
@@ -580,6 +580,16 @@ driven by `EditorBrush*` props.
|
|||||||
no loads AND no LOD mismatches outstanding.
|
no loads AND no LOD mismatches outstanding.
|
||||||
- **SDF cache** (`GetDensityWithParams`): search-BOX validity, not chunk-key — gradient ±1
|
- **SDF cache** (`GetDensityWithParams`): search-BOX validity, not chunk-key — gradient ±1
|
||||||
sampling must not thrash the (expensive) rebuild.
|
sampling must not thrash the (expensive) rebuild.
|
||||||
|
- **Worm block skip** (`VF_TryGetWormBlockSkip`): inside the unchanged `WormNetworkRange` mask,
|
||||||
|
a worker-local direct-indexed cache covers the mesher's 4×4×4 lattice blocks (including the
|
||||||
|
normal halo). It samples N1 once at the transformed block centre and proves the entire block
|
||||||
|
clears the threshold with the actual 3D hash-gradient field bound
|
||||||
|
`L = (15/4) * sqrt(3) * VOXEL_NOISE_SCALE = 8.118988160`, plus a `1e-3` scaled-output
|
||||||
|
rounding margin. The radius is measured after the exact `WormFrequency`, `VerticalScale`, and
|
||||||
|
`WormHorizontalBias` transform, so anisotropic Z is included. The cache is thread-local and
|
||||||
|
keyed by generator owner, seed, params/layout fingerprint, tile origin, step, lattice size, and
|
||||||
|
worm parameters; malformed/non-lattice/failed proofs fall through. A proof only suppresses N1
|
||||||
|
and N2 because N2 is nonnegative; it never changes `NetworkMask` or density arithmetic.
|
||||||
- **Operator channel metadata** (`FVoxelOpStack`): `ChannelReads`, `ChannelWrites`, and
|
- **Operator channel metadata** (`FVoxelOpStack`): `ChannelReads`, `ChannelWrites`, and
|
||||||
`IsAdditive` are called only when an operator is added to a stack and their values are cached in
|
`IsAdditive` are called only when an operator is added to a stack and their values are cached in
|
||||||
the stack entry. `ValidateChannelOrder` is assembly/diagnostic-only; no declaration virtual may
|
the stack entry. `ValidateChannelOrder` is assembly/diagnostic-only; no declaration virtual may
|
||||||
|
|||||||
+6
-2
@@ -341,7 +341,7 @@ This is **where terrain shape lives.**
|
|||||||
| `ApplyPassageCarving` (static) | 197 | Punches passages/elevator through the seal. |
|
| `ApplyPassageCarving` (static) | 197 | Punches passages/elevator through the seal. |
|
||||||
| `InitializeSettings` | 211 | Copies the effective seed, spine radius, and global XY world-bound settings from `UVoxelSettings` (the season owns all three when assigned); radius 0 preserves the unbounded field. |
|
| `InitializeSettings` | 211 | Copies the effective seed, spine radius, and global XY world-bound settings from `UVoxelSettings` (the season owns all three when assigned); radius 0 preserves the unbounded field. |
|
||||||
| **`GetDensityAt`** | 218 | **Entry point.** Picks strate + generator type, materialises a cooked-season recipe on the existing per-chunk refetch when present, dispatches, then adds disturbances/diff. Its `CP_*` state is keyed by `(DensityCacheOwnerId, ChunkCoord, LayoutVersion)`; workers copy immutable recipe/vector data and never read editor composer state. |
|
| **`GetDensityAt`** | 218 | **Entry point.** Picks strate + generator type, materialises a cooked-season recipe on the existing per-chunk refetch when present, dispatches, then adds disturbances/diff. Its `CP_*` state is keyed by `(DensityCacheOwnerId, ChunkCoord, LayoutVersion)`; workers copy immutable recipe/vector data and never read editor composer state. |
|
||||||
| **`GetDensityWithParams`** | 277 | TunnelNetwork pipeline (~1000 lines). See §4. ⚠️ Takes **required** `ParamsFingerprint` + `LayoutVersion` since the AUDIT §C2 fix (2026-07-28) — they go into the SDF cache key so a chunk can no longer be evaluated against a neighbour's rooms. Callers compute the CRC **once per chunk** (`CP_TunnelFP`), never per voxel. |
|
| **`GetDensityWithParams`** | 277 | TunnelNetwork pipeline (~1000 lines). See §4. ⚠️ Takes **required** `ParamsFingerprint` + `LayoutVersion` since the AUDIT §C2 fix (2026-07-28) — they go into the SDF cache key so a chunk can no longer be evaluated against a neighbour's rooms. Callers compute the CRC **once per chunk** (`CP_TunnelFP`), never per voxel. The optional `voxel.WormBlockSkip` path uses `VF_TryGetWormBlockSkip` + a worker-local 4³ lattice-block cache to prove `|N1| >= WormThreshold` and remove both worm noise calls only; it never changes `NetworkMask` or the fallback field path. |
|
||||||
| **`GetSlabDensity`** | 1306 | FlatPlain/CrystalChamber pipeline. See §4.2. |
|
| **`GetSlabDensity`** | 1306 | FlatPlain/CrystalChamber pipeline. See §4.2. |
|
||||||
| `SampleSurfaceStructuralZ` | — | **F20:** the RAW SurfaceWorld heightfield (continents+mountains+detail), BEFORE any terrain op; returns terrain Z + relief M. Cliff re-samples it at an XY offset for a cheap analytic slope. |
|
| `SampleSurfaceStructuralZ` | — | **F20:** the RAW SurfaceWorld heightfield (continents+mountains+detail), BEFORE any terrain op; returns terrain Z + relief M. Cliff re-samples it at an XY offset for a cheap analytic slope. |
|
||||||
| `ComputeSurfaceTerrainZ` / `GetSurfaceDensity` | — | SurfaceWorld heightfield → terrain Z, then density; biome **output-blend** lerps dominant/neighbour heights (`ParamsD`/`ParamsN`/weight). **F20 surface ops** (`FSurfaceGenerationParams`, biome-selected + slope/relief-conditioned, all default off): Cliff (slope-gated STEEPENING — push height from local mean where steep ⇒ sheer walls; 4 structural resamples only when on), Terrace (relief-gated + `TerraceHardness`), LayerLines (sedimentary shelves) — pure per-column height REMAPS applied here so the single height oracle stays consistent (MC/sheets/ClassifyTile/deco/BP bridge). **Phase 2 OVERHANG** (volumetric — real jutting shelves): in `SurfaceDensityFromColumn`, for AIR voxels in a window `(TerrainZ, TerrainZ+OverhangHeight]` above a steep slope, the heightfield is re-sampled UPHILL (toward the cliff) by a reach that GROWS with height (tiny low ⇒ air over the void, full high ⇒ borrows the far cliff rock) and unioned in ⇒ a shelf attached to the cliff, tapering out over the void with air beneath (the sketch). Per-column `OverhangAmp`(=strength·slope-gate) + unit uphill `(DirX,DirY)` resolved once in `ComputeSurfaceColumn` (gradient sampled at the REACH scale so a spot over the void can see the cliff), cached on `FSurfaceColumn`. Genuine 3D (per-voxel structural re-eval, gated to steep overhang columns). Off ⇒ byte-identical. The field-preserving LOD0 pass removes only needless temporaries/default construction; every output remains bit-identical. §8.14. |
|
| `ComputeSurfaceTerrainZ` / `GetSurfaceDensity` | — | SurfaceWorld heightfield → terrain Z, then density; biome **output-blend** lerps dominant/neighbour heights (`ParamsD`/`ParamsN`/weight). **F20 surface ops** (`FSurfaceGenerationParams`, biome-selected + slope/relief-conditioned, all default off): Cliff (slope-gated STEEPENING — push height from local mean where steep ⇒ sheer walls; 4 structural resamples only when on), Terrace (relief-gated + `TerraceHardness`), LayerLines (sedimentary shelves) — pure per-column height REMAPS applied here so the single height oracle stays consistent (MC/sheets/ClassifyTile/deco/BP bridge). **Phase 2 OVERHANG** (volumetric — real jutting shelves): in `SurfaceDensityFromColumn`, for AIR voxels in a window `(TerrainZ, TerrainZ+OverhangHeight]` above a steep slope, the heightfield is re-sampled UPHILL (toward the cliff) by a reach that GROWS with height (tiny low ⇒ air over the void, full high ⇒ borrows the far cliff rock) and unioned in ⇒ a shelf attached to the cliff, tapering out over the void with air beneath (the sketch). Per-column `OverhangAmp`(=strength·slope-gate) + unit uphill `(DirX,DirY)` resolved once in `ComputeSurfaceColumn` (gradient sampled at the REACH scale so a spot over the void can see the cliff), cached on `FSurfaceColumn`. Genuine 3D (per-voxel structural re-eval, gated to steep overhang columns). Off ⇒ byte-identical. The field-preserving LOD0 pass removes only needless temporaries/default construction; every output remains bit-identical. §8.14. |
|
||||||
@@ -359,7 +359,11 @@ This is **where terrain shape lives.**
|
|||||||
> memoizes the strate index per (chunkZ, `GetLayoutVersion()`); `thread_local` per-cell lattice
|
> memoizes the strate index per (chunkZ, `GetLayoutVersion()`); `thread_local` per-cell lattice
|
||||||
> bakes cover slab columns (`GetSlabDensity` step 4), maze open edges, vertical shafts +
|
> bakes cover slab columns (`GetSlabDensity` step 4), maze open edges, vertical shafts +
|
||||||
> cross-connectors, floating-island constants, and the disturbance chasms/bridges/ridges; worm
|
> cross-connectors, floating-island constants, and the disturbance chasms/bridges/ridges; worm
|
||||||
> tunnels short-circuit the 2nd Perlin when N1 ≥ WormThreshold (N2 ≥ 0 ⇒ can't carve). Room
|
> tunnels short-circuit the 2nd Perlin when N1 ≥ WormThreshold (N2 ≥ 0 ⇒ can't carve). With
|
||||||
|
> `voxel.WormBlockSkip=1`, a worker-local cache proves whole 4³ lattice blocks only when the
|
||||||
|
> actual 3D Perlin bound (`L = 8.118988160`, including `VOXEL_NOISE_SCALE`) plus a `1e-3`
|
||||||
|
> output-rounding margin clears the threshold; failed proofs use the original per-sample path.
|
||||||
|
> Room
|
||||||
> shapes are pre-baked in `FCachedRoom` (§3.7). **T2.b:** per-voxel fractal call sites take their
|
> shapes are pre-baked in `FCachedRoom` (§3.7). **T2.b:** per-voxel fractal call sites take their
|
||||||
> octave count through `VoxelGenLOD::Eff(N)` (VoxelGenerator.h) — a `thread_local` bias set per
|
> octave count through `VoxelGenLOD::Eff(N)` (VoxelGenerator.h) — a `thread_local` bias set per
|
||||||
> tile by the mesher drops tail octaves on coarse tiles (opt-in `LODOctaveDrop`, default 0 = off);
|
> tile by the mesher drops tail octaves on coarse tiles (opt-in `LODOctaveDrop`, default 0 = off);
|
||||||
|
|||||||
+172
@@ -928,3 +928,175 @@ Smaller unresolved groups map to `UnrealEditor-Engine.dll + RVA 0x500947` (gener
|
|||||||
|
|
||||||
This closes the actionable part of candidate 5: the previous plugin constructor was removed, while
|
This closes the actionable part of candidate 5: the previous plugin constructor was removed, while
|
||||||
the remaining unknown is an engine-symbolisation limitation. No commit, push, or stash was performed.
|
the remaining unknown is an engine-symbolisation limitation. No commit, push, or stash was performed.
|
||||||
|
|
||||||
|
## Worm block skip round (2026-09-14)
|
||||||
|
|
||||||
|
### Proof and gates
|
||||||
|
|
||||||
|
The worm skip is enabled by default through `voxel.WormBlockSkip=1`. It is fail-closed and only
|
||||||
|
removes the two worm noise calls after the unchanged `WormNetworkRange` mask has passed. The
|
||||||
|
worker-local cache is keyed by generator owner, seed, params/layout fingerprint, tile origin,
|
||||||
|
step, lattice size, and worm parameters; it is never shared across tiles or workers.
|
||||||
|
|
||||||
|
For this implementation's actual `GradDot` set, the unique gradients are
|
||||||
|
`(±1,±1,0)`, `(±1,0,±1)`, and `(0,±1,±1)`. The exact finite-support partial-derivative relaxation
|
||||||
|
with `Fade'(t)=30t²(1-t)²` has supremum `15/4`, attained at `(0.5,0.5,0.5)`. Therefore the
|
||||||
|
noise-space vector bound is
|
||||||
|
|
||||||
|
`L = (15/4) * sqrt(3) * VOXEL_NOISE_SCALE = 8.118988160` (`VOXEL_NOISE_SCALE=1.25`).
|
||||||
|
|
||||||
|
The existing 2D floor constant was proven differently and does not transfer: its comment uses
|
||||||
|
the loose component bound `[-1,1]`, four-edge interpolation difference bound, and
|
||||||
|
`max SmoothCurve' = 1.875`, giving `1 + 4*1.875 = 8.5` per partial. The worm derives its
|
||||||
|
bound from the actual 3D gradient support. The block radius is measured after the exact X/Y/Z
|
||||||
|
noise transform, including `WormFrequency`, `VerticalScale`, and `WormHorizontalBias`; `Abs` is
|
||||||
|
1-Lipschitz and the positive noise scale is included in `L`. A `1e-3` scaled-output margin is
|
||||||
|
subtracted before the threshold comparison. It is deliberately much larger than the accumulated
|
||||||
|
float rounding at the center/radius operations and is covered by the soundness test.
|
||||||
|
|
||||||
|
The adversarial attack used dense corner/face sampling plus random pairs: 17,365,280 pairs,
|
||||||
|
worst scaled ratio `3.187330411`, or `0.392577295*L`. The fail-capable automation test also
|
||||||
|
audited 6,476 proved blocks over 355,945 exact lattice samples with zero violations. A deliberately
|
||||||
|
halved `L` proves the negative-control block while the exact point has `N1=0.011259466 < 0.015`
|
||||||
|
and the full `L` rejects it.
|
||||||
|
|
||||||
|
Default-on export is byte-identical to the baseline: geometry hash
|
||||||
|
`B3E5F4C398DD0547C6C55F415872058246D4A292C2F0D636EE1CD0829DE9B377`, 82,273 vertices,
|
||||||
|
153,346 triangles. The static trace is 841/841 keyed tiles with zero field/empty/triangle
|
||||||
|
differences. Capability remains 20,830 / 10,909.
|
||||||
|
|
||||||
|
### Clean game A/B
|
||||||
|
|
||||||
|
Sampler off, diagnostics off, `voxel.OuterClassifierMode=0`, interleaved static and moving runs.
|
||||||
|
Static runs had 343 LOD0 ready samples; moving runs had 1,274. All valid runs had identical
|
||||||
|
applied geometry totals, zero validation calls, zero obsolete worker tasks, and verified the loaded
|
||||||
|
plugin module. The clean timings below used the staged runtime before the final counter-only
|
||||||
|
diagnostic-accounting rebuild (`81F52389A78543E41EEC8E3E40DEF8F2634A79EB83E2A933FF35EE42CD611E12`).
|
||||||
|
That patch is behind diagnostics, so it does not change this clean path. The final staged runtime
|
||||||
|
used by the last diagnostic run is `E72E140100B3D258F28E9DBA19CD95D379D98794DA49D24C708FDD01B5B5FE08`.
|
||||||
|
|
||||||
|
| run | switch | request p50/p95 (s) | generation p50/p95 (s) | worker s |
|
||||||
|
|---|---|---:|---:|---:|
|
||||||
|
| WormSkipGameOffStaticA_20260914 | off | 0.087151 / 0.150116 | 0.063115 / 0.104426 | 33.308914 |
|
||||||
|
| WormSkipGameOnStaticA_Resume_20260914 | on | 0.092174 / 0.154458 | 0.065730 / 0.110507 | 34.980771 |
|
||||||
|
| WormSkipGameOffMovingA_20260914 | off | 0.066421 / 0.138179 | 0.059918 / 0.108181 | 99.193222 |
|
||||||
|
| WormSkipGameOnMovingA_20260914 | on | 0.064163 / 0.135519 | 0.059273 / 0.106991 | 97.624292 |
|
||||||
|
| WormSkipGameOffStaticB_20260914 | off | 0.090372 / 0.160169 | 0.064016 / 0.109362 | 34.116813 |
|
||||||
|
| WormSkipGameOnStaticB_Resume_20260914 | on | 0.091922 / 0.154718 | 0.063731 / 0.112871 | 33.892686 |
|
||||||
|
| WormSkipGameOffMovingB_Resume_20260914 | off | 0.066659 / 0.142168 | 0.061916 / 0.110043 | 100.995082 |
|
||||||
|
| WormSkipGameOnMovingB_Resume_20260914 | on | 0.065557 / 0.141592 | 0.060515 / 0.110629 | 99.716139 |
|
||||||
|
|
||||||
|
Paired means, on relative to off:
|
||||||
|
|
||||||
|
| mode | request p50/p95 | generation p50/p95 | worker s | on vs off worker |
|
||||||
|
|---|---:|---:|---:|---:|
|
||||||
|
| static | off 0.088762 / 0.155143; on 0.092048 / 0.154588 | off 0.063566 / 0.106894; on 0.064731 / 0.111689 | 33.712864 → 34.436729 | +2.1471% |
|
||||||
|
| moving | off 0.066540 / 0.140174; on 0.064860 / 0.138556 | off 0.060917 / 0.109112; on 0.059894 / 0.108810 | 100.094152 → 98.670216 | −1.4226% |
|
||||||
|
|
||||||
|
The two-run result is mixed: moving improves modestly, while static is within ordinary run noise
|
||||||
|
and is slightly worse on worker sum. The switch remains default-on because the field proof is exact;
|
||||||
|
the timing result is not presented as a universal speed claim.
|
||||||
|
|
||||||
|
### Skip rate
|
||||||
|
|
||||||
|
The final diagnostics-enabled moving run used the final DLL and emitted 2,360 tile profiles. The
|
||||||
|
rate is `worm_block_skipped / worm_eligible`, not a fraction of all density samples:
|
||||||
|
|
||||||
|
| LOD | worm-eligible samples | successful block proofs | skipped eligible samples | skip rate |
|
||||||
|
|---:|---:|---:|---:|---:|
|
||||||
|
| 0 | 12,381,885 | 9,541 | 503,696 | 4.068007% |
|
||||||
|
| 1 | 4,821,000 | 0 | 0 | 0.000000% |
|
||||||
|
| 2 | 188,063 | 0 | 0 | 0.000000% |
|
||||||
|
| 3 | 58,276 | 0 | 0 | 0.000000% |
|
||||||
|
| 4 | 25,253 | 0 | 0 | 0.000000% |
|
||||||
|
|
||||||
|
### Sampler-on moving profile
|
||||||
|
|
||||||
|
`WormSkipGameOnMovingSampler_20260914` retained 96,649 samples with zero drops and zero capture
|
||||||
|
failures: 61,869 LOD0 and 34,780 LOD1+. It ran for 24.972305 s; resolved-frame fraction was
|
||||||
|
52.9174%, unknown-frame fraction 47.0826%, wait-like fraction 0.1011%, and resolved-leaf
|
||||||
|
fraction 95.3388%.
|
||||||
|
|
||||||
|
LOD0 exclusive top 20:
|
||||||
|
|
||||||
|
| # | samples | % | leaf |
|
||||||
|
|---:|---:|---:|---|
|
||||||
|
| 1 | 12472 | 20.1587 | `GetDensityWithParams` — `VoxelGenerator.cpp:3128` |
|
||||||
|
| 2 | 3137 | 5.0704 | `GetDensityAt` — `VoxelGenerator.cpp:2994` |
|
||||||
|
| 3 | 2853 | 4.6114 | unknown |
|
||||||
|
| 4 | 2540 | 4.1054 | `Sqrt` |
|
||||||
|
| 5 | 2238 | 3.6173 | `VoxelSDF::SmoothMin` — `VoxelCaveMorphology.h:170` |
|
||||||
|
| 6 | 1736 | 2.8059 | `VF_ProjectNativePassageFloorUncached` — `VoxelStrateManager.cpp:561` |
|
||||||
|
| 7 | 1704 | 2.7542 | `FChunkSDFSpatialIndex::GetRange` — `VoxelCaveMorphology.cpp:1356` |
|
||||||
|
| 8 | 1434 | 2.3178 | `VF_ProjectTunnelSegmentXY` — `VoxelCaveMorphology.cpp:970` |
|
||||||
|
| 9 | 1244 | 2.0107 | `ProjectWalkableTunnelFloor` — `VoxelPassageGeometry.h:338` |
|
||||||
|
| 10 | 1229 | 1.9865 | `VF_EvaluateSweptTunnelChain` — `VoxelCaveMorphology.cpp:1128` |
|
||||||
|
| 11 | 1151 | 1.8604 | `EvaluateTunnelCoreWorld` lambda — `VoxelCaveMorphology.cpp:7635` |
|
||||||
|
| 12 | 1009 | 1.6309 | swept-chain lambda — `VoxelCaveMorphology.cpp:1074` |
|
||||||
|
| 13 | 938 | 1.5161 | `EvaluateSDFCached` lambda — `VoxelCaveMorphology.cpp:7526` |
|
||||||
|
| 14 | 819 | 1.3238 | `IsFiniteFast` — `VoxelTypes.h:30` |
|
||||||
|
| 15 | 769 | 1.2429 | `IsFinite` — `VoxelTypes.h:47` |
|
||||||
|
| 16 | 764 | 1.2349 | `EvaluateModifierSDF` — `VoxelStrateManager.cpp:2451` |
|
||||||
|
| 17 | 748 | 1.2090 | `VF_EvaluatePassageLandingSDF` — `VoxelCaveMorphology.cpp:5673` |
|
||||||
|
| 18 | 739 | 1.1945 | `GenerateMesh` — `VoxelMarchingCubesMesher.cpp:578` |
|
||||||
|
| 19 | 724 | 1.1702 | `FScopedTimer::End` — `VoxelDensityProfile.h:435` |
|
||||||
|
| 20 | 700 | 1.1314 | `VF_DistanceSquaredToAabb` — `VoxelCaveMorphology.cpp:74` |
|
||||||
|
|
||||||
|
LOD0 inclusive top 20:
|
||||||
|
|
||||||
|
| # | samples | % | inclusive function |
|
||||||
|
|---:|---:|---:|---|
|
||||||
|
| 1 | 61869 | 100.0000 | `Invoke` |
|
||||||
|
| 2 | 61869 | 100.0000 | `LowLevelTasks::FTask::Init` lambda |
|
||||||
|
| 3 | 61869 | 100.0000 | `LowLevelTasks::TTaskDelegate::Call` |
|
||||||
|
| 4 | 61869 | 100.0000 | `UE::Tasks::Private::FTaskBase::Init` lambda |
|
||||||
|
| 5 | 61869 | 100.0000 | `LowLevelTasks::TTaskDelegate::CallAndMove` |
|
||||||
|
| 6 | 61869 | 100.0000 | `UE::Tasks::Private::FTaskBase::TryExecuteTask` |
|
||||||
|
| 7 | 61869 | 100.0000 | `TExecutableTaskBase::ExecuteTask` |
|
||||||
|
| 8 | 61869 | 100.0000 | `AVoxelWorld::LoadTile` lambda — `VoxelWorld.cpp:3782` |
|
||||||
|
| 9 | 61869 | 100.0000 | `AVoxelWorld::GenerateTileResult` — `VoxelWorld.cpp:4142` |
|
||||||
|
| 10 | 61774 | 99.8464 | `GenerateMesh` — `VoxelMarchingCubesMesher.cpp:578` |
|
||||||
|
| 11 | 60310 | 97.4802 | `GetDensityAt` — `VoxelGenerator.cpp:2606` |
|
||||||
|
| 12 | 38073 | 61.5381 | `GetDensityWithParams` — `VoxelGenerator.cpp:3426` |
|
||||||
|
| 13 | 15666 | 25.3212 | `VF_ForEachSpatialCandidate` — `VoxelCaveMorphology.cpp:1570` |
|
||||||
|
| 14 | 9775 | 15.7995 | `EvaluateSDFCached` — `VoxelCaveMorphology.cpp:7484` |
|
||||||
|
| 15 | 7846 | 12.6816 | `VF_EvaluateSweptTunnel` — `VoxelCaveMorphology.cpp:1167` |
|
||||||
|
| 16 | 7460 | 12.0577 | `VF_EvaluateSweptTunnelChain` — `VoxelCaveMorphology.cpp:1128` |
|
||||||
|
| 17 | 7276 | 11.7603 | `ApplyPassageCarvingOnly` — `VoxelStrateManager.cpp:2596` |
|
||||||
|
| 18 | 7209 | 11.6520 | `EvaluateTunnelCoreWorld` — `VoxelCaveMorphology.cpp:7732` |
|
||||||
|
| 19 | 7128 | 11.5211 | `VF_ApplyPassageCarving` — `VoxelDensityPrimitives.h:75` |
|
||||||
|
| 20 | 7106 | 11.4856 | `EvaluateModifierSDF` — `VoxelStrateManager.cpp:2538` |
|
||||||
|
|
||||||
|
### Run ledger
|
||||||
|
|
||||||
|
Every worm-round launch is listed here, including invalidated launches and the compile retry:
|
||||||
|
|
||||||
|
| launch | outcome |
|
||||||
|
|---|---|
|
||||||
|
| Initial staged build | Failed compiling `VoxelForgeWormBlockSkipTest.cpp` because of an ambiguous `FMath::IsNearlyEqual` overload; fixed by an explicit cast. No Unreal process was launched. |
|
||||||
|
| Rebuilt staged plugin | Succeeded; final build log reports `Result: Succeeded`. |
|
||||||
|
| `VoxelForge.Determinism.WormBlockSkipSoundness` | Passed: 6,476 proofs / 355,945 lattice samples / 0 violations; halved-L control fails as intended. |
|
||||||
|
| `WormSkipFieldOn_20260914` | Passed; export byte-identical and loaded module verified. |
|
||||||
|
| `WormSkipStaticTrace_20260914` | Data passed 841/841; harness intentionally stopped the process after `.done` (`ExitCode=-1`), not a crash. |
|
||||||
|
| `WormSkipCapability_20260914` | Passed; capability 20,830 / 10,909. |
|
||||||
|
| `WormSkipGameOffStaticA_20260914` | Completed; valid off A. |
|
||||||
|
| `WormSkipGameOnStaticA_20260914` | Launched before the stop; void by owner instruction and excluded, even though a complete artifact exists. |
|
||||||
|
| `WormSkipGameOffMovingA_20260914` | Completed; valid on/off interleave partner. |
|
||||||
|
| `WormSkipGameOnMovingA_20260914` | Completed; valid on/off interleave partner. |
|
||||||
|
| `WormSkipGameOffStaticB_20260914` | Completed; valid off B. |
|
||||||
|
| `WormSkipGameOnStaticB_20260914` | Interrupted at the stop; partial module/RSS artifacts only, void and excluded. |
|
||||||
|
| `WormSkipGameOnStaticA_Resume_20260914` | Completed; valid on A replacement. |
|
||||||
|
| `WormSkipGameOffMovingB_Resume_20260914` | Completed; valid off B replacement. |
|
||||||
|
| `WormSkipGameOnMovingB_Resume_20260914` | Completed; valid on B replacement. |
|
||||||
|
| `WormSkipGameOnStaticB_Resume_20260914` | Completed; valid on B replacement. |
|
||||||
|
| `WormSkipGameOnMovingSampler_20260914` | Completed; sampler-on moving profile, zero drops/failures. |
|
||||||
|
| `WormSkipDiagnosticsMoving_20260914` | Completed; superseded by the final counter-semantics diagnostic run, not used for reported skip rates. |
|
||||||
|
| `WormSkipDiagnosticsMovingProofs_20260914` | Completed on final DLL; per-LOD skip rates above, module verified. |
|
||||||
|
|
||||||
|
The Saved crash inventory also contains earlier-round UECC artifacts, which are kept visible here:
|
||||||
|
three failed `Task4CurrentTracePIE` attempts on 2026-09-09 (plugin-manager/ShaderCore/DDC launch
|
||||||
|
failures), `GameMovingOn_20260913` (fatal shutdown), `MemoGameStaticOffA_20260913` (no writable
|
||||||
|
DDC graph; retried successfully), `TakeoverFinalMovingSamplerOnHost_20260913` (crash artifact;
|
||||||
|
`...Host2` retry succeeded), `Final3MovingSamplerOn_20260913` (fatal shutdown), and
|
||||||
|
`IsFiniteExact_20260913` (no writable DDC graph). No CrashReportClient crash occurred in the
|
||||||
|
worm-round launches above.
|
||||||
@@ -0,0 +1,350 @@
|
|||||||
|
// Soundness and empirical coverage for the worker-local worm N1 block proof.
|
||||||
|
|
||||||
|
#if WITH_DEV_AUTOMATION_TESTS
|
||||||
|
|
||||||
|
#include "Misc/AutomationTest.h"
|
||||||
|
|
||||||
|
#include "VoxelCaveMorphology.h"
|
||||||
|
#include "VoxelNoise.h"
|
||||||
|
|
||||||
|
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||||
|
FVoxelForgeWormBlockSkipTest,
|
||||||
|
"VoxelForge.Determinism.WormBlockSkipSoundness",
|
||||||
|
EAutomationTestFlags_ApplicationContextMask | EAutomationTestFlags::EngineFilter)
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
constexpr float WormFrequency = 0.015f;
|
||||||
|
constexpr float WormHorizontalBias = 3.0f;
|
||||||
|
constexpr float WormThreshold = 0.06f;
|
||||||
|
constexpr float WormVerticalScale = 1.0f;
|
||||||
|
constexpr int32 TestCellsPerAxis = 32;
|
||||||
|
constexpr int32 TestTileCount = 192;
|
||||||
|
constexpr int32 TestRandomPairCount = 500000;
|
||||||
|
|
||||||
|
FORCEINLINE uint32 NextTestBits(uint32& State)
|
||||||
|
{
|
||||||
|
State += 0x9E3779B9u;
|
||||||
|
uint32 Z = State;
|
||||||
|
Z = (Z ^ (Z >> 16)) * 0x7FEB352Du;
|
||||||
|
Z = (Z ^ (Z >> 15)) * 0x846CA68Bu;
|
||||||
|
return Z ^ (Z >> 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
FORCEINLINE float NextTestUnit(uint32& State)
|
||||||
|
{
|
||||||
|
return static_cast<float>(NextTestBits(State)) / 4294967296.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
FORCEINLINE float ScaledN1(const FVector3f& Position)
|
||||||
|
{
|
||||||
|
return FMath::Abs(VoxelNoise::Perlin3D(Position) * VOXEL_NOISE_SCALE);
|
||||||
|
}
|
||||||
|
|
||||||
|
FORCEINLINE FVector3f MakeWormNoisePosition(
|
||||||
|
int32 WorldX, int32 WorldY, int32 WorldZ, uint32 Seed)
|
||||||
|
{
|
||||||
|
float EffectiveZ = static_cast<float>(WorldZ);
|
||||||
|
if (WormVerticalScale != 1.0f && WormVerticalScale > 0.0f)
|
||||||
|
{
|
||||||
|
EffectiveZ /= WormVerticalScale;
|
||||||
|
}
|
||||||
|
const float WormZFreq = WormFrequency * WormHorizontalBias;
|
||||||
|
return FVector3f(
|
||||||
|
static_cast<float>(WorldX) * WormFrequency
|
||||||
|
+ VoxelHash::SeedOffset(Seed, 1.0f),
|
||||||
|
static_cast<float>(WorldY) * WormFrequency
|
||||||
|
+ VoxelHash::SeedOffset(Seed, 1.7f),
|
||||||
|
EffectiveZ * WormZFreq + VoxelHash::SeedOffset(Seed, 2.3f));
|
||||||
|
}
|
||||||
|
|
||||||
|
FORCEINLINE double FadePrime(double T)
|
||||||
|
{
|
||||||
|
return 30.0 * T * T * (1.0 - T) * (1.0 - T);
|
||||||
|
}
|
||||||
|
|
||||||
|
FORCEINLINE double GradientSupport(double X, double Y, double Z)
|
||||||
|
{
|
||||||
|
const double AX = FMath::Abs(X);
|
||||||
|
const double AY = FMath::Abs(Y);
|
||||||
|
const double AZ = FMath::Abs(Z);
|
||||||
|
return FMath::Max(FMath::Max(AX + AY, AX + AZ), AY + AZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Supremum of the exact finite support relaxation used in the header derivation. This is
|
||||||
|
// intentionally independent of the hash: all eight corners may choose any member of the
|
||||||
|
// actual GradDot set, so this is the field-wide proof envelope rather than an average sample.
|
||||||
|
double SupportRelaxedXDerivative(double X, double Y, double Z)
|
||||||
|
{
|
||||||
|
const double D = FadePrime(X);
|
||||||
|
const double SY = Y * Y * Y * (Y * (Y * 6.0 - 15.0) + 10.0);
|
||||||
|
const double SZ = Z * Z * Z * (Z * (Z * 6.0 - 15.0) + 10.0);
|
||||||
|
double Result = 0.0;
|
||||||
|
for (int32 J = 0; J < 2; ++J)
|
||||||
|
{
|
||||||
|
for (int32 K = 0; K < 2; ++K)
|
||||||
|
{
|
||||||
|
const double Weight = (J != 0 ? SY : 1.0 - SY)
|
||||||
|
* (K != 0 ? SZ : 1.0 - SZ);
|
||||||
|
const double YLocal = Y - static_cast<double>(J);
|
||||||
|
const double ZLocal = Z - static_cast<double>(K);
|
||||||
|
Result += Weight * (
|
||||||
|
GradientSupport(1.0 - X - D * X, -D * YLocal, -D * ZLocal)
|
||||||
|
+ GradientSupport(X - D * (1.0 - X), D * YLocal, D * ZLocal));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FVoxelForgeWormBlockSkipTest::RunTest(const FString& Parameters)
|
||||||
|
{
|
||||||
|
(void)Parameters;
|
||||||
|
|
||||||
|
const double FullL = VoxelNoise::WormN1NoiseSpaceLipschitzBound;
|
||||||
|
const double HalfL = FullL * 0.5;
|
||||||
|
TestTrue(TEXT("the production N1 Lipschitz bound is finite and positive"),
|
||||||
|
FMath::IsFinite(FullL) && FullL > 0.0);
|
||||||
|
TestTrue(TEXT("the actual 3D partial bound is 15/4"),
|
||||||
|
FMath::IsNearlyEqual(VoxelNoise::Perlin3DPartialAbsBound, 3.75, 1.0e-12));
|
||||||
|
TestTrue(TEXT("VOXEL_NOISE_SCALE is included in the N1 bound"),
|
||||||
|
FMath::IsNearlyEqual(
|
||||||
|
FullL,
|
||||||
|
static_cast<double>(VOXEL_NOISE_SCALE)
|
||||||
|
* VoxelNoise::Perlin3DPartialAbsBound
|
||||||
|
* static_cast<double>(1.732050807568877293527446341505872L),
|
||||||
|
1.0e-12));
|
||||||
|
|
||||||
|
// Check the finite support maximization densely, including corners and every cell face. The
|
||||||
|
// closed-form case split is recorded beside the production constant; this grid is a regression
|
||||||
|
// tripwire for changing GradDot/Fade/Lerp without updating that proof.
|
||||||
|
double MaxRelaxedSupport = 0.0;
|
||||||
|
FVector3f MaxRelaxedPoint = FVector3f::ZeroVector;
|
||||||
|
constexpr int32 SupportGrid = 128;
|
||||||
|
for (int32 IX = 0; IX <= SupportGrid; ++IX)
|
||||||
|
{
|
||||||
|
const double X = static_cast<double>(IX) / SupportGrid;
|
||||||
|
for (int32 IY = 0; IY <= SupportGrid; ++IY)
|
||||||
|
{
|
||||||
|
const double Y = static_cast<double>(IY) / SupportGrid;
|
||||||
|
for (int32 IZ = 0; IZ <= SupportGrid; ++IZ)
|
||||||
|
{
|
||||||
|
const double Z = static_cast<double>(IZ) / SupportGrid;
|
||||||
|
const double Value = SupportRelaxedXDerivative(X, Y, Z);
|
||||||
|
if (Value > MaxRelaxedSupport)
|
||||||
|
{
|
||||||
|
MaxRelaxedSupport = Value;
|
||||||
|
MaxRelaxedPoint = FVector3f(
|
||||||
|
static_cast<float>(X), static_cast<float>(Y), static_cast<float>(Z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AddInfo(FString::Printf(
|
||||||
|
TEXT("3D support relaxation max=%.12f at (%.6f,%.6f,%.6f), bound=%.12f; "
|
||||||
|
"the exact case split gives 15/4 at (0.5,0.5,0.5)."),
|
||||||
|
MaxRelaxedSupport, MaxRelaxedPoint.X, MaxRelaxedPoint.Y, MaxRelaxedPoint.Z,
|
||||||
|
VoxelNoise::Perlin3DPartialAbsBound));
|
||||||
|
TestTrue(TEXT("the dense support relaxation stays below the proven partial bound"),
|
||||||
|
MaxRelaxedSupport <= VoxelNoise::Perlin3DPartialAbsBound + 1.0e-10);
|
||||||
|
TestTrue(TEXT("the support relaxation reaches the stated supremum"),
|
||||||
|
MaxRelaxedSupport >= VoxelNoise::Perlin3DPartialAbsBound - 1.0e-10);
|
||||||
|
|
||||||
|
// Empirical attack: samples straddle lattice faces/corners at several scales, then a large
|
||||||
|
// deterministic random-pair population probes ordinary cells and arbitrary directions.
|
||||||
|
double MaxEmpiricalRatio = 0.0;
|
||||||
|
FVector3f WorstA = FVector3f::ZeroVector;
|
||||||
|
FVector3f WorstB = FVector3f::ZeroVector;
|
||||||
|
uint64 PairCount = 0;
|
||||||
|
auto ObservePair = [&](const FVector3f& A, const FVector3f& B)
|
||||||
|
{
|
||||||
|
const double DX = static_cast<double>(A.X) - static_cast<double>(B.X);
|
||||||
|
const double DY = static_cast<double>(A.Y) - static_cast<double>(B.Y);
|
||||||
|
const double DZ = static_cast<double>(A.Z) - static_cast<double>(B.Z);
|
||||||
|
const double Distance = FMath::Sqrt(DX * DX + DY * DY + DZ * DZ);
|
||||||
|
if (!(Distance > 0.0))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
++PairCount;
|
||||||
|
const double Ratio = FMath::Abs(
|
||||||
|
static_cast<double>(ScaledN1(A)) - static_cast<double>(ScaledN1(B))) / Distance;
|
||||||
|
if (Ratio > MaxEmpiricalRatio)
|
||||||
|
{
|
||||||
|
MaxEmpiricalRatio = Ratio;
|
||||||
|
WorstA = A;
|
||||||
|
WorstB = B;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr float FaceEpsilons[] = { 1.0e-5f, 1.0e-4f, 1.0e-3f, 1.0e-2f, 5.0e-2f };
|
||||||
|
constexpr float FaceCoordinates[] = {
|
||||||
|
1.0e-3f, 1.0e-2f, 0.1f, 0.25f, 0.5f, 0.75f, 0.9f, 0.99f, 0.999f };
|
||||||
|
for (int32 X = -12; X < 12; ++X)
|
||||||
|
{
|
||||||
|
for (int32 Y = -12; Y < 12; ++Y)
|
||||||
|
{
|
||||||
|
for (int32 Z = -12; Z < 12; ++Z)
|
||||||
|
{
|
||||||
|
for (const float Epsilon : FaceEpsilons)
|
||||||
|
{
|
||||||
|
for (const float V : FaceCoordinates)
|
||||||
|
{
|
||||||
|
for (const float W : FaceCoordinates)
|
||||||
|
{
|
||||||
|
ObservePair(
|
||||||
|
FVector3f(X + 1.0f - Epsilon, Y + V, Z + W),
|
||||||
|
FVector3f(X + 1.0f + Epsilon, Y + V, Z + W));
|
||||||
|
ObservePair(
|
||||||
|
FVector3f(X + V, Y + 1.0f - Epsilon, Z + W),
|
||||||
|
FVector3f(X + V, Y + 1.0f + Epsilon, Z + W));
|
||||||
|
ObservePair(
|
||||||
|
FVector3f(X + V, Y + W, Z + 1.0f - Epsilon),
|
||||||
|
FVector3f(X + V, Y + W, Z + 1.0f + Epsilon));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ObservePair(
|
||||||
|
FVector3f(X + 1.0f - Epsilon, Y + 1.0f - Epsilon, Z + 1.0f - Epsilon),
|
||||||
|
FVector3f(X + 1.0f + Epsilon, Y + 1.0f + Epsilon, Z + 1.0f + Epsilon));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 RandomState = 0xA5B3571Du;
|
||||||
|
for (int32 Index = 0; Index < TestRandomPairCount; ++Index)
|
||||||
|
{
|
||||||
|
const FVector3f A(
|
||||||
|
NextTestUnit(RandomState) * 128.0f - 64.0f,
|
||||||
|
NextTestUnit(RandomState) * 128.0f - 64.0f,
|
||||||
|
NextTestUnit(RandomState) * 128.0f - 64.0f);
|
||||||
|
const FVector3f B(
|
||||||
|
A.X + NextTestUnit(RandomState) * 0.5f - 0.25f,
|
||||||
|
A.Y + NextTestUnit(RandomState) * 0.5f - 0.25f,
|
||||||
|
A.Z + NextTestUnit(RandomState) * 0.5f - 0.25f);
|
||||||
|
ObservePair(A, B);
|
||||||
|
}
|
||||||
|
AddInfo(FString::Printf(
|
||||||
|
TEXT("Empirical scaled-N1 Lipschitz attack: pairs=%llu worst=%.9f, worst/L=%.9f, "
|
||||||
|
"L=%.9f; worst A=(%.6f,%.6f,%.6f) B=(%.6f,%.6f,%.6f)."),
|
||||||
|
static_cast<unsigned long long>(PairCount), MaxEmpiricalRatio,
|
||||||
|
MaxEmpiricalRatio / FullL, FullL,
|
||||||
|
WorstA.X, WorstA.Y, WorstA.Z, WorstB.X, WorstB.Y, WorstB.Z));
|
||||||
|
TestTrue(TEXT("empirical near-face/corner/random ratios stay below L"),
|
||||||
|
MaxEmpiricalRatio <= FullL + 0.01);
|
||||||
|
|
||||||
|
// Brute-force the exact mesher lattice for a large deterministic set of tile contexts. Every
|
||||||
|
// block admitted by the production proof is checked at every lattice point it covers.
|
||||||
|
uint64 BlocksScanned = 0;
|
||||||
|
uint64 ProductionProofs = 0;
|
||||||
|
uint64 ProductionLatticeSamples = 0;
|
||||||
|
uint64 ProductionViolations = 0;
|
||||||
|
for (int32 TileIndex = 0; TileIndex < TestTileCount; ++TileIndex)
|
||||||
|
{
|
||||||
|
const FIntVector TileOrigin(
|
||||||
|
(TileIndex % 17 - 8) * 64,
|
||||||
|
((TileIndex / 17) % 13 - 6) * 64,
|
||||||
|
((TileIndex / 221) % 9 - 4) * 64);
|
||||||
|
const uint32 Seed = 1337u + static_cast<uint32>(TileIndex) * 0x9E3779B9u;
|
||||||
|
|
||||||
|
for (int32 BlockZ = 0; BlockZ < 9; ++BlockZ)
|
||||||
|
{
|
||||||
|
for (int32 BlockY = 0; BlockY < 9; ++BlockY)
|
||||||
|
{
|
||||||
|
for (int32 BlockX = 0; BlockX < 9; ++BlockX)
|
||||||
|
{
|
||||||
|
++BlocksScanned;
|
||||||
|
const int32 MinX = BlockX * VoxelNoise::WormBlockSampleSide - 1;
|
||||||
|
const int32 MinY = BlockY * VoxelNoise::WormBlockSampleSide - 1;
|
||||||
|
const int32 MinZ = BlockZ * VoxelNoise::WormBlockSampleSide - 1;
|
||||||
|
const int32 MaxX = FMath::Min(
|
||||||
|
TestCellsPerAxis + 1,
|
||||||
|
MinX + VoxelNoise::WormBlockSampleSide - 1);
|
||||||
|
const int32 MaxY = FMath::Min(
|
||||||
|
TestCellsPerAxis + 1,
|
||||||
|
MinY + VoxelNoise::WormBlockSampleSide - 1);
|
||||||
|
const int32 MaxZ = FMath::Min(
|
||||||
|
TestCellsPerAxis + 1,
|
||||||
|
MinZ + VoxelNoise::WormBlockSampleSide - 1);
|
||||||
|
const FVector3f Q0 = MakeWormNoisePosition(
|
||||||
|
TileOrigin.X + MinX, TileOrigin.Y + MinY, TileOrigin.Z + MinZ, Seed);
|
||||||
|
const FVector3f Q1 = MakeWormNoisePosition(
|
||||||
|
TileOrigin.X + MaxX, TileOrigin.Y + MaxY, TileOrigin.Z + MaxZ, Seed);
|
||||||
|
const FVector3f QMin(
|
||||||
|
FMath::Min(Q0.X, Q1.X), FMath::Min(Q0.Y, Q1.Y), FMath::Min(Q0.Z, Q1.Z));
|
||||||
|
const FVector3f QMax(
|
||||||
|
FMath::Max(Q0.X, Q1.X), FMath::Max(Q0.Y, Q1.Y), FMath::Max(Q0.Z, Q1.Z));
|
||||||
|
const FVector3f QCenter(
|
||||||
|
static_cast<float>((static_cast<double>(QMin.X) + QMax.X) * 0.5),
|
||||||
|
static_cast<float>((static_cast<double>(QMin.Y) + QMax.Y) * 0.5),
|
||||||
|
static_cast<float>((static_cast<double>(QMin.Z) + QMax.Z) * 0.5));
|
||||||
|
|
||||||
|
if (!VoxelNoise::ProvesScaledAbsPerlin3DAbove(
|
||||||
|
QCenter, QMin, QMax, WormThreshold, FullL))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
++ProductionProofs;
|
||||||
|
for (int32 Z = MinZ; Z <= MaxZ; ++Z)
|
||||||
|
{
|
||||||
|
for (int32 Y = MinY; Y <= MaxY; ++Y)
|
||||||
|
{
|
||||||
|
for (int32 X = MinX; X <= MaxX; ++X)
|
||||||
|
{
|
||||||
|
++ProductionLatticeSamples;
|
||||||
|
const float ExactN1 = ScaledN1(MakeWormNoisePosition(
|
||||||
|
TileOrigin.X + X, TileOrigin.Y + Y, TileOrigin.Z + Z, Seed));
|
||||||
|
if (ExactN1 < WormThreshold)
|
||||||
|
{
|
||||||
|
++ProductionViolations;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AddInfo(FString::Printf(
|
||||||
|
TEXT("Production block audit: tiles=%d blocks=%llu proofs=%llu lattice_samples=%llu "
|
||||||
|
"violations=%llu, side=%d, threshold=%.6f."),
|
||||||
|
TestTileCount, static_cast<unsigned long long>(BlocksScanned),
|
||||||
|
static_cast<unsigned long long>(ProductionProofs),
|
||||||
|
static_cast<unsigned long long>(ProductionLatticeSamples),
|
||||||
|
static_cast<unsigned long long>(ProductionViolations),
|
||||||
|
VoxelNoise::WormBlockSampleSide, WormThreshold));
|
||||||
|
TestTrue(TEXT("the production-bound block audit found at least one proof"),
|
||||||
|
ProductionProofs > 0);
|
||||||
|
TestEqual(TEXT("every production-bound proof is sound on its exact lattice"),
|
||||||
|
ProductionViolations, static_cast<uint64>(0));
|
||||||
|
|
||||||
|
// Fail-capable negative control. This pair is from a real low-coordinate hash cell. Its exact
|
||||||
|
// scaled-N1 slope is above L/2; the threshold is selected between the two exact values, so a
|
||||||
|
// deliberately halved bound admits a proof that the second lattice point violates.
|
||||||
|
const FVector3f WitnessCenter(1510.5478515625f, 2191.43701171875f, 7279.404296875f);
|
||||||
|
const FVector3f WitnessPoint(1510.54931640625f, 2191.437255859375f, 7279.50390625f);
|
||||||
|
const FVector3f WitnessMin(
|
||||||
|
2.0f * WitnessCenter.X - WitnessPoint.X,
|
||||||
|
2.0f * WitnessCenter.Y - WitnessPoint.Y,
|
||||||
|
2.0f * WitnessCenter.Z - WitnessPoint.Z);
|
||||||
|
const FVector3f WitnessMax = WitnessPoint;
|
||||||
|
constexpr float WitnessThreshold = 0.015f;
|
||||||
|
const float WitnessCenterN1 = ScaledN1(WitnessCenter);
|
||||||
|
const float WitnessPointN1 = ScaledN1(WitnessPoint);
|
||||||
|
const bool bHalfProof = VoxelNoise::ProvesScaledAbsPerlin3DAbove(
|
||||||
|
WitnessCenter, WitnessMin, WitnessMax, WitnessThreshold, HalfL);
|
||||||
|
const bool bFullProof = VoxelNoise::ProvesScaledAbsPerlin3DAbove(
|
||||||
|
WitnessCenter, WitnessMin, WitnessMax, WitnessThreshold, FullL);
|
||||||
|
AddInfo(FString::Printf(
|
||||||
|
TEXT("Halved-L negative control: center_N1=%.9f point_N1=%.9f threshold=%.6f "
|
||||||
|
"half_proof=%d full_proof=%d."),
|
||||||
|
WitnessCenterN1, WitnessPointN1, WitnessThreshold,
|
||||||
|
bHalfProof ? 1 : 0, bFullProof ? 1 : 0));
|
||||||
|
TestTrue(TEXT("the deliberately halved L admits the negative-control block"), bHalfProof);
|
||||||
|
TestFalse(TEXT("the full L rejects the negative-control block"), bFullProof);
|
||||||
|
TestTrue(TEXT("the negative-control lattice point really violates N1 >= threshold"),
|
||||||
|
WitnessPointN1 < WitnessThreshold);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // WITH_DEV_AUTOMATION_TESTS
|
||||||
@@ -688,6 +688,9 @@ namespace VoxelDensityProfile
|
|||||||
case ECounter::FusedTunnelSamples: return TEXT("FusedTunnelSamples");
|
case ECounter::FusedTunnelSamples: return TEXT("FusedTunnelSamples");
|
||||||
case ECounter::FusedTunnelDetailSamples: return TEXT("FusedTunnelDetailSamples");
|
case ECounter::FusedTunnelDetailSamples: return TEXT("FusedTunnelDetailSamples");
|
||||||
case ECounter::FusedTunnelDetailSkipped: return TEXT("FusedTunnelDetailSkipped");
|
case ECounter::FusedTunnelDetailSkipped: return TEXT("FusedTunnelDetailSkipped");
|
||||||
|
case ECounter::WormEligibleSamples: return TEXT("WormEligibleSamples");
|
||||||
|
case ECounter::WormBlockProofs: return TEXT("WormBlockProofs");
|
||||||
|
case ECounter::WormBlockSkippedSamples: return TEXT("WormBlockSkippedSamples");
|
||||||
case ECounter::OpBlockBuilds: return TEXT("OpBlockBuilds");
|
case ECounter::OpBlockBuilds: return TEXT("OpBlockBuilds");
|
||||||
case ECounter::OpBlockSamples: return TEXT("OpBlockSamples");
|
case ECounter::OpBlockSamples: return TEXT("OpBlockSamples");
|
||||||
case ECounter::OpBlockOperators: return TEXT("OpBlockOperators");
|
case ECounter::OpBlockOperators: return TEXT("OpBlockOperators");
|
||||||
|
|||||||
@@ -85,6 +85,32 @@ namespace
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A proved N1-above-threshold block skips both worm noise calls. The switch is parsed once
|
||||||
|
// on the same scalar entry point as the other generation A/B switches; 0 is the exact legacy
|
||||||
|
// path, and 1 is the default after the proof/soundness test passes.
|
||||||
|
int32 GVoxelForgeWormBlockSkip = 1;
|
||||||
|
FAutoConsoleVariableRef CVarVoxelForgeWormBlockSkip(
|
||||||
|
TEXT("voxel.WormBlockSkip"),
|
||||||
|
GVoxelForgeWormBlockSkip,
|
||||||
|
TEXT("Skip worm N1/N2 when a worker-local noise-space block proof says N1 >= threshold."));
|
||||||
|
bool GVoxelForgeWormBlockSkipSwitchParsed = false;
|
||||||
|
|
||||||
|
void VF_ParseWormBlockSkipSwitch()
|
||||||
|
{
|
||||||
|
if (GVoxelForgeWormBlockSkipSwitchParsed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GVoxelForgeWormBlockSkipSwitchParsed = true;
|
||||||
|
int32 CommandLineValue = GVoxelForgeWormBlockSkip;
|
||||||
|
if (FParse::Value(
|
||||||
|
FCommandLine::Get(), TEXT("voxel.WormBlockSkip="), CommandLineValue))
|
||||||
|
{
|
||||||
|
GVoxelForgeWormBlockSkip = CommandLineValue;
|
||||||
|
}
|
||||||
|
GVoxelForgeWormBlockSkip = GVoxelForgeWormBlockSkip != 0 ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
// A tile-sized cache window is the production default on the coarse paths where it wins.
|
// A tile-sized cache window is the production default on the coarse paths where it wins.
|
||||||
// Keep the switches in the generator module so native and op-stack sources make the same A/B
|
// Keep the switches in the generator module so native and op-stack sources make the same A/B
|
||||||
// decision, including when a headless harness applies command-line CVars after module startup.
|
// decision, including when a headless harness applies command-line CVars after module startup.
|
||||||
@@ -309,6 +335,257 @@ struct FSurfaceColumnCache
|
|||||||
// donc un ClassifyTile qui rend Mixed laisse ses colonnes chaudes pour le GenerateMesh qui suit.
|
// donc un ClassifyTile qui rend Mixed laisse ses colonnes chaudes pour le GenerateMesh qui suit.
|
||||||
static thread_local FSurfaceColumnCache GSurfColCache;
|
static thread_local FSurfaceColumnCache GSurfColCache;
|
||||||
|
|
||||||
|
//=============================================================================
|
||||||
|
// WORM N1 BLOCK PROOF (worker-local, scalar GetDensityAt hand-off)
|
||||||
|
//=============================================================================
|
||||||
|
// GenerateMesh installs TileOriginVoxels/SampleStep/TileCellsPerAxis around every density-grid
|
||||||
|
// fill. Keep the verdicts here, rather than in the mesher, because the scalar GetDensityAt path
|
||||||
|
// owns the actual worm call and is also used by standalone/classifier queries. The cache is
|
||||||
|
// thread_local, direct-indexed by the block coordinate, and invalidated by every field identity
|
||||||
|
// that can affect N1. A miss or an invalid context always returns false and therefore executes
|
||||||
|
// the old N1/N2 code.
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
constexpr int32 VF_WormBlockCacheAxis =
|
||||||
|
(CHUNK_SIZE + 3 + VoxelNoise::WormBlockSampleSide - 1)
|
||||||
|
/ VoxelNoise::WormBlockSampleSide;
|
||||||
|
constexpr int32 VF_WormBlockCacheEntryCount =
|
||||||
|
VF_WormBlockCacheAxis * VF_WormBlockCacheAxis * VF_WormBlockCacheAxis;
|
||||||
|
|
||||||
|
static_assert(VF_WormBlockCacheAxis == 9,
|
||||||
|
"The fixed worm block table must cover the 32-cell mesher lattice plus its halo.");
|
||||||
|
|
||||||
|
FORCEINLINE uint32 VF_FloatBits(float Value)
|
||||||
|
{
|
||||||
|
uint32 Bits = 0;
|
||||||
|
FMemory::Memcpy(&Bits, &Value, sizeof(Bits));
|
||||||
|
return Bits;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FWormBlockSkipEntry
|
||||||
|
{
|
||||||
|
bool bValid = false;
|
||||||
|
bool bSkip = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FWormBlockSkipCache
|
||||||
|
{
|
||||||
|
bool bContextValid = false;
|
||||||
|
uint64 OwnerId = 0;
|
||||||
|
uint32 Seed = 0;
|
||||||
|
uint32 ParamsFingerprint = 0xFFFFFFFFu;
|
||||||
|
uint32 LayoutVersion = 0xFFFFFFFFu;
|
||||||
|
FIntVector TileOrigin = FIntVector::ZeroValue;
|
||||||
|
int32 Step = 0;
|
||||||
|
int32 Cells = 0;
|
||||||
|
uint32 WormFrequencyBits = 0;
|
||||||
|
uint32 WormHorizontalBiasBits = 0;
|
||||||
|
uint32 WormThresholdBits = 0;
|
||||||
|
uint32 VerticalScaleBits = 0;
|
||||||
|
FWormBlockSkipEntry Entries[VF_WormBlockCacheEntryCount];
|
||||||
|
|
||||||
|
void SetContext(
|
||||||
|
uint64 InOwnerId, uint32 InSeed, uint32 InParamsFingerprint,
|
||||||
|
uint32 InLayoutVersion, const FIntVector& InTileOrigin,
|
||||||
|
int32 InStep, int32 InCells, const FStrateGenerationParams& Params)
|
||||||
|
{
|
||||||
|
const uint32 InWormFrequencyBits = VF_FloatBits(Params.WormFrequency);
|
||||||
|
const uint32 InWormHorizontalBiasBits = VF_FloatBits(Params.WormHorizontalBias);
|
||||||
|
const uint32 InWormThresholdBits = VF_FloatBits(Params.WormThreshold);
|
||||||
|
const uint32 InVerticalScaleBits = VF_FloatBits(Params.VerticalScale);
|
||||||
|
const bool bChanged =
|
||||||
|
!bContextValid
|
||||||
|
|| OwnerId != InOwnerId
|
||||||
|
|| Seed != InSeed
|
||||||
|
|| ParamsFingerprint != InParamsFingerprint
|
||||||
|
|| LayoutVersion != InLayoutVersion
|
||||||
|
|| TileOrigin != InTileOrigin
|
||||||
|
|| Step != InStep
|
||||||
|
|| Cells != InCells
|
||||||
|
|| WormFrequencyBits != InWormFrequencyBits
|
||||||
|
|| WormHorizontalBiasBits != InWormHorizontalBiasBits
|
||||||
|
|| WormThresholdBits != InWormThresholdBits
|
||||||
|
|| VerticalScaleBits != InVerticalScaleBits;
|
||||||
|
if (!bChanged)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bContextValid = true;
|
||||||
|
OwnerId = InOwnerId;
|
||||||
|
Seed = InSeed;
|
||||||
|
ParamsFingerprint = InParamsFingerprint;
|
||||||
|
LayoutVersion = InLayoutVersion;
|
||||||
|
TileOrigin = InTileOrigin;
|
||||||
|
Step = InStep;
|
||||||
|
Cells = InCells;
|
||||||
|
WormFrequencyBits = InWormFrequencyBits;
|
||||||
|
WormHorizontalBiasBits = InWormHorizontalBiasBits;
|
||||||
|
WormThresholdBits = InWormThresholdBits;
|
||||||
|
VerticalScaleBits = InVerticalScaleBits;
|
||||||
|
FMemory::Memzero(Entries, sizeof(Entries));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static thread_local FWormBlockSkipCache GWormBlockSkipCache;
|
||||||
|
|
||||||
|
FORCEINLINE FVector3f VF_WormN1NoisePosition(
|
||||||
|
float WorldX, float WorldY, float WorldZ,
|
||||||
|
const FStrateGenerationParams& Params, uint32 SeedU)
|
||||||
|
{
|
||||||
|
float EffectiveZ = WorldZ;
|
||||||
|
if (Params.VerticalScale != 1.0f && Params.VerticalScale > 0.0f)
|
||||||
|
{
|
||||||
|
EffectiveZ = WorldZ / Params.VerticalScale;
|
||||||
|
}
|
||||||
|
const float WormZFreq = Params.WormFrequency * Params.WormHorizontalBias;
|
||||||
|
return FVector3f(
|
||||||
|
WorldX * Params.WormFrequency + VoxelHash::SeedOffset(SeedU, 1.0f),
|
||||||
|
WorldY * Params.WormFrequency + VoxelHash::SeedOffset(SeedU, 1.7f),
|
||||||
|
EffectiveZ * WormZFreq + VoxelHash::SeedOffset(SeedU, 2.3f));
|
||||||
|
}
|
||||||
|
|
||||||
|
FORCEINLINE bool VF_TryGetWormBlockSkip(
|
||||||
|
uint64 OwnerId, uint32 SeedU, uint32 ParamsFingerprint, uint32 LayoutVersion,
|
||||||
|
float WorldX, float WorldY, float WorldZ,
|
||||||
|
const FStrateGenerationParams& Params, bool& bOutProofComputed)
|
||||||
|
{
|
||||||
|
bOutProofComputed = false;
|
||||||
|
|
||||||
|
// Point queries and malformed authored parameters stay on the exact original path. In
|
||||||
|
// particular, VerticalScale <= 0 has legacy "no division" semantics; declining the proof
|
||||||
|
// here avoids making a new promise for malformed data.
|
||||||
|
if (!FMath::IsFinite(WorldX) || !FMath::IsFinite(WorldY) || !FMath::IsFinite(WorldZ)
|
||||||
|
|| WorldX != FMath::FloorToFloat(WorldX)
|
||||||
|
|| WorldY != FMath::FloorToFloat(WorldY)
|
||||||
|
|| WorldZ != FMath::FloorToFloat(WorldZ)
|
||||||
|
|| !FMath::IsFinite(Params.WormFrequency)
|
||||||
|
|| !FMath::IsFinite(Params.WormHorizontalBias)
|
||||||
|
|| !FMath::IsFinite(Params.WormThreshold)
|
||||||
|
|| !FMath::IsFinite(Params.VerticalScale)
|
||||||
|
|| !(Params.WormThreshold > 0.0f)
|
||||||
|
|| !(Params.VerticalScale > 0.0f)
|
||||||
|
|| !FMath::IsFinite(Params.WormFrequency * Params.WormHorizontalBias))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int32 Step = VoxelGenLOD::SampleStep;
|
||||||
|
const int32 Cells = VoxelGenLOD::TileCellsPerAxis;
|
||||||
|
if (Step <= 0 || Cells <= 0 || Cells > CHUNK_SIZE)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FMath::RoundToInt is only reached after the finite/integer check and a range guard. The
|
||||||
|
// tile context itself is int32, so coordinates outside this range cannot belong to it.
|
||||||
|
constexpr float SafeInt32Min = -2147483008.0f;
|
||||||
|
constexpr float SafeInt32Max = 2147483008.0f;
|
||||||
|
if (WorldX < SafeInt32Min || WorldX > SafeInt32Max
|
||||||
|
|| WorldY < SafeInt32Min || WorldY > SafeInt32Max
|
||||||
|
|| WorldZ < SafeInt32Min || WorldZ > SafeInt32Max)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const int32 IX = FMath::RoundToInt(WorldX);
|
||||||
|
const int32 IY = FMath::RoundToInt(WorldY);
|
||||||
|
const int32 IZ = FMath::RoundToInt(WorldZ);
|
||||||
|
const FIntVector TileOrigin = VoxelGenLOD::TileOriginVoxels;
|
||||||
|
const int64 DX = static_cast<int64>(IX) - static_cast<int64>(TileOrigin.X);
|
||||||
|
const int64 DY = static_cast<int64>(IY) - static_cast<int64>(TileOrigin.Y);
|
||||||
|
const int64 DZ = static_cast<int64>(IZ) - static_cast<int64>(TileOrigin.Z);
|
||||||
|
if (DX % Step != 0 || DY % Step != 0 || DZ % Step != 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int32 LocalX = static_cast<int32>(DX / Step);
|
||||||
|
const int32 LocalY = static_cast<int32>(DY / Step);
|
||||||
|
const int32 LocalZ = static_cast<int32>(DZ / Step);
|
||||||
|
const int32 MinLocalLattice = -1;
|
||||||
|
const int32 MaxLocalLattice = Cells + 1;
|
||||||
|
if (LocalX < MinLocalLattice || LocalX > MaxLocalLattice
|
||||||
|
|| LocalY < MinLocalLattice || LocalY > MaxLocalLattice
|
||||||
|
|| LocalZ < MinLocalLattice || LocalZ > MaxLocalLattice)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int32 BlockX = (LocalX + 1) / VoxelNoise::WormBlockSampleSide;
|
||||||
|
const int32 BlockY = (LocalY + 1) / VoxelNoise::WormBlockSampleSide;
|
||||||
|
const int32 BlockZ = (LocalZ + 1) / VoxelNoise::WormBlockSampleSide;
|
||||||
|
if (BlockX < 0 || BlockX >= VF_WormBlockCacheAxis
|
||||||
|
|| BlockY < 0 || BlockY >= VF_WormBlockCacheAxis
|
||||||
|
|| BlockZ < 0 || BlockZ >= VF_WormBlockCacheAxis)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
GWormBlockSkipCache.SetContext(
|
||||||
|
OwnerId, SeedU, ParamsFingerprint, LayoutVersion, TileOrigin, Step, Cells, Params);
|
||||||
|
FWormBlockSkipEntry& Entry = GWormBlockSkipCache.Entries[
|
||||||
|
(BlockZ * VF_WormBlockCacheAxis + BlockY) * VF_WormBlockCacheAxis + BlockX];
|
||||||
|
if (Entry.bValid)
|
||||||
|
{
|
||||||
|
return Entry.bSkip;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int32 BlockMinX = BlockX * VoxelNoise::WormBlockSampleSide - 1;
|
||||||
|
const int32 BlockMinY = BlockY * VoxelNoise::WormBlockSampleSide - 1;
|
||||||
|
const int32 BlockMinZ = BlockZ * VoxelNoise::WormBlockSampleSide - 1;
|
||||||
|
const int32 BlockMaxX = FMath::Min(MaxLocalLattice,
|
||||||
|
BlockMinX + VoxelNoise::WormBlockSampleSide - 1);
|
||||||
|
const int32 BlockMaxY = FMath::Min(MaxLocalLattice,
|
||||||
|
BlockMinY + VoxelNoise::WormBlockSampleSide - 1);
|
||||||
|
const int32 BlockMaxZ = FMath::Min(MaxLocalLattice,
|
||||||
|
BlockMinZ + VoxelNoise::WormBlockSampleSide - 1);
|
||||||
|
|
||||||
|
const auto WorldAt = [Step, &TileOrigin](int32 Local, int32 Origin) -> int64
|
||||||
|
{
|
||||||
|
return static_cast<int64>(Origin) + static_cast<int64>(Local) * Step;
|
||||||
|
};
|
||||||
|
const int64 MinWorldX64 = WorldAt(BlockMinX, TileOrigin.X);
|
||||||
|
const int64 MinWorldY64 = WorldAt(BlockMinY, TileOrigin.Y);
|
||||||
|
const int64 MinWorldZ64 = WorldAt(BlockMinZ, TileOrigin.Z);
|
||||||
|
const int64 MaxWorldX64 = WorldAt(BlockMaxX, TileOrigin.X);
|
||||||
|
const int64 MaxWorldY64 = WorldAt(BlockMaxY, TileOrigin.Y);
|
||||||
|
const int64 MaxWorldZ64 = WorldAt(BlockMaxZ, TileOrigin.Z);
|
||||||
|
if (MinWorldX64 < MIN_int32 || MinWorldX64 > MAX_int32
|
||||||
|
|| MinWorldY64 < MIN_int32 || MinWorldY64 > MAX_int32
|
||||||
|
|| MinWorldZ64 < MIN_int32 || MinWorldZ64 > MAX_int32
|
||||||
|
|| MaxWorldX64 < MIN_int32 || MaxWorldX64 > MAX_int32
|
||||||
|
|| MaxWorldY64 < MIN_int32 || MaxWorldY64 > MAX_int32
|
||||||
|
|| MaxWorldZ64 < MIN_int32 || MaxWorldZ64 > MAX_int32)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FVector3f Q0 = VF_WormN1NoisePosition(
|
||||||
|
static_cast<float>(static_cast<int32>(MinWorldX64)),
|
||||||
|
static_cast<float>(static_cast<int32>(MinWorldY64)),
|
||||||
|
static_cast<float>(static_cast<int32>(MinWorldZ64)), Params, SeedU);
|
||||||
|
const FVector3f Q1 = VF_WormN1NoisePosition(
|
||||||
|
static_cast<float>(static_cast<int32>(MaxWorldX64)),
|
||||||
|
static_cast<float>(static_cast<int32>(MaxWorldY64)),
|
||||||
|
static_cast<float>(static_cast<int32>(MaxWorldZ64)), Params, SeedU);
|
||||||
|
const FVector3f QMin(
|
||||||
|
FMath::Min(Q0.X, Q1.X), FMath::Min(Q0.Y, Q1.Y), FMath::Min(Q0.Z, Q1.Z));
|
||||||
|
const FVector3f QMax(
|
||||||
|
FMath::Max(Q0.X, Q1.X), FMath::Max(Q0.Y, Q1.Y), FMath::Max(Q0.Z, Q1.Z));
|
||||||
|
const FVector3f QCenter(
|
||||||
|
static_cast<float>((static_cast<double>(QMin.X) + static_cast<double>(QMax.X)) * 0.5),
|
||||||
|
static_cast<float>((static_cast<double>(QMin.Y) + static_cast<double>(QMax.Y)) * 0.5),
|
||||||
|
static_cast<float>((static_cast<double>(QMin.Z) + static_cast<double>(QMax.Z)) * 0.5));
|
||||||
|
|
||||||
|
Entry.bSkip = VoxelNoise::ProvesScaledAbsPerlin3DAbove(
|
||||||
|
QCenter, QMin, QMax, Params.WormThreshold,
|
||||||
|
VoxelNoise::WormN1NoiseSpaceLipschitzBound);
|
||||||
|
Entry.bValid = true;
|
||||||
|
bOutProofComputed = true;
|
||||||
|
return Entry.bSkip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
// FRACTAL NOISE (fBm — fractional Brownian motion)
|
// FRACTAL NOISE (fBm — fractional Brownian motion)
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
@@ -1812,6 +2089,7 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_GetDensityAt);
|
TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_GetDensityAt);
|
||||||
VF_ParseFastIsFiniteSwitch();
|
VF_ParseFastIsFiniteSwitch();
|
||||||
VF_ParseFusedEvaluatorSwitch();
|
VF_ParseFusedEvaluatorSwitch();
|
||||||
|
VF_ParseWormBlockSkipSwitch();
|
||||||
VoxelDensityProfile::FScopedTimer DensityProfileTimer(
|
VoxelDensityProfile::FScopedTimer DensityProfileTimer(
|
||||||
VoxelDensityProfile::EBucket::GetDensityAt);
|
VoxelDensityProfile::EBucket::GetDensityAt);
|
||||||
VoxelDensityProfile::FScopedTimer DensityPrologueTimer(
|
VoxelDensityProfile::FScopedTimer DensityPrologueTimer(
|
||||||
@@ -3980,30 +4258,58 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
|||||||
|
|
||||||
if (NetworkMask > 0.0f)
|
if (NetworkMask > 0.0f)
|
||||||
{
|
{
|
||||||
float WormZFreq = Params.WormFrequency * Params.WormHorizontalBias;
|
const bool bCollectWormDiagnostics =
|
||||||
|
bCollectFusedDiagnostics && VoxelDensityProfile::AreCountersEnabled();
|
||||||
float N1 = FMath::Abs(VoxelNoise::Perlin3D(FVector3f(
|
if (bCollectWormDiagnostics)
|
||||||
WorldX * Params.WormFrequency + VoxelHash::SeedOffset(SeedU, 1.0f),
|
|
||||||
WorldY * Params.WormFrequency + VoxelHash::SeedOffset(SeedU, 1.7f),
|
|
||||||
EffectiveZ * WormZFreq + VoxelHash::SeedOffset(SeedU, 2.3f)
|
|
||||||
)) * VOXEL_NOISE_SCALE);
|
|
||||||
|
|
||||||
// N2 >= 0, so if N1 alone already clears the threshold the sum can't carve —
|
|
||||||
// skip the second Perlin entirely (most voxels; bit-identical output).
|
|
||||||
if (N1 < Params.WormThreshold)
|
|
||||||
{
|
{
|
||||||
float N2 = FMath::Abs(VoxelNoise::Perlin3D(FVector3f(
|
VoxelDensityProfile::AddCounter(
|
||||||
WorldX * Params.WormFrequency + VoxelHash::SeedOffset(SeedU, 1.0f) + 137.0f,
|
VoxelDensityProfile::ECounter::WormEligibleSamples);
|
||||||
WorldY * Params.WormFrequency + VoxelHash::SeedOffset(SeedU, 1.7f) + 259.0f,
|
}
|
||||||
EffectiveZ * WormZFreq + VoxelHash::SeedOffset(SeedU, 2.3f) + 431.0f
|
|
||||||
|
bool bWormBlockProofComputed = false;
|
||||||
|
const bool bSkipWormBlock = GVoxelForgeWormBlockSkip != 0
|
||||||
|
&& VF_TryGetWormBlockSkip(
|
||||||
|
DensityCacheOwnerId, SeedU, ParamsFingerprint, LayoutVersion,
|
||||||
|
WorldX, WorldY, WorldZ, Params, bWormBlockProofComputed);
|
||||||
|
if (bWormBlockProofComputed && bSkipWormBlock && bCollectWormDiagnostics)
|
||||||
|
{
|
||||||
|
VoxelDensityProfile::AddCounter(
|
||||||
|
VoxelDensityProfile::ECounter::WormBlockProofs);
|
||||||
|
}
|
||||||
|
if (bSkipWormBlock)
|
||||||
|
{
|
||||||
|
if (bCollectWormDiagnostics)
|
||||||
|
{
|
||||||
|
VoxelDensityProfile::AddCounter(
|
||||||
|
VoxelDensityProfile::ECounter::WormBlockSkippedSamples);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const float WormZFreq = Params.WormFrequency * Params.WormHorizontalBias;
|
||||||
|
const float N1 = FMath::Abs(VoxelNoise::Perlin3D(FVector3f(
|
||||||
|
WorldX * Params.WormFrequency + VoxelHash::SeedOffset(SeedU, 1.0f),
|
||||||
|
WorldY * Params.WormFrequency + VoxelHash::SeedOffset(SeedU, 1.7f),
|
||||||
|
EffectiveZ * WormZFreq + VoxelHash::SeedOffset(SeedU, 2.3f)
|
||||||
)) * VOXEL_NOISE_SCALE);
|
)) * VOXEL_NOISE_SCALE);
|
||||||
|
|
||||||
float WormValue = N1 + N2;
|
// N2 >= 0, so if N1 alone already clears the threshold the sum can't carve —
|
||||||
|
// skip the second Perlin entirely (most voxels; bit-identical output).
|
||||||
if (WormValue < Params.WormThreshold)
|
if (N1 < Params.WormThreshold)
|
||||||
{
|
{
|
||||||
float t = 1.0f - (WormValue / Params.WormThreshold);
|
const float N2 = FMath::Abs(VoxelNoise::Perlin3D(FVector3f(
|
||||||
Density -= t * Params.WormStrength * NetworkMask;
|
WorldX * Params.WormFrequency + VoxelHash::SeedOffset(SeedU, 1.0f) + 137.0f,
|
||||||
|
WorldY * Params.WormFrequency + VoxelHash::SeedOffset(SeedU, 1.7f) + 259.0f,
|
||||||
|
EffectiveZ * WormZFreq + VoxelHash::SeedOffset(SeedU, 2.3f) + 431.0f
|
||||||
|
)) * VOXEL_NOISE_SCALE);
|
||||||
|
|
||||||
|
const float WormValue = N1 + N2;
|
||||||
|
|
||||||
|
if (WormValue < Params.WormThreshold)
|
||||||
|
{
|
||||||
|
const float t = 1.0f - (WormValue / Params.WormThreshold);
|
||||||
|
Density -= t * Params.WormStrength * NetworkMask;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3860,6 +3860,16 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
|
|||||||
CycleDelta(VoxelDensityProfile::EBucket::RoomGraphBuild));
|
CycleDelta(VoxelDensityProfile::EBucket::RoomGraphBuild));
|
||||||
const double EvaluationSeconds = FMath::Max(
|
const double EvaluationSeconds = FMath::Max(
|
||||||
0.0, TileSeconds - CacheBuildSeconds);
|
0.0, TileSeconds - CacheBuildSeconds);
|
||||||
|
const uint64 WormEligibleSamples = CounterDelta(
|
||||||
|
VoxelDensityProfile::ECounter::WormEligibleSamples);
|
||||||
|
const uint64 WormBlockProofs = CounterDelta(
|
||||||
|
VoxelDensityProfile::ECounter::WormBlockProofs);
|
||||||
|
const uint64 WormBlockSkippedSamples = CounterDelta(
|
||||||
|
VoxelDensityProfile::ECounter::WormBlockSkippedSamples);
|
||||||
|
const double WormBlockSkipRate = WormEligibleSamples > 0
|
||||||
|
? static_cast<double>(WormBlockSkippedSamples)
|
||||||
|
/ static_cast<double>(WormEligibleSamples)
|
||||||
|
: 0.0;
|
||||||
UE_LOG(LogTemp, Display,
|
UE_LOG(LogTemp, Display,
|
||||||
TEXT("[VoxelForgeTileProfile] tile=(%d,%d,%d) level=%d step=%d cells=%d sheet=%d aborted=%d empty=%d "
|
TEXT("[VoxelForgeTileProfile] tile=(%d,%d,%d) level=%d step=%d cells=%d sheet=%d aborted=%d empty=%d "
|
||||||
"verdict=%d classify=%.6f mesh=%.6f streams=%.6f seconds=%.6f "
|
"verdict=%d classify=%.6f mesh=%.6f streams=%.6f seconds=%.6f "
|
||||||
@@ -3876,7 +3886,9 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
|
|||||||
"cache_op_local=%llu cache_classifier_shared=%llu cache_classifier_local=%llu "
|
"cache_op_local=%llu cache_classifier_shared=%llu cache_classifier_local=%llu "
|
||||||
"cache_unknown=%llu room_candidates=%llu room_evaluated=%llu "
|
"cache_unknown=%llu room_candidates=%llu room_evaluated=%llu "
|
||||||
"tunnel_candidates=%llu tunnel_evaluated=%llu tunnel_core_candidates=%llu "
|
"tunnel_candidates=%llu tunnel_evaluated=%llu tunnel_core_candidates=%llu "
|
||||||
"tunnel_core_evaluated=%llu support_column_candidates=%llu"),
|
"tunnel_core_evaluated=%llu support_column_candidates=%llu "
|
||||||
|
"worm_eligible=%llu worm_block_proofs=%llu worm_block_skipped=%llu "
|
||||||
|
"worm_skip_rate=%.6f"),
|
||||||
Tile.Coord.X, Tile.Coord.Y, Tile.Coord.Z, Tile.Level, Step, Cells,
|
Tile.Coord.X, Tile.Coord.Y, Tile.Coord.Z, Tile.Level, Step, Cells,
|
||||||
bSheetTile ? 1 : 0, Result.bAborted ? 1 : 0,
|
bSheetTile ? 1 : 0, Result.bAborted ? 1 : 0,
|
||||||
Result.bEmpty ? 1 : 0, ClassifyVerdict, ClassifySeconds, MeshSeconds, StreamSeconds,
|
Result.bEmpty ? 1 : 0, ClassifyVerdict, ClassifySeconds, MeshSeconds, StreamSeconds,
|
||||||
@@ -3918,7 +3930,11 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
|
|||||||
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::CaveTunnelEvaluated)),
|
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::CaveTunnelEvaluated)),
|
||||||
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::TunnelCoreCandidates)),
|
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::TunnelCoreCandidates)),
|
||||||
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::TunnelCoreEvaluated)),
|
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::TunnelCoreEvaluated)),
|
||||||
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::TunnelSupportColumnCandidates)));
|
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::TunnelSupportColumnCandidates)),
|
||||||
|
static_cast<unsigned long long>(WormEligibleSamples),
|
||||||
|
static_cast<unsigned long long>(WormBlockProofs),
|
||||||
|
static_cast<unsigned long long>(WormBlockSkippedSamples),
|
||||||
|
WormBlockSkipRate);
|
||||||
|
|
||||||
if (bProfileOps)
|
if (bProfileOps)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -150,6 +150,9 @@ namespace VoxelDensityProfile
|
|||||||
FusedTunnelSamples,
|
FusedTunnelSamples,
|
||||||
FusedTunnelDetailSamples,
|
FusedTunnelDetailSamples,
|
||||||
FusedTunnelDetailSkipped,
|
FusedTunnelDetailSkipped,
|
||||||
|
WormEligibleSamples,
|
||||||
|
WormBlockProofs,
|
||||||
|
WormBlockSkippedSamples,
|
||||||
OpBlockBuilds,
|
OpBlockBuilds,
|
||||||
OpBlockSamples,
|
OpBlockSamples,
|
||||||
OpBlockOperators,
|
OpBlockOperators,
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
|
#include "VoxelTypes.h"
|
||||||
|
|
||||||
#if !defined(VF_NOISE_USE_SIMD)
|
#if !defined(VF_NOISE_USE_SIMD)
|
||||||
#if PLATFORM_ENABLE_VECTORINTRINSICS && PLATFORM_CPU_X86_FAMILY
|
#if PLATFORM_ENABLE_VECTORINTRINSICS && PLATFORM_CPU_X86_FAMILY
|
||||||
@@ -141,6 +142,104 @@ FORCEINLINE float Perlin3D(const FVector3f& P)
|
|||||||
return Perlin3D(P.X, P.Y, P.Z);
|
return Perlin3D(P.X, P.Y, P.Z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//=============================================================================
|
||||||
|
// LIPSCHITZ BOUND FOR THE ACTUAL 3D FIELD
|
||||||
|
//=============================================================================
|
||||||
|
// This is deliberately derived from the implementation above, rather than borrowed from a
|
||||||
|
// textbook Perlin constant. GradDot's h&15 table has the twelve UNNORMALIZED gradients
|
||||||
|
//
|
||||||
|
// (±1,±1,0), (±1,0,±1), (0,±1,±1)
|
||||||
|
//
|
||||||
|
// (the four duplicate selectors do not add any vectors). For one partial derivative, write the
|
||||||
|
// four x-edges of the trilinear interpolation as independent gradient pairs. With
|
||||||
|
// d = Fade'(x) = 30 x²(1-x)², each edge contributes the support of the actual gradient set at
|
||||||
|
//
|
||||||
|
// c0 = (1-x-dx, -dy, -dz), c1 = (x-d(1-x), dy, dz).
|
||||||
|
//
|
||||||
|
// The support is max(|a|+|b|, |a|+|c|, |b|+|c|), because every allowed gradient has exactly two
|
||||||
|
// non-zero ±1 components. Taking the four y/z edge weights and the exact fade derivative, a
|
||||||
|
// finite case split over those three support branches has supremum 15/4 on [0,1]^3 (attained at
|
||||||
|
// x=y=z=1/2). This maximization permits every corner hash to choose any member of the actual
|
||||||
|
// set, so it is an upper bound for every hash cell; it is also the supremum of that finite support
|
||||||
|
// relaxation, not the looser 1 + 4*max(Fade') = 8.5 bound used by the unrelated 2D floor relief.
|
||||||
|
//
|
||||||
|
// The vector bound below follows the same per-axis-bound × sqrt(dimension) method used by the
|
||||||
|
// floor-relief proof. Abs is 1-Lipschitz and VOXEL_NOISE_SCALE is positive, so the scaled N1
|
||||||
|
// field has this bound in noise-space coordinates. WormBlockSkip computes its radius after the
|
||||||
|
// anisotropic world->noise transform, so the HBias and VerticalScale factors are included there.
|
||||||
|
static constexpr double Perlin3DPartialAbsBound = 15.0 / 4.0;
|
||||||
|
static constexpr double Perlin3DGradientBound =
|
||||||
|
Perlin3DPartialAbsBound * 1.732050807568877293527446341505872L;
|
||||||
|
static constexpr double WormN1NoiseSpaceLipschitzBound =
|
||||||
|
static_cast<double>(VOXEL_NOISE_SCALE) * Perlin3DGradientBound;
|
||||||
|
|
||||||
|
// A four-point side gives the natural small worker-local blocks for the mesher's 35-point
|
||||||
|
// (32 cells + one-sample normal halo) LOD0 lattice. The cache is only an optimization; a failed
|
||||||
|
// proof falls through to the original per-sample N1/N2 code.
|
||||||
|
static constexpr int32 WormBlockSampleSide = 4;
|
||||||
|
|
||||||
|
// The center value is evaluated with the same float Perlin expression as N1. The endpoint noise
|
||||||
|
// coordinates are already float results of monotone positive/negative affine transforms, so their
|
||||||
|
// min/max box contains every float lattice sample exactly. The 1e-3 scaled-N1 margin covers the
|
||||||
|
// remaining center-evaluation/interpolation rounding with substantial headroom; the proof remains
|
||||||
|
// strict because both the Lipschitz term and this margin are subtracted before comparing with T.
|
||||||
|
static constexpr double WormBlockOutputRoundMargin = 1.0e-3;
|
||||||
|
|
||||||
|
FORCEINLINE bool ProvesScaledAbsPerlin3DAbove(
|
||||||
|
const FVector3f& CenterNoisePosition,
|
||||||
|
const FVector3f& MinNoisePosition,
|
||||||
|
const FVector3f& MaxNoisePosition,
|
||||||
|
float Threshold,
|
||||||
|
double ScaledFieldLipschitzBound)
|
||||||
|
{
|
||||||
|
if (!FMath::IsFinite(CenterNoisePosition.X)
|
||||||
|
|| !FMath::IsFinite(CenterNoisePosition.Y)
|
||||||
|
|| !FMath::IsFinite(CenterNoisePosition.Z)
|
||||||
|
|| !FMath::IsFinite(MinNoisePosition.X)
|
||||||
|
|| !FMath::IsFinite(MinNoisePosition.Y)
|
||||||
|
|| !FMath::IsFinite(MinNoisePosition.Z)
|
||||||
|
|| !FMath::IsFinite(MaxNoisePosition.X)
|
||||||
|
|| !FMath::IsFinite(MaxNoisePosition.Y)
|
||||||
|
|| !FMath::IsFinite(MaxNoisePosition.Z)
|
||||||
|
|| !FMath::IsFinite(Threshold)
|
||||||
|
|| !(Threshold > 0.0f)
|
||||||
|
|| !FMath::IsFinite(ScaledFieldLipschitzBound)
|
||||||
|
|| !(ScaledFieldLipschitzBound > 0.0))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const double Dx = FMath::Max(
|
||||||
|
FMath::Abs(static_cast<double>(MinNoisePosition.X)
|
||||||
|
- static_cast<double>(CenterNoisePosition.X)),
|
||||||
|
FMath::Abs(static_cast<double>(MaxNoisePosition.X)
|
||||||
|
- static_cast<double>(CenterNoisePosition.X)));
|
||||||
|
const double Dy = FMath::Max(
|
||||||
|
FMath::Abs(static_cast<double>(MinNoisePosition.Y)
|
||||||
|
- static_cast<double>(CenterNoisePosition.Y)),
|
||||||
|
FMath::Abs(static_cast<double>(MaxNoisePosition.Y)
|
||||||
|
- static_cast<double>(CenterNoisePosition.Y)));
|
||||||
|
const double Dz = FMath::Max(
|
||||||
|
FMath::Abs(static_cast<double>(MinNoisePosition.Z)
|
||||||
|
- static_cast<double>(CenterNoisePosition.Z)),
|
||||||
|
FMath::Abs(static_cast<double>(MaxNoisePosition.Z)
|
||||||
|
- static_cast<double>(CenterNoisePosition.Z)));
|
||||||
|
const double Radius = FMath::Sqrt(Dx * Dx + Dy * Dy + Dz * Dz);
|
||||||
|
|
||||||
|
// Keep this operation in float and in the same order as the production N1 expression. The
|
||||||
|
// double conversion is only for the conservative comparison below.
|
||||||
|
const float CenterN1 = FMath::Abs(
|
||||||
|
Perlin3D(CenterNoisePosition) * VOXEL_NOISE_SCALE);
|
||||||
|
if (!FMath::IsFinite(CenterN1))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return static_cast<double>(CenterN1)
|
||||||
|
- ScaledFieldLipschitzBound * Radius
|
||||||
|
>= static_cast<double>(Threshold) + WormBlockOutputRoundMargin;
|
||||||
|
}
|
||||||
|
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
// 4-WIDE BATCH — the SIMD multiplier. Computes 4 independent Perlin samples.
|
// 4-WIDE BATCH — the SIMD multiplier. Computes 4 independent Perlin samples.
|
||||||
// Inputs are 4-element arrays; unused lanes must be zero-filled by the caller
|
// Inputs are 4-element arrays; unused lanes must be zero-filled by the caller
|
||||||
|
|||||||
Reference in new issue
Block a user