Files
VoxelForge/Standalone/Research/WorldAssemblerM3/Research33/REPORT.md
T
Fr0zkaandClaude Opus 5.5 e157847509 research(L64, R33): regression bisect -- Core +16% worker time L14->L61, not 2x; four-strate plan baseline
Per-round bisect of a176d07..3202193 (56 builds; timings inflated by concurrent UE builds) plus a clean
quiet-machine endpoint re-measure: L14 12.62 vs L61 14.65 worker-s (+16%), LOD0 3.99 vs 4.99 ms/tile. The
owner's Windows x2 is therefore mostly on the Unreal side or not like-for-like. Four-strate U2 plan: all
strates generate and are deterministic; passage 0-1 lower mouth is out of FI's band, 0/3 interfaces pass,
FI/TN/VS use legacy evaluators. R33: ranked Unreal-side suspects and Windows experiments.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-26 18:38:27 +01:00

66 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# R33 — Revised report
## Ranked suspects
1. **Resolved-layout composition on density samples — strongest match for per-tile cost ×2.** This path was absent at L14. L23 changed a Core sample return from boundary-band handling to full composition:
```diff
- return ApplyEditBrushes(ApplyResolvedStrateBoundaryBand(NativeDensity,X,Y,Z),X,Y,Z);
+ return ApplyEditBrushes(ApplyResolvedStrateComposition(NativeDensity,X,Y,Z),X,Y,Z);
```
The new call is at [ShapeWorldInternal.h](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:3017). `ApplyResolvedStrateComposition` runs graph lookup, boundary-band composition, passage carving, optional stamps and floor caps at [line 1819](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:1819). L37 also added this per-sample passage scan:
```cpp
for(const auto& passage:ResolvedLayout->passages)
```
at [line 957](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:957), before the early return at [line 966](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:966).
The Unreal adapter emits the resolved layout, including ceiling, bedrock and planned passages, at [VoxelForgeCoreAdapter.cpp:1751](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp:1751), [line 1765](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp:1765), and [line 1785](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp:1785). That serialization dates to L22; it was not added by U1. The L14 adapter and Core sample path had no `resolved_layout_json`, `ResolvedLayout`, or `ApplyResolvedStrateComposition`.
**Call chain:** `BuildDesiredTiles` takes the Core branch at [VoxelWorld.cpp:10988](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelWorld.cpp:10988); `BuildCoreDesiredTiles` calls `World->desiredTiles()` at [line 10525](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelWorld.cpp:10525). A missing key reaches `LoadCoreTile`, which launches a worker and calls `World->generateTile()` at [line 13293](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelWorld.cpp:13293). Core mesh sampling then reaches the changed density return above. This multiplies work across existing samples in eligible native-field tiles; it does not increase their sample count. The checked-in standalone manifest has no `resolved_layout_json`, so the Linux headless bisect may not exercise this Unreal-fed composition path.
**Windows experiment:** There is no existing toggle for the whole composition. For the cheapest broad test, temporarily omit the `Root->SetStringField(TEXT("resolved_layout_json"), …)` line at [VoxelForgeCoreAdapter.cpp:1785](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp:1785), rebuild Runtime, and rerun the same `-StreamingTraceOnly` command. Keep tile keys, LODs and cells per tile fixed. A large fall in per-LOD worker time supports this path; little change refutes it as the dominant cost. This is a measurement-only geometry change. If it falls, a follow-up can isolate passages by constructing the Core world from `Snapshot->withoutResolvedLayoutPassages()` at [VoxelWorld.cpp:7663](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelWorld.cpp:7663). A drop there implicates passage scanning/carving specifically.
2. **Authored CoreGeometry — conditional for the current re-saved assets; ruled out for the U1 trace by its report.** U1 added the optional property at [VoxelStrateDefinition.h:125](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Public/VoxelStrateDefinition.h:125). The adapter skips definitions with no recipe or authored arrays:
```cpp
if (!Definition || !HasCoreGeometry(*Definition)) continue;
```
at [VoxelForgeCoreAdapter.cpp:1037](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp:1037). When active, native recipes, stamps, rooms or tunnels can add per-sample field work. The U1 report says its old assets had CoreGeometry off and arrays empty ([report lines 135–140](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Unreal1/REPORT.md:135)). U2 shows all four `DA_Strate` asset hashes changed, while `DA_Settings` did not ([report lines 9–15](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Unreal2/REPORT.md:9)); it does not audit whether CoreGeometry is active.
**Windows experiment:** In the Asset Editor, for each active `DA_Strate1–4`, set `CoreGeometry.GeneratorRecipe=None` and clear `Stamps`, `Rooms`, and `Tunnels`. Rerun and compare tiles in those strates. A fall confirms authored geometry was contributing; no change refutes it. If those fields are already empty, this cannot explain the result.
3. **L44’s expanded shape-free proof — limited to cold, pre-compile tiles near passage columns.** The native-only path was already present at L14; L44 did not disable it for Unreal. It added a conservative support-column envelope in `ResolvedPassageTouchesBox`, beginning with `if(passage.controlPoints.size()>=2)` at [VoxelForgeCore.cpp:1275](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:1275). Before shape compilation, Unreal calls `isTileShapeFree()` and caches the result at [VoxelWorld.cpp:13100](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelWorld.cpp:13100). Core selects native-only generation while the shape is not ready at [VoxelForgeCore.cpp:2007](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:2007), then checks the proof again inside tile generation.
This can move passage-column tiles from the early native-only path to the compiled-shape path. It cannot explain a doubled cost across every steady-state tile. The proof check itself is per tile, not per density sample.
**Windows experiment:** Add a temporary log at the successful `LoadCoreTile` admission point recording tile key and `bShapeReady`; correlate those entries with worker times and the existing `[VoxelForgeCoreRuntime] compiled=1` log at [VoxelWorld.cpp:7792](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelWorld.cpp:7792). If slow jobs are mostly post-compile, this is refuted. If they cluster among pre-compile tiles near the passage envelope, temporarily revert only L44’s added column-bound block and compare those same tiles. A drop supports an eligibility-path effect; the revert is diagnostic because it weakens the shape-free correctness proof.
4. **A profiling or hash-audit switch left on — possible multiplier, but no evidence it was enabled.** Core density hashing is disabled by default (`bCaptureVoxelForgeCoreHashesForValidation=false` at [VoxelWorld.h:315](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Public/VoxelWorld.h:315)); `Options.hashDensity` receives that value at [VoxelWorld.cpp:13284](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelWorld.cpp:13284). The density and tile profiling CVars also default to zero at [VoxelWorld.cpp:265](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelWorld.cpp:265) and [line 446](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelWorld.cpp:446). These switches existed at L14.
**Windows experiment:** Append `-voxel.ProfileTileGeneration=0 -voxel.ProfileDensity=0 -voxel.ProfileDensityFull=0 -voxel.PerfAttribution=0 -voxel.SampleStacks=0` to the Unreal launch, and confirm the actor’s `bCaptureVoxelForgeCoreHashesForValidation` is false. A large fall confirms an override was active; no change rules this out. U1’s documented trace command contains no profiling flags, but its report does not record effective INI values.
5. **Floating-island upper seal — low and version-gated.** L18 added the upper-seal call at [ShapeWorldInternal.h:6514](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:6514); it is reached only for a floating-island recipe with `geometryVersion` 3 (the surrounding branch is `geometryVersion<4` at [line 6504](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:6504)). The L22 generated recipe version is 4, so the normal generated path skips it. It cannot explain all tiles.
**Windows experiment:** First log the active floating-island recipe version. If it is 3, run once with `-voxel.TunnelAblateBoundarySeal=1`. A fall limited to Floating Islands tiles supports this operation; no change refutes it. At version 4 or later, this test is inapplicable.
## Unreal path versus standalone
Unreal serializes the resolved layout during initialization, parses it into `ResolvedWorldSnapshot`, and constructs the Core `World` at [VoxelWorld.cpp:7610](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelWorld.cpp:7610) and [line 7663](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelWorld.cpp:7663). The headless worker also calls `CoreWorld->desiredTiles()` and `CoreWorld->generateTile()` ([StreamingWorld.h:606](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Source/StreamingWorld.h:606), [line 903](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Source/StreamingWorld.h:903)). Its checked-in owner manifest does not include the resolved-layout field, which Core parses only when that field is present ([VoxelForgeCore.cpp:989](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:989)).
Unreal’s `worker_task_s` includes more than `generateTile()`: after Core returns, it copies vertices, normals, UVs, colors and indices into Unreal arrays and builds the RealtimeMesh stream set before stamping the worker end time ([VoxelWorld.cpp:13333](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelWorld.cpp:13333), [line 13358](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelWorld.cpp:13358)). That can make Unreal tiles costlier than standalone tiles in absolute time, especially if content emits more geometry. The copy path predates L14. Unreal tile and compile work also uses `UE::Tasks::Launch`; those tasks execute asynchronously and can overlap on workers, so reported elapsed task time can include scheduler contention ([Epic Tasks System documentation](https://dev.epicgames.com/documentation/unreal-engine/tasks-systems-in-unreal-engine)).
The shared-cache settings show no LOD0 discrepancy: both paths default to `useSharedColumnCache=true` and `memoizeMcGradients=true`; Core forces direct-mapped column caching for `lod<=2` in either caller at [VoxelForgeCore.cpp:2003](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:2003). `VoxelClipmapDesiredTiles` gained bounded wide-division helpers, but Core `BuildDesiredTiles` returns through `BuildCoreDesiredTiles` before the legacy helper path at [VoxelWorld.cpp:10988](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelWorld.cpp:10988). Those changes do not add density-sample work.
The other named U1 changes are outside the tile worker hot path: `VoxelSettings::PostLoad` migrates units once when loading assets at [VoxelSettings.cpp:31](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelSettings.cpp:31); `StrateManager::Initialize` resolves metre values into cached slot values at [VoxelStrateManager.cpp:1430](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelStrateManager.cpp:1430); `VoxelContentManager::Initialize` parses resolved-layout anchors at [VoxelContentManager.cpp:332](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelContentManager.cpp:332). The Core native SurfaceWorld and Floating Islands sample adapters do not call those conversions per sample. Content landmark checks can affect ticks if authored, but they are not tile-worker density work.
`FullResClipLevels`, `CoarseTileCells`, `CoreClipRadius`, and `CoreMaxClipLevel` still need a like-for-like value check. They alter tile-set composition and/or cells per tile, not the composition cost per existing sample. U1’s `compiled=1` log prints clip radius, max level and voxel size; compare those and per-LOD cell counts with L14 before interpreting total worker-seconds.
## Asset provenance and limits
U1 says its timing run used the “current owner plan” at 25 cm and reports 2,578 initial tiles ([Unreal1 report lines 64–78](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Unreal1/REPORT.md:64)). Its audited BuildHost assets are the old U1 hashes, and it says no packages were saved ([lines 135–171](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Unreal1/REPORT.md:135)). U2 records new hashes for all four `DA_Strate` assets, the same `DA_Settings` hash, and a regenerated four-strata/four-passage manifest ([Unreal2 report lines 9–25](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Unreal2/REPORT.md:9)). The best reading is that U1’s trace used the old U1 asset set, not today’s re-saved packages. The U1 trace itself does not embed asset hashes, however, and the local L14 report is Linux headless rather than the Windows baseline. L14’s loaded Windows asset hashes and exact clip settings are unknown, so content parity is unproven.
U1 reports a cold worker sum of 34.777 seconds, while the request’s rounded regression figure is about 30 seconds. The report also says the comparison was not a controlled A/B. I did not inspect the owner’s raw BuildHost traces or verify effective Windows INI overrides. No scripts, builds, tests, binaries, or source edits were performed.