Files
VoxelForge/Standalone/SHARED_CPP_LIBRARY_PLAN.md
T
Fr0zkaandClaude Opus 5.5 81b7d6d0ff standalone: per-tile speed at plugin level, streaming like the game, softer lighting
Cause found by profiling: room/SDF graph-cache churn (exact XY bounds + sample order rebuilt it
constantly). Region grouping (one LOD3 tile: 1,084 -> 229 builds), taller-tile traversal for LOD6
(36,480 -> 12,160), extent-dependent cache slots, lambdas instead of recursive std::function.
Serial ms/tile, 64 nearest tiles per level (before -> after):
  L0 50.4 -> 50.6, L1 112 -> 110, L2 24 -> 22, L3 223 -> 73, L4 269 -> 134, L5 393 -> 127, L6 283 -> 145.
Hashes of all levels unchanged; parity 3,436/3,436 bit-identical.
Full clipmap, 16 workers BelowNormal: full detail 66.6 s, all 12,054 tiles 175 s. In-stream L0 tail is
high (median 83 ms, mean 198 ms, p95 669 ms) vs 51 ms serial.
Streaming: recentre keeps the intersection, generates only missing tiles nearest/finest first;
one crossing = exactly 441 new tiles in 6.1 s, no stale results.
Lighting: normals already come from the density gradient; the striping was the squared headlamp;
broad linear fill + warmer palette. Daytime CPU rule documented in Standalone/README.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
2026-10-03 06:49:41 +02:00

75 lines
5.1 KiB
Markdown

# Shared C++ library plan
This round keeps the standalone copy in place. The next extraction should make one implementation
serve both hosts; it should not turn the compatibility layer into a second public API.
## Library contents
Move the deterministic CPU pipeline into `VoxelForgeCore`:
- Plain voxel, tile, vector, bounds, and resolved-world input types.
- Deterministic math, noise, density operations, cave and passage fields, strate composition, and
the room/SDF caches they use.
- Marching-cubes tables and meshing, including the density-lattice gradient normals.
- Clipmap desired-set calculation and the shared density evaluation entry points.
Keep JSON and Unreal asset loading, material/actor creation, D3D11 and Unreal rendering, collision,
worker scheduling, cancellation policy, and per-frame mesh upload budgets in the host applications.
The cache-region traversal found in this round belongs with the density/meshing implementation.
The worker-local room graph cache is keyed by exact XY bounds and stratum. Grouping samples by the
legacy cache regions reduced a profiled LOD3 tile from 1,084 graph builds to 229. At coarser sizes,
plane-first traversal still thrashes the same XY windows across a tall Z run, so tiles wider than 16
chunks keep each XY column's Z samples contiguous. The profiled 64-tile LOD6 case fell from 36,480
graph builds (13.189 s in graph construction) to 12,160 (5.210 s), with the same density hash.
Cache capacity is also scale-dependent: a 32-chunk LOD5 tile benefits from two cached stratum
windows (26,926 to 9,700 builds across 64 tiles), while a 64-chunk LOD6 tile regresses with two
slots (12,160 to 23,104), so the current generator selects two only for 32-chunk tile contexts and
one otherwise. Replacing the room-graph `std::function` recursion then reduced profiled LOD5
RoomGraphBuild from 4.531 to 4.417 s and LOD6 from 5.210 to 5.038 s across the same 64 tiles,
without changing graph build counts or density hashes. These timings are profiling evidence, not a
cross-host benchmark. The shared API
should own the traversal and cache policy, with parity tests covering both traversal paths while
writing the same original density-grid indices.
The compatibility-map and allocator change did not move the original LOD0 run; those changes remove
known linear-lookup and heap-growth gaps, but stage profiling put the cost in density evaluation and
room-graph cache churn. The hot room-graph walks also used an owning `std::function` compatibility
alias for recursion; replacing them with generic self-recursive lambdas kept the same depth-first
order and removed repeated type-erasure/allocation overhead. Keep that code in the core rather than
carrying the UE-style callback alias into it. Viewer lighting and its palette remain host renderer
choices and stay outside the shared generator.
## Compatibility boundary
The core should expose standard C++ value types, explicit immutable resolved inputs, and owned mesh
results. No Unreal headers, reflection macros, `UObject`, Unreal containers, renderer types, or host
allocator should cross the boundary. The plugin adapter converts settings and loaded assets into
the resolved input snapshot, then converts the returned vertices, indices, and normals into Unreal
mesh types. The standalone JSON loader maps its exported snapshot into the same input snapshot.
Do not move scheduling into the library. Each host should pass an immutable generation context to a
worker and receive an owned result; this lets the plugin retain its task admission and game-thread
rules while the viewer retains its worker pool and bounded D3D11 upload pump.
## Plugin integration
Add a `VoxelForgeCore` C++20 target and make both builds compile or link that target from the same
source files. For Unreal, first prototype the Unreal Build Tool integration: either a small core
module that compiles the library sources or a platform-built static library with explicit build
rules. Prefer source integration if packaging a binary per Unreal platform would add maintenance.
The plugin then replaces its copied evaluator and mesher calls with the core API behind a thin
adapter. Preserve plugin-owned UObject resolution, task cancellation, collision, game-thread mesh
application, and upload budgets. The standalone keeps its JSON loader and D3D11 renderer.
## Preventing drift before extraction
Until the common target exists, keep a manifest of mirrored files and a sync/check script that names
the canonical source for each file, compares hashes, and refuses when both copies have changed.
Avoid hand-editing only one copy without recording that the edit is pending synchronization. After
each density or meshing change, run the 3,436-sample bitwise corpus and the selected-tile density
hash check. Keep `/fp:precise`; do not accept evaluator changes that alter density bits as a speed
shortcut. A standalone-only optimization must be marked as pending synchronization until it is
ported or explicitly rejected for the canonical core. Delete the duplicate standalone source only
after both host builds use the common target and the parity corpus passes through each adapter.