docs(research): speed research -- sparse compiled bricks, field collision, edit transactions

Sol 6 (high, web search) research round: recommendation, three architectures, determinism
contract, migration with a 5-day standalone kill test. Standalone probe in
Saved/Research/brick_kernel.cpp (not VoxelForge code): 16.8M integer samples in ~0.36-0.48 s,
brick-classified ~0.1 s, 0 sign mismatches.

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-29 15:31:58 +02:00
1 parent bf22f0ffe0
commit 1568f76ca2
2 files changed
+234

No files matched your search

+107
View File
@@ -0,0 +1,107 @@
# VoxelForge speed research — 29 September 2026
## Answer first
**Recommendation: compile the procedural plan into deterministic, sparse 8³-voxel bricks; mesh only bricks whose final field can cross zero; use direct field collision for immediate readiness, with cooked mesh collision relegated to a background compatibility path.** Keep the 25 cm lattice, full 3D operators, walkable route graph and seed/edit replication. Replace the point-at-a-time operator search with feature-to-brick binning, brick-constant operator state, integer or specified fixed-point evaluation, shared lattice samples, and a batched render upload. This is a new generation and collision architecture, not another tuning pass on the current mesher. OpenVDB's hierarchical sparse volume and narrow-band treatment, Voxel Plugin's near-surface storage, and the volumetric terrain example in GPU Gems 3 support the *shape* of the design, not its expected VoxelForge time. [OpenVDB paper](https://museth.org/Ken/Publications_files/Museth_TOG13.pdf), [Voxel Plugin performance notes](https://docs.voxelplugin.com/1.2/technical-notes/performance-and-profiling), [GPU Gems 3 volumetric terrain](https://developer.nvidia.com/gpugems/gpugems3/part-i-geometry/chapter-1-generating-complex-procedural-terrains-using-gpu).
**The one-second target is plausible but not proved.** The arithmetic is decisive about what must change:
| Quantity | Arithmetic or measurement | Consequence |
|---|---:|---|
| Current tile requests | 512 × 35³ = **21,952,000** samples | At the supplied ~2 µs/sample this is 43.9 CPU-seconds before collision/upload. |
| Shared lattice for 64 m cube plus halo | 259³ = **17,373,979** unique points | Sharing alone removes 20.9% of requests, nowhere near enough. |
| Required density rate if generation gets 0.5 s on six cores | 17.374 M / 0.5 / 6 = **5.79 M samples/core/s**, or **173 ns/sample** | Current ~2 µs/sample needs about **11.6×** improvement even after sharing if every point is evaluated. This ignores meshing and upload. |
| Current render apply cap | ~341 nonuniform tiles if one third are uniform; 4 applies/frame = 85.3 frames, **1.42 s at 60 Hz** | Even free workers cannot meet one second with one component per tile and the current apply cap. Batch approximately 64 groups of 2³ tiles, or use a single batched stream with independently editable sections. The actual game-thread time remains unmeasured. |
| Current collision | Owner's 0.4–1.4 s submit-to-ready **per tile** | A per-tile cook cannot be on the critical path. Async cooking only moves CPU work; it does not make collision available before completion. [Epic BodySetup API](https://dev.epicgames.com/documentation/unreal-engine/API/Runtime/Engine/UBodySetup/CreatePhysicsMeshesAsync), [RMC documentation](https://github.com/TriAxis-Games/RealtimeMeshComponent/blob/master/Docs/index.md). |
| Running at 8 m/s ([local design brief](Docs/archive/DESIGN-SOL-2026-09-15.md)) | One 8 m tile crossing/s brings one 8×8 face = **64 new tiles/s**; 64 × 35³ = **2.744 M requests/s** | At current ~2 µs/request, that is ~5.49 CPU-seconds/s, or ~0.915 s on six ideal workers before upload/collision. A shared 32-cell slab is 259² × 32 = 2.147 M new points, still ~0.716 s on six ideal workers. Field collision on demand is also the safety net for unexpected movement or stalls. |
**Measured standalone feasibility probe on this machine:** A synthetic 256³ 3D field with 48 integer sphere features and three octaves of integer lattice noise took **471–481 ms** for 16,777,216 samples using six threads. Conservatively classifying 32,768 8³ bricks took **15.46 ms**; evaluating only the 7,563 uncertain bricks then took **106–130 ms**, with **zero sign mismatches** against the full evaluation over all 16,777,216 points. An eight-thread repeat took 349–367 ms full or 86–110 ms classified plus 15.59 ms classification. This is a *kernel probe*, not VoxelForge or a measured 512-tile load. Its primitive list and noise are far simpler than the current composer stacks. It shows that the required rate is physically credible on this CPU when work is converted to bounded integer kernels; it does **not** establish that the full game, render upload, or Chaos field integration finishes in one second. Benchmark details and limitations are below.
**Build cost:** a substantial rewrite, roughly **3–6 engineer-months for a first playable replacement, then 2–4 months of compatibility/performance hardening** (planning estimate, not measured productivity). The hard part is the numeric ABI and Chaos/character collision integration, followed by edit transaction rendering. A cheap proof or kill experiment is specified under Migration. If exact arbitrary rigid-body collision across all 512 tiles must be active through ordinary Chaos body cooking, the present evidence gives **no credible route to <1 s**. The owner would need to accept direct-field physics integration or relax that interpretation. Also, an unbounded-size edit cannot have a fixed same-frame CPU cost; the game needs a bounded interactive brush contract or an immediate GPU visual edit overlay.
## Local evidence and what it means
The code currently requests 35³ values for each 32-cell marching-cubes tile, and `GetDensityAt` builds/evaluates the stack in `VoxelGenerator.cpp`; `VoxelMarchingCubesMesher.cpp` spends most worker time filling the grid. The existing attribution measured **590.923 ms of 606.128 ms** (97.49%) for a shared-grid fill in a 128³ TunnelNetwork export, with only ~15.2 ms for later meshing/merge. It also measured an interval-classifier route that grew from **0.588 s to 2.057 s** instead of helping. Thus “add generic interval recursion” is already a failed local control; the new plan must compile *cheap, conservative bounds and feature lists per brick*, and fall back to evaluation if a bound is uncertain. [Local attribution](Docs/archive/PERF-ATTRIBUTION.md), [failed early-out control](Docs/archive/CODEX-TASK-2-PERF-REPORT.md).
The owner's **0.4–1.4 s** collision figure is submit-to-ready in the current observation window. The archived 12 September headless run reported a **0.052945 s p95 cook stage** in its different workload and instrumentation window. Neither number is a per-triangle CPU cost, and they should not be pooled or multiplied into a 512-tile total. The planned Unreal spike must capture the new ring's cold maximum, not reuse either statistic. [Archived local timing](Docs/archive/PERF-ATTRIBUTION.md).
The runtime already prioritizes edited and collision-critical work, but it applies at most four visible meshes per frame by default (`VoxelSettings.h:65`) and `ApplyModification` synchronously remeshes only the center tile while neighbors are asynchronous (`VoxelWorld.cpp:10369`). Underfoot carving can disable the old support component until recook (`VoxelWorld.cpp:10290`), directly explaining a transient disappearance. RMC can preserve old cooked collision while a replacement cooks (`VoxelWorld.cpp:9858`), which explains ghost support if the removed surface is still in that body. The default pawn collision box is radius one tile, **3³ = 27 tiles**; the requested 512-tile collidable ring is a much stronger target than this implementation currently attempts (`VoxelSettings.h:88`, `VoxelWorld.cpp:8168`). These are code observations, not new timing measurements.
### Standalone measurement, reproducible
Code: [`Saved/Research/brick_kernel.cpp`](Saved/Research/brick_kernel.cpp). Built only this standalone program with installed MSVC; Unreal was not built or launched. From `Saved\Research` in PowerShell:
```powershell
cmd /c '"E:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat" >nul && cl /nologo /O2 /std:c++20 /EHsc /arch:AVX2 brick_kernel.cpp /Fe:brick_kernel.exe'
./brick_kernel.exe
cmd /c '"E:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat" >nul && cl /nologo /O2 /std:c++20 /EHsc /arch:AVX2 /DVF_WORKERS=6 brick_kernel.cpp /Fe:brick_kernel_6.exe'
./brick_kernel_6.exe
```
The `vcvars64.bat` initialization printed a harmless missing-`vswhere.exe` notice; `cl` compiled and both programs exited successfully. Values are wall milliseconds from `std::chrono::steady_clock`, three repeats, Windows on the stated machine:
| Stage | Eight workers (ms) | Six workers (ms) | Work |
|---|---|---|---|
| Full evaluation | 362.538, 367.161, 349.001 | 479.017, 470.581, 481.011 | 16,777,216 grid points, each checks 48 spheres and computes 3 octave integer value noise. |
| Brick classification | 15.590 | 15.464 | 32,768 bricks: 24,778 solid, 427 air, 7,563 uncertain. Conservative sphere/AABB extrema plus a proven ±224 noise envelope. |
| Classified evaluation | 109.641, 86.959, 86.439 | 106.184, 129.769, 108.978 | Full evaluation only for uncertain bricks; classified bricks retain their correct sign. |
| Verification | 0 sign mismatches / 16,777,216 | 0 sign mismatches / 16,777,216 | The sign of every classified point was checked against the full run. Density *magnitudes* in certified uniform bricks were intentionally not preserved. |
The probe lacks passages, smooth Boolean operators, strata boundaries, edits, meshing, CPU cache competition with UE, upload and collision. Its 76.9% uniform-brick rate is **synthetic** and must never be substituted for the game's observed one-third uniform *tile* rate. The timing is evidence for the kernel's potential only. Full production sign/mesh/collision parity across seeds, strates, edits and CPU models is a release gate. The probe uses scalar integer code compiled with `/arch:AVX2`; that switch alone does not prove SIMD vectorization. AVX2 is a later optimization target, not a factor claimed in these numbers.
## Three architectures considered
| Architecture | Representation and pipeline | Main advantage | Main cost/risk | Verdict |
|---|---|---|---|---|
| **A. Sparse compiled bricks + CPU contouring + field collision** | Seed/layout compile to a stable feature plan. Bin each primitive to touched bricks. Each 8³ brick stores a final-sign certificate (solid/air/uncertain), compact density band and edit version. Mesh uncertain bricks with marching cubes or surface nets; group tiles for upload. Query the same canonical field directly for collision. | Eliminates most repeated feature traversal, supports edits and 3D topology, removes cook from readiness. | Conservative certificates may fail on complex operator stacks; custom Chaos collision is unproved. | **Recommended experiment.** |
| **B. Analytic CSG/BVH + raymarch render and collision** | Keep a spatially indexed graph of implicit primitives and bounded noise, no resident sampled terrain except caches. GPU raymarches image-space rays through an acceleration structure; CPU queries analytic field for physics. Edits add CSG nodes. | Very long view and same-frame edits can avoid remeshing entirely. | Full-screen/stereo ray cost, non-SDF operators break safe sphere tracing, shadows/materials/UE integration; many edits enlarge BVH and query cost. It changes visual appearance. | Radical alternative if meshing/upload still dominate after A. |
| **C. Integer GPU brick generation + compute meshing** | CPU generates canonical integer plan/brick list; compute shader evaluates fixed-point density and emits deterministic mesh indices by prefix-sum, while CPU uses the same integer evaluator for authoritative collision. | Could offload 17–22 M sample and contour work; GPU Gems 3 demonstrates fully 3D GPU generation rather than a heightfield. | Cross-vendor integer ABI, GPU readback/synchronization for collision, VR frame contention, runtime mesh integration. | Research branch only until exact AMD/NVIDIA and CPU/GPU hash parity is demonstrated. |
Architecture A should treat the **plan as the source of truth** and bricks as disposable caches. Compile op nodes once per region into a fixed order and collect local feature references by feature bounding box, with an explicit bound for noise, warps and edits. A brick certificate must enclose the **final** composed field: an operator that can add solid after a carve invalidates a carve-only proof. If any op cannot supply a safe bound, mark the brick uncertain. The current interval route's measured slowdown is a warning that a proof must cost less than the points it avoids. A local feature raster pass reverses the present “each point searches nearby features” loop and keeps shape data hot in cache. The sparse hierarchy stores occupied, empty and uncertain bricks; a compact, flat Morton or Z-major page table is likely better for the active 64 m cube than a pointer-heavy tree. OpenVDB provides a precedent for sparse, editable narrow-band fields, while sparse voxel octrees/DAGs are more compelling for static repeated geometry than for heavily edited procedural bricks. [OpenVDB](https://museth.org/Ken/Publications_files/Museth_TOG13.pdf), [Laine and Karras SVO report](https://research.nvidia.com/sites/default/files/pubs/2010-02_Efficient-Sparse-Voxel/laine2010tr1_paper.pdf), [Kämpe et al. sparse voxel DAG](https://www.cse.chalmers.se/~uffe/HighResolutionSparseVoxelDAGs.pdf).
Retain marching cubes initially: in the local measured export, evaluation dominates, so changing the mesher first has little expected payoff. A later surface-nets or dual-contouring trial may reduce vertex/triangle output or preserve sharp authored features, but QEF placement, topology and transitions add complexity; benchmark the *whole upload path* before changing. Dual contouring's paper addresses Hermite samples and adaptive octrees; Transvoxel has explicit transition cells for adjacent marching-cubes LODs. The current skirts can visually hide cracks but are not a geometric continuity proof. [Dual contouring paper](https://people.eecs.berkeley.edu/~jrs/meshpapers/JuLosassoSchaeferWarren.pdf), [Transvoxel author](https://transvoxel.org/), [surface nets paper](https://www.sciweavers.org/publications/constrained-elastic-surface-nets-generating-smooth-surfaces-binary-segmented-data).
For A's collision, store an immutable field snapshot and expose a broad-phase AABB per resident spatial page, then implement exact local ray/shape queries against its canonical lattice or a small on-demand triangle patch. For player movement, use a purpose-built capsule sweep against the field and make support state an explicit function of the new snapshot. For arbitrary Chaos rigid bodies, test a custom `FImplicitObject`/geometry-query adapter; UE 5.7's installed `Chaos/ImplicitObject.h` exposes virtual `PhiWithNormal` and `Raycast`, but that does **not** prove the complete broad-phase, GJK/contact, CharacterMovement, navmesh or replication integration. A conservative collision box cannot stand in for a cave: it would block playable air. A signed density is not necessarily a distance bound, so ordinary sphere tracing may step through a thin wall; use interval subdivision/voxel traversal or a certified Lipschitz bound. [Chaos API](https://dev.epicgames.com/documentation/unreal-engine/API/Runtime/Chaos), [Hart sphere tracing](https://experts.illinois.edu/en/publications/sphere-tracing-a-geometric-method-for-the-antialiased-ray-tracing/).
## Determinism contract
**A:** Define a versioned numeric ABI: integer world coordinates; fixed-point density with specified scale, rounding, saturation, signed division and overflow; hash-keyed integer lattice noise; explicit polynomial or lookup approximations where needed; fixed op order by stable feature ID; fixed interpolation and tie-breaking at density zero; fixed material selection. Use 64-bit intermediates where 32-bit products could overflow. Brick jobs can run in any order because each writes a disjoint key and canonical shared samples have a single owner. SIMD integer lanes must produce bit-for-bit the same values as the scalar reference; run full-world and edit hashes on distinct x64 CPU models before release. An AVX2 path may accelerate independent lanes, but never change reduction order. FastNoise2 shows the potential of fused SIMD noise graphs, yet its published throughput is for its own noises and does not establish VoxelForge speed or bit parity. [FastNoise2 code and benchmarks](https://github.com/Auburn/FastNoise2), [MSVC floating-point rules](https://learn.microsoft.com/en-us/cpp/build/reference/fp-specify-floating-point-behavior?view=msvc-170).
**B:** Analytic shape transforms and BVH construction must use that same fixed-point ABI and stable sort; GPU raymarch output is visual only unless its field math passes bitwise parity. Floating-point `sin`, `sqrt`, FMA and different compiler choices can move zero crossings or BVH decisions. `/fp:precise` helps source ordering but is not a cross-build magic guarantee; the MSVC CRT selects different transcendental algorithms by CPU FMA3 support. Keep the authority in integer CPU code. [MSVC `/fp`](https://learn.microsoft.com/en-us/cpp/build/reference/fp-specify-floating-point-behavior?view=msvc-170), [CRT FMA3 selection](https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/get-fma3-enable-set-fma3-enable?view=msvc-170).
**C:** Floating-point GPU density is ruled out for world decisions: Vulkan itself is not pixel exact, and available shader float controls have capability/operation limits. An integer GPU path is *potentially* deterministic only after restricting operations to defined 32-bit integer add/XOR/shift/multiply, explicitly emulating wider arithmetic if needed, forbidding unordered atomics for output order, using deterministic prefix sums, and hashing complete density/sign/mesh buffers on AMD, NVIDIA and CPU after every shader/compiler/driver class change. Until those parity tests pass, never make the GPU result authoritative for collision or replicated world state. [Vulkan invariance](https://docs.vulkan.org/spec/latest/appendices/invariance.html), [float controls proposal](https://github.com/KhronosGroup/Vulkan-Docs/blob/main/proposals/VK_KHR_shader_float_controls2.adoc). GPU Gems 3 demonstrates speed opportunities for 3D terrain, but its floating-point GPU pipeline is no determinism proof. [GPU Gems 3](https://developer.nvidia.com/gpugems/gpugems3/part-i-geometry/chapter-1-generating-complex-procedural-terrains-using-gpu).
## Preserving the game
**3D and composition.** The plan retains volumetric SDF/CSG regions, mountains, caves, overhangs and islands. A surface heightfield clipmap alone is disallowed: the original geometry-clipmap paper explicitly targets heightfields. Each current archetype, composer tag, structural post, passage and diff becomes a compiled op with a finite or explicitly unbounded influence declaration. [Geometry clipmaps paper](https://hhoppe.com/geomclipmap.pdf). The shipped examples support broad direction, not a claimed matching performance number: No Man's Sky describes its voxel-to-polygon pipeline and reports a later dual-marching-cubes rewrite improved loading; Astroneer describes re-polygonizing modified chunks; neither publishes a directly comparable one-second/64 m benchmark. Teardown's movable voxel volumes and packed occupancy hierarchy solve a different destructible-object problem, and Enshrouded's developer account confirms editable voxel terrain without exposing a transferable throughput figure. Dual Universe demonstrates a multiplayer editable voxel world, but its published material likewise gives no comparable generation latency. [NMS GDC talk](https://www.gdcvault.com/play/1024265/), [NMS update](https://www.nomanssky.com/worlds-part-I-update/), [Astroneer developer account](https://www.gamedeveloper.com/design/what-i-astroneer-i-s-devs-learned-while-leaving-early-access), [Teardown developer's voxel rendering account](https://blog.voxagon.se/2018/10/17/from-screen-space-to-voxel-space.html), [Enshrouded developer AMA](https://enshrouded.com/pl-PL/news/enshrouded-world-design-team-reddit-ama-recap), [Dual Universe developer diary](https://www.youtube.com/watch?v=MWcuwt9bjHo).
**Walk guarantee.** Keep the composer-generated entrance/exit route as an explicit graph of swept, capsule-clear walking corridors and landing floors. Apply that route as a late, protected operator whose minimum clearance and maximum slope have margins below the player capsule's mechanical limit; validate the final sampled collision representation with capsule sweeps. Decorative ops cannot narrow the protected clearance. If the guarantee must survive *arbitrary filling and floor removal by players*, unrestricted edits and guaranteed traversal conflict: protect the last path, auto-build replacement support, or interpret the guarantee as applying only before edits. This is a product decision; the current code's generated route tests do not settle post-edit behavior.
**Clean digging.** Treat one edit as a transaction: allocate a monotonically numbered edit ID, compute every affected brick and one-cell halo from a single immutable pre-edit snapshot, generate replacement mesh pages, and publish *one* new field/render/collision epoch at a frame boundary. Border vertices use global lattice coordinates and a single owner, with identical edit IDs on both sides. A single render-thread command must switch the complete page set; a sequence of independent RMC section updates is not an atomic render publish. The physics snapshot switches at its matching simulation boundary. During preparation keep the old render and collision snapshot together; at commit swap all touched pages together. Never disable the support body before the replacement is ready, and never let a completed old cook restore it. Character velocity is carried across support replacement only from actual displacement/input; cancel contacts from an obsolete body and recompute grounded state without inventing a downward impulse. A 1 m-radius interactive brush touches at most eight 2 m bricks in the ordinary case; a one-cell halo may add more, so measure the exact maximum fanout. **This does not yet prove same-frame edits.** For bounded brushes, all preparation and the render/physics swap must finish before that frame's submission. For arbitrary larger brushes, render an immediate fixed-point edit overlay covering the whole brush in the same frame while batched meshes catch up, or define a maximum interactive brush size and prove its worst-case transaction time on the target and in VR. Both options require a real UE measurement. Network edits carry server order and ID; prediction displays the same transaction locally and reconciles on authoritative order.
**Streaming and long view.** Keep a 3D clipmap but make LOD1 available first at 16 cells per tile: 19³/35³ = **0.160** of today's per-tile sample requests, an arithmetic count, not a measured 6.25× wall speedup. Applying that ratio to the supplied 16.5 M LOD1 requests yields **2.64 M requests**; at the old ~2 µs/request this is **5.28 CPU-seconds**, or **0.88 s on six ideally scaled cores**, before upload. This arithmetic makes “a few seconds” credible for the preview even without the new brick speed, but it is no end-to-end guarantee. Refine to 32 cells later. Reuse lattice coordinates when coarse samples coincide with resident fine samples; outside the fine cube, evaluate coarse bricks independently. A far shell should be surface-aware and perhaps use a much lower-resolution 3D contour plus impostor for sealed distant strata; preserve visible caves/islands rather than flattening to terrain height. Use Transvoxel or a tested equivalent when meshes at unequal LOD meet. Prioritize spawn/teleport center, movement direction, falling support and edits; apply back-pressure to far work. UE Tasks priorities affect scheduling but do not preempt a running expensive tile; divide work into cancellable bricks. [UE Tasks documentation](https://dev.epicgames.com/documentation/unreal-engine/tasks-systems-in-unreal-engine), [Transvoxel](https://transvoxel.org/).
**Multiplayer.** Replicate seed, numeric ABI/content hash, compact plan/layout ID and ordered edit log. Do not replicate bricks, meshes or cooked physics. Hash canonical brick signs, surface vertices and collision answers on clients; reject incompatible builds rather than silently diverging. An edit arrives as one transaction with all affected brick IDs, never independent tile messages. For late joins, bound and checkpoint the edit log: Teardown's developer describes why replaying a deterministic command stream saves bandwidth but can become too slow after many edits. This is a qualitative design warning, not a VoxelForge timing estimate. [Teardown multiplayer technical account](https://blog.voxagon.se/2026/03/13/teardown-multiplayer.html).
## Unreal rendering and Nanite
Keep RMC for the first experiment, but reduce component count by grouping spatial pages and separating render pages from collision. Replacing it with ProceduralMeshComponent only changes the wrapper around the same asynchronous cook; Epic documents `bUseAsyncCooking`, not a latency guarantee. `UDynamicMeshComponent` has useful edit representation but Epic lists no Nanite/LOD support and warns about render/memory efficiency. RMC's current upstream documentation claims runtime Nanite, and the locally installed RMC 5.4 source contains a runtime Nanite module and provider checks. **Revisit Nanite as an isolated *far/mostly static render* benchmark**, including PC VR stereo, page-build time, edit rebuild time and fallback mesh cost. It does not solve density evaluation or Chaos collision, and the first one-second ring should not wait for Nanite conversion. Nanite collision uses a fallback mesh, not Nanite triangles. [PMC API](https://dev.epicgames.com/documentation/unreal-engine/API/Plugins/ProceduralMeshComponent/UProceduralMeshComponent), [Epic dynamic-mesh guide](https://dev.epicgames.com/documentation/unreal-engine/geometry-scripting-users-guide-in-unreal-engine), [RMC docs](https://github.com/TriAxis-Games/RealtimeMeshComponent/blob/master/Docs/index.md), [Epic Nanite fallback details](https://dev.epicgames.com/documentation/unreal-engine/nanite-technical-details).
## Migration and decisive experiment
1. **Freeze a reference corpus.** Record seed/layout/edit cases for all eight archetypes, passage boundaries, the walk validator, 3D overhangs and the current 128³ profiling scene. Save cell signs, surface hashes, capsule-query answers and production wall-time ledgers. Keep the existing composer, content semantics and diff protocol initially; preserve old saves with a versioned generator.
2. **First cheap kill test (estimate: five working days), standalone then UE when permitted:** extract the actual TunnelNetwork hot path and one passage-bearing mixed recipe into a standalone harness, using recorded seed/layout inputs and output samples from the reference corpus. Compile each plan to fixed per-brick feature lists and mathematically conservative final-field bounds. Measure all 512-tile ring cells on this Ryzen, six worker threads, cold cache, with exact sign parity against `GetDensityAt`, including edited borders. **Continue only if** the cold 64 m cube reaches ≤0.5 s worker wall (or a measured surface-only equivalent), with zero false uniform proofs and reasonable memory. The synthetic benchmark in this report is not this test. A high “uncertain” rate *combined with* slow local feature evaluation, or per-brick proof cost like the current interval route, kills the plan.
3. **Collision spike before a rewrite:** integrate one custom field page into Chaos and CharacterMovement in a throwaway UE test scene, including ray, capsule sweep, rigid body contact and underfoot edit. Require exact query/readiness timing under 0.1–0.2 s across a cold 64 m cube, no false velocity, and no collision/visual epoch mismatch. If arbitrary Chaos physics cannot use the field representation, cost a background cooked-body hybrid or revise the target explicitly.
4. **Render spike:** feed the real 512-tile corpus into 2³-tile grouped RMC pages, measure game/render-thread upload, RHI fence, VR stereo frame time, and a one-frame multi-page edit transaction. Budget approximately 0.2–0.3 s of the one-second window for upload and publication; this is a proposed acceptance budget, **not measured**.
5. **Only after those gates:** port archetypes/composer ops, replace local density cache and classification, add fixed-point parity CI on Intel/AMD, then LOD transition and horizon work. Retain the old path behind a version switch until saves and multiplayer traces match or migration is explicit. The next-level target gets its own cold timing with the intended 16-cell preview and 32-cell refinement clocks reported separately.
## What not to do
- Do not infer a 512-tile deadline from one tile's p95, or from the synthetic 0.1 s classified benchmark. Queueing, bandwidth, upload and physics are separate clocks.
- Do not put another 512 Chaos triangle cooks on the readiness path. This conflicts with the observed 0.4–1.4 s per-tile completion window even if generation became free.
- Do not assume a sparse octree/DAG or OpenVDB automatically speeds a procedural query. Their compression and hierarchy help only if the final-field signs/bounds can be computed cheaply; edits and arbitrary smooth operators can make most nodes uncertain.
- Do not rely on heightfield clipmaps, GPU float world decisions, unordered GPU atomics, or `sin`/CRT results as a network world ABI.
- Do not claim AVX2, dual contouring, Nanite or a different mesh component will erase the observed 97% density cost. Benchmark each in its proper stage.
- Do not publish one touched tile while its neighbors still show the old edit; do not suppress collision under a pawn before a consistent replacement exists. Those are the visible ripple, vanish and false-support failure modes in the present implementation.
**Decision still required:** whether “all 512 tiles collidable” includes general Chaos rigid bodies or means exact player/projectile queries anywhere in the cube, and whether interactive dig tools may have a bounded brush extent. With unrestricted rigid-body cooking and unbounded same-frame edit size, no honest sub-second guarantee follows from the available evidence. The research recommendation preserves the full 3D game by changing the collision and edit contracts in implementation, but its final deadline must be proven by the two UE integration spikes above.
+127
View File
@@ -0,0 +1,127 @@
// Standalone throughput probe, not VoxelForge's generator or an Unreal integration.
// Build from a VS 2022 x64 Native Tools prompt:
// cl /nologo /O2 /std:c++20 /EHsc /arch:AVX2 brick_kernel.cpp /Fe:brick_kernel.exe
#include <algorithm>
#include <array>
#include <chrono>
#include <cstdlib>
#include <cstdint>
#include <cstdio>
#include <thread>
#include <vector>
constexpr int N = 256, B = 8, NB = N / B;
#ifndef VF_WORKERS
#define VF_WORKERS 8
#endif
struct Sphere { int x, y, z, r; };
static std::array<Sphere, 48> spheres;
static std::vector<int16_t> field(size_t(N) * N * N);
static std::vector<uint8_t> reference_sign(size_t(N) * N * N);
static volatile uint64_t sink = 0;
static uint32_t hash3(int x, int y, int z, uint32_t seed) {
uint32_t h = seed ^ (uint32_t(x) * 0x9e3779b1u) ^
(uint32_t(y) * 0x85ebca77u) ^ (uint32_t(z) * 0xc2b2ae3du);
h ^= h >> 16; h *= 0x7feb352du; h ^= h >> 15;
h *= 0x846ca68bu; return h ^ (h >> 16);
}
// Integer lattice noise: fixed shifts and products; no FP/CRT/FMA.
static int noise(int x, int y, int z) {
int sum = 0;
for (int octave = 0; octave != 3; ++octave) {
int s = 16 >> octave, ix = x / s, iy = y / s, iz = z / s;
int fx = (x % s) * 256 / s, fy = (y % s) * 256 / s, fz = (z % s) * 256 / s;
uint32_t seed = 1337u + uint32_t(octave) * 17u;
int yz[2];
for (int dz = 0; dz != 2; ++dz) {
int yy[2];
for (int dy = 0; dy != 2; ++dy) {
int a = int(hash3(ix, iy + dy, iz + dz, seed) & 255u);
int b = int(hash3(ix + 1, iy + dy, iz + dz, seed) & 255u);
yy[dy] = a + ((b - a) * fx >> 8);
}
yz[dz] = yy[0] + ((yy[1] - yy[0]) * fy >> 8);
}
int v = yz[0] + ((yz[1] - yz[0]) * fz >> 8);
sum += (v - 128) >> octave;
}
return sum; // in [-224, 222], conservatively use +/-224.
}
static int density(int x, int y, int z) {
int d = -10000;
for (const Sphere& s : spheres) {
int dx=x-s.x, dy=y-s.y, dz=z-s.z;
int q=s.r*s.r-dx*dx-dy*dy-dz*dz;
d=std::max(d,q);
}
return d + noise(x,y,z);
}
static size_t at(int x,int y,int z) { return (size_t(z)*N+y)*N+x; }
template<class F> static double run(F&& f) {
auto a=std::chrono::steady_clock::now(); f();
return std::chrono::duration<double,std::milli>(std::chrono::steady_clock::now()-a).count();
}
static void parallel_z(auto fn) {
std::array<std::thread,VF_WORKERS> t;
for (int i=0;i<VF_WORKERS;++i) t[i]=std::thread([=]{
fn((i*NB/VF_WORKERS)*B, ((i+1)*NB/VF_WORKERS)*B);
});
for (auto& x:t) x.join();
}
int main() {
std::printf("workers=%d\n", VF_WORKERS);
for(size_t i=0;i<spheres.size();++i) {
uint32_t h=hash3(int(i),17,71,12345);
spheres[i]={int((h&255u)),int((h>>8)&255u),int((h>>16)&255u), 12+int((h>>24)%16u)};
}
for(int trial=0;trial<3;++trial) {
double full=run([]{parallel_z([](int lo,int hi){
for(int z=lo;z<hi;++z) for(int y=0;y<N;++y) for(int x=0;x<N;++x)
field[at(x,y,z)]=int16_t(density(x,y,z));
});});
uint64_t checksum=0; for(size_t i=0;i<field.size();i+=4093) checksum+=uint16_t(field[i]); sink=checksum;
std::printf("full trial=%d ms=%.3f checksum=%llu\n",trial,full,(unsigned long long)checksum);
}
for(size_t i=0;i<field.size();++i) reference_sign[i]=field[i]>0;
// Conservative sphere/brick bounds with a +/-224 noise interval.
std::array<uint8_t, size_t(NB)*NB*NB> kind{};
size_t solid=0,air=0,mixed=0;
double classify=run([&]{
for(int bz=0;bz<NB;++bz) for(int by=0;by<NB;++by) for(int bx=0;bx<NB;++bx) {
int x0=bx*B,y0=by*B,z0=bz*B,x1=x0+B-1,y1=y0+B-1,z1=z0+B-1;
bool possiblyAir=false, definitelyAir=false;
for(const Sphere& s:spheres) {
int dx=std::max({x0-s.x,0,s.x-x1});
int dy=std::max({y0-s.y,0,s.y-y1});
int dz=std::max({z0-s.z,0,s.z-z1});
int near2=dx*dx+dy*dy+dz*dz;
int fx=std::max(std::abs(x0-s.x),std::abs(x1-s.x));
int fy=std::max(std::abs(y0-s.y),std::abs(y1-s.y));
int fz=std::max(std::abs(z0-s.z),std::abs(z1-s.z));
int far2=fx*fx+fy*fy+fz*fz;
possiblyAir |= (s.r*s.r-near2+224>=0);
definitelyAir |= (s.r*s.r-far2-224>0);
}
uint8_t k=definitelyAir?2:(possiblyAir?1:0);
kind[(size_t(bz)*NB+by)*NB+bx]=k;
if(k==0)++solid; else if(k==2)++air; else ++mixed;
}
});
std::printf("classify ms=%.3f solid=%zu air=%zu mixed=%zu total=%zu\n",classify,solid,air,mixed,kind.size());
for(int trial=0;trial<3;++trial) {
double sparse=run([&]{parallel_z([&](int lo,int hi){
for(int bz=lo/B;bz<hi/B;++bz) for(int by=0;by<NB;++by) for(int bx=0;bx<NB;++bx) {
uint8_t k=kind[(size_t(bz)*NB+by)*NB+bx];
for(int z=bz*B;z<(bz+1)*B;++z) for(int y=by*B;y<(by+1)*B;++y)
for(int x=bx*B;x<(bx+1)*B;++x)
field[at(x,y,z)]= k==1?int16_t(density(x,y,z)):int16_t(k==2?1:-1);
}
});});
uint64_t checksum=0; for(size_t i=0;i<field.size();i+=4093) checksum+=uint16_t(field[i]); sink=checksum;
std::printf("sparse trial=%d ms=%.3f checksum=%llu\n",trial,sparse,(unsigned long long)checksum);
}
size_t mismatches=0;
for(size_t i=0;i<field.size();++i) mismatches+=(reference_sign[i]!=(field[i]>0));
std::printf("sign mismatches=%zu of %zu\n",mismatches,field.size());
}