Overnight task 2. Every claim here is end-to-end wall clock on a real 128^3 export,
not a profiled bucket, because the last two rounds shipped large bucket wins worth
0% in reality. Measurement floor is ~17% run-to-run; only Part 2 cleared it.
PART 2 -- KEPT. Density-grid reuse.
The mesher samples an expanded grid (-1..Cells+1), so adjacent tiles re-evaluated
their shared boundary planes. Measured: 2,744,000 requested samples contained
495,909 duplicates -- 18.07% of the field generated twice. A run-scoped read-only
lattice (FVoxelSharedDensityGrid) owns each world sample once and hands it to both
adjacent tile grids. Duplicates now 0.
128^3 TunnelNetwork seed 1337 slot 1, freshly built binary:
before 2.603 / 2.693 / 2.697 s
after 1.488 / 1.499 / 1.486 s -44.6%, 1.80x
Geometry SHA-256 98eaad61..d3c4f IDENTICAL to the pre-change binary's output.
153,740 triangles either way. Pure win, no field change.
The lattice is deliberately not a UObject and not a cache with eviction: ownership,
lifetime and iteration order stay explicit. A malformed or incomplete lattice is
ignored and falls back to the canonical generator path rather than altering geometry.
PART 1 -- REVERTED. Block-level empty-space early-out.
**0 of 4,096 blocks were safely classifiable**, and the attempt made it 85% slower
(3.632 -> 6.735 s). No unsafe bound was retained.
⇒ This is a finding, not just a failure: our conservative bounds are too loose to
prove ANY 8^3 block uniform. Hierarchical evaluation of any kind is blocked until
we know which operator has effectively unbounded reach. Next task.
PART 3 -- REVERTED. Task granularity.
15 workers, 64 tile jobs, mean job 0.594 s, launch fringe ~14.1 us. MinBatchSize=4
was 1.09% SLOWER end-to-end despite a better bucket time -- exactly the trap this
task's rule exists to catch. Default stays 1. ParallelForWithExistingTaskContext
not adopted.
PART 4 -- NO CHANGE. Renderer audited over 3 runs (8.736 s total, 1.483 s raster).
Mesh and BVH are already reused across viewpoints; no per-pixel heap allocation, no
redundant remeshing. Nothing gross to fix; it benefits from the above automatically.
STILL OPEN: the spatial index from ee89c4d has never earned its keep in wall clock
and accounts for ~214 MB of measured allocations. Codex proposes an on/off ablation
then removal if confirmed. Not done unilaterally.
⚠️ PROCESS NOTE: the agent reported "build passed" but the runtime DLL was still
from 03:07 while sources were edited at 05:22/05:42. My first three verification
runs measured the OLD binary and showed no change. Always check binary timestamps
against source timestamps before believing a measurement.
Cumulative on this region: 342.2 s -> 16.2 s -> 2.62 s -> 1.49 s (230x).
Determinism: identical hashes across all repeat runs. Build clean. Nothing outside
Plugins/VoxelForge touched; RealtimeMeshComponent untouched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aheo2crrZmHuYtPpdY2Cb
7.6 KiB
Task 2 performance report
Date: 2026-09-09
Repository: E:\Projet Unreal\VoxelM\Plugins\VoxelForge
Work began at experimental / e63f016.
Measurement rule
All performance numbers below are per-case run.elapsed_seconds from the batch report. This is
the wall clock for a real commandlet case through the export or render result; it includes the
shared-density-grid build when enabled. summary.mesh_seconds and profiler buckets are included
only as diagnostics, never as the performance claim.
The export comparison is the same 128^3 TunnelNetwork, seed 1337, slot 1, operator stack,
step 1, with three cases in one commandlet process on each side. The 17% reporting floor is
therefore met by both run count and the retained density-grid result.
Part 1 — block early-out
I tested an 8^3 block reject behind conservative fallback logic. On this cave/stack world, every
one of the 4,096 blocks was left uncertain and went through normal sampling:
| batch | runs | end-to-end wall seconds | blocks skipped | skip rate | result |
|---|---|---|---|---|---|
| baseline | 3 | 3.805057, 3.522129, 3.569186 (avg 3.632124) | — | — | reference |
| block reject | 3 | 6.341454, 6.562621, 7.300234 (avg 6.734770) | 0 / 4,096 | 0.00% | reverted |
The candidate added classification work without skipping a block (+85.42% wall clock). It also
did not produce a useful supremum for the complete cave stack, so I did not promote a guessed
max parameter into a topology decision. ClassifyTile remains untouched: its stack, disturbance,
diff-layer, and cave guards are not a safe substitute for a new block proof. A false AllSolid
verdict here would delete a 3D cave, which is not an acceptable trade.
This did not clear the floor and was reverted.
Part 2 — density-grid reuse
The canonical mesher requests 35^3 samples per 32-cell tile. For 64 tiles that is:
- tile requests:
64 * 35^3 = 2,744,000; - unique points in the expanded 128-cell lattice:
131^3 = 2,248,091; - duplicate evaluations:
495,909(18.07%).
The first implementation evaluated the shared lattice as Z planes. It removed all duplicates but was slower because it destroyed the generator's spatial locality:
| candidate | runs | end-to-end wall seconds | duplicates | result |
|---|---|---|---|---|
| matched control | 3 | 3.668954, 3.633445, 3.655658 (avg 3.652686) | 495,909 → 495,909 | reference |
| global-plane precompute | 3 | 5.349520, 5.352927, 5.510752 (avg 5.404400) | 495,909 → 0 | reverted |
| owner-grid precompute | 3 | 1.950207, 1.973673, 2.161305 (avg 2.028395) | 495,909 → 0 | retained candidate |
The retained implementation partitions the shared lattice into 64 disjoint rectangular owner regions. Each global sample is generated exactly once, then each tile copies its local halo window from the immutable run-scoped grid. The precompute is deliberately inside the wall-clock timer; the final three-case repeat measured:
| batch | runs | end-to-end wall seconds | average |
|---|---|---|---|
final control (density_grid_reuse=false) |
3 | 3.659587, 3.619013, 3.582257 | 3.620286 s |
final reuse (density_grid_reuse=true) |
3 | 1.908017, 1.854344, 1.884170 | 1.882177 s |
That is a 48.01% wall-clock reduction / 1.92x speedup, clearing the 17% floor. The final
reuse runs averaged 1.474271 s for the shared-grid build, and still won end to end. The 128^3
shared lattice is about 8.6 MiB of float storage. Direct explorer runs default to reuse; a batch
case can pass density_grid_reuse=false for a matched control.
The run-scoped handoff is confined to the explorer's finite canonical export. The streaming game path has no one-shot set of neighboring tiles or invalidation boundary, so it was not given a locking plane cache that could serialize workers or outlive a generation epoch.
Part 3 — task granularity
The final control observed 15 worker thread IDs and 64 logical tile invocations. Across its three runs, the measured tile-job averages were:
| metric | average |
|---|---|
| fastest logical tile job | 0.188817 s |
| mean logical tile job | 0.593751 s |
| slowest logical tile job | 0.955788 s |
| sum of tile-job durations | 38.000045 s |
| tile parallel wall | 3.150925 s |
| launch-fringe estimate | 14.1 us |
| workers / logical jobs | 15 / 64 |
I tested MinBatchSize=4 with three matched cases: wall seconds 3.590884, 3.634734, 3.753986 (avg 3.659868) versus the final control average 3.620286. The tile parallel bucket
was slightly lower (3.125197 s vs 3.150925 s), but end-to-end wall clock was 1.09% slower.
The observed worker count and logical job count stayed 15 / 64, and the launch-fringe estimate
remained about 13.1 us. This is the required bucket-win/no-wall-win case: the tuning was reverted;
the production/default setting remains MinBatchSize=1.
ParallelForWithExistingTaskContext was not adopted. The measured launch fringe is negligible
relative to 0.59 s tile work, and the mesher already uses thread-local scratch storage. No claim is
made here about the separate game workload of many small streaming submissions.
Part 4 — renderer
No renderer rewrite or RMC change was justified. The source audit and three final render cases showed:
- the canonical mesh is built once per explorer world;
- the triangle cache/BVH is built once and reused by all eight viewpoints;
- each viewpoint owns one
FColoroutput allocation; - the per-pixel traversal uses a fixed stack array, not a heap allocation;
- camera movement does not call the mesher again.
The three 512x288 render cases had total case wall seconds 8.704730, 8.985744, 8.517606
(avg 8.736027), including camera-seed walking. Raster-only diagnostics averaged 1.482921 s
for eight views, 0.286297 s for the first view, 0.170946 s per additional view, and 0.083190 s
for one-time acceleration construction. This is consistent with the owner's approximately
0.15 s/view observation; meshing benefits from Part 2 automatically. There was no renderer
change, so there is no before/after performance claim to report or keep.
Memory/index follow-up
The existing profiler research measured 431,162,116 bytes (411.2 MiB) across 15 workers in the large run. In the representative 128^3 breakdown, spatial-index bins and candidate arrays alone accounted for 214,345,644 bytes, inside a 360,063,124-byte cache total. The index addition did not show an end-to-end wall-clock gain in the prior measurements.
I did not remove it silently. The explicit follow-up proposal is an index-on/index-off batch ablation before deletion: removing it should recover roughly that allocation, at the cost of broader room/tunnel candidate scans and potentially slower density queries. That trade is not proven by this task's export comparison.
Determinism, invariants, and scope
All three final control hashes and all three final reuse hashes are ED0F1F31; every case reported
json_deterministic=yes. The three render cases also repeated the same geometry hash
847BE4B9. Owner regions are disjoint, tile results are merged in fixed Z/Y/X order, no RNG or
runtime CPU-feature dispatch was added, and the shared grid is read-only after its deterministic
fill. The only worker writes are to distinct cells of that run-owned output lattice; generator and
world state remain read-only.
The build succeeded with UnrealBuildTool after the final source change. Changes are limited to the
VoxelForge source and this report, with generated evidence under the plugin's Saved directory.
RealtimeMeshComponent was not modified. No VoxelM root, sibling plugin, Content, or Config
files were written; no links or historical worktrees were created.