From 32021930d22689fa3573ee74cccf8a3f579759cc Mon Sep 17 00:00:00 2001 From: Fr0zka Date: Mon, 26 Oct 2026 09:48:37 +0100 Subject: [PATCH] research(R32, self-checked): first-principles work budget for ~1 s and the active-cell/sparse-brick path Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1 --- .../WorldAssemblerM3/Research32/REPORT.md | 92 +++++++++++++++++++ .../WorldAssemblerM3/Research32/first-pass.md | 70 ++++++++++++++ 2 files changed, 162 insertions(+) create mode 100644 Standalone/Research/WorldAssemblerM3/Research32/REPORT.md create mode 100644 Standalone/Research/WorldAssemblerM3/Research32/first-pass.md diff --git a/Standalone/Research/WorldAssemblerM3/Research32/REPORT.md b/Standalone/Research/WorldAssemblerM3/Research32/REPORT.md new file mode 100644 index 0000000..06d65a0 --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Research32/REPORT.md @@ -0,0 +1,92 @@ +# VoxelForge R32 — audited architecture reframe + +## Work estimate + +The L60 matrix used 12 workers, clip radius 3 and LOD0–8. The owner export supplies `FullResClipLevels=2` and `CoarseTileCells=16`; `generateTile` uses 32 cells per axis below LOD2 and 16 thereafter. L60’s actual per-LOD tile counts are in the [matrix JSON]() and its benchmark settings are in the [L60 report](). The owner export itself says clip radius 6, so the benchmark’s explicit radius 3 override matters. + +For `n` cells per axis, the dense baseline is `n³` candidate cells per tile. Marching Cubes needs `(n+1)³` corner values; this implementation adds a one-sample gradient halo on every side, for `(n+3)³` dense grid slots. That halo and the cell selection are visible in [`GenerateMesh`]() and the resolution choice in [`World::generateTile`](). + +| LOD | Tiles: SW / profiles and motifs | Cells per tile | Candidate cells: SW / profiles and motifs | Dense grid slots with halo: SW / profiles and motifs | +|---:|---:|---:|---:|---:| +| 0 | 512 / 512 | 32,768 | 16,777,216 / 16,777,216 | 21,952,000 / 21,952,000 | +| 1 | 448 / 448 | 32,768 | 14,680,064 / 14,680,064 | 19,208,000 / 19,208,000 | +| 2 | 320 / 320 | 4,096 | 1,310,720 / 1,310,720 | 2,194,880 / 2,194,880 | +| 3 | 208 / 208 | 4,096 | 851,968 / 851,968 | 1,426,672 / 1,426,672 | +| 4 | 240 / 144 | 4,096 | 983,040 / 589,824 | 1,646,160 / 987,696 | +| 5 | 144 / 96 | 4,096 | 589,824 / 393,216 | 987,696 / 658,464 | +| 6 | 96 / 96 | 4,096 | 393,216 / 393,216 | 658,464 / 658,464 | +| 7 | 96 / 96 | 4,096 | 393,216 / 393,216 | 658,464 / 658,464 | +| 8 | 66 / 66 | 4,096 | 270,336 / 270,336 | 452,694 / 452,694 | +| **Total** | **2,130 / 1,986** | | **36,249,600 / 35,659,776** | **49,185,030 / 48,197,334** | + +For example, SW LOD0 is `512 × 32³ = 16,777,216` candidate cells and `512 × 35³ = 21,952,000` halo slots. Profiles and motifs have the same counts through LOD3; their smaller total comes from fewer LOD4 and LOD5 tiles. Core MC corners alone are `(33³)` for full resolution and `(17³)` for coarse tiles: 40.248M SW points and 39.540M profile/motif points across tile grids. + +These are **per-tile dense slots**, not unique world coordinates or guaranteed density-function calls. Neighboring grids can repeat coordinates, and whole-tile or block proofs can avoid work. LOD sample spacing in base 25 cm voxels is `1, 2, 8, 16, 32, 64, 128, 256, 512` for LOD0–8. + +**L55 matches the dense arithmetic where it should.** Its SW LOD0 measured candidate/classified total is exactly 16,777,216. Its per-tile instrumentation reports 42,875 LOD0 sample slots; coarse tiles use 4,096 candidate cells and 6,859 slots. The L55 report also records FI at 14,483,456 candidate cells, equivalent to 442 full LOD0 grids, with 892,416 proof-skipped air cells. So 16.78M is a dense candidate ceiling, not the measured candidate count for every generator. L55’s static run had 2,370 tiles, while L60 has 2,130 or 1,986; its cumulative totals cannot be compared directly with the L60 table. The L55 report gives aggregate candidate counters for LOD0, not aggregate candidate totals for every LOD. See [L55 counters and worker totals](). + +## What one second demands + +L60 whole-view medians were SW 1.669 s, profiles 1.303–1.317 s, caves 1.408 s, motifs 1.326 s, lattice 1.701 s and FI 4.926 s. The near ring was about 0.48–0.56 s for all except FI at 0.977 s ([L60 report]()). + +The following is an optimistic work budget: it takes L60 run 1’s summed worker-seconds and shape time, assumes perfect balance across all 12 workers, and holds shape time fixed. “Required cut” is the worker-time reduction needed to fit in one second under those assumptions. The actual cold times are higher than `shape + worker-sum/12`, so real cuts must be larger. Worker sums come from the [SW](), [FI](), [caves](), [lattice](), [profile]() and [motif]() logs. + +| Family | L60 median | Run 1 worker-sum | Shape time | Worker-s budget for 1 s | Minimum worker-s cut | +|---|---:|---:|---:|---:|---:| +| SW | 1.669 s | 13.835 s | 0.412 s | 7.056 s | 6.779 s (49%) | +| Profiles | 1.303–1.317 s | 9.971–10.079 s | 0.412–0.423 s | 6.924–7.056 s | about 3.05 s (about 31%) | +| Caves | 1.408 s | 10.915 s | 0.402 s | 7.176 s | 3.739 s (34%) | +| Motifs | 1.326 s | 10.197 s | 0.412 s | 7.056 s | 3.141 s (31%) | +| Lattice | 1.701 s | 13.779 s | 0.461 s | 6.468 s | 7.311 s (53%) | +| FI | 4.926 s | 49.277 s | 0.450 s | 6.600 s | 42.677 s (87%) | + +The dense counts do not make one second physically impossible: with about 0.4 s for shape setup, the remaining 0.6 s implies roughly 60M candidate cells/s and 82M halo slots/s across 12 workers. But that arithmetic omits field complexity, proof work, normals, mesh output, skirts and scheduling. L60’s measured worker sums show the actual required reductions above. + +L55’s stage totals show why faster sampling alone is insufficient. SW spent 5.503 of 13.316 worker-s in grid work; FI spent 36.414 of 50.178 worker-s there. Even eliminating all measured grid work would leave, ideally, about 1.05 s for SW and 1.59 s for FI after the respective measured shape stages. The FI result needs cuts to other work too. These are optimistic calculations from the [L55 stage report](). + +## External benchmark audit + +There is no public apples-to-apples per-core benchmark for SDF sampling plus CPU Marching Cubes among the named shipped games. The available numbers describe different workloads: + +| Source | Published number or finding | What it measures | +|---|---|---| +| **Teardown** | No SDF/meshing throughput figure found. | The developer interview describes custom voxel volumes, CPU voxel collision and GPU rendering; it gives no per-core terrain meshing rate. [Game Developer interview](https://www.gamedeveloper.com/design/how-beautiful-voxels-laid-the-way-for-i-teardown-s-i-heist-y-framework) | +| **Dreams** | A pre-production PS4 prototype reports 10–100M voxel evaluations/s and over 99% culling. | This is not a per-CPU-core rate, and the source labels the prototype as not representative of the final game. Its MC prototype used compute shaders. [Alex Evans SIGGRAPH slides](https://advances.realtimerendering.com/s2015/AlexEvans_SIGGRAPH-2015-sml.pdf) | +| **Enshrouded** | No CPU voxel-meshing throughput figure found. | Keen identifies its proprietary Holistic Engine but publishes no relevant rate. [Official FAQ](https://enshrouded.com/en-US/faq) | +| **Astroneer** | No throughput figure found. | The developer discusses streaming and optimizing the Marching Cubes terrain-deformation algorithm, without a cell/s or per-core result. [Developer post](https://blog.astroneer.space/p/going-faster/) | +| **No Man’s Sky** | No throughput figure found. | The official update says dual Marching Cubes increased terrain-generation speed, without absolute timing or CPU-core data. [Worlds Part I](https://www.nomanssky.com/worlds-part-i-update/) | +| **Transvoxel** | 256 regular cases, 512 transition cases and 73 transition equivalence classes. | These are algorithm case counts, not throughput measurements. [Transvoxel paper](https://transvoxel.org/Transvoxel.pdf) | + +A few sourced proxies help set scale, but none predicts VoxelForge’s rate: + +- FastNoise2 reports 3D AVX2 rates of 494M value, 261M Perlin, 268M simplex and 52M cellular samples/s on a 4.9 GHz Intel 7820X using clang-cl. Its table does not state thread count, and measures noise alone—not this field graph plus meshing. [FastNoise2 benchmark](https://github.com/Auburn/FastNoise2) +- A single-thread CSG surface-sampling paper reports about 10–50M samples/s for simple CSG and 0.5–2M/s for complex CSG on an i9-9900K. It adaptively samples quadric surfaces; it does not densely evaluate an SDF lattice or mesh it. [Sampling from Quadric-Based CSG Surfaces](https://onlinelibrary.wiley.com/doi/10.1111/cgf.14380) +- A CPU block-voxel mesher author reports 0.48 ms to mesh a 32³ chunk. That uses block occupancy and face merging, rather than smooth SDF sampling and Marching Cubes. [Sector’s Edge mesher notes](https://vercidium.com/blog/further-voxel-world-optimisations/) + +## Ranked architecture ideas mapped to VoxelForge + +1. **Conservative surface support and active-cell selection — highest potential; XL.** `World::generateTile` calls [`ClassifyBoxDetailed`]() and the field bounds live in [`ShapeWorldInternal.h`](). Extend these proofs to identify a narrow, guaranteed surface band, then sample/mesh only cells whose bounds can cross zero. Current L55 proof skips were zero for SW LOD0 and 892,416 FI LOD0 air cells; those measured savings fall short of the 49% and 87% optimistic work cuts. **Expected gain is unmeasured.** A false sign proof creates holes; bounds must cover every field and edit. Preserve negative-is-solid semantics, exact boundary samples and the normal halo. + +2. **Sparse brick/octree data flow — high potential, but coupled to the bounds above; XL.** [`GenerateMesh`]() currently allocates a dense grid and a dense `RequiredGridSamples` mask even when proof blocks skip work. Replace that mask and dense traversal with a compact list of active blocks/cells, while feeding the existing MC case logic. Sparse storage alone does not prove cells empty or reduce field calls; do not count its potential gain twice with surface support. Keep active-cell order canonical to protect cross-compiler hashes and seams. + +3. **SIMD-first field evaluation plus an FI-specific cache pass — medium/high potential; L–XL.** [`DensityAtLatticeX8`]() handles native-only samples lane by lane; native island evaluation is also lane-based. FI’s [`IslandColumnCache`]() has severe measured conflicts: 27.6M local and 3.7M shared conflicts, but only 151 ms measured lock wait, so the value of a cache redesign is not yet proven. Vectorize the expensive field operations and measure grid time by LOD; keep operation order and FMA disabled, and require exact scalar/batched agreement on MSVC too. SIMD applies mainly to field-grid time: L55’s measured grid stages alone are not enough to reach one second for FI. + +4. **Parent-sample reuse and far-field precomputation — medium, uncertain cold-view gain; L–XL.** [`GenerateMesh`]() already reuses aligned samples when the grid step matches. Extend this only where parent/child grids or tile boundaries share exact world coordinates. Far tiles also run repair work, including [`AppendFarConnectorShells`]() and [`AppendFarStratumShells`](). LOD rings have limited spatial overlap; caching helps repeated streaming in a session more clearly than a first cold view. Never drop caps/connectors without replacing their coverage guarantees. Cache keys and invalidation must include seed, layout and the affected edit diff. + +5. **Shape setup reuse or optimization — measured ceiling about 0.4–0.46 s; M.** `World::compile` builds the shape, and the [`World` constructor]() already creates per-world shared column caches. The logged shape stage is material to the simple generators. Eliminating it completely would still leave SW and lattice around 1.24–1.26 s and FI around 4.48 s by rough subtraction. A cache helps warm same-layout reuse; a cold miss still pays setup. Measure and reduce `World::compile` work before building another cache. + +6. **Progressive refinement for VR and fast travel — useful for readiness, no cold-work reduction; S–M.** [`StreamingWorld` already ranks tiles by distance]() and the viewer tracks neighborhood/full-detail readiness in [`StandaloneViewer.cpp`](). Prioritizing predicted travel direction can improve coverage during movement; it cannot reduce whole-view worker-seconds if all tiles are still generated. Keep old meshes until replacements are ready to avoid transition gaps. L60 moving-camera p95 was about 120 ms for most generators and 871 ms for FI, so this remains a separate streaming goal. + +7. **Surface Nets or Dual Contouring — low priority for this target; XL.** This would replace the Marching Cubes extraction inside [`GenerateMesh`](). It still needs field samples and has no measured VoxelForge gain. Expect changed topology, mesh hashes, normals and seam proofs; Dual Contouring also adds QEF arithmetic that must be made deterministic. + +8. **Deterministic fixed-point GPU compute — low confidence; XL.** The mapped mesher is CPU code; there is no existing GPU path to tune. A deterministic port would need fixed-point field evaluation, classification, interpolation and mesh ordering on both CPU and GPU, with exact behavior for all layouts and edits. This could preserve peer regeneration only if the CPU reference and every GPU implementation produce identical results. No relevant VoxelForge benchmark supports the cost. + +## Recommended path + +1. Prototype conservative active-cell bounds with compact brick selection. First measure active cells and field samples by LOD for every generator, then test one surface family and FI against the existing exact outputs. The work budget requires at least a 49% cut for SW and 87% for FI, so stop early if the measured reduction is far below those floors. +2. Build a genuinely batched native field evaluator and run a scalar-versus-batched exact comparison, then measure grid-stage worker time by LOD. Include the FI cache conflict data in that experiment; do not assume its high conflict count translates into a large wall-time saving. +3. Profile FI’s non-grid work and far repair path separately, then target the dominant measured stage. The L55 arithmetic says that removing all grid work still leaves too much FI work for a one-second view; FI needs a dedicated second reduction beyond sampling. + +**This path does not yet demonstrate one second.** It is a plausible research direction for profiles, motifs and caves, which need roughly 31–34% worker-time cuts under optimistic assumptions. SW and lattice need about 49–53%, and FI needs about 87% plus further gains to cover real scheduling overhead. I would treat one second for every cold view as a goal to prove with the same L60 matrix, not as the current forecast. + +The main unknown is how many cells each generator can prove inactive with bounds that include edits and all cross-compiler edge cases. The public engine benchmarks do not answer that question, and the current VoxelForge measurements show that the existing proof path alone is insufficient. \ No newline at end of file diff --git a/Standalone/Research/WorldAssemblerM3/Research32/first-pass.md b/Standalone/Research/WorldAssemblerM3/Research32/first-pass.md new file mode 100644 index 0000000..84a3e7c --- /dev/null +++ b/Standalone/Research/WorldAssemblerM3/Research32/first-pass.md @@ -0,0 +1,70 @@ +# Research32 — Reframe for a 1 s whole view + +## Work estimate + +The strict minimum depends on how much zero surface the world contains. Marching Cubes can avoid cells proven to be entirely solid or air; for an arbitrary field without such proofs, it must consider every candidate cell. The table estimates the current dense path for the 2,130-tile Natural SW view. It counts lattice slots including the mesher’s one-sample normal halo on each side; halo and tile-boundary slots can repeat across tiles. + +| LOD | Tiles | Cell step (voxels) | Candidate cells (M) | Density slots with halo (M) | +|---:|---:|---:|---:|---:| +| 0 | 512 | 1 | 16.777 | 21.952 | +| 1 | 448 | 2 | 14.680 | 19.208 | +| 2 | 320 | 8 | 1.311 | 2.195 | +| 3 | 208 | 16 | 0.852 | 1.427 | +| 4 | 240 | 32 | 0.983 | 1.646 | +| 5 | 144 | 64 | 0.590 | 0.988 | +| 6 | 96 | 128 | 0.393 | 0.658 | +| 7 | 96 | 256 | 0.393 | 0.658 | +| 8 | 66 | 512 | 0.270 | 0.453 | +| **Total** | **2,130** | — | **36.250** | **49.185** | + +The calculation uses `n³` candidate cells and `(n+3)³` density slots per tile: 32 cells per axis at LOD0–1, then 16 at LOD2–8. The tile counts come from the L60 run; the mesher establishes the grid dimensions and halo. ([LOD counts](), [grid and halo]()) + +This is a **dense-path budget, not an unavoidable floor or a count of actual calls for every generator**. LOD0–1 alone account for 41.16M of the 49.19M slots. Proof skips and caches can reduce actual evaluations: in L55’s representative LOD0 data, SW still evaluated 21.95M active lanes, while FI evaluated 5.49M but spent 29.7 worker-seconds on LOD0. The FI result says field cost matters as much as sample count. ([L55 counters](), [L55 LOD0 worker time]()) + +The real surface-oriented lower bound is closer to the number of cells near the zero set, roughly proportional to resolved surface area divided by cell area. That number depends on the generator, caves, edits, and chosen resolution; there is no surface-area census to calculate it here. + +For a 1 s cold view, L55’s roughly 0.4–0.45 s shape setup leaves at most 0.55–0.60 s for tile work. At ideal W12 balance, that is about **6.6–7.2 worker-seconds**. SW’s 13.3 worker-seconds therefore needs about a **46%** work reduction; FI’s 50.2 needs about **87%**. That makes 1 s physically plausible after structural work reduction, but unlikely from more scheduling polish alone. ([L55 budget]()) + +The current L60 medians are 1.30–1.33 s for profiles and motifs, 1.41 s for caves, 1.67 s for Natural SW, 1.70 s for Lattice, and 4.93 s for FI. Its FI near ring is already about 0.98 s. ([L60 matrix]()) + +## External comparisons + +Public **per-core SDF sampling plus meshing** numbers for the named shipped games are scarce; their performance figures are not directly comparable to this workload. + +- **Teardown:** Its voxel volumes support CPU voxel collision and GPU ray-marched rendering; that is not CPU SDF-to-Marching-Cubes throughput. [Game Developer interview](https://www.gamedeveloper.com/design/how-beautiful-voxels-laid-the-way-for-i-teardown-s-i-heist-y-framework) +- **Dreams:** Alex Evans’s prototype slides describe a DX11 Marching Cubes approach and models containing 1–100,000 CSG edits. That is model complexity, not samples or meshes per second; it supplies no comparable final-game per-core figure. [Evans slides](https://www.slideshare.net/slideshow/umbra-ignite-2015-alex-evans-learning-from-failure-prototypes-rd-iteration-and-arriving-at-great-by-throwing-away-lots-of-almostgreat/52801357), [tooling discussion](https://www.gamedeveloper.com/design/how-media-molecule-designed-a-fun-and-robust-toolset-for-i-dreams-i-) +- **Enshrouded:** Keen confirms its proprietary Holistic Engine, but I found no public CPU meshing throughput. [Official FAQ](https://enshrouded.com/en-US/faq) +- **Astroneer:** Its developer identifies Marching Cubes for terrain deformation and discusses optimizing it, but gives no time or throughput. [Developer blog](https://blog.astroneer.space/p/going-faster/) +- **No Man’s Sky:** The official 5.0 notes say dual Marching Cubes increased terrain-generation speed and reduced memory, without quantifying the gain. [Official update](https://www.nomanssky.com/worlds-part-i-update/) +- **Transvoxel:** The algorithm has 256 regular cases and 512 transition cases grouped into 73 equivalence classes. These describe table size, not runtime. [Algorithm paper](https://transvoxel.org/Transvoxel.pdf) + +Two imperfect CPU references help set scale. FastNoise2 reports 261.10M 3D Perlin samples/s and 52.43M cellular samples/s for AVX2 on an i7-7820X at 4.9 GHz with Clang-CL 10; it does not report thread scaling, and these are noise calls without meshing. [FastNoise2 benchmarks](https://github.com/Auburn/FastNoise2) A separate C++ block-world project reports roughly 1 ms for a 32³ block fill, greedy mesh, and visibility pass, with a nine-worker pool; that is block terrain, not smooth SDF/MC. [Project design notes](https://github.com/DanielWLiu07/voxel-engine/blob/main/docs/design.md) Voxel Plugin’s docs identify generator queries as nearly half of a flat-world generation example and describe large gains from conservative range analysis, but give no per-core rate. [Performance notes](https://docs.voxelplugin.com/1.2/technical-notes/performance-and-profiling) + +## Ranked architecture changes + +1. **Prove empty space by bounds, then mesh sparse active bricks.** Add conservative `RangeAtBox` queries for heightfields, features, passages, islands, and edits; recurse or subdivide only boxes whose density range includes zero. Start in `World::generateTile` and `UVoxelMarchingCubesMesher::GenerateMesh`, using the existing tile classification, proof-mask, and required-sample infrastructure. For a thin surface, this can avoid most of a tile’s volume; realized whole-view gain is unknown and will vary by generator. **Size: L/XL.** False bounds cause holes. Retain exact field samples at active cell corners and the normal halo; unknown bounds must fall back to dense evaluation. ([tile generation](), [mesher](), [tile classification]()) + +2. **Give FI a fast exact field path.** Its LOD0 work dominates FI’s cost. `NativeFloatingIslandDensityAt` calls the island field, which includes trigonometric roughness and lobe work; its column cache is already active and uses a 16,384-entry, four-way shared cache. First attribute time to field operations, cache hits/conflicts, and tile-local reuse. Then try a layout-built spatial index and batched exact evaluation so distant islands are not queried for every point. **Size: M/L.** Potentially large for FI, but current substage data do not support a saving estimate. Preserve expression order and output on the scalar path; changing math or field values can break Clang/GCC/MSVC agreement. ([FI evaluator](), [FI cache]()) + +3. **Use progressive proxies and velocity-aware priority for streaming.** Show a coarse representation for distant tiles early, generate near tiles in view and travel direction first, and replace proxies with the final MC mesh. This can improve VR readiness and high-speed travel; it saves **no worker-seconds** if every final tile is still generated. Measure time-to-visible coverage separately from full mesh completion. **Size: M.** The viewer already has desired-tile filters and staged neighborhood/full-detail readiness, so this builds on existing scheduling. ([viewer scheduling]()) + +Other ideas rank lower: + +- **SIMD-first field sampling:** The X8 API has scalar fallbacks for native field paths. Batch only proven-active samples and preserve the same arithmetic order, with contraction off and cross-compiler checks. It could speed the field portion, but it cannot remove the dense candidate-cell count. **Size: M/L.** ([X8 path]()) +- **Far-field sample reuse:** Reuse exact samples across tiles or cache a coarser regional field. The world already creates shared column caches, and clipmap rings largely occupy different regions, limiting parent/child reuse. Far repair and cap work are measurable, but do not remove them without silhouette and interior-view evidence. **Size: M/L.** ([shared caches](), [far refinements]()) +- **Cache shape setup per layout:** Compilation and caches already live for the active `World`; a persistent derived cache could reduce repeat-load cold setup, but not the large tile drain. **Size: M.** ([shape compilation]()) +- **Replace Marching Cubes or move work to GPU:** Surface Nets might reduce some mesh construction, but retains field sampling and changes geometry/topology. Dual Contouring adds floating-point solving and determinism risk. GPU fixed-point could be deterministic in principle, but porting the fields and avoiding readback costs is a large rewrite. Neither is my first step. **Size: L/XL.** + +## Recommended path + +1. Prototype conservative sparse evaluation on a simple profile. First measure active cells and field calls by LOD, then compare final mesh, payload, and topology against dense generation across W1/W8/W12 and compilers. Keep the normal halo and seam samples exact. +2. In parallel, instrument FI LOD0’s evaluator and cache. Its 29.7 LOD0 worker-seconds alone exceed the entire approximately 6.6 worker-second FI budget, even if all other tiles were free. Use the measurements to choose between spatial pruning, tile-local reuse, and a batch evaluator before changing island math. +3. Add or refine coarse-first rendering and velocity-aware queue priority for VR. Keep the full-view timer separate so proxy readiness is not mistaken for total work saved. + +For sparse traversal, skipped samples will change a dense density-grid hash if that hash includes unevaluated space. Keep deterministic field values wherever sampled, and gate on exact mesh/payload/topology plus conservative-proof audits. Preserve the one-sample halo, boundary agreement, worker-count/compiler comparisons, and far-horizon silhouette checks; do not infer safety from tile visibility alone. ([R31 proof/visibility cautions]()) + +## Uncertainties + +The 49.2M figure is a dense slot count for the representative 2,130-tile configuration, not a unique-point count or actual calls for every generator. The profile variants use 1,986 tiles, and existing proofs reduce some generators’ sampling. There is no same-run per-generator sample/cell census or public per-core game benchmark to establish an apples-to-apples target. Far-field visibility and the quality error allowed by a sparse representation also need measured visual gates. + +Read-only review only; no scripts, tests, builds, binaries, or tree changes were made. \ No newline at end of file