Author SHA1 Message Date
Fr0zkaandClaude Opus 5.5 2dfe599148 research: Astra design review of the ~1 s whole-view goal (read-only)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-27 16:39:34 +01:00
Fr0zkaandClaude Opus 5.5 90378a585f profile: per-term legacy FI/TN sample profiler (L76); passage/support is ~75% of field time
Opt-in at build time (VOXELFORGE_PROFILE_LEGACY_TERMS=1; compiled out by default, ~2% when on).
Per legacy sample: passage/support composer ~1.05 us vs primitive scan ~0.19 us, boundary
band ~0.06 us, noise ~0.01 us. Resolved-passage constants are cached at field binding
(arithmetic order unchanged); whole-view effect within noise. Per-tile hashes identical to L75
in all 24 compiler x worker x view comparisons; fast tier PASS; Linux UE host builds.
R40 researcher notes attached.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-27 16:25:19 +01:00
Fr0zkaandClaude Opus 5.5 9d5455ec68 skip: passage proof bounds include wall/floor relief (L75 soundness fix); FI -7.6%
L74's passage bound omitted the 0.21 m wall relief and 0.20 m floor relief, so its whole-tile
proof was unsound in principle (no tested view hit it). Passage support is now bounded per route
segment by four parameter-subinterval boxes using Catmull-Rom derivative extrema, local max
radius and relief padding. Stratum slicing only at LOD0-2 in FI/VS. Legacy sub-tile block
certificates are audit-only (runtime version was slower). TN regression (-80,-20,-3218) kept.

Exact full-grid vs skip hashes on every tile, 4 views, Clang/GCC, W1/W8/W12; control caught;
80-camera sweep 0 violations. Whole view vs c67b200: SW -1.3%, FI -7.6%, TN -2.3%, VS +0.2%.
Compiles in the Linux UE host (VFHostEditor, 27/27 actions).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-27 14:34:49 +01:00
Fr0zkaandClaude Opus 5.5 c67b2006aa skip: sound whole-tile proofs for legacy FI/TN/VS fields (L74); FI -30%, TN -22%
The compiled-analytic stratum interval now certifies a legacy tile uniform only when it stays
beyond the detail envelope (and smooth-min slack for solid) with no intersecting primitive,
edit, native field, composer candidate, passage or active transition; everything else fails
closed. The stream layer uses the proof for whole-tile skips; legacy sub-tile skips stay off
(two block proofs failed the falsifier in TN).

Falsifier: full-grid reference vs skip-enabled run, identical mesh/payload/topology hashes on
every tile of all four owner views, Clang and GCC, W1/W8/W12; 20-camera sweep per strate,
0 violations; a too-tight control changed 3/184/233/238 tiles and was caught.
Whole view (8 workers): SW 2.61->2.48, FI 7.18->5.05, TN 5.97->4.63, VS 2.70->2.40 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-27 10:19:31 +01:00
Fr0zkaandClaude Opus 5.5 bd1b2a8f1d profile: per-LOD whole-view breakdown for the four-strate owner world (L73, no world change)
FI/TN/VS (legacy compiled-analytic) skip ZERO tiles at every LOD and evaluate ~44k density
points per LOD0/1 tile; SW skips 359/512 at LOD0. The whole-tile skip requires a native
tile with a uniform verdict, and the legacy paths return Mixed/unknown (R39 notes attached).
Opt-in stage timers, sampled field timer, per-LOD classification and composer counters.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-27 08:48:57 +01:00
Fr0zkaandClaude Opus 5.5 d970d75be8 streaming: deeper ready queue + startup apply cap; tile-local mouth-support culling (L72)
- Unreal: ready queue depth 8 -> 16 (cap 4 worker-sets / 64), startup mesh-apply cap
  12 -> 24 within the unchanged 14 ms budget. Owner SW arrival, same 2,850 keys:
  initial set ready 4.78 -> 3.74 s, queue p95 123 -> 35 ms, request-to-apply p95
  139 -> 45 ms; walkable gate 1.07 -> 1.11 s; per-frame apply p95 0.73 ms.
- Core composer: mouth-support segments filtered by tile bounds, mouth relief result reused
  (route checks -80/-87% in passage tiles). Hashes identical W1/W8/W12, Clang and GCC.
- Opt-in per-tile composer counters. Verify control meshes stored as .xz (78.5 -> 18.4 MB).
- Fast tier 269 PASS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-27 08:25:14 +01:00
Fr0zkaandClaude Opus 5.5 971206c2ea trace: Unreal timing traces run without hash capture (L71); verify baselines tracked
The L70 "2x SurfaceWorld GenerateTile in Unreal" was the instrument: the trace enabled
bCaptureVoxelForgeCoreHashesForValidation, which disables the native heightfield-band
shortcut and whole-tile skips (W1 same keys: 9.00 s hash-off vs 28.46 s hash-on). With
hashing off, Unreal and headless match at W8 within 1.2-2.5% (Core 14.42 s vs 14.26 s) on
identical tile keys. LOD0 p95 tail = real cost of non-empty passage tiles near the spawn mouth
(p50/p95 18.8/105 ms); most LOD0 tiles are empty.

verify.py reads its baselines from Standalone/Tools/verify/baselines/ instead of untracked
Saved/Linux. The 11 .vfmesh control meshes (~78 MB) are not committed yet (to be stored
compressed). Fast tier green.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-27 06:55:46 +01:00
Fr0zkaandClaude Opus 5.5 018c0e1ec8 verify: track the pinned owner hash baselines (L67/L68/L70); .gitignore had dropped them
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-27 06:23:13 +01:00
Fr0zkaandClaude Opus 5.5 bd68aee1ca parity: Unreal and headless resolve the same four-strate plan (L70, v214)
- Export commandlet writes the live Core adapter's world_assembly + resolved layout pair;
  the headless owner path imports that pair (manifest/layout equal, layout_chunk_width_q
  validated). owner_world.json re-exported from the owner's staged assets on the Linux host.
- Adapter SW route: Core clearance/radius floors, 0.5 m support search, outward-facing
  tie-break; passage mouth/mid radius floors match Core (36/24 owner voxels).
- FI in Unreal now uses the legacy (compiled analytic) generator unless a plan is imported:
  the auto native-FI plan failed the passage audit on the owner plan (1843 blocked samples,
  945 open-air violations). Owner decides whether native FI returns.
- In-engine spawn traces per strate (3 each): SW GenerateTile 42.7 worker-s vs 20.1 headless
  for equal tile counts; LOD0 p95 45-169 ms in UE.
- Fast tier green; all three passages pass on the shared plan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-27 06:22:48 +01:00
Fr0zkaandClaude Opus 5.5 81e2452a3d captures: readable player-view captures with a coverage gate (L69); R36 native-generator survey
- Capture exports now cover a 90-degree player frustum (radius 3, LOD 0-8, overlap patches);
  a 17x9 ray coverage probe aborts before rendering if tiles are missing (truncated-mesh
  control: 480 uncovered samples, rejected). Clear colour is sky blue, so un-meshed space can't
  pass as dark rock. 16 views of the owner's four-strate plan.
- Explore commandlet: finite XYZ spawn override for traces (Source/, Linux-compiled).
- In-engine four-strate trace NOT obtained: the engine-generated plan fails passage validation
  at a solid centerline voxel (~(-43,-34.5,-448.5) m) while the headless owner_world.json
  plan passes. Engine and headless plans diverge.
- R36 (read-only): order TN -> VS -> FI for native migration; native FI is reachable from the
  adapter but not selected in the owner run, cause unknown.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-27 04:00:49 +01:00
Fr0zkaandClaude Opus 5.5 bd7bba994c passages: smooth 12 m lower-mouth radius taper; all four-strate passages pass (L68, v213)
The FI->TN wall beat (4.77 cm at 7.06 m) was the lower-mouth flare: the full mouth radius
was assigned to the penultimate control point, a one-segment radius jump. The shared route
builder now blends to the mouth radius over 12 m with a smoothstep. Wall ripple 1-2:
7.27 -> 1.62 cm. All three passages pass support/blocked/grade/ripple/naturalness; controls
detected. Fast tier 269/269 incl. the new-plan stage; W1/W8/W12 equal, Clang and GCC.
Hash pins: FI style 2 re-pinned at v213.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-27 03:17:27 +01:00
Fr0zkaandClaude Opus 5.5 087333bf8a passages: four-strate passage floors supported (L67, evaluator v212); 1-2 wall beat still red
- Shared composer: a passage's restored support is applied after a physically overlapping
  later bore, instead of being carved away by it (0-1: 59 -> 0 unsupported).
- Capsule clearance for support restoration is tested against nearby route segments only
  (1-2: 3 -> 0, 2-3: 2 -> 0 unsupported).
- L66 index switches replaced by a physical criterion (both mouths inside their strata bands,
  neither a SurfaceWorld landing).
- Audit: floor zero-crossing refined by bisection (the 0.2498 m "step" was the gauge);
  wall ripple keeps the authored radius, cubic detrend per 32 m window. Controls detected:
  L35 ribs, beads, synthetic 0.25 m step, L36 washboard, L23 terrace.
- Result: 0-1 and 2-3 pass all audits; 1-2 fails wall ripple 7.27 cm (limit 3.5) from an
  authored-radius beat at 7.06 m. Fast tier was green before the final gauge change and was
  not re-run after it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-27 02:27:40 +01:00
Fr0zkaandClaude Opus 5.5 5e2fc53d39 planner: four-strate owner plan spawns (L66); pinned 2-strate regression fixture
- Core caps each passage mouth reach to its own strate's usable content band (owner
  DA_Strate1 exports ReachMax=5000 owner voxels, which put the SW->FI lower mouth 79.5 m
  below FI, inside TN).
- Unreal adapter: SW route search keeps the old 64 m candidates first, then a bounded
  single-mouth search with shorter lengths; logs candidate/rejection counts.
- Internal-chain passage floor/support tweaks (only with >2 strates): ripple 1-2 4.78->2.90 cm,
  2-3 3.57->0.10 cm. Interfaces still 0/3 walkable (59/3/2 unsupported).
- verify.py: legacy tiers use Standalone/data/regression/owner_world_2strate_l61.json; new
  four-strate stage reads its expectations from owner_world.json (currently red on interfaces).
- Unreal trace L14 vs HEAD, same assets (16-chunk FlatPlain): 12.20 vs 12.56 worker-s; no x2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-26 22:13:22 +01:00
Fr0zkaandClaude Opus 5.5 47148e0a0d build: VoxelForge compiles in Unreal 5.7 on Linux (L65); Linux-only Unreal trace timing fields
Linux UE builds failed on the Core wrapper module: the Linux-only PerModuleInline.gen.cpp saw Standalone/Compat
shadow headers instead of the engine's, and WITH_EDITOR was redefined under -Werror. Compat headers now forward
to the engine headers with include_next for generated Linux module TUs (canonical Core TUs opt into the Compat
definitions via CoreNamespacePreamble.h); CoreMinimal.h undefines WITH_EDITOR first. VoxelForgeCore.Build.cs
enables that forwarding and AVX2 on Linux only; VoxelForge.Build.cs disables Unity on Linux only. Portability:
std::int64_t consistently in VoxelContentManager.cpp; grouped &&/|| in VoxelRedefinedDensityField.cpp. Linux-only
trace fields split Core generateTile / mesh copy / RMC stream build. Standalone Clang and GCC whole-view hashes
are unchanged vs 3202193. The Unreal spawn trace is blocked by the owner's new assets (SW route sweep fails).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-26 19:19:07 +01:00
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
Fr0zkaandClaude Opus 5.5 9136f5f0e7 chore(unreal): U2 -- owner's four-generator plan staged; owner_world.json re-exported
Owner re-saved strates 2026-10-26: DA_Strate1 SurfaceWorld, 2 FloatingIslands,
3 TunnelNetwork, 4 VerticalShafts (default params). BuildHost copies and the
enforced SHA-256 tables updated (old U1 set kept in the report); the export
commandlet handles the four-strate plan; Standalone/data/owner_world.json
regenerated (SHA-256 F42F96AD...). One normal BuildHost gate passed (64/64
tiles) but its top-strate window produced 0 triangles, and a missing
NewDataAsset reference warns -- both to check. No measurements this round.
Report: Standalone/Research/WorldAssemblerM3/Unreal2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 14:18:19 +01:00
Fr0zkaandClaude Opus 5.5 fc9227f9e0 fix(unreal): U1 -- Unreal compiles again on L61; CoreGeometry asset section; versioned layout-unit upgrade
Windows MSVC catch-up after abyss L15-L61 (no Unreal there): 64-bit clipmap
shift (C4334), qualified VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel, UHT
metadata and duplicate-signature fixes. Optional CoreGeometry section on
UVoxelStrateDefinition (native caves, lattice, profiled heightfields, named
stamps, rooms/tunnels; off/empty by default), resolved after passage planning.
VoxelForgeAssetVersion 2 converts strate height/transition/gap to metres on
load (exact, 0 m change on the owner's 5 assets; packages never saved).
Gates pass (SurfaceWorld, FloatingIslands, Core-off, M2, W1/W8/W16).
KNOWN REGRESSION vs L14 (2026-10-20, same command, same PC): full set 3.9 ->
5.37 s, worker-seconds 15.8 -> 30, LOD0 1.11 -> 1.69 s, ring 0.50 -> 0.80 s;
not yet attributed (Core L15-L61 vs Unreal side).
Report: Standalone/Research/WorldAssemblerM3/Unreal1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 12:12:08 +01:00
Fr0zkaandClaude Opus 5.5 32021930d2 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-26 09:48:37 +01:00
Fr0zkaandClaude Opus 5.5 135957831b fix: sanitizer sweep across all generators; bound signed shifts/division/overflow at coordinate limits (L61); R31
New verify.py sanitize tier (Clang address,undefined,float-cast-overflow; GCC address,undefined;
-fno-sanitize-recover) runs 19 cases per compiler at LOD0-8 across every generator, passages and the
crossing: 0 runtime reports; a planted signed-overflow control is caught by both. A static scan fixed
INT_MIN negation in floor division, unbounded signed LOD shifts and coordinate-times-extent overflow in
VoxelClipmapDesiredTiles.h and VoxelTypes.h (plugin and standalone), and VoxelWorld.cpp, VoxelContentManager.cpp
and VoxelDensityVolume.cpp (plugin only, uncompiled here). Supported-input output unchanged (version 211).
fast/medium/full PASS. Research R31 (self-checked): far-LOD 4-8 cut plan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-26 05:19:18 +01:00
Fr0zkaandClaude Opus 5.5 c0c204ba50 fix: signed-overflow UB in VoxelHash made cave placement compiler-dependent; all generators LOD0-8; speed matrix (L60)
VoxelHash::Cell/Pair, legacy Cellular3D and the shared Core cellular hash added 0x7FFFFFFF to a signed
int32 before casting to uint32; overflow is undefined, so Clang and GCC placed cave passage 1 differently
(first differing sample at LOD1 tile (0,64,-512)). The bias is now done in unsigned arithmetic, in both the
standalone copy and the Unreal plugin header (Source/VoxelForge/Public/VoxelCaveMorphology.h). Native
caves, motifs and lattice now stream LOD0-8 with skirts and cross-level transitions. New verify.py
--speed-matrix runs every generator on one asserted-identical view (5 interleaved runs): motifs 1.33,
profiles 1.30-1.32, caves 1.41, natural SW 1.67, lattice 1.70, FI 4.93 s whole view. fast/medium/full PASS,
Clang/GCC peer hashes equal. Research R29 (open-edge ledge) and R30 (compiler-divergence suspects).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-26 01:50:15 +01:00
Fr0zkaandClaude Opus 5.5 fbae82e024 research(R28, self-checked): per-generator cost ledger; caves/motifs/lattice timings were LOD0-only views, not comparable
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-25 20:27:56 +01:00
244 changed files with 15345 additions and 642 deletions

No files matched your search

+24
View File
@@ -2292,6 +2292,30 @@ it also checks the orphan control, brute-force interval samples and the composed
The Linux58 report contains the measured reachability, surface, peer-determinism, timing and capture
results; the code path is not promoted to legacy Maze/VerticalShafts generation.
### 8.55 Unreal CoreGeometry asset bridge and U1 asset version
`UVoxelStrateDefinition::CoreGeometry` is optional and defaults to recipe `None` with no stamps,
rooms, or tunnels. The Unreal adapter preserves the established passage planner, then resolves
authored stamps and cavern graph records, then finalizes the layout. Stable names are required and
are scoped by strate in Core; landmarks may target a stamp, room, or tunnel by that stable name.
Core profile heightfields are restricted to SurfaceWorld. Native caves require the TunnelNetwork or
Underwater archetype; native lattice requires Maze or VerticalShafts. Core recipe/version fields
enter the resolved layout identity when selected. An imported manifest cannot be mixed with a
per-strate profiled heightfield.
The checked-out Core API contains stamp vocabularies V1, V2, and V3. Unreal uses V1 for the original
stamp kinds and V3 for monolith/platform motifs, then combines and strictly validates the result.
Research17 names V2, but the source API and motif vocabulary require V3; source was followed.
Research27 water remains out of scope for U1.
The shared VoxelForge custom asset version is 2: version 1 records the tunnel-floor slope migration;
version 2 adds load-time conversion for inter-strate gap, strate height, and transition width. The
saved strate-layout width is used, currently 8 m. Old packages are converted in memory and marked
dirty, but U1 does not save them. This does not yet migrate the rest of Research8's reflected
spatial-property inventory: legacy chunk distances, owner/Core voxel lengths and frequencies,
placement vectors, and other centimetre fields remain in their prior units. The U1 report records
the five BuildHost asset values, hashes, and remaining work.
## 9. Multiplayer model (listen-server first, dedicated-friendly)
> **Status: transport is DESIGN ONLY — nothing is networked in-tree yet** (no `Replicated`/`HasAuthority`/RPCs; a
+38 -9
View File
@@ -88,19 +88,22 @@ therefore **128 m**, and a 64-chunk strate is **512 m**. Core still stores work
at 25 cm those tiles happen to span 8 m, but they are not the layout unit. The gameplay capsule remains
**0.34 m radius / 1.76 m tall** (**2.72 / 14.08 legacy voxels**). Research-field dimensions are authored
in metres and converted at runtime. The live
strate generator continues to read voxel-valued parameters from its assets, so those authored
dimensions become half as large in metres unless their asset values are doubled.
strate generator continues to read voxel-valued shape parameters from its assets, so those authored
dimensions become half as large in metres unless their asset values are doubled. U1 adds a
versioned in-memory conversion for strate height, transition width, and inter-strate gap only; the
broader Research8 voxel/chunk/centimetre inventory is not yet migrated.
The intended metre-scale families remain: room diameters **8-20 m**, origin hub **24 m**, room cells
**32 m**, round tunnel bores **3-4 m**, maze cells **16 m** with a **6.25 m nominal bore** (**3.125 m**
radius; **3.0625 m** worst-case usable floor after roughness), slab open span **22.4 m**, shaft
diameters **4-7 m**, island diameters **12-24 m**, and ordinary inter-strate bores **3-4 m**. Source
new fixed physical dimensions are authored in metres and converted to voxel counts at the boundary.
The owner's voxel-valued live strate assets are not migrated by C++: unchanged saved values therefore
produce half-size rooms, tunnels, features and noise periods in metres. See the step-3 migration list
in the handoff report for the exact `DA_Settings` fields and values.
The owner's voxel-valued live strate shape settings are not migrated by U1: unchanged saved values
therefore produce half-size rooms, tunnels, features and noise periods in metres. See the U1 report
for the conversion coverage and remaining Research8 fields.
**Owner asset migration (no packages were edited):** preserve `DA_Settings` distances by changing
**Prior reauthoring recommendation (not applied in U1; no packages were edited):** preserve
`DA_Settings` distances by changing
`ViewDistanceXY 32→64`, `ViewDistanceUp/Down 10→20`, `PawnCollisionRadiusChunks 2→4`,
`StrateViewMarginChunks 6→12`, `ClipRadius 6→12`, `RenderDistanceChunks 1536→3072`,
`EdgeSealThickness 128→256 voxels`, decoration radius/near radius `12/6→24/12 chunks`, region
@@ -110,8 +113,9 @@ size `8→16 cells`, spacing/far spacing `8→16 voxels`, march step `4→8 voxe
`12→24 cells`; it is currently disabled. Keep `MaxClipLevel=6` and zero-valued disabled/unbounded
limits unchanged; double finite world radii and gaps, and multiply finite voxel-volume caps by 8.
The loaded `Crystal Cave` slot in `DA_Strate3` is 64 chunks tall; use 128 chunks to preserve its
former 512 m span. Its voxel-authored live-generator lengths remain half-size until that asset is
re-authored; the editor-only research field is the step-3 target.
former 512 m span. U1 automatically converts only strate height and transition widths and the
settings gap at load; the remaining recommendations above are pending. Its voxel-authored
live-generator lengths remain half-size until their asset fields are migrated.
Step-3 six-worker scale benchmark (idle machine, `Saved/Research/step3/scale/step3_scale_cacheperf_v1`):
the 519³ field took **7.133834–7.825292 s** across three runs (median **7.347496 s**); the 259³
@@ -173,7 +177,8 @@ remains `0`, and lateral regions remain gated off.
| `ResolvedLayoutStratePassageCapabilitiesV1::{hasNativeCaveField,hasNativeLatticeField}`, `IsNativeLatticeFieldStratum`, `HasNativeGraphField` | `Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` / `Standalone/Core/src/ResolvedLayoutInternal.h` | Explicit per-strate native graph capability; `Core.Lattice.PrimitiveField` is restricted to Maze/VerticalShafts recipe version 1. Lattice seeds include world seed, strate, recipe version and stable feature ID; explicit polyline records keep their endpoints and receive conservative bounds. §8.54. |
| `FCavernRuntimeFeature::FSegmentNode`, `ApplyResolvedNativeCavesField`, `ClassifyNativeCaveFieldBox` | `Standalone/Core/src/ShapeWorldInternal.h` | A deterministic nested segment BVH accelerates native graph distance and conservative interval evaluation; the outer graph BVH still supplies tile candidates and protects `isTileShapeFree`. The 0.4 m feature-union width is separate from the floor fillet width. §8.54. |
| `../../Standalone/Core/src/FloatingIslandsInternal.h` | Canonical standalone/Core floating-island planner, geometry-versioned field, manifest serializer, influence bounds, capsule-route checks, and thickness audit. |
| `Tools/VoxelForgeTest.ps1` | The single guarded test entry point. `-Scenario canonical|owner|probe|perf|tests|surface-fall|crossing|gate-stress|visual-edit|visual-lod|visual-gap-walk|visual-gap-inject|dig-motion|parity|horizon|horizon-render|determinism|region-eval|step2d|step2e|step2f|step2f-capture|step3-scale|step3-game`, optional `-Build`, `-Assets owner|default`, `-Cvars`, absolute `-Out`, `-Label`, `-HorizonLevel`, `-HorizonRenderDistanceChunks`, `-Step2fProfile=0..7`, `-Step2fCaptureView=inside|wall-closeup`, `-ParityExportSize=32|64|128`, `-ParityExportTile=x,y,z`, `-SharedDDC`, `-SampleDensityStacks`, `-StackSampleIntervalUs`, and `-RegionHashProbe -RegionWorkers N -RegionReferenceHash HASH`; stages the host/assets, isolates user/DDC/config state, serializes one Unreal launch at a time, enforces the 8 GiB/30-minute guards, waits for 6 GiB free RAM before launches, and emits compact `result.json`/`summary.txt` plus full logs. Parity can render its game leg with `-EnableRender` and compares the selected export region against the exact streamed tile hashes. `visual-gap-walk` records camera-frustum surface-point coverage at 6000 cm/s; `visual-gap-inject` proves an early component hide is detected; `dig-motion` logs per-frame vertical velocity, gate, support readiness, and movement mode. `-Cvars @{ 'voxel.TestGapAuditLegacyHandoff'=1 }` replays the old visibility handoff for A/B audit baselines. Horizon traces report desired/loaded tiles, resident stream geometry, worker seconds, steady wall time, and peak private memory; `horizon-render` captures the owner view; `step2f-capture` selects one of eight parameter sets, finds a positive-density camera point and aims at a sampled rock wall for an inside or wall-closeup view, then saves it under `Saved/Research/step2f/captures` (pass the persistent render DDC with `-SharedDDC`); `determinism` compares per-tile hashes across the default CRT path and `-voxel.CrtFma3=0`; `region-eval` compares the live path with the default-off editor chunk program on the 259³ lattice and five-seed probe set; `step2d` retains the earlier independent field; `step2e` retains its earlier benchmark; `step2f` compares frozen step 2e and the coherent field for all eight profiles on the same 259³ ring, applies geometry gates, and checks sound bounds, seams, and 1-vs-6-worker hashes. `-RegionHashProbe -RegionWorkers N -RegionReferenceHash HASH` checks a full-ring hash at another partition count. `-SampleDensityStacks` captures profiles, and `-StackSampleIntervalUs` sets the sampling interval.
| `ApplyOwnerFourStratePlan` | `Source/VoxelForgeEditor/Private/VoxelForgeExploreCommandlet.cpp` | BuildHost-only transient fixed-slot plan from the four staged owner strate assets; leaves the authored settings and strate packages untouched. |
| `Tools/VoxelForgeTest.ps1` | The single guarded test entry point. `-Scenario canonical|owner|probe|perf|tests|surface-fall|crossing|gate-stress|visual-edit|visual-lod|visual-gap-walk|visual-gap-inject|dig-motion|parity|horizon|horizon-render|determinism|region-eval|standalone-export|compile-only|step2d|step2e|step2f|step2f-capture|step3-scale|step3-game`, optional `-Build`, `-Assets owner|default`, `-Cvars`, absolute `-Out`, `-Label`, `-HorizonLevel`, `-HorizonRenderDistanceChunks`, `-Step2fProfile=0..7`, `-Step2fCaptureView=inside|wall-closeup`, `-ParityExportSize=32|64|128`, `-ParityExportTile=x,y,z`, `-SharedDDC`, `-SampleDensityStacks`, `-StackSampleIntervalUs`, and `-RegionHashProbe -RegionWorkers N -RegionReferenceHash HASH`; stages the host/assets, isolates user/DDC/config state, allows the owner's interactive editor while refusing overlapping `UnrealEditor-Cmd` launches, runs owned processes at Normal priority with a 12-logical-CPU cap, enforces the 8 GiB/30-minute guards, waits for 6 GiB free RAM before launches, and emits compact `result.json`/`summary.txt` plus full logs. Parity can render its game leg with `-EnableRender` and compares the selected export region against the exact streamed tile hashes. `visual-gap-walk` records camera-frustum surface-point coverage at 6000 cm/s; `visual-gap-inject` proves an early component hide is detected; `dig-motion` logs per-frame vertical velocity, gate, support readiness, and movement mode. `-Cvars @{ 'voxel.TestGapAuditLegacyHandoff'=1 }` replays the old visibility handoff for A/B audit baselines. Horizon traces report desired/loaded tiles, resident stream geometry, worker seconds, steady wall time, and peak private memory; `horizon-render` captures the owner view; `step2f-capture` selects one of eight parameter sets, finds a positive-density camera point and aims at a sampled rock wall for an inside or wall-closeup view, then saves it under `Saved/Research/step2f/captures` (pass the persistent render DDC with `-SharedDDC`); `determinism` compares per-tile hashes across the default CRT path and `-voxel.CrtFma3=0`; `region-eval` compares the live path with the default-off editor chunk program on the 259³ lattice and five-seed probe set; `step2d` retains the earlier independent field; `step2e` retains its earlier benchmark; `step2f` compares frozen step 2e and the coherent field for all eight profiles on the same 259³ ring, applies geometry gates, and checks sound bounds, seams, and 1-vs-6-worker hashes. `-RegionHashProbe -RegionWorkers N -RegionReferenceHash HASH` checks a full-ring hash at another partition count. `-SampleDensityStacks` captures profiles, and `-StackSampleIntervalUs` sets the sampling interval.
| Step-3 scale and game validation | `step3-scale` measures the same 519³ and 259³ six-worker lattices at 12.5 cm. The cached medians are 7.347496 s and 1.169567 s respectively; determinism and brick sound-proof audits pass. `step3-game` routes in-game generation through the brick evaluator and records tile timings, spawn fill, moving frames, and profile-switch rebuilds. The visible D3D12 run measured tile generation p50/p95/max 3.326/31.045/169.867 ms, full-detail L0 fill 12.369 s for 2,765 tiles, and moving-frame p50/p95/p99/max 4.856/8.922/13.090/80.575 ms. Switching profiles rebuilt 5,815 loaded tiles. Both high-resolution screenshot requests were logged, but the harness found only one fresh PNG and it is a blank pale viewport; no lit comparison capture is accepted. Raw output: `Saved/TestHarness/step3_game_visible_final`; scale output: `Saved/Research/step3/scale/step3_scale_cacheperf_v1`. |
| `Public/VoxelForgeModule.h` / `Private/VoxelForgeModule.cpp` | `FVoxelForgeModule` boilerplate (Startup/Shutdown just log). |
| `Source/VoxelForgeEditor/VoxelForgeEditor.Build.cs` / `Private/VoxelForgeExploreCommandlet.*` | Editor/commandlet-only `-run=VoxelForgeExplore`; schema-v1 owner-asset export check and BuildHost Core actor tests. `-coreworldassembly=PATH` supplies a reviewed manifest transiently to `-coreworldtest=1`; it does not save a data asset. `FStandaloneCoreAdapterScheduler` runs Core work at 1/8/16 workers; `VerifyStandaloneCoreAdapter` checks hashes and walkability. Existing bounded render/`VF_` walk/OBJ orchestration and research options remain editor/commandlet-only and are not linked by Game/Shipping. |
@@ -1323,6 +1328,30 @@ See ARCHITECTURE §8.54 and
[`Standalone/Research/WorldAssemblerM3/Linux58/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux58/REPORT.md)
for resolved records, final-field reachability, controls, timing, hashes and captures.
### 3.37 Unreal CoreGeometry assets and partial metre upgrade — World Assembler U1
`Public/VoxelStrateTypes.h` declares optional `FVoxelForgeCoreGeometry`. Its default recipe is Off
and its stamp, room, and tunnel arrays are empty. `Private/VoxelForgeCoreAdapter.cpp` maps authored
metres to Core layout requests after passage planning and before finalizing the layout. Stamps use
the V1 resolver for original stamp kinds and V3 for monolith/platform motifs because the checked-out
Core API exposes V3; authored cavern records use `ResolveResolvedCavernGraphV1`. Stable feature
names are namespaced by strate in the resolved layout. `FStrateLandmark::CoreGeometryStableName`
binds one landmark to such a resolved record, and `VoxelContentManager.cpp` builds its lookup from
the finalized snapshot. `VoxelWorld.cpp` retains resolved layout identity in content hashing.
`Private/VoxelForgeAssetVersion.h` sets the shared custom version to 2. `UVoxelSettings::Serialize`
and `UVoxelStrateDefinition::Serialize`/`PostLoad` upgrade only inter-strate gap, strate height,
and transition width using the saved 8 m layout chunk. These conversions are version-guarded and
idempotent. No package save is performed by the audit. Other Research8 chunks, owner-voxel lengths,
Core-voxel lengths/frequencies, and centimetre fields remain on their existing units and need a
separate migration.
Likely compile/UHT touchpoints are the reflected nested fields in `VoxelStrateTypes.h`, Core snapshot
and resolver signatures in `VoxelForgeCoreAdapter.cpp`, `FCustomVersionRegistration` and the old
tunnel-slope version in `VoxelStrateDefinition.cpp`, plus the standalone `VoxelForgeExplore` audit
in `VoxelForgeExploreCommandlet.cpp`. `GetStrateForChunk` still receives layout-chunk Z, not a Core
tile index. See `Standalone/Research/WorldAssemblerM3/Unreal1/REPORT.md` for BuildHost evidence.
## 4. The density pipeline (most-edited hot path)
### 4.1 `GetDensityWithParams` (TunnelNetwork) — VoxelGenerator.cpp:3749
@@ -399,6 +399,7 @@ bool FVoxelForgeComposerSeasonTest::RunTest(const FString& Parameters)
LegacySettings->WorldRadiusVoxels = 987.0f;
LegacySettings->TotalStrates = 1;
LegacyDefinition->StrateHeightInChunks = 7;
LegacyDefinition->StrateHeightMeters = 7.0 * 8.0;
LegacySettings->StratePool.Add(
TSoftObjectPtr<UVoxelStrateDefinition>(LegacyDefinition.Get()));
TStrongObjectPtr<UVoxelStrateManager> LegacyManager(
@@ -525,6 +525,7 @@ bool FVoxelForgeZeroTransitionBlendTest::RunTest(const FString& Parameters)
{
UVoxelStrateDefinition* Definition = World.Definitions[Index].Get();
Definition->TransitionBlendChunks = 0;
Definition->TransitionWidthMeters = 0.0;
Definition->TransitionType = (Index & 1) == 0
? EVoxelStrateTransition::Gradient : EVoxelStrateTransition::Interleaved;
}
@@ -44,6 +44,7 @@ namespace
Settings->Seed = LayoutSeed;
Settings->TotalStrates = NumPoolDefinitions;
Settings->InterStrateGapChunks = 1;
Settings->InterStrateGapMeters = Settings->StrateLayoutChunkSizeMeters;
Settings->bOpenSurfaceEntry = true;
static const ECaveGeneratorType GeneratorTypes[] = {
@@ -69,6 +70,8 @@ namespace
FString::Printf(TEXT("Layout order test strate %d"), Index));
Def->GeneratorType = GeneratorTypes[Index];
Def->StrateHeightInChunks = 3 + Index;
Def->StrateHeightMeters = static_cast<double>(Def->StrateHeightInChunks)
* Settings->StrateLayoutChunkSizeMeters;
Def->TransitionType = EVoxelStrateTransition::Hard;
// Non-trivial, distinct passage configs exercise the connection index and the
@@ -79,6 +79,8 @@ namespace VoxelForgeTest
NewObject<UVoxelSettings>(GetTransientPackage(), NAME_None, RF_Transient));
Settings->Seed = InSeed;
Settings->InterStrateGapChunks = InGapChunks;
Settings->InterStrateGapMeters = static_cast<double>(InGapChunks)
* Settings->StrateLayoutChunkSizeMeters;
// WorldRadiusVoxels/EdgeSealThickness intentionally inherit the UVoxelSettings defaults;
// WorldEdgeSeal overrides them explicitly when it needs a small test world or radius 0.
@@ -110,6 +112,8 @@ namespace VoxelForgeTest
// refinement and roll tests use the smaller synthetic volume as a bounded memory
// control; the owner-facing showcase opts into 8 chunks below.
Def->StrateHeightInChunks = FMath::Max(1, InStrateHeightInChunks);
Def->StrateHeightMeters = static_cast<double>(Def->StrateHeightInChunks)
* Settings->StrateLayoutChunkSizeMeters;
// Hard transitions: param blending across a boundary would make "which archetype
// owns this chunk" ambiguous, and these tests want an unambiguous mapping.
Def->TransitionType = EVoxelStrateTransition::Hard;
@@ -4,6 +4,27 @@
namespace VoxelClipmapDesiredTiles
{
FORCEINLINE int64 FloorDivWide(int64 Value, int64 Divisor)
{
if (Divisor <= 0) { return 0; }
const int64 Quotient = Value / Divisor;
const int64 Remainder = Value % Divisor;
return Remainder < 0 ? Quotient - 1 : Quotient;
}
FORCEINLINE int64 CeilDivWide(int64 Value, int64 Divisor)
{
if (Divisor <= 0) { return 0; }
const int64 Quotient = Value / Divisor;
const int64 Remainder = Value % Divisor;
return Remainder > 0 ? Quotient + 1 : Quotient;
}
FORCEINLINE int32 ClampInt32(int64 Value)
{
return static_cast<int32>(FMath::Clamp<int64>(Value, MIN_int32, MAX_int32));
}
struct FParameters
{
int32 ClipRadius = 3;
@@ -13,7 +34,10 @@ namespace VoxelClipmapDesiredTiles
FORCEINLINE int32 FloorDiv(int32 Value, int32 Divisor)
{
return Value >= 0 ? (Value / Divisor) : -(((-Value) + Divisor - 1) / Divisor);
if (Divisor <= 0) { return 0; }
const int32 Quotient = Value / Divisor;
const int32 Remainder = Value % Divisor;
return Remainder < 0 ? Quotient - 1 : Quotient;
}
FORCEINLINE FIntVector FloorDiv(const FIntVector& Value, int32 Divisor)
@@ -24,7 +48,10 @@ namespace VoxelClipmapDesiredTiles
FORCEINLINE int32 CeilDiv(int32 Value, int32 Divisor)
{
return -FloorDiv(-Value, Divisor);
if (Divisor <= 0) { return 0; }
const int32 Quotient = Value / Divisor;
const int32 Remainder = Value % Divisor;
return Remainder > 0 ? Quotient + 1 : Quotient;
}
FORCEINLINE void AlignedRingBounds(const FIntVector& Center, int32 Radius, int32 Alignment,
@@ -33,10 +60,12 @@ namespace VoxelClipmapDesiredTiles
const auto AlignAxis = [Radius, Alignment](int32 CenterAxis,
int32& OutAxisMin, int32& OutAxisMax)
{
const int32 BaseMin = CenterAxis - Radius;
const int32 BaseEnd = CenterAxis + Radius + 1;
OutAxisMin = FloorDiv(BaseMin, Alignment) * Alignment;
OutAxisMax = CeilDiv(BaseEnd, Alignment) * Alignment - 1;
const int64 SafeRadius = FMath::Max<int64>(0, Radius);
const int64 SafeAlignment = FMath::Max<int64>(1, Alignment);
const int64 BaseMin = static_cast<int64>(CenterAxis) - SafeRadius;
const int64 BaseEnd = static_cast<int64>(CenterAxis) + SafeRadius + 1;
OutAxisMin = ClampInt32(FloorDivWide(BaseMin, SafeAlignment) * SafeAlignment);
OutAxisMax = ClampInt32(CeilDivWide(BaseEnd, SafeAlignment) * SafeAlignment - 1);
};
AlignAxis(Center.X, OutMin.X, OutMax.X);
AlignAxis(Center.Y, OutMin.Y, OutMax.Y);
@@ -49,8 +78,13 @@ namespace VoxelClipmapDesiredTiles
{
return Radius;
}
return FMath::Max(Radius,
(Parameters.RenderDistanceChunks + (1 << MaxLevel) - 1) >> MaxLevel);
const int32 SafeLevel = FMath::Clamp(MaxLevel, 0, 8);
const int64 Scale = int64{1} << static_cast<uint32>(SafeLevel);
const int64 Numerator = static_cast<int64>(Parameters.RenderDistanceChunks)
+ Scale - 1;
const int64 DesiredRadius = Numerator / Scale;
return static_cast<int32>(FMath::Clamp<int64>(
FMath::Max<int64>(Radius, DesiredRadius), 0, MAX_int32));
}
FORCEINLINE void OuterShell(const FParameters& Parameters, int32 Radius, int32 MaxLevel,
@@ -73,12 +107,12 @@ namespace VoxelClipmapDesiredTiles
return;
}
OutMinChunkZ = CenterChunkZ - ViewDistanceDown;
OutMaxChunkZ = CenterChunkZ + ViewDistanceUp;
OutMinChunkZ = ClampInt32(static_cast<int64>(CenterChunkZ) - ViewDistanceDown);
OutMaxChunkZ = ClampInt32(static_cast<int64>(CenterChunkZ) + ViewDistanceUp);
if (bHasStrate)
{
OutMinChunkZ = StrateBottomChunkZ - StrateViewMarginChunks;
OutMaxChunkZ = StrateTopChunkZ + StrateViewMarginChunks;
OutMinChunkZ = ClampInt32(static_cast<int64>(StrateBottomChunkZ) - StrateViewMarginChunks);
OutMaxChunkZ = ClampInt32(static_cast<int64>(StrateTopChunkZ) + StrateViewMarginChunks);
}
}
@@ -94,7 +128,7 @@ namespace VoxelClipmapDesiredTiles
for (int32 Level = 0; Level <= MaxLevel; ++Level)
{
const int32 Pow = 1 << Level;
const int32 Pow = static_cast<int32>(uint32{1} << static_cast<uint32>(Level));
const FIntVector CenterLevel = FloorDiv(Center, Pow);
const int32 LevelRadius = (Level == MaxLevel) ? OuterRadius : Radius;
@@ -105,24 +139,23 @@ namespace VoxelClipmapDesiredTiles
FIntVector RingMin, RingMax;
AlignedRingBounds(CenterLevel, LevelRadius, RingAlignment, RingMin, RingMax);
int32 DeltaZMin = RingMin.Z - CenterLevel.Z;
int32 DeltaZMax = RingMax.Z - CenterLevel.Z;
int64 TileMinZ = RingMin.Z;
int64 TileMaxZ = RingMax.Z;
if (LevelRadius > Radius && MinChunkZ != MIN_int32)
{
DeltaZMin = FMath::Max(DeltaZMin, FloorDiv(MinChunkZ, Pow) - CenterLevel.Z);
DeltaZMax = FMath::Min(DeltaZMax, FloorDiv(MaxChunkZ, Pow) - CenterLevel.Z);
TileMinZ = FMath::Max<int64>(TileMinZ, FloorDiv(MinChunkZ, Pow));
TileMaxZ = FMath::Min<int64>(TileMaxZ, FloorDiv(MaxChunkZ, Pow));
}
for (int32 DeltaZ = DeltaZMin; DeltaZ <= DeltaZMax; ++DeltaZ)
for (int32 DeltaY = RingMin.Y - CenterLevel.Y;
DeltaY <= RingMax.Y - CenterLevel.Y; ++DeltaY)
for (int32 DeltaX = RingMin.X - CenterLevel.X;
DeltaX <= RingMax.X - CenterLevel.X; ++DeltaX)
for (int64 Z = TileMinZ; Z <= TileMaxZ; ++Z)
for (int64 Y = RingMin.Y; Y <= RingMax.Y; ++Y)
for (int64 X = RingMin.X; X <= RingMax.X; ++X)
{
const FIntVector Tile = CenterLevel + FIntVector(DeltaX, DeltaY, DeltaZ);
const int32 TileMinZ = Tile.Z << Level;
const int32 TileMaxZ = ((Tile.Z + 1) << Level) - 1;
if (TileMaxZ < MinChunkZ || TileMinZ > MaxChunkZ)
const FIntVector Tile(static_cast<int32>(X), static_cast<int32>(Y),
static_cast<int32>(Z));
const int64 TileBottomChunkZ = static_cast<int64>(Tile.Z) * Pow;
const int64 TileTopChunkZ = (static_cast<int64>(Tile.Z) + 1) * Pow - 1;
if (TileTopChunkZ < MinChunkZ || TileBottomChunkZ > MaxChunkZ)
{
continue;
}
@@ -130,12 +163,12 @@ namespace VoxelClipmapDesiredTiles
if (Level > 0)
{
const bool bCovered =
(2 * Tile.X >= PreviousRingMin.X)
&& (2 * Tile.X + 1 <= PreviousRingMax.X)
&& (2 * Tile.Y >= PreviousRingMin.Y)
&& (2 * Tile.Y + 1 <= PreviousRingMax.Y)
&& (2 * Tile.Z >= PreviousRingMin.Z)
&& (2 * Tile.Z + 1 <= PreviousRingMax.Z);
(2 * static_cast<int64>(Tile.X) >= PreviousRingMin.X)
&& (2 * static_cast<int64>(Tile.X) + 1 <= PreviousRingMax.X)
&& (2 * static_cast<int64>(Tile.Y) >= PreviousRingMin.Y)
&& (2 * static_cast<int64>(Tile.Y) + 1 <= PreviousRingMax.Y)
&& (2 * static_cast<int64>(Tile.Z) >= PreviousRingMin.Z)
&& (2 * static_cast<int64>(Tile.Z) + 1 <= PreviousRingMax.Z);
if (bCovered)
{
continue;
+124 -24
View File
@@ -18,6 +18,7 @@
#include "Materials/MaterialInterface.h"
#include "Tasks/Task.h"
#include "HAL/PlatformProcess.h"
#include "Misc/Crc.h"
#include "ProfilingDebugging/CpuProfilerTrace.h" // Unreal Insights scopes
#include <VoxelForgeCore/VoxelForgeCore.h>
#include <cmath>
@@ -240,10 +241,10 @@ static std::vector<VoxelForgeCore::WorldAssemblyFeatureQueryV1> VF_ContentFeatur
{
using namespace VoxelForgeCore;
constexpr double Q = WorldAssemblySubvoxelsPerVoxel;
const std::array<int64,3> Min{static_cast<int64>(std::floor(MinX*Q)),
static_cast<int64>(std::floor(MinY*Q)),static_cast<int64>(std::floor(MinZ*Q))};
const std::array<int64,3> Max{static_cast<int64>(std::ceil(MaxX*Q)),
static_cast<int64>(std::ceil(MaxY*Q)),static_cast<int64>(std::ceil(MaxZ*Q))};
const std::array<std::int64_t,3> Min{static_cast<std::int64_t>(std::floor(MinX*Q)),
static_cast<std::int64_t>(std::floor(MinY*Q)),static_cast<std::int64_t>(std::floor(MinZ*Q))};
const std::array<std::int64_t,3> Max{static_cast<std::int64_t>(std::ceil(MaxX*Q)),
static_cast<std::int64_t>(std::ceil(MaxY*Q)),static_cast<std::int64_t>(std::ceil(MaxZ*Q))};
std::vector<WorldAssemblyFeatureQueryV1> Queries;
if (Surface)
if (const auto* Plan = Surface->FindPlan(StrateIndex))
@@ -322,11 +323,54 @@ void UVoxelContentManager::Initialize(AActor* InOwner, UVoxelStrateManager* InSt
Seed = InSeed;
NativeSurfaceSnapshot.Reset();
NativeFloatingIslandSnapshot.Reset();
CoreGeometryAnchors.Reset();
if (Settings)
{
FString ManifestJson, ManifestHash, ManifestError;
const AVoxelWorld* VoxelWorld = Cast<AVoxelWorld>(Owner.Get());
FString ResolvedLayoutJson, ResolvedLayoutIdentity;
if (VoxelWorld != nullptr
&& VoxelWorld->GetVoxelForgeCoreResolvedLayout(ResolvedLayoutJson, ResolvedLayoutIdentity))
{
FTCHARToUTF8 Utf8Layout(*ResolvedLayoutJson);
std::string LayoutError;
const std::optional<VoxelForgeCore::ResolvedLayoutSnapshotV3> ResolvedLayout =
VoxelForgeCore::ResolvedLayoutSnapshotV3::FromJson(
std::string_view(Utf8Layout.Get(), static_cast<std::size_t>(Utf8Layout.Length())), LayoutError);
if (ResolvedLayout)
{
const auto AddAnchor = [this](const std::string& StableId, int32 StrateIndex,
const std::array<std::int64_t, 3>& PositionMicrometres)
{
const FVector PositionCm(
static_cast<double>(PositionMicrometres[0]) / 10000.0,
static_cast<double>(PositionMicrometres[1]) / 10000.0,
static_cast<double>(PositionMicrometres[2]) / 10000.0);
const FString FullName = UTF8_TO_TCHAR(StableId.c_str());
FResolvedCoreGeometryAnchor Anchor{ StrateIndex, PositionCm };
CoreGeometryAnchors.Add(FullName, Anchor);
const int32 Marker = FullName.Find(TEXT("/core/"), ESearchCase::CaseSensitive, ESearchDir::FromEnd);
if (Marker != INDEX_NONE)
{
const FString ShortName = FullName.Mid(Marker + 6);
if (!ShortName.IsEmpty()) CoreGeometryAnchors.Add(ShortName, Anchor);
}
};
for (const VoxelForgeCore::ResolvedLayoutStampV1& Stamp : ResolvedLayout->stamps)
AddAnchor(Stamp.stableId, Stamp.strateIndex, Stamp.positionMicrometres);
for (const VoxelForgeCore::ResolvedCavernRoomV1& Room : ResolvedLayout->cavernRooms)
AddAnchor(Room.stableId, Room.strateIndex, Room.centerMicrometres);
for (const VoxelForgeCore::ResolvedCavernEdgeV1& Edge : ResolvedLayout->cavernEdges)
if (!Edge.polylineMicrometres.empty())
AddAnchor(Edge.stableId, Edge.strateIndex, Edge.polylineMicrometres.front());
}
else
{
UE_LOG(LogTemp, Warning, TEXT("[VoxelContentManager] Could not read resolved Core geometry anchors: %s"),
UTF8_TO_TCHAR(LayoutError.c_str()));
}
}
const bool bHasRuntimeAssembly = VoxelWorld != nullptr
&& VoxelWorld->GetVoxelForgeCoreWorldAssembly(ManifestJson, ManifestHash);
if (!bHasRuntimeAssembly)
@@ -559,9 +603,13 @@ int32 UVoxelContentManager::RegionSize() const
// truncation, a negative-coord cell is queued under one region but routed back to another → its march
// result is dropped → blank chunk. Truncation == floor for A>=0 and for R==1, which is exactly why only
// NEGATIVE coordinates at R>=2 were affected. R (region size) is always > 0 here.
static FORCEINLINE int32 FloorDivPos(int32 A, int32 R)
static FORCEINLINE int32 FloorDivPos(int64 A, int32 R)
{
return (A >= 0) ? (A / R) : -(((-A) + R - 1) / R);
if (R <= 0) { return 0; }
const int64 Quotient = A / R;
const int64 Remainder = A % R;
return static_cast<int32>(FMath::Clamp<int64>(
Remainder < 0 ? Quotient - 1 : Quotient, MIN_int32, MAX_int32));
}
static FORCEINLINE FIntPoint CellToRegion(const FIntPoint& Cell, int32 R)
@@ -573,10 +621,10 @@ static FORCEINLINE FIntPoint CellToRegion(const FIntPoint& Cell, int32 R)
static FORCEINLINE bool IsRegionDesired(const FIntPoint& Region, const FIntPoint& PlayerCell,
int32 FarR, int32 R)
{
const int32 MinX = FloorDivPos(PlayerCell.X - FarR, R);
const int32 MaxX = FloorDivPos(PlayerCell.X + FarR, R);
const int32 MinY = FloorDivPos(PlayerCell.Y - FarR, R);
const int32 MaxY = FloorDivPos(PlayerCell.Y + FarR, R);
const int32 MinX = FloorDivPos(static_cast<int64>(PlayerCell.X) - FarR, R);
const int32 MaxX = FloorDivPos(static_cast<int64>(PlayerCell.X) + FarR, R);
const int32 MinY = FloorDivPos(static_cast<int64>(PlayerCell.Y) - FarR, R);
const int32 MaxY = FloorDivPos(static_cast<int64>(PlayerCell.Y) + FarR, R);
return Region.X >= MinX && Region.X <= MaxX && Region.Y >= MinY && Region.Y <= MaxY;
}
@@ -1038,8 +1086,19 @@ bool UVoxelContentManager::MeasurePlacementCostForValidation(
OutAudit.MaximumDensityResidual=FMath::Max(OutAudit.MaximumDensityResidual,
FMath::Abs(static_cast<double>(Density)));
++OutAudit.AnchorChecks;
if (FMath::Abs(Density)>0.25f)
{ OutError=TEXT("Populated fixture is not anchored on the canonical field."); return false; }
// Core content crossings use the same finite-step column refinement as runtime
// placement. Allow at most half a Core voxel of field residual in this audit.
// Les croisements de contenu Core gardent le raffinement discret du runtime;
// l'audit tolere au plus un demi-voxel Core de residu de champ.
if (FMath::Abs(Density)>0.5f)
{
OutError=FString::Printf(
TEXT("Populated fixture is not anchored on the canonical field: residual=%.4f core_vox=(%.3f,%.3f,%.3f) local_cm=(%.2f,%.2f,%.2f)."),
Density, Local.X/CurrentCtx.VoxelSizeCm,
Local.Y/CurrentCtx.VoxelSizeCm, Local.Z/CurrentCtx.VoxelSizeCm,
Local.X, Local.Y, Local.Z);
return false;
}
}
}
if (!Spawns.IsEmpty())
@@ -1102,17 +1161,22 @@ void UVoxelContentManager::RebuildDesiredCells(FDecoGrid& G, const FIntPoint& Pl
const int32 R = RegionSize();
// Desired regions = every region whose footprint touches the radius-FarR cell box around the player.
const FIntPoint RMin(FloorDivPos(PlayerCell.X - FarR, R),
FloorDivPos(PlayerCell.Y - FarR, R));
const FIntPoint RMax(FloorDivPos(PlayerCell.X + FarR, R),
FloorDivPos(PlayerCell.Y + FarR, R));
const FIntPoint RMin(FloorDivPos(static_cast<int64>(PlayerCell.X) - FarR, R),
FloorDivPos(static_cast<int64>(PlayerCell.Y) - FarR, R));
const FIntPoint RMax(FloorDivPos(static_cast<int64>(PlayerCell.X) + FarR, R),
FloorDivPos(static_cast<int64>(PlayerCell.Y) + FarR, R));
TSet<FIntPoint> DesiredRegions;
DesiredRegions.Reserve((RMax.X - RMin.X + 1) * (RMax.Y - RMin.Y + 1));
for (int32 ry = RMin.Y; ry <= RMax.Y; ++ry)
for (int32 rx = RMin.X; rx <= RMax.X; ++rx)
constexpr int64 MaxRegionReserve = 4096;
const int64 WidthX = static_cast<int64>(RMax.X) - RMin.X + 1;
const int64 WidthY = static_cast<int64>(RMax.Y) - RMin.Y + 1;
const int64 ReserveCount = WidthY > 0 && WidthX > MaxRegionReserve / WidthY
? MaxRegionReserve : FMath::Max<int64>(0, WidthX * WidthY);
DesiredRegions.Reserve(static_cast<int32>(ReserveCount));
for (int64 ry = RMin.Y; ry <= RMax.Y; ++ry)
for (int64 rx = RMin.X; rx <= RMax.X; ++rx)
{
DesiredRegions.Add(FIntPoint(rx, ry));
DesiredRegions.Add(FIntPoint(static_cast<int32>(rx), static_cast<int32>(ry)));
}
// Unload loaded regions no longer desired (plain DestroyComponent — no per-instance removal).
@@ -1464,8 +1528,15 @@ void UVoxelContentManager::BuildCellSpawns(const UVoxelGenerator* Gen, const FTr
const uint32 CH = VoxelHash::Mix(H ^ (0x00C0FFEEu + (uint32)ci * 0x9E3779B1u));
if (VoxelHash::ToFloat01(CH) > Comp.Probability) continue;
const int32 Span = FMath::Max(0, Comp.CountMax - Comp.CountMin);
const int32 Count = Comp.CountMin + (int32)(VoxelHash::ToFloat01(VoxelHash::Mix(CH ^ 0x1234u)) * (float)(Span + 1));
const int64 Span = FMath::Max<int64>(0,
static_cast<int64>(Comp.CountMax) - Comp.CountMin);
const uint64 CountRange = static_cast<uint64>(Span) + 1u;
const float CountOffset = VoxelHash::ToFloat01(
VoxelHash::Mix(CH ^ 0x1234u)) * static_cast<float>(CountRange);
const int64 CountWide = static_cast<int64>(Comp.CountMin)
+ static_cast<int64>(CountOffset);
const int32 Count = static_cast<int32>(FMath::Clamp<int64>(
CountWide, MIN_int32, MAX_int32));
for (int32 ii = 0; ii < Count; ++ii)
{
if (CompBudget <= 0) break;
@@ -1514,8 +1585,15 @@ void UVoxelContentManager::BuildCellSpawns(const UVoxelGenerator* Gen, const FTr
const uint32 SH = VoxelHash::Mix(IH ^ (0x0000544Bu + (uint32)si * 0x27D4EB2Fu));
if (VoxelHash::ToFloat01(SH) > Sub.Probability) continue;
const int32 SubSpan = FMath::Max(0, Sub.CountMax - Sub.CountMin);
const int32 SubCount = Sub.CountMin + (int32)(VoxelHash::ToFloat01(VoxelHash::Mix(SH ^ 0x1234u)) * (float)(SubSpan + 1));
const int64 SubSpan = FMath::Max<int64>(0,
static_cast<int64>(Sub.CountMax) - Sub.CountMin);
const uint64 SubCountRange = static_cast<uint64>(SubSpan) + 1u;
const float SubCountOffset = VoxelHash::ToFloat01(
VoxelHash::Mix(SH ^ 0x1234u)) * static_cast<float>(SubCountRange);
const int64 SubCountWide = static_cast<int64>(Sub.CountMin)
+ static_cast<int64>(SubCountOffset);
const int32 SubCount = static_cast<int32>(FMath::Clamp<int64>(
SubCountWide, MIN_int32, MAX_int32));
for (int32 sj = 0; sj < SubCount; ++sj)
{
if (CompBudget <= 0) break;
@@ -2374,6 +2452,28 @@ void UVoxelContentManager::UpdateLandmarks(const FVector& PlayerWorldPos)
const float ExclWorld = FMath::Max(0.0f, L.ExclusionRadiusChunks) * ChunkWorld;
if (ExclWorld > 0.0f) bAnyExclusion = true;
if (!L.CoreGeometryStableName.IsEmpty())
{
const FResolvedCoreGeometryAnchor* Anchor = CoreGeometryAnchors.Find(L.CoreGeometryStableName);
if (Anchor == nullptr || Anchor->StrateIndex != StrateIndex) continue;
const uint32 StableNameHash = FCrc::StrCrc32(*L.CoreGeometryStableName);
const uint32 H = DecoHash(static_cast<int32>(StableNameHash), StrateIndex,
0, 0, 0, EntryIdx, LocalSeed, 0xC04E6E0u);
if (VoxelHash::ToFloat01(H) > L.SpawnProbability) continue;
const float LocalX = static_cast<float>(Anchor->LocalPositionCm.X);
const float LocalY = static_cast<float>(Anchor->LocalPositionCm.Y);
const float dx = LocalX - LocalPlayer.X, dy = LocalY - LocalPlayer.Y;
const float DistSq = dx * dx + dy * dy;
if (DistSq <= GatherWorld * GatherWorld)
{
// A Core-bound landmark is one stable candidate, independent of the hash lattice.
// Un landmark lie a Core reste un candidat stable, independant de la grille de hachage.
Cands.Add({ FIntVector(EntryIdx, 0, EntryIdx), EntryIdx, H, LocalX, LocalY,
ExclWorld, L.Priority, DistSq <= RadiusWorld * RadiusWorld });
}
continue;
}
if (L.AnchorMode == ELandmarkAnchor::HashLattice)
{
const float SpacingChunks = FMath::Max(1.0f, L.SpacingChunks);
@@ -155,9 +155,11 @@ int32 UVoxelDensityVolume::NumLevels() const
FORCEINLINE int32 UVoxelDensityVolume::FloorDiv(int32 A, int32 B)
{
// True floor division (B > 0). FMath::DivideAndRoundDown truncates toward zero for negatives —
// a footgun the content manager hit too (see FloorDivPos there). Cells span the origin, so floor.
return (A >= 0) ? (A / B) : -(((-A) + B - 1) / B);
// True floor division (B > 0). Remainder adjustment handles INT_MIN without negating it.
if (B <= 0) { return 0; }
const int32 Quotient = A / B;
const int32 Remainder = A % B;
return Remainder < 0 ? Quotient - 1 : Quotient;
}
FORCEINLINE uint8 UVoxelDensityVolume::Quantize(float MCDensity)
@@ -180,7 +182,8 @@ void UVoxelDensityVolume::EnsureAllocated()
for (int32 L = 0; L < N; ++L)
{
FClipLevel& Lv = Levels[L];
Lv.Step = 1 << L;
Lv.Step = static_cast<int32>(uint32{1}
<< static_cast<uint32>(FMath::Clamp(L, 0, 8)));
Lv.OriginCells = FIntVector(INT32_MAX, INT32_MAX, INT32_MAX);
Lv.bHasData = false;
Lv.Density.SetNumZeroed(Count); // start all-air (0)
@@ -632,7 +635,8 @@ void UVoxelDensityVolume::ProcessOneFill(const FPendingFill& F)
if (!Gen) return;
if (bShuttingDown.load(std::memory_order_relaxed) || bFillThreadStop.load(std::memory_order_relaxed)) return;
const int32 Step = 1 << F.Level;
const int32 Step = static_cast<int32>(uint32{1}
<< static_cast<uint32>(FMath::Clamp(F.Level, 0, 8)));
FFillResult R;
R.Level = F.Level;
R.Epoch = F.Epoch;
@@ -0,0 +1,12 @@
#pragma once
#include "CoreMinimal.h"
// Shared custom package version for idempotent VoxelForge asset migrations.
// Version de package partagee pour les migrations VoxelForge idempotentes.
namespace VoxelForgeAssetVersion
{
inline const FGuid GUID(0xA76D5E31, 0x4B2C4F9A, 0xB8C1D204, 0x6E9F7738);
inline constexpr int32 TunnelFloorSlopeDegrees = 1;
inline constexpr int32 MetreAuthoringV1 = 2;
}
@@ -82,6 +82,113 @@ std::int64_t WorldAssemblyWavelength(double Frequency, double FallbackVoxels)
return std::max<std::int64_t>(1, WorldAssemblyFixed(Wavelength));
}
std::int64_t WorldAssemblyMicrometresToSubvoxels(
std::int64_t Micrometres, std::uint32_t CoreVoxelMicrometres);
bool WorldAssemblyMetresToMicrometres(double Metres, std::int64_t& OutMicrometres)
{
if (!std::isfinite(Metres)
|| std::abs(Metres) > static_cast<double>(std::numeric_limits<std::int64_t>::max()) / 1000000.0)
{
return false;
}
OutMicrometres = static_cast<std::int64_t>(std::llround(Metres * 1000000.0));
return true;
}
bool WorldAssemblyMetresToSubvoxels(double Metres, std::uint32_t CoreVoxelMicrometres,
std::int64_t& OutSubvoxels)
{
std::int64_t Micrometres = 0;
if (!WorldAssemblyMetresToMicrometres(Metres, Micrometres) || CoreVoxelMicrometres == 0)
{
return false;
}
OutSubvoxels = WorldAssemblyMicrometresToSubvoxels(Micrometres, CoreVoxelMicrometres);
return true;
}
bool WorldAssemblyVectorToMicrometres(const FVector& Metres, std::array<std::int64_t, 3>& Out)
{
return WorldAssemblyMetresToMicrometres(Metres.X, Out[0])
&& WorldAssemblyMetresToMicrometres(Metres.Y, Out[1])
&& WorldAssemblyMetresToMicrometres(Metres.Z, Out[2]);
}
bool WorldAssemblyVectorToMicrometres(const FVector2D& Metres, std::array<std::int64_t, 2>& Out)
{
return WorldAssemblyMetresToMicrometres(Metres.X, Out[0])
&& WorldAssemblyMetresToMicrometres(Metres.Y, Out[1]);
}
std::string CoreGeometryStableId(int32 StrateIndex, const FString& StableName)
{
FTCHARToUTF8 Utf8Name(*StableName);
return std::string("strate/") + std::to_string(StrateIndex) + "/core/" + Utf8Name.Get();
}
bool HasCoreGeometry(const UVoxelStrateDefinition& Definition)
{
const FVoxelForgeCoreGeometry& Geometry = Definition.CoreGeometry;
return Geometry.GeneratorRecipe != EVoxelForgeCoreGeneratorRecipe::None
|| !Geometry.Stamps.IsEmpty() || !Geometry.Rooms.IsEmpty() || !Geometry.Tunnels.IsEmpty();
}
bool ConfigureCoreGeometryStratum(const FStrateSlot& Slot, ECaveGeneratorType GeneratorType,
bool bImportedOverride, VoxelForgeCore::ResolvedLayoutStratumV2& Item, FString& OutError)
{
const FVoxelForgeCoreGeometry& Geometry = Slot.Definition->CoreGeometry;
switch (Geometry.GeneratorRecipe)
{
case EVoxelForgeCoreGeneratorRecipe::None:
return true;
case EVoxelForgeCoreGeneratorRecipe::NativeCaves:
if (GeneratorType != ECaveGeneratorType::TunnelNetwork
&& GeneratorType != ECaveGeneratorType::Underwater)
{
OutError = FString::Printf(TEXT("Strate %d selects NativeCaves but its generator archetype is not TunnelNetwork/Underwater."),
Slot.StrateIndex);
return false;
}
Item.generatorKind = "Caves";
Item.recipeId = "Core.Caves.PrimitiveField";
Item.recipeVersion = 1;
Item.boundDispatchPath = "Core native cave graph uses resolved room and tunnel records";
Item.queryDispatchPath = "NativeCaveDensityAt evaluates the resolved cavern graph";
return true;
case EVoxelForgeCoreGeneratorRecipe::NativeLattice:
if (GeneratorType != ECaveGeneratorType::Maze
&& GeneratorType != ECaveGeneratorType::VerticalShafts)
{
OutError = FString::Printf(TEXT("Strate %d selects NativeLattice but its generator archetype is not Maze/VerticalShafts."),
Slot.StrateIndex);
return false;
}
Item.recipeId = "Core.Lattice.PrimitiveField";
Item.recipeVersion = 1;
Item.boundDispatchPath = "Core native lattice uses resolved room and explicit tunnel path records";
Item.queryDispatchPath = "NativeLatticeDensityAt evaluates the resolved cavern graph";
return true;
case EVoxelForgeCoreGeneratorRecipe::ProfiledHeightfield:
if (GeneratorType != ECaveGeneratorType::SurfaceWorld)
{
OutError = FString::Printf(TEXT("Strate %d selects ProfiledHeightfield but its generator archetype is not SurfaceWorld."),
Slot.StrateIndex);
return false;
}
if (bImportedOverride)
{
OutError = FString::Printf(TEXT("Strate %d has ProfiledHeightfield authoring, but an imported WorldAssemblyManifest owns that recipe. Clear the imported override to use per-strate CoreGeometry."),
Slot.StrateIndex);
return false;
}
Item.recipeId = "SurfaceWorld.ProfiledHeightfield";
Item.recipeVersion = 1;
return true;
}
return true;
}
std::uint64_t WorldAssemblyFeatureId(std::uint32_t Seed, std::int64_t X,
std::int64_t Y, std::uint32_t Kind)
{
@@ -111,7 +218,7 @@ std::int64_t WorldAssemblyMicrometresToSubvoxels(
std::int64_t Micrometres, std::uint32_t CoreVoxelMicrometres)
{
if (CoreVoxelMicrometres == 0) return 0;
return WorldAssemblyRoundRatio(Micrometres * WorldAssemblySubvoxelsPerVoxel,
return WorldAssemblyRoundRatio(Micrometres * VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel,
static_cast<std::int64_t>(CoreVoxelMicrometres));
}
@@ -139,7 +246,8 @@ std::uint64_t WorldAssemblyRoundedSqrt(std::uint64_t Value)
}
bool BuildFiniteSurfaceRoute(VoxelForgeCore::WorldAssemblyRecipeV1& Recipe,
VoxelForgeCore::WorldAssemblyStratumPlanV1& Stratum, std::string& Error)
VoxelForgeCore::WorldAssemblyStratumPlanV1& Stratum,
std::int64_t ArrivalRadiusQ, std::int64_t DepartureRadiusQ, std::string& Error)
{
using namespace VoxelForgeCore;
constexpr std::int64_t PlayerRadiusQ = 348;
@@ -147,7 +255,10 @@ bool BuildFiniteSurfaceRoute(VoxelForgeCore::WorldAssemblyRecipeV1& Recipe,
constexpr std::int64_t HalfHeightQ = PlayerHeightQ / 2;
constexpr std::int64_t StepQ = 128;
constexpr std::int64_t ProbeRadiusQ = 174;
constexpr std::int64_t SupportDepthQ = 256;
// The live owner surface recipe can sit slightly below the fixed-point route
// heightfield used during candidate search; allow up to 0.5 m for the final
// resolved-field support probe before rejecting an otherwise clear route.
constexpr std::int64_t SupportDepthQ = 512;
constexpr std::int64_t GradeQ16 = 17560;
constexpr std::int64_t RouteRadiusQ = 4 * WorldAssemblySubvoxelsPerVoxel;
struct FDirection { std::int32_t X; std::int32_t Y; };
@@ -157,9 +268,12 @@ bool BuildFiniteSurfaceRoute(VoxelForgeCore::WorldAssemblyRecipeV1& Recipe,
struct FCandidate
{
std::int64_t Score = 0;
std::int64_t OutwardProjection = 0;
std::uint64_t EndpointProximityCost = 0;
std::int32_t CenterX = 0;
std::int32_t CenterY = 0;
std::int32_t Direction = 0;
std::int32_t HalfLength = 128;
std::vector<WorldAssemblyRoutePointV1> Points;
};
@@ -171,7 +285,7 @@ bool BuildFiniteSurfaceRoute(VoxelForgeCore::WorldAssemblyRecipeV1& Recipe,
Constraints.sweepSpacingSubvoxels = StepQ;
Constraints.supportProbeRadiusSubvoxels = ProbeRadiusQ;
Constraints.supportSearchDepthSubvoxels = SupportDepthQ;
Constraints.minimumClearanceSubvoxels = 8;
Constraints.minimumClearanceSubvoxels = 128;
Constraints.minimumSupportProbes = 3;
const Heightfield2D BaseField(Recipe, Stratum);
@@ -182,11 +296,14 @@ bool BuildFiniteSurfaceRoute(VoxelForgeCore::WorldAssemblyRecipeV1& Recipe,
}
std::vector<FCandidate> Candidates;
constexpr std::int32_t WaypointSpacing = 8;
std::uint64_t LegacyCandidatesVisited = 0;
std::uint64_t LegacyGradeRejected = 0;
for (std::int32_t CenterY = -256; CenterY <= 256; CenterY += 64)
for (std::int32_t CenterX = -256; CenterX <= 256; CenterX += 64)
for (std::int32_t DirectionIndex = 0;
DirectionIndex < static_cast<std::int32_t>(Directions.size()); ++DirectionIndex)
{
++LegacyCandidatesVisited;
FCandidate Candidate;
Candidate.CenterX = CenterX;
Candidate.CenterY = CenterY;
@@ -234,15 +351,33 @@ bool BuildFiniteSurfaceRoute(VoxelForgeCore::WorldAssemblyRecipeV1& Recipe,
PreviousY = Y;
}
if (bGradePass) Candidates.push_back(std::move(Candidate));
else ++LegacyGradeRejected;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeSurfaceRouteCandidates] stage=legacy_64m visited=%llu rejected_grade=%llu eligible=%llu arrival_radius_q=%lld departure_radius_q=%lld"),
static_cast<unsigned long long>(LegacyCandidatesVisited),
static_cast<unsigned long long>(LegacyGradeRejected),
static_cast<unsigned long long>(Candidates.size()),
static_cast<long long>(ArrivalRadiusQ), static_cast<long long>(DepartureRadiusQ));
const auto TryCandidates = [&](const TCHAR* Stage)
{
std::stable_sort(Candidates.begin(), Candidates.end(), [](const FCandidate& A, const FCandidate& B)
{
if (A.EndpointProximityCost != B.EndpointProximityCost)
return A.EndpointProximityCost < B.EndpointProximityCost;
if (A.OutwardProjection != B.OutwardProjection)
return A.OutwardProjection > B.OutwardProjection;
if (A.Score != B.Score) return A.Score < B.Score;
if (A.CenterY != B.CenterY) return A.CenterY < B.CenterY;
if (A.CenterX != B.CenterX) return A.CenterX < B.CenterX;
return A.Direction < B.Direction;
});
std::uint64_t SweepAttempts = 0;
std::uint64_t BestFailureCount = std::numeric_limits<std::uint64_t>::max();
WorldAssemblyRouteAuditResult BestAudit;
const FCandidate* BestCandidate = nullptr;
std::int64_t BestLiftQ = 0;
for (const FCandidate& Candidate : Candidates)
{
Stratum.route = Candidate.Points;
@@ -260,11 +395,189 @@ bool BuildFiniteSurfaceRoute(VoxelForgeCore::WorldAssemblyRecipeV1& Recipe,
}
}
const Heightfield2D CandidateField(Recipe, Stratum);
if (AuditHeightfieldRoute(CandidateField).passed) return true;
const WorldAssemblyRouteAuditResult Audit = AuditHeightfieldRoute(CandidateField);
++SweepAttempts;
const std::uint64_t FailureCount = Audit.blockedCapsuleSamples
+ Audit.unsupportedCenterSamples + Audit.gradeFailures;
if (FailureCount < BestFailureCount)
{
BestFailureCount = FailureCount;
BestAudit = Audit;
BestCandidate = &Candidate;
BestLiftQ = LiftQ;
}
if (Audit.passed)
{
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeSurfaceRouteAudit] stage=%s candidates=%llu sweep_attempts=%llu blocked=%llu unsupported=%llu grade_failures=%llu min_clearance_q=%lld max_support_depth_q=%lld center_x=%d center_y=%d direction=%d half_length_vox=%d lift_q=%lld status=PASS"),
Stage, static_cast<unsigned long long>(Candidates.size()),
static_cast<unsigned long long>(SweepAttempts),
static_cast<unsigned long long>(Audit.blockedCapsuleSamples),
static_cast<unsigned long long>(Audit.unsupportedCenterSamples),
static_cast<unsigned long long>(Audit.gradeFailures),
static_cast<long long>(Audit.minimumClearanceSubvoxels),
static_cast<long long>(Audit.maximumSupportDepthSubvoxels),
Candidate.CenterX, Candidate.CenterY, Candidate.Direction,
Candidate.HalfLength, static_cast<long long>(LiftQ));
return true;
}
if (LiftQ >= MaximumRepairLiftQ) break;
LiftQ += RepairStepQ;
}
}
UE_LOG(LogTemp, Warning,
TEXT("[VoxelForgeSurfaceRouteAudit] stage=%s candidates=%llu sweep_attempts=%llu best_blocked=%llu best_unsupported=%llu best_grade_failures=%llu min_clearance_q=%lld max_support_depth_q=%lld center_x=%d center_y=%d direction=%d half_length_vox=%d lift_q=%lld status=FAIL"),
Stage, static_cast<unsigned long long>(Candidates.size()),
static_cast<unsigned long long>(SweepAttempts),
static_cast<unsigned long long>(BestAudit.blockedCapsuleSamples),
static_cast<unsigned long long>(BestAudit.unsupportedCenterSamples),
static_cast<unsigned long long>(BestAudit.gradeFailures),
static_cast<long long>(BestAudit.minimumClearanceSubvoxels),
static_cast<long long>(BestAudit.maximumSupportDepthSubvoxels),
BestCandidate ? BestCandidate->CenterX : 0,
BestCandidate ? BestCandidate->CenterY : 0,
BestCandidate ? BestCandidate->Direction : 0,
BestCandidate ? BestCandidate->HalfLength : 0,
static_cast<long long>(BestLiftQ));
return false;
};
// Keep the established candidate set first so worlds that already pass retain
// their exact route. The bounded-mouth fallback searches different route lengths.
if (TryCandidates(TEXT("legacy_64m"))) return true;
if (ArrivalRadiusQ > 0 || DepartureRadiusQ > 0)
{
// The endpoint radius constrains the mouth end, not the full route length. Keep
// enough reach for a route to leave a steep portal area and find gentler terrain.
constexpr std::int32_t maximumHalfLength = 128;
const std::vector<std::int32_t> HalfLengths{128, 112, 96, 80, 64, 48, 32, 24, 16};
Candidates.clear();
std::uint64_t FallbackCandidatesVisited = 0;
std::uint64_t FallbackGradeRejected = 0;
std::uint64_t FallbackPlacementRejected = 0;
std::uint64_t BestGradeEndpointDistanceQ = std::numeric_limits<std::uint64_t>::max();
FCandidate BestGradeCandidate;
for (const std::int32_t HalfLength : HalfLengths)
for (std::int32_t CenterY = -256; CenterY <= 256; CenterY += 16)
for (std::int32_t CenterX = -256; CenterX <= 256; CenterX += 16)
for (std::int32_t DirectionIndex = 0;
DirectionIndex < static_cast<std::int32_t>(Directions.size()); ++DirectionIndex)
{
++FallbackCandidatesVisited;
FCandidate Candidate;
Candidate.CenterX = CenterX;
Candidate.CenterY = CenterY;
Candidate.Direction = DirectionIndex;
Candidate.HalfLength = HalfLength;
Candidate.Points.reserve(33);
std::int64_t PreviousGround = 0;
std::int64_t PreviousX = 0;
std::int64_t PreviousY = 0;
bool bGradePass = true;
bool bPlacementPass = true;
for (std::int32_t Index = 0; Index <= 32; ++Index)
{
const std::int64_t Along = (Index - 16) * HalfLength / 16;
const std::int64_t X = CenterX + WorldAssemblyRoundRatio(
Along * Directions[DirectionIndex].X, 65536);
const std::int64_t Y = CenterY + WorldAssemblyRoundRatio(
Along * Directions[DirectionIndex].Y, 65536);
const std::int64_t XQ = WorldAssemblyFixed(static_cast<double>(X));
const std::int64_t YQ = WorldAssemblyFixed(static_cast<double>(Y));
const auto InRadius = [](std::int64_t XValue, std::int64_t YValue,
std::int64_t Radius)
{
return Radius <= 0 || XValue * XValue + YValue * YValue <= Radius * Radius;
};
if ((Index == 0 && ArrivalRadiusQ > 0
&& !InRadius(XQ, YQ, ArrivalRadiusQ))
|| (Index == 32 && DepartureRadiusQ > 0
&& !InRadius(XQ, YQ, DepartureRadiusQ)))
{
bPlacementPass = false;
}
if (Index == 0 && ArrivalRadiusQ > 0)
{
Candidate.EndpointProximityCost += static_cast<std::uint64_t>(XQ * XQ + YQ * YQ) * 16ull;
}
if (Index == 32 && DepartureRadiusQ > 0)
{
Candidate.EndpointProximityCost += static_cast<std::uint64_t>(XQ * XQ + YQ * YQ);
// Approach the surface portal from the world-facing side. The
// resolved passage leaves the mouth along its outward tangent;
// a route that approaches from beyond the mouth overlaps that
// bore and turns its centerline into solid ground.
const std::int64_t OutwardProjection = XQ * Directions[DirectionIndex].X
+ YQ * Directions[DirectionIndex].Y;
Candidate.OutwardProjection = OutwardProjection;
}
const auto Column = BaseField.point(WorldAssemblyFixed(static_cast<double>(X)),
WorldAssemblyFixed(static_cast<double>(Y)), 0);
const std::int64_t DeltaX = X - PreviousX;
const std::int64_t DeltaY = Y - PreviousY;
const std::uint64_t HorizontalVoxels = WorldAssemblyRoundedSqrt(
static_cast<std::uint64_t>(DeltaX * DeltaX + DeltaY * DeltaY));
const std::int64_t HorizontalQ = static_cast<std::int64_t>(HorizontalVoxels)
* WorldAssemblySubvoxelsPerVoxel;
if (Index > 0)
{
const std::int64_t RiseQ = std::abs(Column.groundHeightSubvoxels - PreviousGround);
if (HorizontalQ == 0 || RiseQ * 65536 > HorizontalQ * GradeQ16)
{
bGradePass = false;
}
Candidate.Score += RiseQ * 65536 / std::max<std::int64_t>(1, HorizontalQ);
}
WorldAssemblyRoutePointV1 Point;
Point.positionSubvoxels = {XQ, YQ, Column.groundHeightSubvoxels + HalfHeightQ};
Point.radiusSubvoxels = RouteRadiusQ;
Point.stableFeatureId = WorldAssemblyFeatureId(Recipe.seed, X, Y,
0x52540000u + static_cast<std::uint32_t>(Index));
Candidate.Points.push_back(Point);
PreviousGround = Column.groundHeightSubvoxels;
PreviousX = X;
PreviousY = Y;
}
Candidate.Score /= 32;
if (bGradePass)
{
const auto& Endpoint = DepartureRadiusQ > 0
? Candidate.Points.back().positionSubvoxels
: Candidate.Points.front().positionSubvoxels;
const std::uint64_t EndpointDistanceQ = WorldAssemblyRoundedSqrt(
static_cast<std::uint64_t>(Endpoint[0] * Endpoint[0]
+ Endpoint[1] * Endpoint[1]));
if (EndpointDistanceQ < BestGradeEndpointDistanceQ)
{
BestGradeEndpointDistanceQ = EndpointDistanceQ;
BestGradeCandidate.CenterX = CenterX;
BestGradeCandidate.CenterY = CenterY;
BestGradeCandidate.Direction = DirectionIndex;
BestGradeCandidate.HalfLength = HalfLength;
}
}
if (bGradePass && bPlacementPass) Candidates.push_back(std::move(Candidate));
else
{
if (!bGradePass) ++FallbackGradeRejected;
if (!bPlacementPass) ++FallbackPlacementRejected;
}
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeSurfaceRouteCandidates] stage=bounded_single_mouth visited=%llu rejected_grade=%llu rejected_placement=%llu eligible=%llu max_half_length_vox=%d half_length_variants=%d best_grade_endpoint_q=%llu best_grade_center_x=%d best_grade_center_y=%d best_grade_direction=%d best_grade_half_length_vox=%d arrival_radius_q=%lld departure_radius_q=%lld"),
static_cast<unsigned long long>(FallbackCandidatesVisited),
static_cast<unsigned long long>(FallbackGradeRejected),
static_cast<unsigned long long>(FallbackPlacementRejected),
static_cast<unsigned long long>(Candidates.size()), maximumHalfLength,
static_cast<int32>(HalfLengths.size()),
static_cast<unsigned long long>(BestGradeEndpointDistanceQ),
BestGradeCandidate.CenterX, BestGradeCandidate.CenterY,
BestGradeCandidate.Direction, BestGradeCandidate.HalfLength,
static_cast<long long>(ArrivalRadiusQ),
static_cast<long long>(DepartureRadiusQ));
if (TryCandidates(TEXT("bounded_single_mouth"))) return true;
}
Error = "SurfaceWorld route candidates failed the capsule and support sweep.";
return false;
}
@@ -370,6 +683,54 @@ bool BuildSurfaceStratumPlan(const UVoxelStrateManager* StrateManager,
BottomVoxel, HeightVoxels));
}
if (Definition != nullptr
&& Definition->CoreGeometry.GeneratorRecipe == EVoxelForgeCoreGeneratorRecipe::ProfiledHeightfield)
{
const FVoxelForgeCoreGeometry& Geometry = Definition->CoreGeometry;
WorldAssemblySurfaceProfileV1 Profile;
Profile.version = 1;
switch (Geometry.HeightfieldProfile)
{
case EVoxelForgeCoreHeightfieldProfile::PeriodicHills:
Profile.kind = WorldAssemblySurfaceProfileKindV1::periodicHills;
break;
case EVoxelForgeCoreHeightfieldProfile::MirroredRelief:
Profile.kind = WorldAssemblySurfaceProfileKindV1::mirroredRelief;
break;
case EVoxelForgeCoreHeightfieldProfile::FlatField:
Profile.kind = WorldAssemblySurfaceProfileKindV1::flatField;
break;
}
std::array<std::int64_t, 2> AmplitudeMicrometres{};
std::array<std::int64_t, 2> WavelengthMicrometres{};
std::int64_t BaseSubvoxels = 0;
std::int64_t ClearanceSubvoxels = 0;
if (!WorldAssemblyVectorToMicrometres(Geometry.HeightfieldAmplitudeMeters, AmplitudeMicrometres)
|| !WorldAssemblyVectorToMicrometres(Geometry.HeightfieldWavelengthMeters, WavelengthMicrometres)
|| !WorldAssemblyMetresToSubvoxels(Geometry.HeightfieldBaseMeters, CoreVoxelMicrometres, BaseSubvoxels)
|| !WorldAssemblyMetresToSubvoxels(Geometry.MinimumArenaClearanceMeters,
CoreVoxelMicrometres, ClearanceSubvoxels)
|| !std::isfinite(Geometry.MirrorLambda))
{
OutError = FString::Printf(TEXT("Strate %d Core profiled-heightfield fields contain a non-finite or out-of-range metre value."),
Slot.StrateIndex);
return false;
}
Profile.h0Subvoxels = BaseSubvoxels;
Profile.amplitudeXSubvoxels = WorldAssemblyMicrometresToSubvoxels(
AmplitudeMicrometres[0], CoreVoxelMicrometres);
Profile.amplitudeYSubvoxels = WorldAssemblyMicrometresToSubvoxels(
AmplitudeMicrometres[1], CoreVoxelMicrometres);
Profile.wavelengthXSubvoxels = WorldAssemblyMicrometresToSubvoxels(
WavelengthMicrometres[0], CoreVoxelMicrometres);
Profile.wavelengthYSubvoxels = WorldAssemblyMicrometresToSubvoxels(
WavelengthMicrometres[1], CoreVoxelMicrometres);
Profile.mirrorLambdaQ16 = WorldAssemblyQ16(Geometry.MirrorLambda);
Profile.minimumArenaClearanceSubvoxels = ClearanceSubvoxels;
ResolveWorldAssemblySurfaceProfilePhasesV1(Profile, Recipe.seed);
Recipe.surfaceProfile = Profile;
}
Stratum.strateIndex = Slot.StrateIndex;
Stratum.topChunkZ = Slot.TopChunkZ;
Stratum.bottomChunkZ = Slot.BottomChunkZ;
@@ -476,7 +837,29 @@ bool BuildSurfaceStratumPlan(const UVoxelStrateManager* StrateManager,
}
}
std::string RouteError;
if (!BuildFiniteSurfaceRoute(Recipe, Stratum, RouteError))
const auto OwnerVoxelsToCoreQ = [CoreVoxelMicrometres](double OwnerVoxels)
{
const double OwnerVoxelMicrometres = OwnerVoxelSizeCm * 10000.0;
return static_cast<std::int64_t>(std::llround(OwnerVoxels * OwnerVoxelMicrometres
* WorldAssemblySubvoxelsPerVoxel / std::max<std::uint32_t>(1, CoreVoxelMicrometres)));
};
std::int64_t ArrivalRadiusQ = 0;
std::int64_t DepartureRadiusQ = 0;
for (const FStrateSlot& Neighbor : StrateManager->GetLayout())
{
if (Neighbor.Definition == nullptr) continue;
if (Neighbor.StrateIndex + 1 == Slot.StrateIndex)
{
ArrivalRadiusQ = std::max(OwnerVoxelsToCoreQ(Neighbor.Definition->PassageConfig.DistanceMin),
OwnerVoxelsToCoreQ(Neighbor.Definition->PassageConfig.DistanceMax));
}
if (Neighbor.StrateIndex == Slot.StrateIndex + 1 && Definition != nullptr)
{
DepartureRadiusQ = std::max(OwnerVoxelsToCoreQ(Definition->PassageConfig.DistanceMin),
OwnerVoxelsToCoreQ(Definition->PassageConfig.DistanceMax));
}
}
if (!BuildFiniteSurfaceRoute(Recipe, Stratum, ArrivalRadiusQ, DepartureRadiusQ, RouteError))
{
OutError = UTF8_TO_TCHAR(RouteError.c_str());
return false;
@@ -836,6 +1219,349 @@ void WriteCoreJsonValue(FCoreJsonWriter& Writer, const TCHAR* Identifier,
}
}
bool ResolveAuthoredCoreGeometry(VoxelForgeCore::ResolvedLayoutSnapshotV3& Layout,
const UVoxelStrateManager* StrateManager, std::int32_t Seed, FString& OutError)
{
using namespace VoxelForgeCore;
std::vector<ResolvedLayoutStampRequestV1> LegacyStamps, MotifStamps;
std::vector<ResolvedCavernRoomV1> Rooms;
std::vector<ResolvedCavernEdgeV1> Edges;
TSet<FString> FeatureNames;
TSet<int32> NativeGraphStrates;
const auto StableId = [&FeatureNames, &OutError](const FString& Name, int32 StrateIndex,
const TCHAR* Kind, std::string& OutId)
{
if (Name.IsEmpty() || Name != Name.TrimStartAndEnd())
{
OutError = FString::Printf(TEXT("Core %s on strate %d needs a non-empty stable name without surrounding whitespace."), Kind, StrateIndex);
return false;
}
if (FeatureNames.Contains(Name))
{
OutError = FString::Printf(TEXT("Core stable name '%s' is duplicated across this world."), *Name);
return false;
}
FeatureNames.Add(Name);
OutId = CoreGeometryStableId(StrateIndex, Name);
return true;
};
const auto Microns = [&OutError](double Value, int32 StrateIndex, const TCHAR* Field, std::int64_t& Out)
{
if (WorldAssemblyMetresToMicrometres(Value, Out)) return true;
OutError = FString::Printf(TEXT("Strate %d CoreGeometry.%s must be finite and bounded in metres."), StrateIndex, Field);
return false;
};
const auto VectorMicrons = [&OutError](const FVector& Value, int32 StrateIndex, const TCHAR* Field,
std::array<std::int64_t, 3>& Out)
{
if (WorldAssemblyVectorToMicrometres(Value, Out)) return true;
OutError = FString::Printf(TEXT("Strate %d CoreGeometry.%s must contain finite, bounded metre coordinates."), StrateIndex, Field);
return false;
};
for (const FStrateSlot& Slot : StrateManager->GetLayout())
{
const UVoxelStrateDefinition* Definition = Slot.Definition;
if (!Definition || !HasCoreGeometry(*Definition)) continue;
const FVoxelForgeCoreGeometry& Geometry = Definition->CoreGeometry;
if (Geometry.GeneratorRecipe == EVoxelForgeCoreGeneratorRecipe::NativeCaves
|| Geometry.GeneratorRecipe == EVoxelForgeCoreGeneratorRecipe::NativeLattice)
NativeGraphStrates.Add(Slot.StrateIndex);
for (const FVoxelForgeCoreStampDefinition& Authored : Geometry.Stamps)
{
ResolvedLayoutStampRequestV1 Request;
if (!StableId(Authored.StableName, Slot.StrateIndex, TEXT("stamp"), Request.stableId)
|| !VectorMicrons(Authored.AnchorPositionMeters, Slot.StrateIndex, TEXT("Stamps.AnchorPositionMeters"), Request.anchorPositionMicrometres)
|| !VectorMicrons(Authored.HalfExtentsMeters, Slot.StrateIndex, TEXT("Stamps.HalfExtentsMeters"), Request.halfExtentsMicrometres)
|| !Microns(Authored.PlacementRadiusMeters, Slot.StrateIndex, TEXT("Stamps.PlacementRadiusMeters"), Request.placementRadiusMicrometres)
|| !Microns(Authored.SmoothWidthMeters, Slot.StrateIndex, TEXT("Stamps.SmoothWidthMeters"), Request.smoothWidthMicrometres)
|| !Microns(Authored.IrregularityAmplitudeMeters, Slot.StrateIndex, TEXT("Stamps.IrregularityAmplitudeMeters"), Request.irregularityAmplitudeMicrometres)
|| !Microns(Authored.IrregularityWavelengthMeters, Slot.StrateIndex, TEXT("Stamps.IrregularityWavelengthMeters"), Request.irregularityWavelengthMicrometres)
|| !Microns(Authored.RoundingRadiusMeters, Slot.StrateIndex, TEXT("Stamps.RoundingRadiusMeters"), Request.roundingRadiusMicrometres)
|| !Microns(Authored.MinimumSpacingMeters, Slot.StrateIndex, TEXT("Stamps.MinimumSpacingMeters"), Request.minimumSpacingMicrometres)
|| !Microns(Authored.FloorMeters, Slot.StrateIndex, TEXT("Stamps.FloorMeters"), Request.floorMicrometres)) return false;
Request.strateIndex = Slot.StrateIndex;
switch (Authored.Kind)
{
case EVoxelForgeCoreStampKind::CarveEllipsoid: Request.kind = ResolvedSdfStampKindV1::carveEllipsoid; break;
case EVoxelForgeCoreStampKind::CarveArch: Request.kind = ResolvedSdfStampKindV1::carveArch; break;
case EVoxelForgeCoreStampKind::FillPillar: Request.kind = ResolvedSdfStampKindV1::fillPillar; break;
case EVoxelForgeCoreStampKind::FillDais: Request.kind = ResolvedSdfStampKindV1::fillDais; break;
case EVoxelForgeCoreStampKind::FillMonolith: Request.kind = ResolvedSdfStampKindV1::fillMonolith; break;
case EVoxelForgeCoreStampKind::FillPlatform: Request.kind = ResolvedSdfStampKindV1::fillPlatform; break;
}
Request.attachmentPolicy = Authored.bAttachToFloor ? 1u : 0u;
Request.yawQuarterTurns = Authored.YawQuarterTurns;
if (Request.kind == ResolvedSdfStampKindV1::fillMonolith || Request.kind == ResolvedSdfStampKindV1::fillPlatform)
MotifStamps.push_back(std::move(Request));
else LegacyStamps.push_back(std::move(Request));
}
TSet<FString> RoomNames;
for (const FVoxelForgeCoreRoomDefinition& Authored : Geometry.Rooms)
{
ResolvedCavernRoomV1 Room;
if (!StableId(Authored.StableName, Slot.StrateIndex, TEXT("room"), Room.stableId)
|| !VectorMicrons(Authored.CenterMeters, Slot.StrateIndex, TEXT("Rooms.CenterMeters"), Room.centerMicrometres)
|| !VectorMicrons(Authored.HalfExtentsMeters, Slot.StrateIndex, TEXT("Rooms.HalfExtentsMeters"), Room.halfExtentsMicrometres)
|| !Microns(Authored.FloorMeters, Slot.StrateIndex, TEXT("Rooms.FloorMeters"), Room.floorMicrometres)
|| !Microns(Authored.SmoothWidthMeters, Slot.StrateIndex, TEXT("Rooms.SmoothWidthMeters"), Room.smoothWidthMicrometres)
|| !Microns(Authored.UnionWidthMeters, Slot.StrateIndex, TEXT("Rooms.UnionWidthMeters"), Room.unionWidthMicrometres)
|| !Microns(Authored.IrregularityAmplitudeMeters, Slot.StrateIndex, TEXT("Rooms.IrregularityAmplitudeMeters"), Room.irregularityAmplitudeMicrometres)
|| !Microns(Authored.IrregularityWavelengthMeters, Slot.StrateIndex, TEXT("Rooms.IrregularityWavelengthMeters"), Room.irregularityWavelengthMicrometres)) return false;
Room.strateIndex = Slot.StrateIndex;
RoomNames.Add(Authored.StableName);
Rooms.push_back(std::move(Room));
}
for (const FVoxelForgeCoreTunnelDefinition& Authored : Geometry.Tunnels)
{
ResolvedCavernEdgeV1 Edge;
if (!StableId(Authored.StableName, Slot.StrateIndex, TEXT("tunnel"), Edge.stableId)) return false;
if (Authored.StartAnchorStableName.IsEmpty() || Authored.EndRoomStableName.IsEmpty()
|| !RoomNames.Contains(Authored.EndRoomStableName))
{
OutError = FString::Printf(TEXT("Core tunnel '%s' needs a start-anchor stable name and an end-room name that identifies a room on strate %d."), *Authored.StableName, Slot.StrateIndex);
return false;
}
FTCHARToUTF8 StartAnchorUtf8(*Authored.StartAnchorStableName);
Edge.startAnchorStableId = StartAnchorUtf8.Get();
Edge.endRoomStableId = CoreGeometryStableId(Slot.StrateIndex, Authored.EndRoomStableName);
Edge.strateIndex = Slot.StrateIndex;
const FVector ControlPoints[4] = { Authored.ControlPoint0Meters, Authored.ControlPoint1Meters,
Authored.ControlPoint2Meters, Authored.ControlPoint3Meters };
for (std::size_t Point = 0; Point < 4; ++Point)
if (!WorldAssemblyVectorToMicrometres(ControlPoints[Point], Edge.controlPointsMicrometres[Point]))
{
OutError = FString::Printf(TEXT("Core tunnel '%s' has a non-finite or out-of-range control point in metres."), *Authored.StableName);
return false;
}
if (!Microns(Authored.BoreRadiusMeters, Slot.StrateIndex, TEXT("Tunnels.BoreRadiusMeters"), Edge.boreRadiusMicrometres)
|| !Microns(Authored.BoreHeightMeters, Slot.StrateIndex, TEXT("Tunnels.BoreHeightMeters"), Edge.boreHeightMicrometres)
|| !Microns(Authored.FloorSupportDepthMeters, Slot.StrateIndex, TEXT("Tunnels.FloorSupportDepthMeters"), Edge.floorSupportDepthMicrometres)
|| !Microns(Authored.SmoothWidthMeters, Slot.StrateIndex, TEXT("Tunnels.SmoothWidthMeters"), Edge.smoothWidthMicrometres)
|| !Microns(Authored.UnionWidthMeters, Slot.StrateIndex, TEXT("Tunnels.UnionWidthMeters"), Edge.unionWidthMicrometres)
|| !Microns(Authored.IrregularityAmplitudeMeters, Slot.StrateIndex, TEXT("Tunnels.IrregularityAmplitudeMeters"), Edge.irregularityAmplitudeMicrometres)
|| !Microns(Authored.IrregularityWavelengthMeters, Slot.StrateIndex, TEXT("Tunnels.IrregularityWavelengthMeters"), Edge.irregularityWavelengthMicrometres)) return false;
for (const FVector& Point : Authored.PolylineMeters)
{
std::array<std::int64_t, 3> Converted{};
if (!WorldAssemblyVectorToMicrometres(Point, Converted))
{
OutError = FString::Printf(TEXT("Core tunnel '%s' has an invalid metre polyline point."), *Authored.StableName);
return false;
}
Edge.polylineMicrometres.push_back(Converted);
}
Edges.push_back(std::move(Edge));
}
if ((!Geometry.Rooms.IsEmpty() || !Geometry.Tunnels.IsEmpty())
&& Geometry.GeneratorRecipe != EVoxelForgeCoreGeneratorRecipe::NativeCaves
&& Geometry.GeneratorRecipe != EVoxelForgeCoreGeneratorRecipe::NativeLattice)
{
OutError = FString::Printf(TEXT("Strate %d authors a cavern graph without selecting NativeCaves or NativeLattice."), Slot.StrateIndex);
return false;
}
}
for (const FStrateSlot& Slot : StrateManager->GetLayout())
{
if (!Slot.Definition) continue;
for (const FStrateLandmark& Landmark : Slot.Definition->Landmarks)
if (!Landmark.CoreGeometryStableName.IsEmpty() && !FeatureNames.Contains(Landmark.CoreGeometryStableName))
{
OutError = FString::Printf(TEXT("Landmark on strate %d references unknown Core stable name '%s'."), Slot.StrateIndex, *Landmark.CoreGeometryStableName);
return false;
}
}
for (int32 StrateIndex : NativeGraphStrates)
{
const bool bHasRoom = std::any_of(Rooms.begin(), Rooms.end(), [StrateIndex](const auto& Room) { return Room.strateIndex == StrateIndex; });
const bool bHasEdge = std::any_of(Edges.begin(), Edges.end(), [StrateIndex](const auto& Edge) { return Edge.strateIndex == StrateIndex; });
if (!bHasRoom || !bHasEdge)
{
OutError = FString::Printf(TEXT("Native cave/lattice recipe on strate %d needs at least one authored room and one tunnel."), StrateIndex);
return false;
}
}
if (!LegacyStamps.empty() || !MotifStamps.empty())
{
std::string CoreError;
const ResolvedLayoutSnapshotV3 BaseLayout = Layout;
if (!LegacyStamps.empty() && !ResolveResolvedLayoutStampsV1(Layout, Seed, LegacyStamps, CoreError))
{
OutError = FString::Printf(TEXT("Core authored stamp resolution failed: %s"), UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
if (!MotifStamps.empty())
{
ResolvedLayoutSnapshotV3 MotifLayout = BaseLayout;
if (!ResolveResolvedLayoutStampsV3(MotifLayout, Seed, MotifStamps, CoreError))
{
OutError = FString::Printf(TEXT("Core authored motif resolution failed: %s"), UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
if (LegacyStamps.empty()) Layout = std::move(MotifLayout);
else
{
Layout.stamps.insert(Layout.stamps.end(), MotifLayout.stamps.begin(), MotifLayout.stamps.end());
Layout.stampVocabularyVersion = ResolvedLayoutStampVocabularyVersionV3;
std::vector<ResolvedLayoutIssueV2> Issues;
if (!Layout.finalize(CoreError) || !Layout.validate(ResolvedLayoutValidationModeV3::strict, Issues, CoreError))
{
OutError = FString::Printf(TEXT("Combined Core stamp layout failed validation: %s"), UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
}
}
}
if (!Rooms.empty() || !Edges.empty())
{
std::string CoreError;
if (!ResolveResolvedCavernGraphV1(Layout, Seed, Rooms, Edges, CoreError))
{
OutError = FString::Printf(TEXT("Core authored cavern graph resolution failed: %s"), UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
}
return true;
}
bool AttachCoreGeometryOnlyLayout(const UVoxelStrateManager* StrateManager,
double OwnerVoxelCm, double CoreVoxelCm, std::uint32_t LayoutChunkMicrometres,
std::int64_t LayoutChunkSubvoxels, const TSharedRef<FJsonObject>& Root, FString& OutError)
{
using namespace VoxelForgeCore;
ResolvedLayoutSnapshotV3 Layout;
Layout.worldIdentity = std::string("owner-world/seed/") + std::to_string(StrateManager->GetWorldSeed());
Layout.ownerVoxelSizeMicrometres = static_cast<std::uint32_t>(std::llround(OwnerVoxelCm * 10000.0));
Layout.coreVoxelSizeMicrometres = static_cast<std::uint32_t>(std::llround(CoreVoxelCm * 10000.0));
Layout.strateLayoutChunkSizeMicrometres = LayoutChunkMicrometres;
Layout.strateLayoutChunkWidthSubvoxels = LayoutChunkSubvoxels;
const std::uint32_t CoreVoxelMicrometres = Layout.coreVoxelSizeMicrometres;
const auto GeneratorName = [](ECaveGeneratorType Type) -> const char*
{
switch (Type)
{
case ECaveGeneratorType::TunnelNetwork: return "TunnelNetwork";
case ECaveGeneratorType::FlatPlain: return "FlatPlain";
case ECaveGeneratorType::CrystalChamber: return "CrystalChamber";
case ECaveGeneratorType::Maze: return "Maze";
case ECaveGeneratorType::SurfaceWorld: return "SurfaceWorld";
case ECaveGeneratorType::VerticalShafts: return "VerticalShafts";
case ECaveGeneratorType::FloatingIslands: return "FloatingIslands";
case ECaveGeneratorType::Underwater: return "Underwater";
}
return "Unknown";
};
const auto TransitionName = [](EVoxelStrateTransition Type) -> const char*
{
switch (Type)
{
case EVoxelStrateTransition::Gradient: return "Gradient";
case EVoxelStrateTransition::Hard: return "Hard";
case EVoxelStrateTransition::Interleaved: return "Interleaved";
}
return "Unknown";
};
std::vector<ResolvedLayoutPassageConfigV1> PassageConfigs;
const std::int64_t OwnerVoxelMicrometres = Layout.ownerVoxelSizeMicrometres;
for (const FStrateSlot& Slot : StrateManager->GetLayout())
{
if (!Slot.Definition) continue;
const ECaveGeneratorType Type = StrateManager->GetGeneratorTypeForChunk(FIntVector(0, 0, Slot.TopChunkZ));
ResolvedLayoutStratumV2 Item;
Item.strateIndex = Slot.StrateIndex;
Item.generatorKind = GeneratorName(Type);
Item.stableId = Layout.worldIdentity + "/strate/" + std::to_string(Slot.StrateIndex)
+ "/generator/" + GeneratorName(Type);
Item.recipeId = Type == ECaveGeneratorType::SurfaceWorld ? "SurfaceWorld.Heightfield2D"
: Type == ECaveGeneratorType::FloatingIslands ? "FloatingIslands.ClusterField"
: std::string("legacy.") + GeneratorName(Type);
Item.recipeVersion = 0;
if (!ConfigureCoreGeometryStratum(Slot, Type, false, Item, OutError)) return false;
Item.voxelSizeMicrometres = CoreVoxelMicrometres;
Item.interiorBottomMicrometres = Slot.BottomWorldMicrometres;
Item.interiorTopMicrometres = Slot.TopWorldMicrometres;
Item.managerChunkWidthMicrometres = LayoutChunkMicrometres;
Item.dispatchChunkWidthMicrometres = LayoutChunkMicrometres;
Item.transitionMode = TransitionName(Slot.ResolvedTransitionType);
Item.transitionBlendChunks = Slot.ResolvedTransitionBlendChunks;
Item.transitionWidthMicrometres = Slot.TransitionWidthMicrometres;
Item.transitionWarpAmplitudeMicrometres = Item.transitionMode == "Interleaved"
? std::min(Item.transitionWidthMicrometres / 2, static_cast<std::int64_t>(LayoutChunkMicrometres)) : 0;
Item.transitionInterpenetrationMicrometres = Item.transitionMode == "Interleaved"
? Item.transitionWidthMicrometres : Item.transitionMode == "Gradient" ? Item.transitionWidthMicrometres / 4 : 0;
Item.transitionFallsBackToHard = Item.transitionMode != "Hard" && Item.transitionWidthMicrometres <= 0;
Item.transitionHonouredInCore = Item.transitionMode == "Hard" || !Item.transitionFallsBackToHard;
Item.managerLookup = "UVoxelStrateManager::GetGeneratorTypeForChunk; upper strate owns transitions";
Item.boundDispatchPath = "CoreGeometry-only resolved strate layout";
Item.queryDispatchPath = "CoreGeometry-only native layout field";
const std::int64_t SealMicrometres = static_cast<std::int64_t>(std::llround(
std::max(0.0f, StrateBoundarySealThickness(*Slot.Definition)) * OwnerVoxelMicrometres));
if (SealMicrometres <= 0)
{
OutError = FString::Printf(TEXT("Strate %d has no positive BoundarySealThickness for the shared ceiling and bedrock bands."), Slot.StrateIndex);
return false;
}
const std::int64_t SolidDensityQ16 = std::max<std::int64_t>(1,
WorldAssemblyQ16(std::max(0.05f, StrateBoundarySolidDensity(*Slot.Definition))));
Item.ceiling = { "ceiling", Item.interiorTopMicrometres - SealMicrometres,
Item.interiorTopMicrometres, true, true, false, "solid ceiling from strate BoundarySealThickness",
"interior-facing lower surface", "shared resolved-layout density band", SolidDensityQ16 };
Item.bedrock = { "bedrock", Item.interiorBottomMicrometres,
Item.interiorBottomMicrometres + SealMicrometres, true, true, false,
"solid bedrock floor from strate BoundarySealThickness", "interior-facing upper surface",
"shared resolved-layout density band", SolidDensityQ16 };
Item.content = { "content", Item.interiorBottomMicrometres + SealMicrometres,
Item.interiorTopMicrometres - SealMicrometres, true, true, false,
"resolved generator interior", "none", "none" };
Layout.strates.push_back(std::move(Item));
const FStratePassageConfig& Passage = Slot.ResolvedPassageConfig;
ResolvedLayoutPassageConfigV1 Config;
Config.upperStrateIndex = Slot.StrateIndex;
Config.connections = Passage.Connections;
Config.style = static_cast<std::uint32_t>(Passage.Style);
Config.mouthRadiusSubvoxels = std::max(WorldAssemblyMicrometresToSubvoxels(
Slot.PassageMouthRadiusMicrometres, CoreVoxelMicrometres),
WorldAssemblyMicrometresToSubvoxels(36 * OwnerVoxelMicrometres, CoreVoxelMicrometres));
Config.midRadiusSubvoxels = std::max(WorldAssemblyMicrometresToSubvoxels(
Slot.PassageMidRadiusMicrometres, CoreVoxelMicrometres),
WorldAssemblyMicrometresToSubvoxels(24 * OwnerVoxelMicrometres, CoreVoxelMicrometres));
Config.reachMinimumSubvoxels = WorldAssemblyMicrometresToSubvoxels(Slot.PassageReachMinimumMicrometres, CoreVoxelMicrometres);
Config.reachMaximumSubvoxels = WorldAssemblyMicrometresToSubvoxels(Slot.PassageReachMaximumMicrometres, CoreVoxelMicrometres);
Config.distanceMinimumSubvoxels = WorldAssemblyMicrometresToSubvoxels(Slot.PassageDistanceMinimumMicrometres, CoreVoxelMicrometres);
Config.distanceMaximumSubvoxels = WorldAssemblyMicrometresToSubvoxels(Slot.PassageDistanceMaximumMicrometres, CoreVoxelMicrometres);
Config.wanderSubvoxels = WorldAssemblyMicrometresToSubvoxels(Slot.PassageWanderMicrometres, CoreVoxelMicrometres);
Config.segments = static_cast<std::uint32_t>(FMath::Clamp(Passage.Segments, 1, 48));
Config.verticalWobbleSubvoxels = WorldAssemblyMicrometresToSubvoxels(Slot.PassageVerticalWobbleMicrometres, CoreVoxelMicrometres);
Config.spiralRadiusSubvoxels = WorldAssemblyMicrometresToSubvoxels(Slot.PassageSpiralRadiusMicrometres, CoreVoxelMicrometres);
Config.spiralTurnsQ16 = static_cast<std::int64_t>(std::llround(static_cast<double>(Passage.SpiralTurns) * 65536.0));
Config.cascadeSteps = static_cast<std::uint32_t>(FMath::Clamp(Passage.CascadeSteps, 1, 16));
Config.cascadeLedgeSubvoxels = WorldAssemblyMicrometresToSubvoxels(Slot.PassageCascadeLedgeMicrometres, CoreVoxelMicrometres);
PassageConfigs.push_back(Config);
}
std::string CoreError;
if (!PlanResolvedLayoutPassagesV1(Layout, StrateManager->GetWorldSeed(), PassageConfigs, CoreError))
{
OutError = FString::Printf(TEXT("Resolved inter-strate passage planning failed: %s"), UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
if (!ResolveAuthoredCoreGeometry(Layout, StrateManager, StrateManager->GetWorldSeed(), OutError)) return false;
if (!Layout.finalize(CoreError))
{
OutError = FString::Printf(TEXT("Core resolved-layout export failed: %s"), UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
const std::string Json = Layout.ToJson();
Root->SetStringField(TEXT("resolved_layout_json"), UTF8_TO_TCHAR(Json.c_str()));
return true;
}
bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
const UVoxelStrateManager* StrateManager,
double CoreVoxelSizeCm,
@@ -876,6 +1602,7 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
Generated.recipe.layoutChunkWidthSubvoxels=LayoutChunkWidthSubvoxels;
int32 SurfaceSlotCount = 0;
int32 IslandSlotCount = 0;
int32 CoreGeometrySlotCount = 0;
for (const FStrateSlot& Slot : StrateManager->GetLayout())
{
if (Slot.Definition == nullptr) continue;
@@ -883,10 +1610,13 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
FIntVector(0, 0, Slot.TopChunkZ));
SurfaceSlotCount += Type == ECaveGeneratorType::SurfaceWorld ? 1 : 0;
IslandSlotCount += Type == ECaveGeneratorType::FloatingIslands ? 1 : 0;
CoreGeometrySlotCount += HasCoreGeometry(*Slot.Definition) ? 1 : 0;
}
if (SurfaceSlotCount == 0 && IslandSlotCount == 0)
{
return true;
return CoreGeometrySlotCount == 0 ? true
: AttachCoreGeometryOnlyLayout(StrateManager, OwnerVoxelSizeCm, CoreVoxelSizeCm,
LayoutChunkSizeMicrometres, LayoutChunkWidthSubvoxels, Root, OutError);
}
if (SurfaceSlotCount == 0)
{
@@ -918,7 +1648,11 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
Generated.plan.strates.push_back(std::move(Stratum));
++BuiltSurfaceCount;
}
else if (Type == ECaveGeneratorType::FloatingIslands)
// Native floating-island evaluation remains an explicit imported-plan opt-in.
// The default owner path uses the legacy generator for this slot; emitting an
// automatic FI plan here silently switches density dispatch and changes the
// shared passage mouth before the owner has opted into that field.
else if (Type == ECaveGeneratorType::FloatingIslands && bImportedOverride)
{
if (!BuildFloatingIslandStratumPlan(StrateManager, Slot, Generated,
CoreVoxelSizeCm, LayoutChunkWidthSubvoxels, OutError))
@@ -991,10 +1725,11 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
}
}
if (Manifest->plan.strates.size() != static_cast<std::size_t>(SurfaceSlotCount)
|| Manifest->floatingIslands.size() != static_cast<std::size_t>(FloatingIslandSlotCount))
|| (!Manifest->floatingIslands.empty()
&& Manifest->floatingIslands.size() != static_cast<std::size_t>(FloatingIslandSlotCount)))
{
OutError = FString::Printf(
TEXT("Core world assembly has %d SurfaceWorld and %d floating-island plans for %d resolved SurfaceWorld and %d FloatingIslands strates."),
TEXT("Core world assembly has %d SurfaceWorld and %d floating-island plans for %d resolved SurfaceWorld strates; an explicit FI plan must cover all %d FloatingIslands strates."),
static_cast<int32>(Manifest->plan.strates.size()),
static_cast<int32>(Manifest->floatingIslands.size()),
SurfaceSlotCount, FloatingIslandSlotCount);
@@ -1088,12 +1823,12 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
WorldAssemblyMicrometresToSubvoxels(
Slot.PassageMouthRadiusMicrometres, ExpectedCoreVoxelMicrons),
WorldAssemblyMicrometresToSubvoxels(
12 * OwnerVoxelMicrons, ExpectedCoreVoxelMicrons));
36 * OwnerVoxelMicrons, ExpectedCoreVoxelMicrons));
PassageConfig.midRadiusSubvoxels = std::max(
WorldAssemblyMicrometresToSubvoxels(
Slot.PassageMidRadiusMicrometres, ExpectedCoreVoxelMicrons),
WorldAssemblyMicrometresToSubvoxels(
12 * OwnerVoxelMicrons, ExpectedCoreVoxelMicrons));
24 * OwnerVoxelMicrons, ExpectedCoreVoxelMicrons));
PassageConfig.reachMinimumSubvoxels = WorldAssemblyMicrometresToSubvoxels(
Slot.PassageReachMinimumMicrometres, ExpectedCoreVoxelMicrons);
PassageConfig.reachMaximumSubvoxels = WorldAssemblyMicrometresToSubvoxels(
@@ -1128,6 +1863,10 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
: Type == ECaveGeneratorType::FloatingIslands ? "FloatingIslands.ClusterField"
: std::string("legacy.") + generatorName(Type);
Item.recipeVersion = Type == ECaveGeneratorType::SurfaceWorld ? Manifest->recipe.schemaVersion : 0;
if (!ConfigureCoreGeometryStratum(Slot, Type, bImportedOverride, Item, OutError))
{
return false;
}
Item.voxelSizeMicrometres = ExpectedCoreVoxelMicrons;
Item.interiorBottomMicrometres = Slot.BottomWorldMicrometres;
Item.interiorTopMicrometres = Slot.TopWorldMicrometres;
@@ -1264,6 +2003,11 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
UTF8_TO_TCHAR(PassagePlanError.c_str()));
return false;
}
if (!ResolveAuthoredCoreGeometry(Layout, StrateManager,
StrateManager->GetWorldSeed(), OutError))
{
return false;
}
std::string LayoutError;
if (!Layout.finalize(LayoutError))
{
@@ -1372,6 +2116,10 @@ bool VoxelForgeCoreAdapter::SerializeResolvedWorld(
static_cast<std::uint32_t>(std::llround(OwnerVoxelSizeCm * 10000.0)));
Root->SetNumberField(TEXT("core_voxel_size_cm"), ResolvedCoreVoxelSizeCm);
Root->SetNumberField(TEXT("core_voxel_size_microns"), CoreVoxelSizeMicrons);
// Core import des longueurs owner avec le rapport physique resolu, pas un ratio suppose.
// Core imports owner lengths with the resolved physical ratio, never an assumed scale.
Root->SetNumberField(TEXT("owner_to_core_length_scale"),
OwnerVoxelSizeCm / ResolvedCoreVoxelSizeCm);
Root->SetNumberField(TEXT("seed"), Seed);
Root->SetBoolField(TEXT("open_surface_entry"), Settings->bOpenSurfaceEntry);
Root->SetNumberField(TEXT("origin_spine_radius"), OriginSpineRadius);
@@ -2590,8 +2590,8 @@ static float EvaluateInternal(const UVoxelGenerator& Generator,
{
EnsurePlan(Generator);
}
const bool bChunkResolved = Context != nullptr && Context->bChunkResolved
|| ParameterizedContext != nullptr && ParameterizedContext->bChunkResolved;
const bool bChunkResolved = (Context != nullptr && Context->bChunkResolved)
|| (ParameterizedContext != nullptr && ParameterizedContext->bChunkResolved);
const FIntVector Chunk = Context != nullptr && Context->bChunkResolved
? Context->Chunk
: ParameterizedContext != nullptr && ParameterizedContext->bChunkResolved
@@ -1958,10 +1958,11 @@ namespace VoxelSeasonCompositionPrivate
}
void CopyDefinitionParams(const UVoxelStrateDefinition& Definition,
FVoxelSeasonFixedStrate& OutFixed)
double LayoutChunkMeters, FVoxelSeasonFixedStrate& OutFixed)
{
OutFixed = FVoxelSeasonFixedStrate();
OutFixed.HeightInChunks = Definition.StrateHeightInChunks;
OutFixed.HeightInChunks = FMath::Max(1, FMath::RoundToInt(
Definition.StrateHeightMeters / FMath::Max(0.001, LayoutChunkMeters)));
OutFixed.Archetype = Definition.GeneratorType;
OutFixed.SourceDefinitionPath = Definition.GetPathName();
OutFixed.bUsesRecipe = false;
@@ -1999,7 +2000,9 @@ namespace VoxelSeasonCompositionPrivate
return false;
}
FVoxelSeasonFixedStrate Fixed;
CopyDefinitionParams(*Definition, Fixed);
const double LayoutChunkMeters = Settings.WorldSettings != nullptr
? Settings.WorldSettings->StrateLayoutChunkSizeMeters : 8.0;
CopyDefinitionParams(*Definition, LayoutChunkMeters, Fixed);
Fixed.Seed = SeasonSeed;
if (Fixed.SourceDefinitionPath.IsEmpty())
{
@@ -2974,7 +2977,9 @@ FVoxelSeasonManifest VF_ComposeSeason(int32 SeasonSeed, const UVoxelSettings* Wo
if (WorldSettings != nullptr)
{
Settings.SelectedStrateCount = WorldSettings->TotalStrates;
Settings.InterStrateGapChunks = WorldSettings->InterStrateGapChunks;
Settings.InterStrateGapChunks = FMath::Max(0, FMath::RoundToInt(
WorldSettings->InterStrateGapMeters * 1000000.0
/ FMath::Max(1.0, static_cast<double>(WorldSettings->StrateLayoutChunkSizeMeters) * 1000000.0)));
Settings.OriginSpineRadius = WorldSettings->OriginSpineRadius;
Settings.WorldRadiusVoxels = WorldSettings->WorldRadiusVoxels;
Settings.SeasonNumber = WorldSettings->CurrentSeason;
@@ -1,5 +1,7 @@
#include "VoxelSettings.h"
#include "VoxelForgeAssetVersion.h"
#include "VoxelStrateDefinition.h"
#include "VoxelSeasonAsset.h"
#if WITH_EDITOR
@@ -9,6 +11,74 @@
#include <VoxelForgeCore/VoxelForgeCore.h>
#include <string>
void UVoxelSettings::Serialize(FArchive& Ar)
{
Ar.UsingCustomVersion(VoxelForgeAssetVersion::GUID);
Super::Serialize(Ar);
if (Ar.IsLoading() && Ar.CustomVer(VoxelForgeAssetVersion::GUID)
< VoxelForgeAssetVersion::MetreAuthoringV1)
{
const double LayoutChunkMeters = FMath::IsFinite(StrateLayoutChunkSizeMeters)
? FMath::Max(0.001, static_cast<double>(StrateLayoutChunkSizeMeters)) : 8.0;
InterStrateGapMeters = static_cast<double>(FMath::Max(0, InterStrateGapChunks))
* LayoutChunkMeters;
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeAssetUpgrade] type=Settings asset=%s gap_chunks=%d gap_m=%.6f layout_chunk_m=%.6f"),
*GetPathName(), InterStrateGapChunks, InterStrateGapMeters, LayoutChunkMeters);
}
}
void UVoxelSettings::PostLoad()
{
Super::PostLoad();
if (GetLinkerCustomVersion(VoxelForgeAssetVersion::GUID)
>= VoxelForgeAssetVersion::MetreAuthoringV1)
{
return;
}
// Resolve the legacy count against this settings asset's saved physical layout width.
// Convertit les anciens comptes avec la largeur physique sauvegardee dans ces reglages.
const double LayoutChunkMeters = FMath::IsFinite(StrateLayoutChunkSizeMeters)
? FMath::Max(0.001, static_cast<double>(StrateLayoutChunkSizeMeters)) : 8.0;
InterStrateGapMeters = static_cast<double>(FMath::Max(0, InterStrateGapChunks))
* LayoutChunkMeters;
TSet<UVoxelStrateDefinition*> Migrated;
const auto MigrateStrate = [this, LayoutChunkMeters, &Migrated](
const TSoftObjectPtr<UVoxelStrateDefinition>& SoftDefinition)
{
UVoxelStrateDefinition* Definition = SoftDefinition.LoadSynchronous();
if (Definition == nullptr || Migrated.Contains(Definition)
|| Definition->GetLinkerCustomVersion(VoxelForgeAssetVersion::GUID)
>= VoxelForgeAssetVersion::MetreAuthoringV1)
{
return;
}
Migrated.Add(Definition);
const int32 OldHeightChunks = Definition->StrateHeightInChunks;
const int32 OldTransitionChunks = Definition->TransitionBlendChunks;
Definition->StrateHeightMeters = static_cast<double>(FMath::Max(1, OldHeightChunks))
* LayoutChunkMeters;
Definition->TransitionWidthMeters = static_cast<double>(FMath::Max(0, OldTransitionChunks))
* LayoutChunkMeters;
Definition->MarkPackageDirty();
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeAssetUpgrade] type=Strate asset=%s height_chunks=%d height_m=%.6f transition_chunks=%d transition_m=%.6f effective_layout_chunk_m=%.6f"),
*Definition->GetPathName(), OldHeightChunks, Definition->StrateHeightMeters,
OldTransitionChunks, Definition->TransitionWidthMeters, LayoutChunkMeters);
};
for (const TSoftObjectPtr<UVoxelStrateDefinition>& Definition : StratePool)
{
MigrateStrate(Definition);
}
for (const TPair<int32, TSoftObjectPtr<UVoxelStrateDefinition>>& Definition : FixedStrates)
{
MigrateStrate(Definition.Value);
}
MarkPackageDirty();
}
int32 UVoxelSettings::GetEffectiveWorldSeed() const
{
if (!Season.IsNull())
@@ -297,7 +297,11 @@ void RecordTile(const FTileSample& Sample)
"\"submitted_t_s\":%s,\"worker_start_t_s\":%s,\"worker_end_t_s\":%s,\"worker_thread_id\":%u,"
"\"verdict\":%d,\"proof\":%d,\"empty\":%d,\"vertices\":%d,\"triangles\":%d,\"geometry_bytes\":%llu,"
"\"request_to_apply_s\":%s,\"queue_wait_s\":%s,\"worker_queue_s\":%s,\"result_queue_s\":%s,\"worker_task_s\":%s,"
"\"generation_s\":%s,\"classify_s\":%s,\"mesh_s\":%s,\"stream_s\":%s,\"apply_s\":%s"),
"\"generation_s\":%s,"
#if PLATFORM_LINUX
"\"core_generate_s\":%s,\"mesh_copy_s\":%s,\"rmc_stream_build_s\":%s,"
#endif
"\"classify_s\":%s,\"mesh_s\":%s,\"stream_s\":%s,\"apply_s\":%s"),
Sample.TileX, Sample.TileY, Sample.TileZ,
Sample.Level, Sample.WorkPriority,
static_cast<unsigned long long>(Sample.RequestOrdinal),
@@ -312,8 +316,12 @@ void RecordTile(const FTileSample& Sample)
static_cast<unsigned long long>(Sample.GeometryBytes),
*Number(Sample.RequestToApplySeconds), *Number(Sample.QueueWaitSeconds),
*Number(Sample.WorkerQueueSeconds), *Number(Sample.ResultQueueSeconds),
*Number(Sample.WorkerTaskSeconds),
*Number(Sample.GenerationSeconds), *Number(Sample.ClassifySeconds),
*Number(Sample.WorkerTaskSeconds), *Number(Sample.GenerationSeconds),
#if PLATFORM_LINUX
*Number(Sample.CoreGenerateSeconds), *Number(Sample.MeshCopySeconds),
*Number(Sample.StreamBuildSeconds),
#endif
*Number(Sample.ClassifySeconds),
*Number(Sample.MeshSeconds), *Number(Sample.StreamSeconds), *Number(Sample.ApplySeconds)));
}
@@ -1,20 +1,17 @@
// Serialized strate-definition migrations.
#include "VoxelStrateDefinition.h"
#include "VoxelForgeAssetVersion.h"
#include "Serialization/CustomVersion.h"
#include "VoxelPassageGeometry.h"
namespace
{
// The field name is retained for package compatibility, but version 1 changes its meaning
// from a raw tangent/gradient to authored degrees. This GUID is local to the plugin.
const FGuid GVoxelForgeStrateSerializationVersion(
0xA76D5E31, 0x4B2C4F9A, 0xB8C1D204, 0x6E9F7738);
constexpr int32 GVoxelForgeStrateGentleSlopeDegreesVersion = 1;
constexpr int32 GVoxelForgeStrateGentleSlopeDegreesVersion = VoxelForgeAssetVersion::TunnelFloorSlopeDegrees;
FCustomVersionRegistration GVoxelForgeStrateSerializationRegistration(
GVoxelForgeStrateSerializationVersion,
GVoxelForgeStrateGentleSlopeDegreesVersion,
VoxelForgeAssetVersion::GUID,
VoxelForgeAssetVersion::MetreAuthoringV1,
TEXT("VoxelForgeStrateSerialization"));
float LegacyGradientToDegrees(float LegacyGradient)
@@ -58,6 +55,31 @@ namespace
}
}
void UVoxelStrateDefinition::Serialize(FArchive& Ar)
{
Ar.UsingCustomVersion(VoxelForgeAssetVersion::GUID);
Super::Serialize(Ar);
if (Ar.IsLoading() && Ar.CustomVer(VoxelForgeAssetVersion::GUID)
< VoxelForgeAssetVersion::MetreAuthoringV1)
{
// A stand-alone strate has no owning settings object during Serialize. Convert using the
// current 8 m layout default here; UVoxelSettings corrects referenced old strates using
// their saved effective width during its own load upgrade.
// Une strate seule n'a pas ses reglages proprietaires ici; UVoxelSettings corrige ensuite
// les references avec leur largeur physique sauvegardee.
constexpr double LegacyDefaultLayoutChunkMeters = 8.0;
StrateHeightMeters = static_cast<double>(FMath::Max(1, StrateHeightInChunks))
* LegacyDefaultLayoutChunkMeters;
TransitionWidthMeters = static_cast<double>(FMath::Max(0, TransitionBlendChunks))
* LegacyDefaultLayoutChunkMeters;
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeAssetUpgrade] type=Strate asset=%s height_chunks=%d height_m=%.6f transition_chunks=%d transition_m=%.6f fallback_layout_chunk_m=%.6f"),
*GetPathName(), StrateHeightInChunks, StrateHeightMeters,
TransitionBlendChunks, TransitionWidthMeters, LegacyDefaultLayoutChunkMeters);
}
}
void UVoxelStrateDefinition::PostLoad()
{
Super::PostLoad();
@@ -67,7 +89,7 @@ void UVoxelStrateDefinition::PostLoad()
// is normalized to the ignored compatibility default before any runtime copy is made.
GenerationParams.bTunnelFloorEnabled = true;
if (GetLinkerCustomVersion(GVoxelForgeStrateSerializationVersion)
if (GetLinkerCustomVersion(VoxelForgeAssetVersion::GUID)
>= GVoxelForgeStrateGentleSlopeDegreesVersion)
{
return;
@@ -1255,7 +1255,8 @@ bool UVoxelStrateManager::Initialize(UVoxelSettings* Settings, int32 WorldSeed)
? SeasonManifest.Strates.Num() : Settings->TotalStrates;
const int32 LayoutGapChunks = bUseSeason
? SeasonManifest.InterStrateGapChunks
: FMath::Max(0, Settings->InterStrateGapChunks);
: FMath::Max(0, FMath::RoundToInt(static_cast<double>(Settings->InterStrateGapMeters)
* 1000000.0 / std::max<uint32>(1, StrateLayoutChunkSizeMicrometres)));
//=========================================================================
// STEP 1: Build shuffled pool (seed-based randomization)
@@ -1369,7 +1370,8 @@ bool UVoxelStrateManager::Initialize(UVoxelSettings* Settings, int32 WorldSeed)
}
Slot.Definition->GeneratorType = SeasonStrate.Archetype;
Slot.Definition->StrateHeightInChunks = SeasonStrate.HeightInChunks;
Slot.Definition->StrateHeightMeters = static_cast<double>(SeasonStrate.HeightInChunks)
* StrateLayoutChunkSizeMicrometres / 1000000.0;
Slot.Definition->GenerationParams = SeasonStrate.Params.TunnelNetworkParams;
Slot.Definition->SlabParams = SeasonStrate.Params.SlabParams;
Slot.Definition->MazeParams = SeasonStrate.Params.MazeParams;
@@ -1426,7 +1428,9 @@ bool UVoxelStrateManager::Initialize(UVoxelSettings* Settings, int32 WorldSeed)
}
// Resolve the authored chunk height into an exact physical interval.
Slot.HeightInChunks = Slot.Definition->StrateHeightInChunks;
Slot.HeightInChunks = FMath::Max(1, FMath::RoundToInt(
Slot.Definition->StrateHeightMeters * 1000000.0
/ std::max<uint32>(1, StrateLayoutChunkSizeMicrometres)));
Slot.TopWorldMicrometres = CurrentTopWorldMicrometres;
Slot.HeightMicrometres = static_cast<int64>(Slot.HeightInChunks)
* StrateLayoutChunkSizeMicrometres;
@@ -1439,9 +1443,11 @@ bool UVoxelStrateManager::Initialize(UVoxelSettings* Settings, int32 WorldSeed)
}
Slot.ResolvedTransitionType = Slot.Definition->TransitionType;
Slot.ResolvedTransitionBlendChunks = Slot.Definition->TransitionBlendChunks;
Slot.TransitionWidthMicrometres = static_cast<int64>(
Slot.ResolvedTransitionBlendChunks) * StrateLayoutChunkSizeMicrometres;
Slot.ResolvedTransitionBlendChunks = FMath::Max(0, FMath::RoundToInt(
Slot.Definition->TransitionWidthMeters * 1000000.0
/ std::max<uint32>(1, StrateLayoutChunkSizeMicrometres)));
Slot.TransitionWidthMicrometres = static_cast<int64>(Slot.ResolvedTransitionBlendChunks)
* StrateLayoutChunkSizeMicrometres;
Slot.ResolvedPassageConfig = Slot.Definition->PassageConfig;
Slot.PassageMouthRadiusMicrometres = VF_OwnerVoxelsToMicrometres(
Slot.ResolvedPassageConfig.MouthRadius);
+136 -40
View File
@@ -82,6 +82,17 @@
#include <atomic>
#include <unordered_set>
static FORCEINLINE int32 VF_ClipLevelScale(int32 Level)
{
const uint32 SafeLevel = static_cast<uint32>(FMath::Clamp(Level, 0, 8));
return static_cast<int32>(uint32{1} << SafeLevel);
}
static FORCEINLINE int32 VF_ClipLevelExtentVoxels(int32 Level)
{
return static_cast<int32>(static_cast<uint32>(CHUNK_SIZE)
* static_cast<uint32>(VF_ClipLevelScale(Level)));
}
static bool VF_TileOverlapsVoxelBounds(const FVoxelTileKey& Tile,
const FVector& BoundsMin, const FVector& BoundsMax);
static bool VF_TileFootprintsOverlap(const FVoxelTileKey& A, const FVoxelTileKey& B);
@@ -227,11 +238,11 @@ namespace
GVoxelForgeStreamingResultBacklogCap,
TEXT("Maximum queued results plus admitted tile tasks; 0 disables the safety cap. This is separate from the worker-slot cap."));
int32 GVoxelForgeStreamingReadyQueueDepth = 8;
int32 GVoxelForgeStreamingReadyQueueDepth = 16;
FAutoConsoleVariableRef CVarVoxelForgeStreamingReadyQueueDepth(
TEXT("voxel.StreamingReadyQueueDepth"),
GVoxelForgeStreamingReadyQueueDepth,
TEXT("Extra admitted tile tasks kept ready beyond the worker slots, up to one worker-set; 0 disables the refill queue."));
TEXT("Extra admitted tile tasks kept ready beyond worker slots, up to four worker-sets/64 tasks; 0 disables the refill queue."));
int32 GVoxelForgeStreamingBackgroundWorkerCap = 1;
FAutoConsoleVariableRef CVarVoxelForgeStreamingBackgroundWorkerCap(
@@ -301,8 +312,8 @@ namespace
FAutoConsoleVariableRef CVarVoxelForgeStreamingStartupApplyBoost(
TEXT("voxel.StreamingStartupApplyBoost"),
GVoxelForgeStreamingStartupApplyBoost,
TEXT("Bounded cold-view upload budget (default on): 1 allows a 14 ms / 12-mesh budget until the initial desired set and walkable ring are ready; 0 uses the authored per-frame budget."));
constexpr int32 StreamingStartupMaxMeshApplies = 12;
TEXT("Bounded cold-view upload budget (default on): 1 allows a 14 ms / 24-mesh cap until the initial desired set and walkable ring are ready; 0 uses the authored per-frame budget."));
constexpr int32 StreamingStartupMaxMeshApplies = 24;
constexpr float StreamingStartupApplyBudgetMilliseconds = 14.0f;
constexpr double StreamingStartupApplyMaxSeconds = 30.0;
@@ -2196,7 +2207,7 @@ void AVoxelWorld::BeginPlay()
CommandLineStreamingReadyQueueDepth))
{
GVoxelForgeStreamingReadyQueueDepth = FMath::Clamp(
CommandLineStreamingReadyQueueDepth, 0, 32);
CommandLineStreamingReadyQueueDepth, 0, 64);
}
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.StreamingBackgroundWorkerCap="),
CommandLineStreamingBackgroundWorkerCap))
@@ -2623,7 +2634,8 @@ void AVoxelWorld::BeginPlay()
FTaskGraphInterface& TaskGraph = FTaskGraphInterface::Get();
LowLevelTasks::FScheduler& TaskScheduler = LowLevelTasks::FScheduler::Get();
const int32 QueueDepth = FMath::Clamp(
GVoxelForgeStreamingReadyQueueDepth, 0, GetMaxConcurrentStreamingTasks());
GVoxelForgeStreamingReadyQueueDepth, 0,
FMath::Min(64, FMath::Max(1, GetMaxConcurrentStreamingTasks()) * 4));
VoxelForgeStartupTrace::RecordEvent(TEXT("worker_backend_config"), FString::Printf(
TEXT("\"logical_cores\":%d,\"effective_tile_slots\":%d,"
"\"task_api\":\"UE::Tasks::Launch\",\"task_scheduler_workers\":%u,"
@@ -6240,17 +6252,19 @@ static void VF_AppendTouchingAncestors(
const FVoxelTileKey& Tile, int32 AncestorLevel, CandidateArray& OutCandidates)
{
if (AncestorLevel <= Tile.Level) { return; }
const int32 Divisor = 1 << (AncestorLevel - Tile.Level);
const auto FloorDiv = [Divisor](int32 Value)
const int32 Divisor = VF_ClipLevelScale(AncestorLevel - Tile.Level);
const auto FloorDiv = [Divisor](int64 Value)
{
return Value >= 0 ? Value / Divisor : -(((-Value) + Divisor - 1) / Divisor);
const int64 Quotient = Value / Divisor;
const int64 Remainder = Value % Divisor;
return static_cast<int32>(Remainder < 0 ? Quotient - 1 : Quotient);
};
const auto AxisCandidates = [&FloorDiv, Divisor](int32 Value)
{
TArray<int32, TInlineAllocator<2>> Candidates;
int32 First = FloorDiv(Value);
if ((Value % Divisor) == 0) { --First; }
const int32 Last = FloorDiv(Value + 1);
const int32 Last = FloorDiv(static_cast<int64>(Value) + 1);
for (int32 Coord = First; Coord <= Last; ++Coord) { Candidates.Add(Coord); }
return Candidates;
};
@@ -6338,7 +6352,7 @@ void AVoxelWorld::UpdateVisualStalenessAudit()
const FVector& WorldPosition, int32 Level, auto& OutCandidates)
{
const FVector LocalVoxel = WorldToLocalVoxel(WorldPosition);
const float TileExtentVoxels = static_cast<float>(CHUNK_SIZE * (1 << Level));
const float TileExtentVoxels = static_cast<float>(VF_ClipLevelExtentVoxels(Level));
TArray<int32, TInlineAllocator<2>> XCandidates;
TArray<int32, TInlineAllocator<2>> YCandidates;
TArray<int32, TInlineAllocator<2>> ZCandidates;
@@ -7182,7 +7196,7 @@ void AVoxelWorld::Tick(float DeltaTime)
{
const FIntVector Min = SteadyLevelMins.FindChecked(Level);
const FIntVector Max = SteadyLevelMaxs.FindChecked(Level);
const int32 ExtentVoxels = CHUNK_SIZE << Level;
const int32 ExtentVoxels = VF_ClipLevelExtentVoxels(Level);
const double TileExtentMetres = static_cast<double>(ExtentVoxels) * VOXEL_SIZE / 100.0;
const double SpanX = static_cast<double>(Max.X - Min.X + 1) * TileExtentMetres;
const double SpanY = static_cast<double>(Max.Y - Min.Y + 1) * TileExtentMetres;
@@ -7554,6 +7568,14 @@ bool AVoxelWorld::GetVoxelForgeCoreWorldAssembly(
return bUseVoxelForgeCore && !OutCanonicalJson.IsEmpty() && !OutContentHash.IsEmpty();
}
bool AVoxelWorld::GetVoxelForgeCoreResolvedLayout(
FString& OutCanonicalJson, FString& OutStableIdentity) const
{
OutCanonicalJson = VoxelForgeCoreResolvedLayoutJson;
OutStableIdentity = VoxelForgeCoreResolvedLayoutIdentity;
return bUseVoxelForgeCore && !OutCanonicalJson.IsEmpty() && !OutStableIdentity.IsEmpty();
}
bool AVoxelWorld::QueryVoxelForgeCoreDensityAt(FVector WorldLocation, float& OutDensity) const
{
OutDensity = 0.0f;
@@ -7572,6 +7594,8 @@ bool AVoxelWorld::InitializeVoxelForgeCoreWorld(FString& OutFailureReason)
OutFailureReason.Reset();
VoxelForgeCoreWorldAssemblyJson.Reset();
VoxelForgeCoreWorldAssemblyHash.Reset();
VoxelForgeCoreResolvedLayoutJson.Reset();
VoxelForgeCoreResolvedLayoutIdentity.Reset();
if (!bUseVoxelForgeCore) return true;
if (!Settings || !StrateManager)
{
@@ -7604,6 +7628,17 @@ bool AVoxelWorld::InitializeVoxelForgeCoreWorld(FString& OutFailureReason)
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeCoreRuntime] %s"), *OutFailureReason);
return false;
}
if (const VoxelForgeCore::ResolvedLayoutSnapshotV3* ResolvedLayout = Snapshot->resolvedLayout())
{
const std::string CanonicalLayout = ResolvedLayout->ToJson();
if (CanonicalLayout.empty())
{
OutFailureReason = TEXT("the resolved Core layout could not be serialized canonically.");
return false;
}
VoxelForgeCoreResolvedLayoutJson = UTF8_TO_TCHAR(CanonicalLayout.c_str());
VoxelForgeCoreResolvedLayoutIdentity = UTF8_TO_TCHAR(ResolvedLayout->stableIdentity.c_str());
}
if (const VoxelForgeCore::WorldAssemblyManifest* Assembly = Snapshot->worldAssembly())
{
const std::string CanonicalAssembly = Assembly->ToJson();
@@ -7663,10 +7698,20 @@ bool AVoxelWorld::InitializeVoxelForgeCoreWorld(FString& OutFailureReason)
};
for (const VoxelForgeCore::TileKey& Key : InitialDesired)
{
const int32 Extent = 32 << Key.lod;
const int32 OriginX = Key.coordinate.x * Extent;
const int32 OriginY = Key.coordinate.y * Extent;
const int32 OriginZ = Key.coordinate.z * Extent;
const int32 Extent = VF_ClipLevelExtentVoxels(Key.lod);
const int64 OriginXWide = static_cast<int64>(Key.coordinate.x) * Extent;
const int64 OriginYWide = static_cast<int64>(Key.coordinate.y) * Extent;
const int64 OriginZWide = static_cast<int64>(Key.coordinate.z) * Extent;
if (OriginXWide < MIN_int32 || OriginYWide < MIN_int32 || OriginZWide < MIN_int32
|| OriginXWide + Extent > MAX_int32 || OriginYWide + Extent > MAX_int32
|| OriginZWide + Extent > MAX_int32)
{
OutFailureReason = TEXT("Core initial clipmap exceeds the supported voxel-coordinate range.");
return false;
}
const int32 OriginX = static_cast<int32>(OriginXWide);
const int32 OriginY = static_cast<int32>(OriginYWide);
const int32 OriginZ = static_cast<int32>(OriginZWide);
IncludeExtent(ClipMin.x, ClipMax.x, OriginX, Extent);
IncludeExtent(ClipMin.y, ClipMax.y, OriginY, Extent);
IncludeExtent(ClipMin.z, ClipMax.z, OriginZ, Extent);
@@ -7770,6 +7815,8 @@ void AVoxelWorld::FallBackToLegacyAfterCoreInitializationFailure(const FString&
}
VoxelForgeCoreWorldAssemblyJson.Reset();
VoxelForgeCoreWorldAssemblyHash.Reset();
VoxelForgeCoreResolvedLayoutJson.Reset();
VoxelForgeCoreResolvedLayoutIdentity.Reset();
bUseVoxelForgeCore = false;
// The Core adapter sets this before validation; legacy generation must not inherit it.
// L'adaptateur Core l'active avant validation ; le générateur historique ne doit pas en hériter.
@@ -9914,6 +9961,9 @@ bool AVoxelWorld::ApplyTileResult(FChunkResult& Result)
Sample.QueueWaitSeconds = WorkerQueueSeconds + ResultQueueSeconds;
Sample.WorkerTaskSeconds = WorkerTaskSeconds;
Sample.GenerationSeconds = GenerationSeconds;
Sample.CoreGenerateSeconds = Result.CoreGenerateSeconds;
Sample.MeshCopySeconds = Result.MeshCopySeconds;
Sample.StreamBuildSeconds = Result.StreamBuildSeconds;
Sample.ClassifySeconds = Result.ClassifySeconds;
Sample.MeshSeconds = Result.MeshSeconds;
Sample.StreamSeconds = Result.StreamSeconds;
@@ -10074,8 +10124,11 @@ void AVoxelWorld::SyncRemeshTile(const FVoxelTileKey& Tile)
{
BandChunkLo = MeshBandChunkLo;
BandChunkHi = MeshBandChunkHi;
BandVoxLo = MeshBandChunkLo * CHUNK_SIZE;
BandVoxHi = (MeshBandChunkHi + 1) * CHUNK_SIZE - 1;
BandVoxLo = static_cast<int32>(FMath::Clamp<int64>(
static_cast<int64>(MeshBandChunkLo) * CHUNK_SIZE, MIN_int32, MAX_int32));
BandVoxHi = static_cast<int32>(FMath::Clamp<int64>(
(static_cast<int64>(MeshBandChunkHi) + 1) * CHUNK_SIZE - 1,
MIN_int32, MAX_int32));
}
FChunkResult Result;
@@ -10238,7 +10291,7 @@ void AVoxelWorld::ReconcileReadyTransitionVisibility()
ReadyToHide.Reserve(TileComponents.Num());
auto GetAncestorAtLevel = [](const FVoxelTileKey& Tile, int32 AncestorLevel)
{
const int32 Divisor = 1 << (AncestorLevel - Tile.Level);
const int32 Divisor = VF_ClipLevelScale(AncestorLevel - Tile.Level);
return FVoxelTileKey(VoxelClipmapDesiredTiles::FloorDiv(Tile.Coord, Divisor), AncestorLevel);
};
for (const TPair<FVoxelTileKey, URealtimeMeshComponent*>& Pair : TileComponents)
@@ -10361,7 +10414,10 @@ void AVoxelWorld::ReconcileReadyTransitionVisibility()
// Integer floor-division (correct for negatives), scalar + vector.
static FORCEINLINE int32 VF_FloorDiv(int32 V, int32 D)
{
return V >= 0 ? (V / D) : -(((-V) + D - 1) / D);
if (D <= 0) { return 0; }
const int32 Quotient = V / D;
const int32 Remainder = V % D;
return Remainder < 0 ? Quotient - 1 : Quotient;
}
static FORCEINLINE FIntVector VF_FloorDiv(const FIntVector& V, int32 D)
{
@@ -11441,13 +11497,13 @@ bool AVoxelWorld::IsTileInClipRange(const FVoxelTileKey& Tile, const FIntVector&
{
VF_OuterShell(Settings, R, MaxLevelBase, MaxLevel, ROuter);
}
const int32 PowMax = 1 << MaxLevel;
const int32 PowMax = VF_ClipLevelScale(MaxLevel);
const FIntVector CMax = VF_FloorDiv(Center, PowMax);
FVector CV;
if (bUseVoxelForgeCore)
{
const double TileExtentVoxels = static_cast<double>(CHUNK_SIZE << Tile.Level);
const double TileExtentVoxels = static_cast<double>(VF_ClipLevelExtentVoxels(Tile.Level));
CV = FVector(
(static_cast<double>(Tile.Coord.X) + 0.5) * TileExtentVoxels,
(static_cast<double>(Tile.Coord.Y) + 0.5) * TileExtentVoxels,
@@ -11463,9 +11519,9 @@ bool AVoxelWorld::IsTileInClipRange(const FVoxelTileKey& Tile, const FIntVector&
VF_FloorDiv(FMath::FloorToInt(CV.Y), SizeMax),
VF_FloorDiv(FMath::FloorToInt(CV.Z), SizeMax));
return FMath::Abs(TMax.X - CMax.X) <= ROuter
&& FMath::Abs(TMax.Y - CMax.Y) <= ROuter
&& FMath::Abs(TMax.Z - CMax.Z) <= ROuter;
return FMath::Abs(static_cast<int64>(TMax.X) - CMax.X) <= ROuter
&& FMath::Abs(static_cast<int64>(TMax.Y) - CMax.Y) <= ROuter
&& FMath::Abs(static_cast<int64>(TMax.Z) - CMax.Z) <= ROuter;
}
int32 AVoxelWorld::GetMaxConcurrentTasks() const
@@ -11818,11 +11874,10 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
}
}
const int32 MaxTasks = GetMaxConcurrentStreamingTasks();
// Keep at most one worker-set of ready work so an admission pass can feed idle slots
// without letting far work consume the near reserve. / Garder une file d'un ensemble
// de workers pour nourrir les slots sans consommer la reserve proche.
// Keep several bounded worker-sets ready so frame-paced admission can feed the
// background lane continuously without moving mesh application onto the game thread.
const int32 ReadyQueueDepth = FMath::Clamp(
GVoxelForgeStreamingReadyQueueDepth, 0, MaxTasks);
GVoxelForgeStreamingReadyQueueDepth, 0, FMath::Min(64, FMath::Max(1, MaxTasks) * 4));
const int32 TotalAdmissionCap = MaxTasks + ReadyQueueDepth;
const int32 AuthoredNearTaskReserve = GetNearTaskReserve(MaxTasks);
const FIntVector CenterChunk = GetTileCoordAtLocalCm(WorldToLocalCm(CenterPosition));
@@ -12058,7 +12113,7 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
LevelSummaries.Num(), Leavers.Num(), CollisionOnlyTiles.Num()));
for (const FDesiredLevelSummary& Summary : LevelSummaries)
{
const int32 ExtentVoxels = CHUNK_SIZE << Summary.Level;
const int32 ExtentVoxels = VF_ClipLevelExtentVoxels(Summary.Level);
const double TileExtentMetres = static_cast<double>(ExtentVoxels) * VOXEL_SIZE / 100.0;
const double SpanX = static_cast<double>(Summary.Max.X - Summary.Min.X + 1) * TileExtentMetres;
const double SpanY = static_cast<double>(Summary.Max.Y - Summary.Min.Y + 1) * TileExtentMetres;
@@ -12109,8 +12164,9 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
for (const FVoxelTileKey& T : LoadedTiles)
{
if (T.Level < CutMin) continue;
const int32 CLo = T.Coord.Z << T.Level;
const int32 CHi = ((T.Coord.Z + 1) << T.Level) - 1;
const int64 LevelScale = VF_ClipLevelScale(T.Level);
const int64 CLo = static_cast<int64>(T.Coord.Z) * LevelScale;
const int64 CHi = (static_cast<int64>(T.Coord.Z) + 1) * LevelScale - 1;
const bool bInOld = CLo >= MeshBandChunkLo && CHi <= MeshBandChunkHi;
const bool bInNew = CLo >= NewLo && CHi <= NewHi;
const bool bOutOld = CHi < MeshBandChunkLo || CLo > MeshBandChunkHi;
@@ -12330,7 +12386,7 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
}
Heading = Heading.GetSafeNormal();
const FVector PlayerLocalCm = WorldToLocalCm(CenterPosition);
const double TileSizeCm = static_cast<double>(CHUNK_SIZE << Level)
const double TileSizeCm = static_cast<double>(VF_ClipLevelExtentVoxels(Level))
* GetActiveVoxelSizeCm();
LevelTiles.Sort([&](const FVoxelTileKey& A, const FVoxelTileKey& B)
{
@@ -12731,7 +12787,7 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
const int32 MaxTasks = GetMaxConcurrentStreamingTasks(); // Core and worker lane clamped. / Core et voie worker plafonnes.
const int32 ReadyQueueDepth = FMath::Clamp(
GVoxelForgeStreamingReadyQueueDepth, 0, MaxTasks);
GVoxelForgeStreamingReadyQueueDepth, 0, FMath::Min(64, FMath::Max(1, MaxTasks) * 4));
const int32 TotalAdmissionCap = MaxTasks + ReadyQueueDepth;
const bool bNearPriority = WorkPriority != EVoxelTileWorkPriority::Horizon;
const int32 NearReserve = EffectiveNearTaskReserve >= 0
@@ -12788,7 +12844,7 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
const int32 FullRes = GVoxelForgeTestFullResClipLevels >= 0
? FMath::Clamp(GVoxelForgeTestFullResClipLevels, 1, 9)
: (Settings ? FMath::Max(1, Settings->FullResClipLevels) : 2);
const int32 Extent = CHUNK_SIZE << Tile.Level;
const int32 Extent = VF_ClipLevelExtentVoxels(Tile.Level);
const int32 Cells = (Tile.Level < FullRes)
? CHUNK_SIZE
@@ -12853,7 +12909,9 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
// et mélanges de matériaux qu'avant la coupe) — la tuile ne peut rendre que des artefacts
// ⇒ on n'émet RIEN. Résultat vide via ProcessQueue (bookkeeping normal : PendingTiles,
// LoadedTiles, epoch) ; jamais figé — le changement de bande re-queue via BandRemeshQueue.
if (Step > (BandChunkHi - BandChunkLo + 1) * CHUNK_SIZE)
const int64 BandHeightVoxels =
(static_cast<int64>(BandChunkHi) - BandChunkLo + 1) * CHUNK_SIZE;
if (static_cast<int64>(Step) > BandHeightVoxels)
{
FChunkResult Empty;
Empty.Tile = Tile;
@@ -13072,7 +13130,7 @@ void AVoxelWorld::LoadCoreTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority
const int32 MaxTasks = GetMaxConcurrentStreamingTasks();
const int32 ReadyQueueDepth = FMath::Clamp(
GVoxelForgeStreamingReadyQueueDepth, 0, MaxTasks);
GVoxelForgeStreamingReadyQueueDepth, 0, FMath::Min(64, FMath::Max(1, MaxTasks) * 4));
const int32 TotalAdmissionCap = MaxTasks + ReadyQueueDepth;
const bool bNearPriority = WorkPriority != EVoxelTileWorkPriority::Horizon;
const int32 NearReserve = EffectiveNearTaskReserve >= 0
@@ -13180,6 +13238,11 @@ void AVoxelWorld::LoadCoreTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority
Result.WorkPriority = WorkPriority;
Result.GenerationStartCycles = FPlatformTime::Cycles64();
Result.GenerationOrdinal = NextStreamingGenerationOrdinal.fetch_add(1, std::memory_order_relaxed) + 1;
#if PLATFORM_LINUX
const bool bMeasureWorkerPhases = VoxelForgeStartupTrace::IsActive();
#else
constexpr bool bMeasureWorkerPhases = false;
#endif
auto EnqueueObsolete = [&]()
{
@@ -13235,7 +13298,14 @@ void AVoxelWorld::LoadCoreTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority
FReadScopeLock Lock(State->WorldLock);
if (State->World)
{
const uint64 CoreGenerateStartCycles = bMeasureWorkerPhases
? FPlatformTime::Cycles64() : 0;
CoreMesh = State->World->generateTile(CoreKey, Options, CoreError);
if (CoreGenerateStartCycles != 0)
{
Result.CoreGenerateSeconds = FPlatformTime::ToSeconds64(
FPlatformTime::Cycles64() - CoreGenerateStartCycles);
}
}
}
Result.GenerationEndCycles = FPlatformTime::Cycles64();
@@ -13277,6 +13347,8 @@ void AVoxelWorld::LoadCoreTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority
if (!Result.bEmpty)
{
const uint64 MeshCopyStartCycles = bMeasureWorkerPhases
? FPlatformTime::Cycles64() : 0;
FVoxelMeshData MeshData;
MeshData.Vertices.Reserve(Result.NumVertices);
MeshData.Normals.Reserve(Result.NumVertices);
@@ -13297,8 +13369,20 @@ void AVoxelWorld::LoadCoreTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority
MeshData.NumSurfaceVertices = static_cast<int32>(CoreMesh->groups.surfaceVertexCount);
MeshData.NumCeilingTriangles = Result.NumCeilingTriangles;
MeshData.NumSkirtTriangles = Result.NumSkirtTriangles;
if (MeshCopyStartCycles != 0)
{
Result.MeshCopySeconds = FPlatformTime::ToSeconds64(
FPlatformTime::Cycles64() - MeshCopyStartCycles);
}
const uint64 StreamBuildStartCycles = bMeasureWorkerPhases
? FPlatformTime::Cycles64() : 0;
Result.Streams = MakeShared<RealtimeMesh::FRealtimeMeshStreamSet>();
BuildTileStreamSet(*Result.Streams, MeshData);
if (StreamBuildStartCycles != 0)
{
Result.StreamBuildSeconds = FPlatformTime::ToSeconds64(
FPlatformTime::Cycles64() - StreamBuildStartCycles);
}
}
Result.GenerationEndCycles = FPlatformTime::Cycles64();
if (Cancellation->bObsolete.load(std::memory_order_acquire)
@@ -13988,7 +14072,8 @@ bool AVoxelWorld::ApplyMeshToTile(FChunkResult& Result)
// Convertir les bornes physiques de tuile vers les strates owner, y compris les rapports fractionnaires.
const double OwnerChunksPerCoreTile = static_cast<double>(GetActiveVoxelSizeCm())
/ VOXEL_SIZE;
const double TileExtentCoreVoxels = static_cast<double>(CHUNK_SIZE << Tile.Level);
const double TileExtentCoreVoxels =
static_cast<double>(VF_ClipLevelExtentVoxels(Tile.Level));
const int32 MinOwnerChunkX = FMath::FloorToInt(
Tile.Coord.X * TileExtentCoreVoxels * OwnerChunksPerCoreTile / CHUNK_SIZE);
const int32 MinOwnerChunkY = FMath::FloorToInt(
@@ -15084,6 +15169,7 @@ FString AVoxelWorld::GetCurrentSeasonContentHash() const
}
FString AssemblyHash = VoxelForgeCoreWorldAssemblyHash;
FString ResolvedLayoutIdentity = VoxelForgeCoreResolvedLayoutIdentity;
if (AssemblyHash.IsEmpty())
{
FString AssemblyJson;
@@ -15120,12 +15206,17 @@ FString AVoxelWorld::GetCurrentSeasonContentHash() const
AssemblyHash = FString::Printf(TEXT("%016llx"),
static_cast<unsigned long long>(Assembly->contentHash));
}
if (const VoxelForgeCore::ResolvedLayoutSnapshotV3* ResolvedLayout = Snapshot->resolvedLayout())
{
ResolvedLayoutIdentity = UTF8_TO_TCHAR(ResolvedLayout->stableIdentity.c_str());
}
}
}
const bool bHybridMesher = CoreMesherRecipe != EVoxelForgeCoreMesherRecipe::MarchingCubesV1;
const uint32 CoreVoxelSizeMicrons = static_cast<uint32>(
FMath::RoundToInt(GetActiveVoxelSizeCm() * 10000.0f));
if (AssemblyHash.IsEmpty() && !bHybridMesher && CoreVoxelSizeMicrons == 250000)
if (AssemblyHash.IsEmpty() && ResolvedLayoutIdentity.IsEmpty()
&& !bHybridMesher && CoreVoxelSizeMicrons == 250000)
{
return SeasonHash;
}
@@ -15150,6 +15241,11 @@ FString AVoxelWorld::GetCurrentSeasonContentHash() const
Payload += TEXT("|world_assembly=");
Payload += AssemblyHash;
}
if (!ResolvedLayoutIdentity.IsEmpty())
{
Payload += TEXT("|resolved_layout=");
Payload += ResolvedLayoutIdentity;
}
if (CoreVoxelSizeMicrons != 250000)
{
Payload += FString::Printf(TEXT("|core_voxel_size_microns=%u"), CoreVoxelSizeMicrons);
@@ -15180,7 +15276,7 @@ void AVoxelWorld::RemeshDirtyChunks(const TArray<FIntVector>& DirtyCoords, const
// BackgroundHigh; the old component remains visible until its post-edit replacement is applied.
auto TileCoversDirtyCoord = [&DirtyCoords](const FVoxelTileKey& Tile) -> bool
{
const int32 Span = 1 << Tile.Level;
const int32 Span = VF_ClipLevelScale(Tile.Level);
const FIntVector MinChunk = Tile.Coord * Span;
const FIntVector MaxChunk = MinChunk + FIntVector(Span, Span, Span);
for (const FIntVector& Coord : DirtyCoords)
+20 -9
View File
@@ -261,8 +261,12 @@ namespace VoxelHash
FORCEINLINE uint32 Cell(int32 CellX, int32 CellY, uint32 Seed)
{
uint32 H = Seed;
H ^= Mix((uint32)(CellX + 0x7FFFFFFF));
H ^= Mix((uint32)(CellY + 0x7FFFFFFF) * 2654435761u);
// Bias in unsigned arithmetic. Adding the signed cell coordinate first overflows for
// positive cells and makes the world hash compiler-dependent.
const uint32 BiasedX = static_cast<uint32>(CellX) + 0x7FFFFFFFu;
const uint32 BiasedY = static_cast<uint32>(CellY) + 0x7FFFFFFFu;
H ^= Mix(BiasedX);
H ^= Mix(BiasedY * 2654435761u);
return Mix(H);
}
@@ -274,10 +278,14 @@ namespace VoxelHash
int32 MinX = FMath::Min(AX, BX), MinY = FMath::Min(AY, BY);
int32 MaxX = FMath::Max(AX, BX), MaxY = FMath::Max(AY, BY);
uint32 H = Seed ^ 0xDEADBEEF;
H ^= Mix((uint32)(MinX + 0x7FFFFFFF));
H ^= Mix((uint32)(MinY + 0x7FFFFFFF) * 2654435761u);
H ^= Mix((uint32)(MaxX + 0x7FFFFFFF) * 374761393u);
H ^= Mix((uint32)(MaxY + 0x7FFFFFFF) * 668265263u);
const uint32 BiasedMinX = static_cast<uint32>(MinX) + 0x7FFFFFFFu;
const uint32 BiasedMinY = static_cast<uint32>(MinY) + 0x7FFFFFFFu;
const uint32 BiasedMaxX = static_cast<uint32>(MaxX) + 0x7FFFFFFFu;
const uint32 BiasedMaxY = static_cast<uint32>(MaxY) + 0x7FFFFFFFu;
H ^= Mix(BiasedMinX);
H ^= Mix(BiasedMinY * 2654435761u);
H ^= Mix(BiasedMaxX * 374761393u);
H ^= Mix(BiasedMaxY * 668265263u);
return Mix(H);
}
@@ -477,10 +485,13 @@ namespace VoxelNoise
// Hash the neighbor cell to get a feature point position [0,1)
// Using three different hash mixes for X, Y, Z offsets
const uint32 BiasedX = static_cast<uint32>(NX) + 0x7FFFFFFFu;
const uint32 BiasedY = static_cast<uint32>(NY) + 0x7FFFFFFFu;
const uint32 BiasedZ = static_cast<uint32>(NZ) + 0x7FFFFFFFu;
uint32 H = VoxelHash::Mix(
(uint32)(NX + 0x7FFFFFFF)
^ VoxelHash::Mix((uint32)(NY + 0x7FFFFFFF) * 2654435761u)
^ VoxelHash::Mix((uint32)(NZ + 0x7FFFFFFF) * 374761393u)
BiasedX
^ VoxelHash::Mix(BiasedY * 2654435761u)
^ VoxelHash::Mix(BiasedZ * 374761393u)
);
float FPX = (float)DX + VoxelHash::ToFloat01(H) - FracX;
@@ -340,6 +340,14 @@ private:
float SuppressRadiusWorld = 0.0f;
};
// Game-thread lookup copied from the immutable resolved Core layout.
// Recherche game-thread copiee depuis la disposition Core resolue et immuable.
struct FResolvedCoreGeometryAnchor
{
int32 StrateIndex = INDEX_NONE;
FVector LocalPositionCm = FVector::ZeroVector;
};
/** WORKER-THREAD surface find → fills OutSpawns for one cell. SurfaceWorld uses the height oracle
* (cheap, O(1)/column); other archetypes ray-march the density column. No UObject access except
* Generator (thread-safe). Determinism-critical. Resolves the dominant biome PER COLUMN
@@ -436,6 +444,7 @@ private:
// (kept until the cell leaves range so the surface-find isn't repeated). Strate-bounded.
// Keyed by FIntVector(cellX|passageIdx, cellY|side, entryIndex) — lattice cell OR passage endpoint.
TMap<FIntVector, FLandmarkInstance> LandmarkInstances;
TMap<FString, FResolvedCoreGeometryAnchor> CoreGeometryAnchors;
int32 LastLandmarkStrate = INT32_MIN; // strate change → wipe + rebuild landmarks
// Set in BeginDestroy; worker tasks check it before touching us.
+10 -6
View File
@@ -21,6 +21,8 @@ class UVoxelSettings : public UPrimaryDataAsset
GENERATED_BODY()
public:
virtual void Serialize(FArchive& Ar) override;
virtual void PostLoad() override;
/** Season seed/radius replace the mutable authored values only when Season is assigned. */
int32 GetEffectiveWorldSeed() const;
@@ -411,14 +413,16 @@ public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Strates", meta = (ClampMin = "1"))
int32 TotalStrates = 10;
// Vertical SEPARATION between consecutive strates, in chunks of solid bedrock.
// 0 = strates touch (just the seals between them). Higher = a thick rock layer
// between each biome that the player must dig through at (0,0), and that the
// auto-carved passages tunnel across.
// 0 → adjacent layers (default) · 1-2 → a real bedrock band · 4+ → deep separation
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Strates", meta = (ClampMin = "0", ClampMax = "16"))
// Legacy count retained so pre-metre assets still deserialize during the upgrade.
// Ancien compte conserve pour lire les assets precedant les metres.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Strates", meta = (DeprecatedProperty, DeprecationMessage = "Use InterStrateGapMeters."))
int32 InterStrateGapChunks = 0;
// Physical separation between strata; the layout remains aligned to whole layout chunks.
// Separation physique; la disposition reste alignee sur des chunks entiers.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Strates", meta = (Units = "m", ClampMin = "0", ClampMax = "1024"))
double InterStrateGapMeters = 0.0;
// Physical width of one vertical strate-layout chunk. This is independent of the
// Core tile width (32^3 voxels) and of the source generator's voxel resolution.
// 8 m preserves the owner's current SurfaceWorld extent at 25 cm Core voxels.
@@ -35,6 +35,9 @@ namespace VoxelForgeStartupTrace
double ResultQueueSeconds = 0.0;
double WorkerTaskSeconds = 0.0;
double GenerationSeconds = 0.0;
double CoreGenerateSeconds = 0.0;
double MeshCopySeconds = 0.0;
double StreamBuildSeconds = 0.0;
double ClassifySeconds = 0.0;
double MeshSeconds = 0.0;
double StreamSeconds = 0.0;
@@ -48,6 +48,7 @@ class VOXELFORGE_API UVoxelStrateDefinition : public UPrimaryDataAsset
GENERATED_BODY()
public:
virtual void Serialize(FArchive& Ar) override;
virtual void PostLoad() override;
//=========================================================================
@@ -66,14 +67,14 @@ public:
// DIMENSIONS
//=========================================================================
// How many chunks tall this strate is.
// Each strate can have a different height!
// Human-scale defaults use 16 chunks = 64 m. This gives a 20 m+ vertical budget for
// cathedral rooms, tall shafts, and the surface sky cap without changing the lateral gate.
// 1 chunk = 32 voxels x 12.5cm = 4 m; 16 chunks preserves the 64 m authored default.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Dimensions", meta = (ClampMin = "1", ClampMax = "256"))
// Legacy serialized chunk count kept for Blueprint/package compatibility during the unit migration.
// Ancien nombre de chunks conserve pour la compatibilite Blueprint/package pendant la migration.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Dimensions", meta = (DeprecatedProperty, DeprecationMessage = "Use StrateHeightMeters."))
int32 StrateHeightInChunks = 16;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Dimensions", meta = (Units = "m", ClampMin = "1", ClampMax = "2048"))
double StrateHeightMeters = 128.0;
//=========================================================================
// BOUNDARY TRANSITION
//=========================================================================
@@ -99,9 +100,13 @@ public:
// 2 → moderate (good default)
// 4 → wide, gentle blend across many chunks
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Boundary",
meta = (ClampMin = "1", ClampMax = "8", EditCondition = "TransitionType != EVoxelStrateTransition::Hard"))
meta = (DeprecatedProperty, DeprecationMessage = "Use TransitionWidthMeters."))
int32 TransitionBlendChunks = 2;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Boundary",
meta = (Units = "m", ClampMin = "0", ClampMax = "512", EditCondition = "TransitionType != EVoxelStrateTransition::Hard"))
double TransitionWidthMeters = 16.0;
//=========================================================================
// GENERATOR TYPE
//=========================================================================
@@ -115,6 +120,11 @@ public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Generation")
ECaveGeneratorType GeneratorType = ECaveGeneratorType::TunnelNetwork;
// Optional native Core authoring; leaving it empty keeps the established generator path.
// Edition Core native facultative; laissez vide pour garder le generateur existant.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Core Geometry")
FVoxelForgeCoreGeometry CoreGeometry;
/**
* Authoritative per-strate worm switch. Existing assets default to enabled. The manager
* resolves a disabled strate to WormStrength == 0 before any fused, stack, composer, or
+207
View File
@@ -200,6 +200,208 @@ enum class ECaveGeneratorType : uint8
Underwater UMETA(DisplayName = "Underwater (flooded cavern system)"),
};
// Optional native-Core recipe selected independently for one strate.
// Recette Core native facultative, choisie independamment pour chaque strate.
UENUM(BlueprintType)
enum class EVoxelForgeCoreGeneratorRecipe : uint8
{
None UMETA(DisplayName = "Off (legacy recipe)"),
NativeCaves UMETA(DisplayName = "Native caves"),
NativeLattice UMETA(DisplayName = "Native lattice"),
ProfiledHeightfield UMETA(DisplayName = "Profiled heightfield")
};
UENUM(BlueprintType)
enum class EVoxelForgeCoreHeightfieldProfile : uint8
{
PeriodicHills,
MirroredRelief,
FlatField
};
UENUM(BlueprintType)
enum class EVoxelForgeCoreStampKind : uint8
{
CarveEllipsoid,
CarveArch,
FillPillar,
FillDais,
FillMonolith,
FillPlatform
};
// Stable authored volume stamp. All distances and positions are metres at the asset boundary.
// Volume stable, defini par son auteur. Toutes les positions et distances sont en metres.
USTRUCT(BlueprintType)
struct VOXELFORGE_API FVoxelForgeCoreStampDefinition
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry")
FString StableName;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry")
EVoxelForgeCoreStampKind Kind = EVoxelForgeCoreStampKind::CarveEllipsoid;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry", meta = (Units = "m"))
FVector AnchorPositionMeters = FVector::ZeroVector;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry", meta = (Units = "m", ClampMin = "0"))
double PlacementRadiusMeters = 0.0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry", meta = (Units = "m", ClampMin = "0.5"))
FVector HalfExtentsMeters = FVector(2.0, 2.0, 2.0);
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry", meta = (Units = "m", ClampMin = "0"))
double SmoothWidthMeters = 0.0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry", meta = (Units = "m", ClampMin = "0"))
double IrregularityAmplitudeMeters = 0.0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry", meta = (Units = "m", ClampMin = "0"))
double IrregularityWavelengthMeters = 0.0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry", meta = (Units = "m", ClampMin = "0"))
double RoundingRadiusMeters = 0.5;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry", meta = (Units = "m", ClampMin = "0"))
double MinimumSpacingMeters = 0.0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry", meta = (ClampMin = "0", ClampMax = "3"))
uint8 YawQuarterTurns = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry")
bool bAttachToFloor = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry", meta = (Units = "m"))
double FloorMeters = 0.0;
};
// Authored cavern chamber, in local world metres.
// Salle cavernicole authored, en metres locaux du monde.
USTRUCT(BlueprintType)
struct VOXELFORGE_API FVoxelForgeCoreRoomDefinition
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry")
FString StableName;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry", meta = (Units = "m"))
FVector CenterMeters = FVector::ZeroVector;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry", meta = (Units = "m", ClampMin = "0.5"))
FVector HalfExtentsMeters = FVector(8.0, 8.0, 4.0);
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry", meta = (Units = "m"))
double FloorMeters = 0.0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry", meta = (Units = "m", ClampMin = "0"))
double SmoothWidthMeters = 0.0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry", meta = (Units = "m", ClampMin = "0"))
double UnionWidthMeters = 0.0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry", meta = (Units = "m", ClampMin = "0"))
double IrregularityAmplitudeMeters = 0.0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry", meta = (Units = "m", ClampMin = "0"))
double IrregularityWavelengthMeters = 0.0;
};
// Authored cubic tunnel between a stable start anchor and a stable room name.
// Tunnel cubique authored entre une ancre stable et une salle nommee.
USTRUCT(BlueprintType)
struct VOXELFORGE_API FVoxelForgeCoreTunnelDefinition
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry")
FString StableName;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry")
FString StartAnchorStableName;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry")
FString EndRoomStableName;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry", meta = (Units = "m"))
FVector ControlPoint0Meters = FVector::ZeroVector;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry", meta = (Units = "m"))
FVector ControlPoint1Meters = FVector::ZeroVector;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry", meta = (Units = "m"))
FVector ControlPoint2Meters = FVector::ZeroVector;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry", meta = (Units = "m"))
FVector ControlPoint3Meters = FVector::ZeroVector;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry", meta = (Units = "m", ClampMin = "0.5"))
double BoreRadiusMeters = 2.0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry", meta = (Units = "m", ClampMin = "1"))
double BoreHeightMeters = 4.0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry", meta = (Units = "m", ClampMin = "0"))
double FloorSupportDepthMeters = 0.5;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry", meta = (Units = "m", ClampMin = "0"))
double SmoothWidthMeters = 0.0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry", meta = (Units = "m", ClampMin = "0"))
double UnionWidthMeters = 0.0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry", meta = (Units = "m", ClampMin = "0"))
double IrregularityAmplitudeMeters = 0.0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry", meta = (Units = "m", ClampMin = "0"))
double IrregularityWavelengthMeters = 0.0;
// Lattice tunnels require an explicit bounded polyline; cave tunnels use the cubic curve.
// Les tunnels en treillis exigent une polyligne bornee; les tunnels caves utilisent la courbe cubique.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry")
TArray<FVector> PolylineMeters;
};
// Optional Core authored recipe. Empty arrays and None preserve the prior asset output.
// Recette Core authored facultative. None et les tableaux vides conservent la sortie historique.
USTRUCT(BlueprintType)
struct VOXELFORGE_API FVoxelForgeCoreGeometry
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry")
EVoxelForgeCoreGeneratorRecipe GeneratorRecipe = EVoxelForgeCoreGeneratorRecipe::None;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry")
EVoxelForgeCoreHeightfieldProfile HeightfieldProfile = EVoxelForgeCoreHeightfieldProfile::PeriodicHills;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry|Heightfield", meta = (Units = "m"))
double HeightfieldBaseMeters = 0.0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry|Heightfield", meta = (Units = "m"))
FVector2D HeightfieldAmplitudeMeters = FVector2D(20.0, 20.0);
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry|Heightfield", meta = (Units = "m", ClampMin = "0"))
FVector2D HeightfieldWavelengthMeters = FVector2D(256.0, 256.0);
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry|Heightfield", meta = (ClampMin = "0.01", ClampMax = "4.0"))
double MirrorLambda = 1.0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry|Heightfield", meta = (Units = "m", ClampMin = "0"))
double MinimumArenaClearanceMeters = 0.0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry")
TArray<FVoxelForgeCoreStampDefinition> Stamps;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry")
TArray<FVoxelForgeCoreRoomDefinition> Rooms;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Core Geometry")
TArray<FVoxelForgeCoreTunnelDefinition> Tunnels;
};
/**
* EVoxelStrateTransition — How two adjacent strates meet at their shared boundary.
*
@@ -2289,6 +2491,11 @@ struct VOXELFORGE_API FStrateLandmark
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Landmark")
ELandmarkAnchor AnchorMode = ELandmarkAnchor::HashLattice;
// Optional stable Core feature binding; an empty value keeps lattice/mouth placement unchanged.
// Liaison facultative vers une ancre Core stable; vide, le placement existant reste identique.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Landmark|Core Geometry")
FString CoreGeometryStableName;
// ----- Hash lattice (AnchorMode == HashLattice — scattered placement; cheap at any radius) -----
// Average spacing between instances, IN CHUNKS = the lattice cell size (one candidate per cell, so cost
+21 -4
View File
@@ -332,16 +332,33 @@ struct FVoxelTileKey
FVoxelTileKey(const FIntVector& InCoord, int32 InLevel) : Coord(InCoord), Level(InLevel) {}
// Cell size (sampling step) in voxels, and tile extent in voxels per axis.
int32 StepVoxels() const { return 1 << Level; }
int32 ExtentVoxels() const { return CHUNK_SIZE << Level; }
int32 StepVoxels() const
{
const uint32 SafeLevel = static_cast<uint32>(FMath::Clamp(Level, 0, 8));
return static_cast<int32>(uint32{1} << SafeLevel);
}
int32 ExtentVoxels() const
{
const uint32 SafeLevel = static_cast<uint32>(FMath::Clamp(Level, 0, 8));
return static_cast<int32>(static_cast<uint32>(CHUNK_SIZE) * (uint32{1} << SafeLevel));
}
// Min-corner of the tile in VOXEL coords (what the mesher takes).
FIntVector OriginVoxels() const { return Coord * (CHUNK_SIZE << Level); }
FIntVector OriginVoxels() const
{
const int64 Extent = ExtentVoxels();
const auto ScaleAxis = [Extent](int32 Axis)
{
return static_cast<int32>(FMath::Clamp<int64>(
static_cast<int64>(Axis) * Extent, MIN_int32, MAX_int32));
};
return FIntVector(ScaleAxis(Coord.X), ScaleAxis(Coord.Y), ScaleAxis(Coord.Z));
}
// Tile centre in world cm (for distance sorting / LOD selection).
FVector CenterCm() const
{
const double Ext = (double)(CHUNK_SIZE << Level) * (double)VOXEL_SIZE;
const double Ext = static_cast<double>(ExtentVoxels()) * static_cast<double>(VOXEL_SIZE);
return FVector((Coord.X + 0.5) * Ext, (Coord.Y + 0.5) * Ext, (Coord.Z + 0.5) * Ext);
}
+7
View File
@@ -145,6 +145,10 @@ struct FChunkResult
// per-tile profiling remains opt-in.
uint64 GenerationStartCycles = 0;
uint64 GenerationEndCycles = 0;
// Optional phase timings recorded only while the bounded startup trace is active.
double CoreGenerateSeconds = 0.0;
double MeshCopySeconds = 0.0;
double StreamBuildSeconds = 0.0;
uint64 ApplyStartCycles = 0;
EVoxelTileWorkPriority WorkPriority = EVoxelTileWorkPriority::Horizon;
// Ordinals make admitted order and actual worker start order auditable across MPSC results.
@@ -614,6 +618,7 @@ public:
FVector GetVoxelForgeCoreDefaultCenterWorld() const;
bool QueryVoxelForgeCoreDensityAt(FVector WorldLocation, float& OutDensity) const;
bool GetVoxelForgeCoreWorldAssembly(FString& OutCanonicalJson, FString& OutContentHash) const;
bool GetVoxelForgeCoreResolvedLayout(FString& OutCanonicalJson, FString& OutStableIdentity) const;
UFUNCTION(BlueprintPure, Category = "Voxel World|Strate")
int32 GetStrateAtPosition(FVector WorldPosition) const;
@@ -778,6 +783,8 @@ private:
TSharedPtr<FVoxelForgeCoreRuntimeState, ESPMode::ThreadSafe> VoxelForgeCoreState;
FString VoxelForgeCoreWorldAssemblyJson;
FString VoxelForgeCoreWorldAssemblyHash;
FString VoxelForgeCoreResolvedLayoutJson;
FString VoxelForgeCoreResolvedLayoutIdentity;
FVector CoreFallbackCenterLocalCm = FVector::ZeroVector;
bool bCoreRegionCompilePending = false;
TArray<FVoxelTileKey> PendingCoreRegionTiles;
+7
View File
@@ -7,6 +7,13 @@ public class VoxelForge : ModuleRules
{
public VoxelForge(ReadOnlyTargetRules Target) : base(Target)
{
if (Target.Platform == UnrealTargetPlatform.Linux)
{
// Test translation units have independent anonymous-namespace helpers that collide in
// Linux Unity batches. Keep Linux compilation one source per TU; Windows remains unchanged.
bUseUnity = false;
}
// PCH = Precompiled Headers - speeds up compilation
// UseExplicitOrSharedPCHs is the modern recommended setting
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
@@ -39,6 +39,13 @@ public class VoxelForgeCore : ModuleRules
PublicDependencyModuleNames.Add("Core");
CppStandard = CppStandardVersion.Cpp20;
if (Target.Platform == UnrealTargetPlatform.Linux)
{
// The compatibility include directory contains Unreal-shaped placeholders. On Linux,
// let generated module TUs pass those names through to UE while Core shims opt in.
PrivateDefinitions.Add("VFCORE_COMPAT_FORWARD_HEADERS=1");
MinCpuArchX64 = MinimumCpuArchitectureX64.AVX2;
}
// Canonical Standalone code predates UE's stricter warning defaults; keep this narrow to legal local shadowing.
CppCompileWarningSettings.ShadowVariableWarningLevel = WarningLevel.Off;
FPSemantics = FPSemanticsMode.Precise;
@@ -878,6 +878,8 @@ struct FExploreArguments
bool bPassageFloorWritesOverride = false;
int32 PassageFloorWrites = 1;
bool bStandaloneWorldExport = false;
bool bOwnerFourStratePlan = false;
bool bSkipStandaloneCoreAdapterParity = false;
bool bCoreWorldTest = false;
bool bCoreWorldStreamTraceOnly = false;
bool bRenderCameraOverride = false;
@@ -922,6 +924,12 @@ bool ParseArguments(const FString& Params, FExploreArguments& OutArguments, FStr
int32 StandaloneWorldExport = 0;
FParse::Value(*Params, TEXT("standaloneworldexport="), StandaloneWorldExport);
OutArguments.bStandaloneWorldExport = StandaloneWorldExport != 0;
int32 OwnerFourStratePlan = 0;
FParse::Value(*Params, TEXT("ownerfourstrateplan="), OwnerFourStratePlan);
OutArguments.bOwnerFourStratePlan = OwnerFourStratePlan != 0;
int32 SkipStandaloneCoreAdapterParity = 0;
FParse::Value(*Params, TEXT("skipstandalonecoreadapterparity="), SkipStandaloneCoreAdapterParity);
OutArguments.bSkipStandaloneCoreAdapterParity = SkipStandaloneCoreAdapterParity != 0;
int32 CoreWorldTest = 0;
FParse::Value(*Params, TEXT("coreworldtest="), CoreWorldTest);
OutArguments.bCoreWorldTest = CoreWorldTest != 0;
@@ -1266,12 +1274,11 @@ bool ParseArguments(const FString& Params, FExploreArguments& OutArguments, FStr
return false;
}
// The historical explorer contract needs two adjacent mouths around an interior slot. A
// parity export can instead target the game's authored single-strate layout, which has no
// such mouths and is valid for export-only mode. Build() still validates the slot against
// the loaded settings layout.
const int32 MinimumSlot = OutArguments.bGameRuntimeConfig ? 0 : 1;
const int32 MaximumSlot = OutArguments.bGameRuntimeConfig
// Passage walks need both mouths; a render/export-only generation can inspect an end slot.
// Parity exports also accept the game's authored single-strate layout.
const bool bRequiresInteriorSlot = OutArguments.bWalk;
const int32 MinimumSlot = OutArguments.bGameRuntimeConfig || !bRequiresInteriorSlot ? 0 : 1;
const int32 MaximumSlot = OutArguments.bGameRuntimeConfig || !bRequiresInteriorSlot
? MaxSyntheticStrates - 1 : MaxSyntheticStrates - 2;
if (OutArguments.Slot < MinimumSlot || OutArguments.Slot > MaximumSlot)
{
@@ -1284,8 +1291,8 @@ bool ParseArguments(const FString& Params, FExploreArguments& OutArguments, FStr
else
{
OutError = FString::Printf(
TEXT("slot must be an interior slot in [1,%d] so arrival and departure mouths exist."),
MaxSyntheticStrates - 2);
TEXT("slot must be in [0,%d]; walk mode requires an interior slot in [1,%d] for both passage mouths."),
MaxSyntheticStrates - 1, MaxSyntheticStrates - 2);
}
return false;
}
@@ -1498,6 +1505,17 @@ bool ParseArguments(const FString& Params, FExploreArguments& OutArguments, FStr
OutError = TEXT("standaloneworldexport requires modes=export and settingsref, and must omit strateref.");
return false;
}
if (OutArguments.bOwnerFourStratePlan
&& (OutArguments.SettingsReference.IsEmpty() || !OutArguments.StrateReference.IsEmpty()))
{
OutError = TEXT("ownerfourstrateplan requires settingsref and must omit strateref.");
return false;
}
if (OutArguments.bSkipStandaloneCoreAdapterParity && !OutArguments.bStandaloneWorldExport)
{
OutError = TEXT("skipstandalonecoreadapterparity requires standaloneworldexport.");
return false;
}
if (OutArguments.bCoreWorldTest
&& (OutArguments.SettingsReference.IsEmpty() || !OutArguments.StrateReference.IsEmpty()))
{
@@ -1702,6 +1720,66 @@ struct FExploreMeshBvhNode
}
};
bool ApplyOwnerFourStratePlan(UVoxelSettings* Settings,
TArray<TStrongObjectPtr<UVoxelStrateDefinition>>& OutDefinitions,
bool bDuplicateDefinitions, FString& OutError)
{
if (Settings == nullptr)
{
OutError = TEXT("The transient owner plan has no settings object.");
return false;
}
if (!Settings->Season.IsNull())
{
OutError = TEXT("The transient four-strate owner plan cannot replace an assigned season manifest.");
return false;
}
static const TCHAR* AssetPaths[] = {
TEXT("/Game/VoxelForge/DA_Strate1.DA_Strate1"),
TEXT("/Game/VoxelForge/DA_Strate2.DA_Strate2"),
TEXT("/Game/VoxelForge/DA_Strate3.DA_Strate3"),
TEXT("/Game/VoxelForge/DA_Strate4.DA_Strate4")
};
Settings->TotalStrates = UE_ARRAY_COUNT(AssetPaths);
Settings->StratePool.Reset();
Settings->FixedStrates.Reset();
OutDefinitions.Reset();
OutDefinitions.Reserve(UE_ARRAY_COUNT(AssetPaths));
for (int32 Index = 0; Index < UE_ARRAY_COUNT(AssetPaths); ++Index)
{
UVoxelStrateDefinition* AuthoredDefinition = LoadObject<UVoxelStrateDefinition>(
nullptr, AssetPaths[Index]);
if (AuthoredDefinition == nullptr)
{
OutError = FString::Printf(
TEXT("Could not load staged owner strate asset '%s'."), AssetPaths[Index]);
return false;
}
UVoxelStrateDefinition* Definition = AuthoredDefinition;
if (bDuplicateDefinitions)
{
Definition = DuplicateObject<UVoxelStrateDefinition>(
AuthoredDefinition, GetTransientPackage(),
FName(*FString::Printf(TEXT("VoxelForgeOwnerPlan_Strate_%d"), Index)));
if (Definition == nullptr)
{
OutError = FString::Printf(
TEXT("Could not make a transient copy of owner strate asset '%s'."), AssetPaths[Index]);
return false;
}
}
Settings->FixedStrates.Add(Index,
TSoftObjectPtr<UVoxelStrateDefinition>(Definition));
OutDefinitions.Add(TStrongObjectPtr<UVoxelStrateDefinition>(Definition));
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeOwnerPlan] transient fixed slots=4 assets=DA_Strate1,DA_Strate2,DA_Strate3,DA_Strate4 settings_asset_untouched=1"));
return true;
}
struct FExploreWorld
{
TStrongObjectPtr<UVoxelSettings> Settings;
@@ -1834,7 +1912,7 @@ struct FExploreWorld
Settings->InterStrateGapChunks, Settings->SkirtCells);
}
const int32 NumStrates = bUseAuthoredSettings
const int32 NumStrates = Arguments.bOwnerFourStratePlan ? 4 : bUseAuthoredSettings
? (Arguments.bGameRuntimeConfig
? FMath::Max(Settings->TotalStrates, 1)
: FMath::Max(Settings->TotalStrates, Arguments.Slot + 2))
@@ -1864,10 +1942,18 @@ struct FExploreWorld
// loaded package remains untouched.
Settings->TotalStrates = NumStrates;
if (Arguments.bOwnerFourStratePlan)
{
if (!ApplyOwnerFourStratePlan(Settings.Get(), Definitions, true, OutError))
{
return false;
}
}
else
{
if (bUseAuthoredStrate)
{
// The owner’s snapshot states that every slot uses DA_Strate3. Clone it per slot so
// commandlet overrides never mutate a loaded package or leak between slots.
// Keep the historical single-asset interior probe isolated from loaded packages.
Settings->StratePool.Reset();
Settings->FixedStrates.Reset();
}
@@ -1923,6 +2009,7 @@ struct FExploreWorld
}
Definitions.Add(TStrongObjectPtr<UVoxelStrateDefinition>(Definition));
}
}
auto ApplyTargetOverrides = [&Arguments](UVoxelStrateDefinition* Definition) -> bool
{
@@ -13472,14 +13559,16 @@ bool VerifyStandaloneCoreAdapter(const FString& Json, FString& OutError)
const std::array<std::uint64_t, 4> Actual = StandaloneCoreAggregateHashes(Rows);
const std::array<std::uint64_t, 4> Expected{
0xdb2991c73215af4cull, 0x8236c5edd7fc8e4full,
0x4c48fa75a543d58eull, 0xbea22e76b34a4dfcull};
0xde345030348f2c5dull, 0xcbf0888004618b87ull,
0x7a11e28e052d69a8ull, 0x52b88607e8dbcbfdull};
const VoxelForgeCore::ShapeBuildStats Stats = CoreWorld.buildStats();
const bool bPassageWalkabilityPassed = Stats.walkSamples == 5642 && Stats.walkFailures == 0;
if (Rows.size() != 2226 || Actual != Expected || !bPassageWalkabilityPassed)
constexpr std::uint64_t ExpectedWalkSamples = 21425;
const bool bPassageWalkabilityPassed = Stats.walkSamples == ExpectedWalkSamples
&& Stats.walkFailures == 0;
if (Rows.size() != 2578 || Actual != Expected || !bPassageWalkabilityPassed)
{
OutError = FString::Printf(
TEXT("Core adapter parity failed at workers=%llu: tiles=%llu density=0x%016llx mesh=0x%016llx payload=0x%016llx topology=0x%016llx walk=%llu/%llu expected=5642/5642"),
TEXT("Core adapter parity failed at workers=%llu: tiles=%llu density=0x%016llx mesh=0x%016llx payload=0x%016llx topology=0x%016llx walk=%llu/%llu expected_walk=%llu"),
static_cast<unsigned long long>(WorkerLimit),
static_cast<unsigned long long>(Rows.size()),
static_cast<unsigned long long>(Actual[0]),
@@ -13487,12 +13576,13 @@ bool VerifyStandaloneCoreAdapter(const FString& Json, FString& OutError)
static_cast<unsigned long long>(Actual[2]),
static_cast<unsigned long long>(Actual[3]),
static_cast<unsigned long long>(Stats.walkSamples - Stats.walkFailures),
static_cast<unsigned long long>(Stats.walkSamples));
static_cast<unsigned long long>(Stats.walkSamples),
static_cast<unsigned long long>(ExpectedWalkSamples));
return false;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreAdapter] pass schema=1 workers=%llu asset_clip_radius=%d asset_max_level=%d reference_clip_radius=3 reference_max_level=8 tiles=%llu density=0x%016llx mesh=0x%016llx payload=0x%016llx topology=0x%016llx walk=5642/5642"),
TEXT("[VoxelForgeCoreAdapter] pass schema=2 workers=%llu asset_clip_radius=%d asset_max_level=%d reference_clip_radius=3 reference_max_level=8 tiles=%llu density=0x%016llx mesh=0x%016llx payload=0x%016llx topology=0x%016llx walk=%llu/%llu"),
static_cast<unsigned long long>(WorkerLimit),
OwnerClipRadius,
OwnerMaxLod,
@@ -13500,7 +13590,9 @@ bool VerifyStandaloneCoreAdapter(const FString& Json, FString& OutError)
static_cast<unsigned long long>(Actual[0]),
static_cast<unsigned long long>(Actual[1]),
static_cast<unsigned long long>(Actual[2]),
static_cast<unsigned long long>(Actual[3]));
static_cast<unsigned long long>(Actual[3]),
static_cast<unsigned long long>(Stats.walkSamples - Stats.walkFailures),
static_cast<unsigned long long>(ExpectedWalkSamples));
}
return true;
}
@@ -13515,6 +13607,24 @@ bool RunStandaloneWorldExport(const FExploreArguments& Arguments, FString& OutEr
*Arguments.SettingsReference);
return false;
}
TStrongObjectPtr<UVoxelSettings> OwnerPlanSettings;
TArray<TStrongObjectPtr<UVoxelStrateDefinition>> OwnerPlanDefinitions;
UVoxelSettings* ExportSettings = AuthoredSettings;
if (Arguments.bOwnerFourStratePlan)
{
OwnerPlanSettings = TStrongObjectPtr<UVoxelSettings>(
DuplicateObject<UVoxelSettings>(AuthoredSettings, GetTransientPackage()));
if (!OwnerPlanSettings.IsValid())
{
OutError = TEXT("Could not make a transient copy of owner settings for the four-strate plan.");
return false;
}
ExportSettings = OwnerPlanSettings.Get();
if (!ApplyOwnerFourStratePlan(ExportSettings, OwnerPlanDefinitions, false, OutError))
{
return false;
}
}
UVoxelStrateManager* Manager = NewObject<UVoxelStrateManager>(
GetTransientPackage(), NAME_None, RF_Transient);
int32 Seed = AuthoredSettings->Seed;
@@ -13526,7 +13636,7 @@ bool RunStandaloneWorldExport(const FExploreArguments& Arguments, FString& OutEr
OriginSpineRadius = Season->GetOriginSpineRadius();
WorldRadiusVoxels = Season->GetWorldRadiusVoxels();
}
if (!Manager->Initialize(AuthoredSettings, Seed))
if (!Manager->Initialize(ExportSettings, Seed))
{
OutError = TEXT("Owner settings did not produce a valid resolved strate layout.");
return false;
@@ -13534,7 +13644,7 @@ bool RunStandaloneWorldExport(const FExploreArguments& Arguments, FString& OutEr
Seed = Manager->GetWorldSeed();
UVoxelGenerator* Generator = NewObject<UVoxelGenerator>(
GetTransientPackage(), NAME_None, RF_Transient);
Generator->InitializeSettings(AuthoredSettings);
Generator->InitializeSettings(ExportSettings);
Generator->SetStrateManager(Manager);
TArray<FStandaloneExportSample> Samples;
@@ -13668,25 +13778,25 @@ bool RunStandaloneWorldExport(const FExploreArguments& Arguments, FString& OutEr
Writer->WriteValue(TEXT("source"), TEXT("VoxelForgeExplore standalone base-world export"));
Writer->WriteValue(TEXT("voxel_size_cm"), StandaloneCoreVoxelSizeCm);
Writer->WriteValue(TEXT("seed"), Seed);
Writer->WriteValue(TEXT("open_surface_entry"), AuthoredSettings->bOpenSurfaceEntry);
Writer->WriteValue(TEXT("open_surface_entry"), ExportSettings->bOpenSurfaceEntry);
Writer->WriteValue(TEXT("origin_spine_radius"), OriginSpineRadius);
Writer->WriteValue(TEXT("world_radius_voxels"), WorldRadiusVoxels);
Writer->WriteValue(TEXT("edge_seal_thickness"), AuthoredSettings->EdgeSealThickness);
Writer->WriteValue(TEXT("edge_seal_thickness"), ExportSettings->EdgeSealThickness);
Writer->WriteValue(TEXT("total_strates"), Manager->GetNumStrates());
Writer->WriteValue(TEXT("inter_strate_gap_chunks"), AuthoredSettings->InterStrateGapChunks);
Writer->WriteValue(TEXT("clip_radius"), AuthoredSettings->ClipRadius);
Writer->WriteValue(TEXT("max_clip_level"), AuthoredSettings->MaxClipLevel);
Writer->WriteValue(TEXT("full_res_clip_levels"), AuthoredSettings->FullResClipLevels);
Writer->WriteValue(TEXT("coarse_tile_cells"), AuthoredSettings->CoarseTileCells);
Writer->WriteValue(TEXT("render_distance_chunks"), AuthoredSettings->RenderDistanceChunks);
Writer->WriteValue(TEXT("generate_skirts"), AuthoredSettings->bGenerateSkirts);
Writer->WriteValue(TEXT("skirt_cells"), AuthoredSettings->SkirtCells);
Writer->WriteValue(TEXT("lod_octave_drop"), AuthoredSettings->LODOctaveDrop);
Writer->WriteValue(TEXT("strate_content_cut_min_level"), AuthoredSettings->GetEffectiveStrateContentCutMinLevel());
Writer->WriteValue(TEXT("inter_strate_gap_chunks"), ExportSettings->InterStrateGapChunks);
Writer->WriteValue(TEXT("clip_radius"), ExportSettings->ClipRadius);
Writer->WriteValue(TEXT("max_clip_level"), ExportSettings->MaxClipLevel);
Writer->WriteValue(TEXT("full_res_clip_levels"), ExportSettings->FullResClipLevels);
Writer->WriteValue(TEXT("coarse_tile_cells"), ExportSettings->CoarseTileCells);
Writer->WriteValue(TEXT("render_distance_chunks"), ExportSettings->RenderDistanceChunks);
Writer->WriteValue(TEXT("generate_skirts"), ExportSettings->bGenerateSkirts);
Writer->WriteValue(TEXT("skirt_cells"), ExportSettings->SkirtCells);
Writer->WriteValue(TEXT("lod_octave_drop"), ExportSettings->LODOctaveDrop);
Writer->WriteValue(TEXT("strate_content_cut_min_level"), ExportSettings->GetEffectiveStrateContentCutMinLevel());
TSet<const UObject*> ActiveObjects;
StandaloneWriteJsonValue(*Writer, TEXT("settings_asset"),
StandaloneSerializeStruct(AuthoredSettings->GetClass(), AuthoredSettings,
ActiveObjects, AuthoredSettings));
StandaloneSerializeStruct(ExportSettings->GetClass(), ExportSettings,
ActiveObjects, ExportSettings));
Writer->WriteArrayStart(TEXT("strates"));
for (const FStrateSlot& Slot : Manager->GetLayout())
@@ -13756,6 +13866,68 @@ bool RunStandaloneWorldExport(const FExploreArguments& Arguments, FString& OutEr
Writer->WriteObjectEnd();
Writer->Close();
// Export the exact Core manifest and resolved layout that the Unreal adapter just
// generated from these live settings and slots. The standalone research tools consume
// these snapshots instead of maintaining a second copy of the planning rules.
FString RuntimeSnapshotJson;
if (!VoxelForgeCoreAdapter::SerializeResolvedWorld(
ExportSettings, Manager, 3, 8, StandaloneCoreVoxelSizeCm,
RuntimeSnapshotJson, OutError))
{
return false;
}
FTCHARToUTF8 RuntimeSnapshotUtf8(*RuntimeSnapshotJson);
std::string CoreSnapshotError;
const std::optional<VoxelForgeCore::ResolvedWorldSnapshot> CoreSnapshot =
VoxelForgeCore::ResolvedWorldSnapshot::FromJson(
std::string_view(RuntimeSnapshotUtf8.Get(),
static_cast<std::size_t>(RuntimeSnapshotUtf8.Length())), CoreSnapshotError);
if (!CoreSnapshot || CoreSnapshot->worldAssembly() == nullptr
|| CoreSnapshot->resolvedLayout() == nullptr)
{
OutError = FString::Printf(
TEXT("Core adapter export did not produce a complete manifest and resolved layout: %s"),
UTF8_TO_TCHAR(CoreSnapshotError.c_str()));
return false;
}
const std::string CoreAssemblyText = CoreSnapshot->worldAssembly()->ToJson();
const std::string CoreLayoutText = CoreSnapshot->resolvedLayout()->ToJson();
if (CoreAssemblyText.empty() || CoreLayoutText.empty())
{
OutError = TEXT("Core adapter export produced an empty canonical manifest or layout.");
return false;
}
const FString CanonicalAssemblyJson = UTF8_TO_TCHAR(CoreAssemblyText.c_str());
const FString CanonicalLayoutJson = UTF8_TO_TCHAR(CoreLayoutText.c_str());
TSharedPtr<FJsonObject> ExportRoot;
const TSharedRef<TJsonReader<>> ExportReader = TJsonReaderFactory<>::Create(Json);
if (!FJsonSerializer::Deserialize(ExportReader, ExportRoot) || !ExportRoot.IsValid())
{
OutError = TEXT("Could not reopen the owner export to attach the Core plan snapshots.");
return false;
}
TSharedPtr<FJsonObject> AssemblyObject;
const TSharedRef<TJsonReader<>> AssemblyReader =
TJsonReaderFactory<>::Create(CanonicalAssemblyJson);
if (!FJsonSerializer::Deserialize(AssemblyReader, AssemblyObject) || !AssemblyObject.IsValid())
{
OutError = TEXT("Could not parse the Core adapter's canonical manifest for export.");
return false;
}
ExportRoot->SetObjectField(TEXT("world_assembly"), AssemblyObject.ToSharedRef());
ExportRoot->SetStringField(TEXT("core_world_assembly_json"), CanonicalAssemblyJson);
ExportRoot->SetStringField(TEXT("resolved_layout_json"), CanonicalLayoutJson);
FString ExportedJson;
const TSharedRef<TJsonWriter<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>> ExportWriter =
TJsonWriterFactory<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>::Create(&ExportedJson);
if (!FJsonSerializer::Serialize(ExportRoot.ToSharedRef(), ExportWriter))
{
OutError = TEXT("Could not serialize the owner export with its canonical Core plan.");
return false;
}
Json = MoveTemp(ExportedJson);
const FString Path = FPaths::Combine(Arguments.OutDirectory, TEXT("owner_world.json"));
if (!FFileHelper::SaveStringToFile(
Json, *Path, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM))
@@ -13764,11 +13936,16 @@ bool RunStandaloneWorldExport(const FExploreArguments& Arguments, FString& OutEr
return false;
}
if (OutJson) *OutJson = Json;
if (!VerifyStandaloneCoreAdapter(Json, OutError))
if (!Arguments.bSkipStandaloneCoreAdapterParity && !VerifyStandaloneCoreAdapter(Json, OutError))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeCoreAdapter] %s"), *OutError);
return false;
}
if (Arguments.bSkipStandaloneCoreAdapterParity)
{
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreAdapter] skipped legacy W1/W8/W16 fixture parity for this export-only update."));
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeStandaloneExport] seed=%d voxel_size_cm=%.3f strates=%d passages=%d samples=%d camera_found=%d file=%s"),
Seed, StandaloneCoreVoxelSizeCm, Manager->GetNumStrates(), Manager->GetPassages().Num(),
@@ -14808,7 +14985,7 @@ bool CreateLoggedOwnerSurfaceSettingsCopy(
UVoxelSettings* Source, int32 Seed,
TStrongObjectPtr<UVoxelSettings>& OutSettings,
TStrongObjectPtr<UVoxelStrateDefinition>& OutSurfaceDefinition,
FString& OutError)
FString& OutError, bool bCompactTracePlan = false)
{
if (Source == nullptr)
{
@@ -14824,11 +15001,10 @@ bool CreateLoggedOwnerSurfaceSettingsCopy(
nullptr, TEXT("/Game/VoxelForge/DA_Strate1.DA_Strate1"));
if (SurfaceDefinition == nullptr
|| SurfaceDefinition->GeneratorType != ECaveGeneratorType::SurfaceWorld
|| SurfaceDefinition->StrateHeightInChunks <= 0
|| Source->TotalStrates != 1)
|| SurfaceDefinition->StrateHeightInChunks <= 0)
{
OutError = FString::Printf(
TEXT("The staged owner assets do not describe the logged single SurfaceWorld (definition_loaded=%d type=%d height=%d settings_strates=%d)."),
TEXT("The staged owner assets do not contain a usable DA_Strate1 SurfaceWorld (definition_loaded=%d type=%d height=%d settings_strates=%d)."),
SurfaceDefinition != nullptr ? 1 : 0,
SurfaceDefinition != nullptr ? static_cast<int32>(SurfaceDefinition->GeneratorType) : INDEX_NONE,
SurfaceDefinition != nullptr ? SurfaceDefinition->StrateHeightInChunks : INDEX_NONE,
@@ -14843,9 +15019,24 @@ bool CreateLoggedOwnerSurfaceSettingsCopy(
return false;
}
OutSettings = TStrongObjectPtr<UVoxelSettings>(SettingsCopy);
if (bCompactTracePlan)
{
SurfaceDefinition = DuplicateObject<UVoxelStrateDefinition>(SurfaceDefinition,
GetTransientPackage(), TEXT("VoxelForgeCompactSurfaceTraceDefinition"));
if (SurfaceDefinition == nullptr)
{
OutError = TEXT("Could not make a transient DA_Strate1 copy for the compact common-plan trace.");
return false;
}
// L14's historical fixed-length SurfaceWorld route search cannot place the
// new trace fixture even at 16 chunks. Use the same simple, walkable FlatPlain
// plan in both host revisions so the streaming comparison reaches AVoxelWorld.
SurfaceDefinition->GeneratorType = ECaveGeneratorType::FlatPlain;
SurfaceDefinition->StrateHeightInChunks = 16;
}
OutSurfaceDefinition = TStrongObjectPtr<UVoxelStrateDefinition>(SurfaceDefinition);
SettingsCopy->Seed = Seed;
SettingsCopy->TotalStrates = Source->TotalStrates;
SettingsCopy->TotalStrates = 1;
SettingsCopy->StratePool.Reset();
SettingsCopy->FixedStrates.Reset();
SettingsCopy->FixedStrates.Add(0,
@@ -14855,8 +15046,10 @@ bool CreateLoggedOwnerSurfaceSettingsCopy(
SettingsCopy->bUseWorldAssemblyManifestOverride = false;
const int32 BottomChunkZ = 1 - SurfaceDefinition->StrateHeightInChunks;
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreTestLayout] source=owner_asset_transient_copy definition=DA_Strate1 seed=%d strata=1 bounds=[0,%d] height_chunks=%d gap=%d imported_override=cleared"),
Seed, BottomChunkZ, SurfaceDefinition->StrateHeightInChunks, SettingsCopy->InterStrateGapChunks);
TEXT("[VoxelForgeCoreTestLayout] source=owner_asset_transient_copy definition=DA_Strate1 generator=%d seed=%d strata=1 bounds=[0,%d] height_chunks=%d compact_trace=%d gap=%d imported_override=cleared"),
static_cast<int32>(SurfaceDefinition->GeneratorType), Seed, BottomChunkZ,
SurfaceDefinition->StrateHeightInChunks,
bCompactTracePlan ? 1 : 0, SettingsCopy->InterStrateGapChunks);
return true;
}
@@ -16432,7 +16625,15 @@ bool RunCoreFloatingIslandActorIntegrationTest(
}
float StagedWalkSpeedCmPerSecond = 0.0f;
float StagedSprintSpeedCmPerSecond = 0.0f;
if (!ReadStagedPlayerSpeeds(
if (Arguments.bCoreWorldStreamTraceOnly)
{
StagedWalkSpeedCmPerSecond = 600.0f;
StagedSprintSpeedCmPerSecond = 6000.0f;
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgePawnSpeedFixture] source=trace_fixture_defaults walk_cm_s=%.3f sprint_cm_s=%.3f"),
StagedWalkSpeedCmPerSecond, StagedSprintSpeedCmPerSecond);
}
else if (!ReadStagedPlayerSpeeds(
StagedWalkSpeedCmPerSecond, StagedSprintSpeedCmPerSecond, OutError))
{
return false;
@@ -17654,7 +17855,15 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
float StagedWalkSpeedCmPerSecond = 0.0f;
float StagedSprintSpeedCmPerSecond = 0.0f;
if (!ReadStagedPlayerSpeeds(
if (Arguments.bCoreWorldStreamTraceOnly)
{
StagedWalkSpeedCmPerSecond = 600.0f;
StagedSprintSpeedCmPerSecond = 6000.0f;
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgePawnSpeedFixture] source=trace_fixture_defaults walk_cm_s=%.3f sprint_cm_s=%.3f"),
StagedWalkSpeedCmPerSecond, StagedSprintSpeedCmPerSecond);
}
else if (!ReadStagedPlayerSpeeds(
StagedWalkSpeedCmPerSecond, StagedSprintSpeedCmPerSecond, OutError))
{
return false;
@@ -17663,8 +17872,26 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
const int32 OwnerSeed = ResolveCoreTestWorldSeed(AuthoredSettings);
TStrongObjectPtr<UVoxelSettings> OwnerSurfaceSettings;
TStrongObjectPtr<UVoxelStrateDefinition> OwnerSurfaceDefinition;
if (!CreateLoggedOwnerSurfaceSettingsCopy(AuthoredSettings, OwnerSeed,
OwnerSurfaceSettings, OwnerSurfaceDefinition, OutError))
TArray<TStrongObjectPtr<UVoxelStrateDefinition>> OwnerPlanDefinitions;
if (Arguments.bOwnerFourStratePlan)
{
UVoxelSettings* PlanSettings = DuplicateObject<UVoxelSettings>(
AuthoredSettings, GetTransientPackage(), TEXT("VoxelForgeOwnerFourStrateTraceSettings"));
if (PlanSettings == nullptr)
{
OutError = TEXT("Could not duplicate owner settings for the transient four-strate Core trace.");
return false;
}
PlanSettings->Seed = OwnerSeed;
if (!ApplyOwnerFourStratePlan(PlanSettings, OwnerPlanDefinitions, true, OutError))
{
return false;
}
OwnerSurfaceSettings = TStrongObjectPtr<UVoxelSettings>(PlanSettings);
}
else if (!CreateLoggedOwnerSurfaceSettingsCopy(AuthoredSettings, OwnerSeed,
OwnerSurfaceSettings, OwnerSurfaceDefinition, OutError,
Arguments.bCoreWorldStreamTraceOnly))
{
return false;
}
@@ -17822,6 +18049,25 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
OutError = TEXT("Could not create the BuildHost stream-trace pawn.");
return false;
}
FVector StartupTraceSpawnCm = FVector::ZeroVector;
FString StartupTraceSpawnText;
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.StartupTraceSpawnCm="),
StartupTraceSpawnText, false))
{
TArray<FString> Components;
StartupTraceSpawnText.ParseIntoArray(Components, TEXT(","), true);
if (Components.Num() != 3
|| !LexTryParseString(StartupTraceSpawnCm.X, *Components[0])
|| !LexTryParseString(StartupTraceSpawnCm.Y, *Components[1])
|| !LexTryParseString(StartupTraceSpawnCm.Z, *Components[2])
|| !FMath::IsFinite(StartupTraceSpawnCm.X)
|| !FMath::IsFinite(StartupTraceSpawnCm.Y)
|| !FMath::IsFinite(StartupTraceSpawnCm.Z))
{
OutError = TEXT("voxel.StartupTraceSpawnCm must be finite X,Y,Z centimetres.");
return false;
}
}
USceneComponent* PawnRoot = NewObject<USceneComponent>(PlayerPawn, NAME_None, RF_Transient);
if (PawnRoot == nullptr)
{
@@ -17830,7 +18076,7 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
}
PlayerPawn->SetRootComponent(PawnRoot);
PawnRoot->RegisterComponent();
PlayerPawn->SetActorLocation(FVector::ZeroVector, false, nullptr, ETeleportType::TeleportPhysics);
PlayerPawn->SetActorLocation(StartupTraceSpawnCm, false, nullptr, ETeleportType::TeleportPhysics);
PlayerController->Possess(PlayerPawn);
if (TestWorld->GetFirstPlayerController() != PlayerController
|| PlayerController->GetPawn() != PlayerPawn)
@@ -17859,7 +18105,10 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
: EVoxelForgeCoreMesherRecipe::MarchingCubesV1);
Actor->CoreClipRadius = 3;
Actor->CoreMaxClipLevel = 8;
Actor->bCaptureVoxelForgeCoreHashesForValidation = true;
// A timing trace must use the production tile path. Core density hashing disables the
// native heightfield band shortcut and measures a different workload; hash validation
// remains enabled in the dedicated parity tests below.
Actor->bCaptureVoxelForgeCoreHashesForValidation = false;
Actor->FinishSpawning(ActorTransform);
TestWorld->InitializeActorsForPlay(FURL());
TestWorld->BeginPlay();
@@ -17869,6 +18118,11 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
return false;
}
Actor->SetActorTickEnabled(true);
VoxelForgeStartupTrace::RecordEvent(TEXT("trace_spawn_location"), FString::Printf(
TEXT("\"location_cm\":[%s,%s,%s]"),
*FString::SanitizeFloat(StartupTraceSpawnCm.X),
*FString::SanitizeFloat(StartupTraceSpawnCm.Y),
*FString::SanitizeFloat(StartupTraceSpawnCm.Z)));
auto DesiredTilesLoaded = [](const AVoxelWorld* World)
{
@@ -18023,6 +18277,7 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
const double CarveBrushRadiusCoreVoxels =
CarveBrushRadiusLegacyVoxels * static_cast<double>(VOXEL_SIZE) / CoreVoxelSizeCm;
const int32 CollisionRadius = FMath::Clamp(AuthoredSettings->PawnCollisionRadiusChunks, 1, 4);
FIntVector ColdStartCollisionCenter = FIntVector::ZeroValue;
uint64 ExpectedEditDigest = 0;
std::array<std::uint64_t, 4> InitialAggregates{};
std::array<std::uint64_t, 4> TravelAggregates{};
@@ -18208,10 +18463,17 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
BiomeProbeSpanVoxels);
VoxelForgeCore::World ReferenceWorld(std::move(*Snapshot));
const std::vector<VoxelForgeCore::TileKey> Desired = ReferenceWorld.desiredTiles({});
// Match the actor's cold-start fallback camera when the transient world has no pawn.
// Aligner la reference sur la camera de repli de l'acteur sans pawn au demarrage.
const VoxelForgeCore::WorldConfiguration& ReferenceConfiguration =
ReferenceWorld.configuration();
const VoxelForgeCore::Vec3d InitialCameraVoxels = ReferenceConfiguration.viewerStartFound
? ReferenceConfiguration.viewerStartVoxels : VoxelForgeCore::Vec3d{};
const std::vector<VoxelForgeCore::TileKey> Desired =
ReferenceWorld.desiredTiles(InitialCameraVoxels);
if (Desired.empty())
{
OutError = TEXT("Standalone Core returned an empty reference clipmap at the origin.");
OutError = TEXT("Standalone Core returned an empty reference clipmap at the cold-start camera.");
return false;
}
ExpectedInitialTileCount = Desired.size();
@@ -18258,7 +18520,23 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
ReferenceWorld.retainTileRegions(Desired);
// Match the runtime collision probe around the same cold-start viewer tile.
// Reproduire la sonde de collision autour de la meme tuile de depart a froid.
ColdStartCollisionCenter = FIntVector(
FMath::FloorToInt(InitialCameraVoxels.x / 32.0),
FMath::FloorToInt(InitialCameraVoxels.y / 32.0),
FMath::FloorToInt(InitialCameraVoxels.z / 32.0));
std::vector<VoxelForgeCore::TileKey> ColdStartCollisionTiles;
ColdStartCollisionTiles.reserve(static_cast<std::size_t>(2 * CollisionRadius + 1)
* static_cast<std::size_t>(2 * CollisionRadius + 1)
* static_cast<std::size_t>(2 * CollisionRadius + 1));
for (int32 Z = -CollisionRadius; Z <= CollisionRadius; ++Z)
for (int32 Y = -CollisionRadius; Y <= CollisionRadius; ++Y)
for (int32 X = -CollisionRadius; X <= CollisionRadius; ++X)
ColdStartCollisionTiles.push_back({
{ColdStartCollisionCenter.X + X,
ColdStartCollisionCenter.Y + Y,
ColdStartCollisionCenter.Z + Z}, 0});
if (Arguments.bCoreHybridCliffOnly)
{
@@ -18332,6 +18610,47 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
Options.mesherRecipe = VoxelForgeCore::MesherRecipe::worldAssemblerHybridV1;
else
Options.mesherRecipe = VoxelForgeCore::MesherRecipe::marchingCubesV1;
// Keep collision hashes outside ExpectedInitialDesiredSet; the collision cube is an
// additional runtime request even where it overlaps the visual clipmap.
// Garder les hachages de collision hors de l'ensemble visuel initial, meme s'ils se
// recoupent avec le clipmap.
std::vector<VoxelForgeCore::TileKey> RetainedReferenceTiles = Desired;
RetainedReferenceTiles.insert(RetainedReferenceTiles.end(),
ColdStartCollisionTiles.begin(), ColdStartCollisionTiles.end());
ReferenceWorld.retainTileRegions(RetainedReferenceTiles);
if (!ReferenceWorld.compileTileRegion(ColdStartCollisionTiles, 16, Scheduler, CoreError))
{
OutError = FString::Printf(TEXT("Standalone collision reference compile failed: %s"),
UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
std::size_t CollisionBodyTiles = 0;
for (const VoxelForgeCore::TileKey& Key : ColdStartCollisionTiles)
{
const std::optional<VoxelForgeCore::TileMesh> Mesh =
ReferenceWorld.generateTile(Key, Options, CoreError);
if (!Mesh)
{
OutError = FString::Printf(TEXT("Standalone collision tile generation failed: %s"),
UTF8_TO_TCHAR(CoreError.c_str()));
return false;
}
FVoxelForgeCoreTileHashRecord Record;
Record.bEmpty = Mesh->indices.empty();
Record.bHasHashes = Mesh->hasDensityHash && Mesh->hasMeshHash
&& Mesh->hasPayloadHash && Mesh->hasTopologyHash;
if (!Record.bEmpty) ++CollisionBodyTiles;
ExpectedHashes.Add(FVoxelTileKey(
FIntVector(Key.coordinate.x, Key.coordinate.y, Key.coordinate.z), Key.lod), Record);
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreWorldTest] standalone_collision_reference=1 center=(%d,%d,%d) tiles=%d body=%llu no_body=%llu"),
ColdStartCollisionCenter.X, ColdStartCollisionCenter.Y, ColdStartCollisionCenter.Z,
static_cast<int32>(ColdStartCollisionTiles.size()),
static_cast<unsigned long long>(CollisionBodyTiles),
static_cast<unsigned long long>(ColdStartCollisionTiles.size() - CollisionBodyTiles));
std::vector<FStandaloneCoreAdapterTileHashes> AggregateRows;
AggregateRows.reserve(Desired.size());
bool bFoundCarveVertex = false;
@@ -19367,7 +19686,7 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
for (int32 Y = -CollisionRadius; Y <= CollisionRadius; ++Y)
for (int32 X = -CollisionRadius; X <= CollisionRadius; ++X)
{
const FVoxelTileKey Key(FIntVector(X, Y, Z), 0);
const FVoxelTileKey Key(ColdStartCollisionCenter + FIntVector(X, Y, Z), 0);
ExpectedCollisionSet.Add(Key);
const FVoxelForgeCoreTileHashRecord* Record = ExpectedHashes.Find(Key);
if (Record == nullptr)
@@ -21361,6 +21680,44 @@ int32 RunBatch(const FString& Params)
int32 UVoxelForgeExploreCommandlet::Main(const FString& Params)
{
if (FParse::Param(*Params, TEXT("coreassetupgradeaudit")))
{
// Load the untouched BuildHost copies and emit their in-memory conversions; never save a package.
// Charge les copies BuildHost intactes et affiche les conversions en memoire, sans sauver de package.
UVoxelSettings* SettingsAsset = LoadObject<UVoxelSettings>(nullptr,
TEXT("/Game/VoxelForge/DA_Settings.DA_Settings"));
if (SettingsAsset == nullptr)
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeAssetMigrationAudit] missing DA_Settings copy"));
return 2;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeAssetMigrationAudit] asset=%s old_gap_chunks=%d new_gap_m=%.6f layout_chunk_m=%.6f"),
*SettingsAsset->GetPathName(), SettingsAsset->InterStrateGapChunks,
SettingsAsset->InterStrateGapMeters, SettingsAsset->StrateLayoutChunkSizeMeters);
const TCHAR* StratePaths[] = {
TEXT("/Game/VoxelForge/DA_Strate1.DA_Strate1"),
TEXT("/Game/VoxelForge/DA_Strate2.DA_Strate2"),
TEXT("/Game/VoxelForge/DA_Strate3.DA_Strate3"),
TEXT("/Game/VoxelForge/DA_Strate4.DA_Strate4")
};
for (const TCHAR* Path : StratePaths)
{
UVoxelStrateDefinition* Definition = LoadObject<UVoxelStrateDefinition>(nullptr, Path);
if (Definition == nullptr)
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeAssetMigrationAudit] missing strate copy %s"), Path);
return 2;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeAssetMigrationAudit] asset=%s old_height_chunks=%d new_height_m=%.6f old_transition_chunks=%d new_transition_m=%.6f"),
*Definition->GetPathName(), Definition->StrateHeightInChunks,
Definition->StrateHeightMeters, Definition->TransitionBlendChunks,
Definition->TransitionWidthMeters);
}
return 0;
}
if (FParse::Param(*Params, TEXT("step2f")))
{
return RunStep2f(Params);
+22 -3
View File
@@ -13,6 +13,8 @@ COMPILER="clang++-19"
VARIANT="clang19"
FP_CONTRACT="off"
JOBS=12
SANITIZERS=""
EXCLUDE_UNSIGNED_OVERFLOW=0
while (($#)); do
case "$1" in
@@ -20,6 +22,8 @@ while (($#)); do
--variant=*) VARIANT="${1#*=}" ;;
--fp-contract=*) FP_CONTRACT="${1#*=}" ;;
--jobs=*) JOBS="${1#*=}" ;;
--sanitize=*) SANITIZERS="${1#*=}" ;;
--exclude-unsigned-integer-overflow) EXCLUDE_UNSIGNED_OVERFLOW=1 ;;
*) printf 'Unknown build option: %s\n' "$1" >&2; exit 2 ;;
esac
shift
@@ -60,14 +64,28 @@ INCLUDES=(
"-I$ROOT/Source/VoxelForge/Public"
"-I$ROOT/Source/VoxelForge/Private"
)
OPT_LEVEL=-O2
LINK_FLAGS=()
if [[ -n "$SANITIZERS" ]]; then
OPT_LEVEL=-O1
LINK_FLAGS+=("-fsanitize=$SANITIZERS" -fno-sanitize-recover=undefined)
if ((EXCLUDE_UNSIGNED_OVERFLOW)); then
LINK_FLAGS+=(-fno-sanitize=unsigned-integer-overflow)
fi
fi
FLAGS=(
-std=c++20 -O2 -mavx2 -mfma -fno-fast-math
-std=c++20 "$OPT_LEVEL" -mavx2 -mfma -fno-fast-math
-fexceptions -pthread "${INCLUDES[@]}"
"-DVOXELFORGE_SOURCE_STAMP=\"$SOURCE_STAMP\""
)
FLAGS+=("${LINK_FLAGS[@]}")
if [[ "$FP_CONTRACT" != "default" ]]; then
FLAGS+=("-ffp-contract=$FP_CONTRACT")
fi
if [[ "${VOXELFORGE_PROFILE_LEGACY_TERMS:-0}" == "1" ]]; then
FLAGS+=(-DVF_L76_PROFILE_LEGACY_TERMS=1)
fi
if [[ "$($COMPILER --version | head -n 1)" == *clang* ]]; then
FLAGS+=(-Wno-c++11-narrowing)
else
@@ -131,7 +149,7 @@ PIDS=()
CORE_LIBRARY="$BUILD_DIR/libVoxelForgeCore.a"
"$(command -v ar)" rcs "$CORE_LIBRARY" "${CORE_OBJECTS[@]}"
OUTPUT="$BIN_DIR/VoxelForgeCoreHeadless-$VARIANT"
"$COMPILER" -pthread "${LEGACY_OBJECTS[@]}" "$CORE_LIBRARY" -o "$OUTPUT"
"$COMPILER" -pthread "${LINK_FLAGS[@]}" "${LEGACY_OBJECTS[@]}" "$CORE_LIBRARY" -o "$OUTPUT"
CAPTURE_OUTPUT="$BIN_DIR/VoxelForgeOffscreenCapture-$VARIANT"
"$COMPILER" -std=c++20 -O2 -fno-fast-math -pthread \
"-DVOXELFORGE_SOURCE_STAMP=\"$SOURCE_STAMP\"" -I"$ROOT/Source" \
@@ -145,4 +163,5 @@ if [[ "$POST_BUILD_STAMP" != "$SOURCE_STAMP" ]]; then
fi
printf 'Built Core library: %s\nBuilt headless benchmark: %s\n' "$CORE_LIBRARY" "$OUTPUT"
printf 'Built EGL offscreen capture: %s\n' "$CAPTURE_OUTPUT"
printf 'Compiler: %s | variant: %s | fp-contract: %s | jobs: %s\n' "$COMPILER" "$VARIANT" "$FP_CONTRACT" "$JOBS"
printf 'Compiler: %s | variant: %s | fp-contract: %s | sanitizers: %s | jobs: %s\n' \
"$COMPILER" "$VARIANT" "$FP_CONTRACT" "${SANITIZERS:-off}" "$JOBS"
@@ -1 +1,6 @@
#pragma once
#if defined(VFCORE_COMPAT_FORWARD_HEADERS) && !defined(VFCORE_USE_COMPAT) \
&& (defined(__clang__) || defined(__GNUC__))
#include_next <AssetRegistry/AssetData.h>
#endif
@@ -1 +1,6 @@
#pragma once
#if defined(VFCORE_COMPAT_FORWARD_HEADERS) && !defined(VFCORE_USE_COMPAT) \
&& (defined(__clang__) || defined(__GNUC__))
#include_next <AssetRegistry/AssetRegistryModule.h>
#endif
+5
View File
@@ -1 +1,6 @@
#pragma once
#if defined(VFCORE_COMPAT_FORWARD_HEADERS) && !defined(VFCORE_USE_COMPAT) \
&& (defined(__clang__) || defined(__GNUC__))
#include_next <Async/ParallelFor.h>
#endif
+9
View File
@@ -1,5 +1,10 @@
#pragma once
#if defined(VFCORE_COMPAT_FORWARD_HEADERS) && !defined(VFCORE_USE_COMPAT) \
&& (defined(__clang__) || defined(__GNUC__))
#include_next <CoreMinimal.h>
#else
// Deliberately small UE-style surface used by the copied VoxelForge CPU sources.
// This is a value/container compatibility layer; it does not start or link Unreal.
#include <algorithm>
@@ -45,6 +50,9 @@
#define GENERATED_UCLASS_BODY()
#define VOXELFORGE_API
#define FORCEINLINE inline
#ifdef WITH_EDITOR
#undef WITH_EDITOR
#endif
#define WITH_EDITOR 0
#define TRACE_CPUPROFILER_EVENT_SCOPE(...)
#define PRAGMA_DISABLE_SHADOW_VARIABLE_WARNINGS
@@ -705,3 +713,4 @@ struct FParse
struct FCrc { static uint32 MemCrc32(const void*Data,int32 Length,uint32 Crc=0){const auto*P=static_cast<const uint8*>(Data);uint32 V=~Crc;for(int32 I=0;I<Length;++I){V^=P[I];for(int J=0;J<8;++J)V=(V>>1)^(0xEDB88320u&static_cast<uint32>(-static_cast<int32>(V&1)));}return ~V;} static uint32 StrCrc32(const char*S,uint32 Crc=0){return MemCrc32(S,static_cast<int32>(std::strlen(S)),Crc);} };
template<class T> using TAtomic = std::atomic<T>;
class FThreadSafeCounter { public:int32 Increment(){return ++Value;}int32 Decrement(){return --Value;}int32 GetValue()const{return Value.load();}void Reset(){Value.store(0);}private:std::atomic<int32>Value{0};};
#endif
+5
View File
@@ -1 +1,6 @@
#pragma once
#if defined(VFCORE_COMPAT_FORWARD_HEADERS) && !defined(VFCORE_USE_COMPAT) \
&& (defined(__clang__) || defined(__GNUC__))
#include_next <Dom/JsonObject.h>
#endif
+5
View File
@@ -1 +1,6 @@
#pragma once
#if defined(VFCORE_COMPAT_FORWARD_HEADERS) && !defined(VFCORE_USE_COMPAT) \
&& (defined(__clang__) || defined(__GNUC__))
#include_next <Dom/JsonValue.h>
#endif
+5
View File
@@ -1 +1,6 @@
#pragma once
#if defined(VFCORE_COMPAT_FORWARD_HEADERS) && !defined(VFCORE_USE_COMPAT) \
&& (defined(__clang__) || defined(__GNUC__))
#include_next <Engine/DataAsset.h>
#endif
+5
View File
@@ -1 +1,6 @@
#pragma once
#if defined(VFCORE_COMPAT_FORWARD_HEADERS) && !defined(VFCORE_USE_COMPAT) \
&& (defined(__clang__) || defined(__GNUC__))
#include_next <GameplayTagContainer.h>
#endif
+5
View File
@@ -1 +1,6 @@
#pragma once
#if defined(VFCORE_COMPAT_FORWARD_HEADERS) && !defined(VFCORE_USE_COMPAT) \
&& (defined(__clang__) || defined(__GNUC__))
#include_next <HAL/CriticalSection.h>
#endif
+5
View File
@@ -1 +1,6 @@
#pragma once
#if defined(VFCORE_COMPAT_FORWARD_HEADERS) && !defined(VFCORE_USE_COMPAT) \
&& (defined(__clang__) || defined(__GNUC__))
#include_next <HAL/Event.h>
#endif
+5
View File
@@ -1 +1,6 @@
#pragma once
#if defined(VFCORE_COMPAT_FORWARD_HEADERS) && !defined(VFCORE_USE_COMPAT) \
&& (defined(__clang__) || defined(__GNUC__))
#include_next <HAL/FileManager.h>
#endif
+5
View File
@@ -1 +1,6 @@
#pragma once
#if defined(VFCORE_COMPAT_FORWARD_HEADERS) && !defined(VFCORE_USE_COMPAT) \
&& (defined(__clang__) || defined(__GNUC__))
#include_next <HAL/IConsoleManager.h>
#endif
+5
View File
@@ -1 +1,6 @@
#pragma once
#if defined(VFCORE_COMPAT_FORWARD_HEADERS) && !defined(VFCORE_USE_COMPAT) \
&& (defined(__clang__) || defined(__GNUC__))
#include_next <HAL/PlatformProcess.h>
#endif
+5
View File
@@ -1 +1,6 @@
#pragma once
#if defined(VFCORE_COMPAT_FORWARD_HEADERS) && !defined(VFCORE_USE_COMPAT) \
&& (defined(__clang__) || defined(__GNUC__))
#include_next <HAL/PlatformTime.h>
#endif
+6
View File
@@ -1,5 +1,10 @@
#pragma once
#if defined(VFCORE_COMPAT_FORWARD_HEADERS) && !defined(VFCORE_USE_COMPAT) \
&& (defined(__clang__) || defined(__GNUC__))
#include_next <Json.h>
#else
#include <charconv>
#include <cmath>
#include <cstddef>
@@ -162,3 +167,4 @@ namespace VFJson
return Parser(std::move(Text)).Parse(Out, Error);
}
}
#endif
+5
View File
@@ -1 +1,6 @@
#pragma once
#if defined(VFCORE_COMPAT_FORWARD_HEADERS) && !defined(VFCORE_USE_COMPAT) \
&& (defined(__clang__) || defined(__GNUC__))
#include_next <Math/RandomStream.h>
#endif
+5
View File
@@ -1 +1,6 @@
#pragma once
#if defined(VFCORE_COMPAT_FORWARD_HEADERS) && !defined(VFCORE_USE_COMPAT) \
&& (defined(__clang__) || defined(__GNUC__))
#include_next <Misc/CommandLine.h>
#endif
+5
View File
@@ -1 +1,6 @@
#pragma once
#if defined(VFCORE_COMPAT_FORWARD_HEADERS) && !defined(VFCORE_USE_COMPAT) \
&& (defined(__clang__) || defined(__GNUC__))
#include_next <Misc/Crc.h>
#endif
+5
View File
@@ -1 +1,6 @@
#pragma once
#if defined(VFCORE_COMPAT_FORWARD_HEADERS) && !defined(VFCORE_USE_COMPAT) \
&& (defined(__clang__) || defined(__GNUC__))
#include_next <Misc/FileHelper.h>
#endif
+5
View File
@@ -1 +1,6 @@
#pragma once
#if defined(VFCORE_COMPAT_FORWARD_HEADERS) && !defined(VFCORE_USE_COMPAT) \
&& (defined(__clang__) || defined(__GNUC__))
#include_next <Misc/Parse.h>
#endif
+5
View File
@@ -1 +1,6 @@
#pragma once
#if defined(VFCORE_COMPAT_FORWARD_HEADERS) && !defined(VFCORE_USE_COMPAT) \
&& (defined(__clang__) || defined(__GNUC__))
#include_next <Misc/Paths.h>
#endif
+5
View File
@@ -1 +1,6 @@
#pragma once
#if defined(VFCORE_COMPAT_FORWARD_HEADERS) && !defined(VFCORE_USE_COMPAT) \
&& (defined(__clang__) || defined(__GNUC__))
#include_next <Misc/ScopeLock.h>
#endif
+5
View File
@@ -1 +1,6 @@
#pragma once
#if defined(VFCORE_COMPAT_FORWARD_HEADERS) && !defined(VFCORE_USE_COMPAT) \
&& (defined(__clang__) || defined(__GNUC__))
#include_next <Misc/ScopeRWLock.h>
#endif
+5
View File
@@ -1 +1,6 @@
#pragma once
#if defined(VFCORE_COMPAT_FORWARD_HEADERS) && !defined(VFCORE_USE_COMPAT) \
&& (defined(__clang__) || defined(__GNUC__))
#include_next <Misc/SecureHash.h>
#endif
@@ -1 +1,6 @@
#pragma once
#if defined(VFCORE_COMPAT_FORWARD_HEADERS) && !defined(VFCORE_USE_COMPAT) \
&& (defined(__clang__) || defined(__GNUC__))
#include_next <Modules/ModuleManager.h>
#endif
@@ -1 +1,6 @@
#pragma once
#if defined(VFCORE_COMPAT_FORWARD_HEADERS) && !defined(VFCORE_USE_COMPAT) \
&& (defined(__clang__) || defined(__GNUC__))
#include_next <ProfilingDebugging/CpuProfilerTrace.h>
#endif
@@ -1 +1,6 @@
#pragma once
#if defined(VFCORE_COMPAT_FORWARD_HEADERS) && !defined(VFCORE_USE_COMPAT) \
&& (defined(__clang__) || defined(__GNUC__))
#include_next <Serialization/CustomVersion.h>
#endif
@@ -1 +1,6 @@
#pragma once
#if defined(VFCORE_COMPAT_FORWARD_HEADERS) && !defined(VFCORE_USE_COMPAT) \
&& (defined(__clang__) || defined(__GNUC__))
#include_next <Serialization/JsonSerializer.h>
#endif
@@ -1 +1,6 @@
#pragma once
#if defined(VFCORE_COMPAT_FORWARD_HEADERS) && !defined(VFCORE_USE_COMPAT) \
&& (defined(__clang__) || defined(__GNUC__))
#include_next <Serialization/JsonWriter.h>
#endif
+6
View File
@@ -1,4 +1,9 @@
#pragma once
#if defined(VFCORE_COMPAT_FORWARD_HEADERS) && !defined(VFCORE_USE_COMPAT) \
&& (defined(__clang__) || defined(__GNUC__))
#include_next <Stats/Stats.h>
#else
#define STATCAT_Advanced 0
#define DECLARE_STATS_GROUP(Name,Group,Category) constexpr int32 Group=0
#define DECLARE_DWORD_COUNTER_STAT_EXTERN(Name,Stat,Group,Api) constexpr int32 Stat=0
@@ -8,3 +13,4 @@
#define SET_DWORD_STAT(Stat,Value) ((void)0)
#define SCOPE_CYCLE_COUNTER(Stat) ((void)0)
#define QUICK_SCOPE_CYCLE_COUNTER(Stat) ((void)0)
#endif
+5
View File
@@ -1 +1,6 @@
#pragma once
#if defined(VFCORE_COMPAT_FORWARD_HEADERS) && !defined(VFCORE_USE_COMPAT) \
&& (defined(__clang__) || defined(__GNUC__))
#include_next <Templates/SubclassOf.h>
#endif
+5
View File
@@ -1 +1,6 @@
#pragma once
#if defined(VFCORE_COMPAT_FORWARD_HEADERS) && !defined(VFCORE_USE_COMPAT) \
&& (defined(__clang__) || defined(__GNUC__))
#include_next <Templates/UniquePtr.h>
#endif
+5
View File
@@ -1 +1,6 @@
#pragma once
#if defined(VFCORE_COMPAT_FORWARD_HEADERS) && !defined(VFCORE_USE_COMPAT) \
&& (defined(__clang__) || defined(__GNUC__))
#include_next <UObject/EnumProperty.h>
#endif
@@ -1 +1,6 @@
#pragma once
#if defined(VFCORE_COMPAT_FORWARD_HEADERS) && !defined(VFCORE_USE_COMPAT) \
&& (defined(__clang__) || defined(__GNUC__))
#include_next <UObject/FieldIterator.h>
#endif
+5
View File
@@ -1 +1,6 @@
#pragma once
#if defined(VFCORE_COMPAT_FORWARD_HEADERS) && !defined(VFCORE_USE_COMPAT) \
&& (defined(__clang__) || defined(__GNUC__))
#include_next <UObject/Object.h>
#endif
@@ -1 +1,6 @@
#pragma once
#if defined(VFCORE_COMPAT_FORWARD_HEADERS) && !defined(VFCORE_USE_COMPAT) \
&& (defined(__clang__) || defined(__GNUC__))
#include_next <UObject/UObjectGlobals.h>
#endif
+5
View File
@@ -1 +1,6 @@
#pragma once
#if defined(VFCORE_COMPAT_FORWARD_HEADERS) && !defined(VFCORE_USE_COMPAT) \
&& (defined(__clang__) || defined(__GNUC__))
#include_next <UObject/UnrealType.h>
#endif
@@ -3,6 +3,7 @@
#include "VoxelForgeCore/CpuCycleCounter.h"
#include <array>
#include <chrono>
#include <cstddef>
#include <cstdint>
@@ -26,9 +27,12 @@ namespace VoxelForgeCore::floating_island_detail
static constexpr std::uint64_t SampleMask=255;
std::array<std::uint64_t,static_cast<std::size_t>(EIslandFieldCaller::Count)> sampledFieldCalls{};
std::array<std::uint64_t,static_cast<std::size_t>(EIslandFieldCaller::Count)> fieldCycles{};
std::array<std::uint64_t,static_cast<std::size_t>(EIslandFieldCaller::Count)> fieldNanoseconds{};
std::array<std::uint64_t,static_cast<std::size_t>(EIslandFieldCaller::Count)> sampledNativeCalls{};
std::array<std::uint64_t,static_cast<std::size_t>(EIslandFieldCaller::Count)> nativeCycles{};
std::array<std::uint64_t,static_cast<std::size_t>(EIslandFieldCaller::Count)> nativeNanoseconds{};
std::array<std::uint64_t,static_cast<std::size_t>(EIslandAttributionBucket::Count)> exclusiveCycles{};
std::array<std::uint64_t,static_cast<std::size_t>(EIslandAttributionBucket::Count)> exclusiveNanoseconds{};
std::array<std::uint64_t,static_cast<std::size_t>(EIslandAttributionBucket::Count)> scopeSamples{};
std::uint64_t traceSampledMisses=0,traceSeenBefore=0,traceEvictions=0,
traceReusedEvictions=0;
@@ -118,7 +122,11 @@ namespace VoxelForgeCore::floating_island_detail
&FIslandFieldAttributionInstrumentation::SampleMask;
sampled=(((calls+phase)&FIslandFieldAttributionInstrumentation::SampleMask)==0);
}
if(sampled)start=VoxelForgeCoreShared::ReadCpuCycles();
if(sampled)
{
start=VoxelForgeCoreShared::ReadCpuCycles();
wallStart=std::chrono::steady_clock::now();
}
}
~FScopedIslandAttributionTimer(){Stop();}
std::uint64_t Stop() noexcept
@@ -128,12 +136,18 @@ namespace VoxelForgeCore::floating_island_detail
const std::uint64_t raw=VoxelForgeCoreShared::ReadCpuCycles()-start;
const std::uint64_t overhead=VoxelForgeCoreShared::CpuCycleReadPairOverhead();
const std::uint64_t cycles=raw>overhead?raw-overhead:0;
const auto elapsed=std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now()-wallStart).count();
metrics->exclusiveCycles[bucketIndex]+=cycles;
metrics->exclusiveNanoseconds[bucketIndex]+=elapsed>0
?static_cast<std::uint64_t>(elapsed):0;
++metrics->scopeSamples[bucketIndex];
if(bucketIndex==static_cast<std::size_t>(EIslandAttributionBucket::Field))
{
const std::size_t callerIndex=static_cast<std::size_t>(ActiveIslandFieldCaller());
metrics->fieldCycles[callerIndex]+=cycles;
metrics->fieldNanoseconds[callerIndex]+=elapsed>0
?static_cast<std::uint64_t>(elapsed):0;
++metrics->sampledFieldCalls[callerIndex];
}
return cycles;
@@ -141,6 +155,7 @@ namespace VoxelForgeCore::floating_island_detail
FIslandFieldAttributionInstrumentation* metrics=nullptr;
std::size_t bucketIndex=0;
std::uint64_t start=0;
std::chrono::steady_clock::time_point wallStart{};
bool sampled=false;
};
@@ -160,6 +175,7 @@ namespace VoxelForgeCore::floating_island_detail
sampledCaller=caller;
sampled=true;
start=VoxelForgeCoreShared::ReadCpuCycles();
wallStart=std::chrono::steady_clock::now();
++attribution->sampledNativeCalls[callerIndex];
}
~FScopedIslandAttributionSample()
@@ -168,12 +184,17 @@ namespace VoxelForgeCore::floating_island_detail
const std::uint64_t raw=VoxelForgeCoreShared::ReadCpuCycles()-start;
const std::uint64_t overhead=VoxelForgeCoreShared::CpuCycleReadPairOverhead();
const std::uint64_t duration=raw>overhead?raw-overhead:0;
const auto elapsed=std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now()-wallStart).count();
const std::size_t callerIndex=static_cast<std::size_t>(sampledCaller);
sampledAttribution->nativeCycles[callerIndex]+=duration;
sampledAttribution->nativeNanoseconds[callerIndex]+=elapsed>0
?static_cast<std::uint64_t>(elapsed):0;
}
FIslandFieldAttributionInstrumentation* sampledAttribution=nullptr;
EIslandFieldCaller sampledCaller=EIslandFieldCaller::Other;
std::uint64_t start=0;
std::chrono::steady_clock::time_point wallStart{};
bool sampled=false;
};
@@ -131,9 +131,50 @@ namespace VoxelForgeCore
std::uint64_t mixedBoxes = 0;
std::uint64_t unknownBoxes = 0;
std::uint64_t fieldCalls = 0;
// Opt-in composer diagnostics. These stay zero during ordinary generation.
bool passageComposerProfiled = false;
std::uint64_t passageTileFeatureCount = 0;
std::uint64_t passageTileRouteSegmentCandidates = 0;
std::uint64_t passageTileMouthSegmentCandidates = 0;
std::uint64_t passageComposerQueries = 0;
std::uint64_t passageIterations = 0;
std::uint64_t passageUpperMouthFilterChecks = 0;
std::uint64_t passageColumnCandidates = 0;
std::uint64_t passageColumnDistanceCalls = 0;
std::uint64_t passageNegativeBoundChecks = 0;
std::uint64_t passageNegativeBoundRejects = 0;
std::uint64_t passagePositiveBoundChecks = 0;
std::uint64_t passagePositiveBoundRejects = 0;
std::uint64_t passageCarveSegmentVisits = 0;
std::uint64_t passageSmoothCarveJoins = 0;
std::uint64_t passageSupportSegmentVisits = 0;
std::uint64_t passageSmoothSupportJoins = 0;
std::uint64_t passageSupportCapsuleChecks = 0;
std::uint64_t passageLaterBoreChecks = 0;
std::uint64_t passageOtherCapsuleChecks = 0;
std::uint64_t passageMouthSupportCalls = 0;
std::uint64_t passageMouthRouteSegmentChecks = 0;
std::uint64_t passageReliefFieldCalls = 0;
std::uint64_t passageSmoothstepEvaluations = 0;
std::uint64_t floatFieldCalls = 0;
std::uint64_t latticeFieldCalls = 0;
std::uint64_t unclampedFieldCalls = 0;
std::uint64_t densityPointCalls = 0;
std::uint64_t densityFloatPointCalls = 0;
std::uint64_t densityLatticePointCalls = 0;
std::uint64_t densityUnclampedPointCalls = 0;
std::uint64_t densityX8PointCalls = 0;
std::uint64_t densityFieldEvalNanoseconds = 0;
std::uint64_t densityFieldEvalSamplePoints = 0;
std::uint64_t legacyFieldEvalCalls = 0;
std::array<std::uint64_t,9> legacyFieldTermCalls{};
std::array<std::uint64_t,9> legacyFieldTermNanoseconds{};
std::array<std::uint64_t,9> legacyFieldTermTimedCalls{};
std::uint64_t legacyPrimitiveCandidateVisits = 0;
std::uint64_t legacyPrimitiveDistanceEvaluations = 0;
std::uint64_t legacySmoothMinCalls = 0;
std::uint64_t legacySmoothMinNanoseconds = 0;
std::uint64_t legacySmoothMinTimedCalls = 0;
std::uint64_t columnCacheHits = 0;
std::uint64_t columnCacheMisses = 0;
std::uint64_t columnCacheConflictMisses = 0;
@@ -220,6 +261,9 @@ namespace VoxelForgeCore
std::uint32_t farHorizonVersion = 0;
bool farShellRepairSkipped = false;
bool tileWasClassified = false;
std::uint8_t tileProofVetoReason = 0;
bool tileProofSafe = false;
bool tileLegacyBlockProofRelevant = false;
bool meshingSkipped = false;
bool featureRefinedResolution = false;
bool heightfieldBandLimited = false;
@@ -400,7 +444,10 @@ namespace VoxelForgeCore
// version 169 sank every connector 2 m; it left four FI route support probes low.
// version 170 sinks only connectors touching the anchored root by 1 m.
// Version 208 keeps the crater tread scoped to style-zero FI mouths.
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 209;
// Version 210 keeps bulk native-cave sampling on the scalar field path at strata edges.
// Version 211 makes coordinate biasing in the shared cell hash unsigned, removing signed-overflow UB.
// Version 212 balances generated entry routes against existing walk lanes and preserves their shared treads.
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 213;
enum class FieldChannel : std::uint32_t
{
@@ -1724,6 +1771,9 @@ namespace VoxelForgeCore
std::int32_t bandMinZVoxel = -2147483647 - 1;
std::int32_t bandMaxZVoxel = 2147483647;
bool hashDensity = false;
// Falsifier mode: retain mesh/topology hashes while permitting proven empty tiles
// to skip their density lattice. The density hash of such a skipped tile is empty.
bool allowUniformSkipsWhenHashing = false;
bool captureDensitySamples = false;
// Diagnostic only; materialize the full density lattice even when block proofs
// establish that the tile has no surface.
@@ -1742,6 +1792,8 @@ namespace VoxelForgeCore
// Separate cold pass only; samples the MC inner stages without changing output.
// Passe froide separee seulement; mesure les etapes internes MC sans changer la sortie.
bool profileMesherStages = false;
// Research-only: count passage-composer work per generated tile.
bool profilePassageComposer = false;
// Research-only: collect tile-drain kernel counters and floating-island cache metrics.
// Inactive on ordinary generation so the hot path only pays the option check.
bool instrumentTileDrain = false;
@@ -1,5 +1,9 @@
#pragma once
// Core implementation TUs opt into the standalone Unreal-shaped compatibility headers. UE's
// Linux-generated module TUs leave this unset so those same header names forward to the engine.
#define VFCORE_USE_COMPAT 1
// Every standard or platform header used by the copied implementation must be included before
// its wrapper opens vfcore::detail. This prevents system declarations from being nested in the
// private namespace when the implementation includes its Unreal-shaped compatibility headers.
+378 -28
View File
@@ -5,6 +5,7 @@
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <iomanip>
#include <limits>
#include <sstream>
@@ -2325,8 +2326,22 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
const std::int64_t midRadius=std::max<std::int64_t>(
static_cast<std::int64_t>(std::llround((upperIsSurfaceWorld?3.5:1.5)*qPerMetre)),
cfg?cfg->midRadiusSubvoxels:mouthRadius);
const std::int64_t reachMinimum=cfg?std::max<std::int64_t>(0,cfg->reachMinimumSubvoxels):8*qPerMetre;
const std::int64_t reachMaximum=cfg?std::max<std::int64_t>(reachMinimum,cfg->reachMaximumSubvoxels):24*qPerMetre;
const std::int64_t requestedReachMinimum=cfg
?std::max<std::int64_t>(0,cfg->reachMinimumSubvoxels):8*qPerMetre;
const std::int64_t requestedReachMaximum=cfg
?std::max<std::int64_t>(requestedReachMinimum,cfg->reachMaximumSubvoxels):24*qPerMetre;
// Keep the landing several metres inside the stratum's authored content.
// Near a band edge, the sampled native terrain can exist at the centerline
// while the full capsule footprint has no supporting ground.
const std::int64_t mouthClearance=std::max(mouthRadius,midRadius)+2*qPerMetre;
const auto usableReach=[&](const ResolvedLayoutStratumV2& stratum)
{
const std::int64_t depth=contentZQ(stratum.content.topMicrometres)
-contentZQ(stratum.content.bottomMicrometres);
return std::max<std::int64_t>(0,depth-mouthClearance);
};
const std::int64_t upperReachLimit=usableReach(upper);
const std::int64_t lowerReachLimit=usableReach(lower);
const std::uint32_t style=cfg?std::min<std::uint32_t>(cfg->style,3u):1u;
const std::uint32_t segments=cfg?std::clamp<std::uint32_t>(cfg->segments,16u,48u):20u;
const std::int64_t wander=cfg?std::max<std::int64_t>(0,cfg->wanderSubvoxels):3*qPerMetre;
@@ -2362,16 +2377,30 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
if(!hasUpperRoute)
{
upperMouth[0]=xy[0];upperMouth[1]=xy[1];
const auto reach=reachValue(PassageSeed(seed,upper.strateIndex,lower.strateIndex,
connection,0x55505248u),reachMinimum,reachMaximum);
const auto requestedReach=reachValue(PassageSeed(seed,upper.strateIndex,
lower.strateIndex,connection,0x55505248u),
requestedReachMinimum,requestedReachMaximum);
const auto reach=std::min(requestedReach,upperReachLimit);
upperMouth[2]=contentZQ(upper.content.bottomMicrometres)+reach;
if(requestedReach>upperReachLimit)
std::printf("[ResolvedPassageReachClamp] id=%s side=upper sampled_m=%.3f usable_max_m=%.3f selected_m=%.3f status=CLAMPED\n",
passage.stableId.c_str(),requestedReach/static_cast<double>(qPerMetre),
upperReachLimit/static_cast<double>(qPerMetre),
reach/static_cast<double>(qPerMetre));
}
if(!hasLowerRoute)
{
lowerMouth[0]=xy[0];lowerMouth[1]=xy[1];
const auto reach=reachValue(PassageSeed(seed,upper.strateIndex,lower.strateIndex,
connection,0x4c4f5752u),reachMinimum,reachMaximum);
const auto requestedReach=reachValue(PassageSeed(seed,upper.strateIndex,
lower.strateIndex,connection,0x4c4f5752u),
requestedReachMinimum,requestedReachMaximum);
const auto reach=std::min(requestedReach,lowerReachLimit);
lowerMouth[2]=contentZQ(lower.content.topMicrometres)-reach;
if(requestedReach>lowerReachLimit)
std::printf("[ResolvedPassageReachClamp] id=%s side=lower sampled_m=%.3f usable_max_m=%.3f selected_m=%.3f status=CLAMPED\n",
passage.stableId.c_str(),requestedReach/static_cast<double>(qPerMetre),
lowerReachLimit/static_cast<double>(qPerMetre),
reach/static_cast<double>(qPerMetre));
}
}
if(!radialWithin(upperMouth,placementRadius)||!radialWithin(lowerMouth,placementRadius))
@@ -2431,18 +2460,29 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
}
std::uint32_t phase=static_cast<std::uint32_t>(PassageSeed(seed,
upper.strateIndex,lower.strateIndex,connection,0x50484153u)%16);
if(hasUpperRoute)
// Authored tangents and island anchors already determine the phase.
// A generated mouth in a native cave uses its radial approach; an
// island landing uses the tangent fit below to ease off the crater rim.
const bool generatedIslandWormEntry=!hasUpperRoute&&!hasUpperTangent
&&!anchoredSpiral&&upper.generatorKind=="FloatingIslands";
if(hasUpperRoute||(anchoredIslandPassage&&!anchoredSpiral)
||(!hasUpperRoute&&!anchoredIslandPassage&&!anchoredSpiral
&&upper.generatorKind!="FloatingIslands"))
{
std::int64_t bestAlignment=std::numeric_limits<std::int64_t>::min();
const std::uint32_t hashedPhase=phase;
const std::int64_t phaseX=style==0&&anchoredIslandPassage
&&!anchoredSpiral&&passageAnchor
?upperMouth[0]-passageAnchor->values[0]
:(hasUpperTangent?upperTangent[0]:upperMouth[0]);
:(hasUpperTangent?upperTangent[0]:upperMouth[0]
-(anchoredIslandPassage&&passageAnchor
?passageAnchor->values[0]:0));
const std::int64_t phaseY=style==0&&anchoredIslandPassage
&&!anchoredSpiral&&passageAnchor
?upperMouth[1]-passageAnchor->values[1]
:(hasUpperTangent?upperTangent[1]:upperMouth[1]);
:(hasUpperTangent?upperTangent[1]:upperMouth[1]
-(anchoredIslandPassage&&passageAnchor
?passageAnchor->values[1]:0));
for(std::uint32_t offset=0;offset<16;++offset)
{
const std::uint32_t candidate=static_cast<std::uint32_t>(
@@ -2462,23 +2502,6 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
{bestAlignment=alignment;phase=candidate;}
}
}
if(hasUpperRoute&&upperIsSurfaceWorld)
{
const auto& directionAtPhase=directions[phase];
const std::int64_t radialAlignment=upperMouth[0]*directionAtPhase[0]
+upperMouth[1]*directionAtPhase[1];
if(radialAlignment<0)
{
std::int64_t bestOutwardAlignment=std::numeric_limits<std::int64_t>::min();
for(std::uint32_t candidate=0;candidate<directions.size();++candidate)
{
const std::int64_t alignment=upperMouth[0]*directions[candidate][0]
+upperMouth[1]*directions[candidate][1];
if(alignment>bestOutwardAlignment)
{bestOutwardAlignment=alignment;phase=candidate;}
}
}
}
bool clockwise=(PassageSeed(seed,upper.strateIndex,lower.strateIndex,
connection,0x44495245u)&1u)!=0;
if(style==0&&anchoredIslandPassage&&!anchoredSpiral&&passageAnchor)
@@ -2500,7 +2523,7 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
clockwise=clockwiseTangentX*upperTangent[0]
+clockwiseTangentY*upperTangent[1]>=0.0;
}
const int direction=clockwise?1:-1;
int direction=clockwise?1:-1;
constexpr std::int64_t TargetGradeQ16=12500; // leave margin below the 15-degree helix contract.
const std::int64_t wormRadialWobble=std::min<std::int64_t>(
std::max<std::int64_t>(0,wander/3),2*qPerMetre);
@@ -2569,6 +2592,267 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
static_cast<std::int64_t>(std::llround(centerX+radius*directions[directionIndex][0]/65536.0)),
static_cast<std::int64_t>(std::llround(centerY+radius*directions[directionIndex][1]/65536.0))};
};
if(generatedIslandWormEntry)
{
struct FPhaseScore
{
std::uint32_t phase=0;
int direction=1;
std::uint64_t routeLength=0;
double clearance=0.0;
double tangent=0.0;
};
std::array<FPhaseScore,32> phaseScores{};
std::size_t phaseScoreCount=0;
const std::uint32_t hashedPhase=phase;
const int hashedDirection=direction;
const std::int64_t entryVerticalDistance=std::abs(
lowerMouth[2]-upperMouth[2]);
const std::int64_t entryRouteGradeQ16=profiledSurfaceJoin?8000
:style==0&&anchoredIslandPassage&&!anchoredSpiral?15500
:TargetGradeQ16;
const auto candidateRouteLength=[&](std::uint32_t candidate,int candidateDirection)
{
std::vector<std::array<std::int64_t,2>> candidateXY;
const auto append=[&](std::array<std::int64_t,2> point)
{if(candidateXY.empty()||candidateXY.back()!=point)
candidateXY.push_back(point);};
append({upperMouth[0],upperMouth[1]});
append(ringPoint(candidate));
const auto measure=[&]()
{
std::uint64_t length=0;
for(std::size_t i=1;i<candidateXY.size();++i)
length+=distanceQ(candidateXY[i][0]-candidateXY[i-1][0],
candidateXY[i][1]-candidateXY[i-1][1]);
length+=distanceQ(lowerMouth[0]-candidateXY.back()[0],
lowerMouth[1]-candidateXY.back()[1]);
return length;
};
std::uint32_t loops=0;
std::uint64_t length=0;
while(loops<256)
{
length=measure();
if(length*static_cast<std::uint64_t>(entryRouteGradeQ16)
>=static_cast<std::uint64_t>(entryVerticalDistance)*65536ull)break;
for(int step=1;step<=16;++step)
{
int directionIndex=static_cast<int>(candidate)
+candidateDirection*step;
directionIndex=(directionIndex%16+16)%16;
append(ringPoint(static_cast<std::uint32_t>(directionIndex),
loops+(step==16?1u:0u)));
}
++loops;
}
if(loops>=256)return std::uint64_t{0};
const std::int64_t mouthPlanSeparation=distanceQ(
lowerMouth[0]-upperMouth[0],lowerMouth[1]-upperMouth[1]);
if(!hasLowerTangent&&!anchoredSpiral&&pathStyle==1u&&loops<=1
&&mouthPlanSeparation<=2*mouthRadius
&&entryVerticalDistance<=2*mouthRadius+qPerMetre/2)
{
double bestExitScore=-std::numeric_limits<double>::infinity();
std::uint32_t lowerPhase=candidate;
const auto firstRing=ringPoint(candidate);
for(std::uint32_t exitCandidate=0;exitCandidate<16;++exitCandidate)
{
if(exitCandidate==candidate)continue;
const std::uint32_t previous=static_cast<std::uint32_t>(
(static_cast<int>(exitCandidate)-candidateDirection+16)%16);
const auto terminal=ringPoint(exitCandidate,loops);
const auto beforeTerminal=ringPoint(previous,loops);
const double incomingX=static_cast<double>(terminal[0]-beforeTerminal[0]);
const double incomingY=static_cast<double>(terminal[1]-beforeTerminal[1]);
const double outgoingX=static_cast<double>(lowerMouth[0]-terminal[0]);
const double outgoingY=static_cast<double>(lowerMouth[1]-terminal[1]);
const double incomingLength=std::hypot(incomingX,incomingY);
const double outgoingLength=std::hypot(outgoingX,outgoingY);
if(incomingLength<=1.0e-6||outgoingLength<=1.0e-6)continue;
const double alignment=(incomingX*outgoingX+incomingY*outgoingY)
/(incomingLength*outgoingLength);
const double separation=std::hypot(
static_cast<double>(terminal[0]-firstRing[0]),
static_cast<double>(terminal[1]-firstRing[1]));
const double score=alignment+0.20*std::clamp(
separation/(2.0*qPerMetre),0.0,2.0);
if(score>bestExitScore)
{bestExitScore=score;lowerPhase=exitCandidate;}
}
for(int step=1;step<=16;++step)
{
int directionIndex=static_cast<int>(candidate)
+candidateDirection*step;
directionIndex=(directionIndex%16+16)%16;
append(ringPoint(static_cast<std::uint32_t>(directionIndex),loops));
if(static_cast<std::uint32_t>(directionIndex)==lowerPhase)break;
}
}
return measure();
};
for(std::uint32_t offset=0;offset<16;++offset)
{
const std::uint32_t candidate=static_cast<std::uint32_t>(
(hashedPhase+16-offset)%16);
const auto point=ringPoint(candidate);
const double entryX=static_cast<double>(point[0]-upperMouth[0]);
const double entryY=static_cast<double>(point[1]-upperMouth[1]);
const double entryLength=std::hypot(entryX,entryY);
if(entryLength<=1.0e-6)continue;
const auto entryDistance=distanceQ(point[0]-upperMouth[0],
point[1]-upperMouth[1]);
for(const int candidateDirection:{hashedDirection,-hashedDirection})
{
const std::uint64_t routeLength=candidateRouteLength(candidate,
candidateDirection);
if(routeLength==0)continue;
const std::uint32_t next=static_cast<std::uint32_t>(
(static_cast<int>(candidate)+candidateDirection+16)%16);
const auto nextPoint=ringPoint(next);
const double tangentX=static_cast<double>(nextPoint[0]-point[0]);
const double tangentY=static_cast<double>(nextPoint[1]-point[1]);
const double tangentLength=std::hypot(tangentX,tangentY);
if(tangentLength<=1.0e-6)continue;
const double tangentAlignment=(entryX*tangentX+entryY*tangentY)
/(entryLength*tangentLength);
const std::int64_t firstAxisZ=upperMouth[2]+mouthRadius
-playerCenterAboveFloorQ;
const std::int64_t secondAxisZ=firstAxisZ+PassageRoundRatio(
lowerMouth[2]-upperMouth[2],static_cast<std::int64_t>(entryDistance),
static_cast<std::int64_t>(routeLength));
double candidateTreadClearance=std::numeric_limits<double>::infinity();
const int entrySamples=std::max(1,static_cast<int>(std::ceil(
entryLength/(0.25*qPerMetre))));
for(int sample=0;sample<=entrySamples;++sample)
{
const double t=static_cast<double>(sample)/entrySamples;
const double qx=upperMouth[0]+t*entryX;
const double qy=upperMouth[1]+t*entryY;
const double qz=firstAxisZ+t*(secondAxisZ-firstAxisZ)
-mouthRadius+0.20*qPerMetre;
for(const auto& previousPassage:layout.passages)
{
if(previousPassage.stableId==passage.stableId
||previousPassage.controlPoints.size()<2)continue;
for(std::size_t previousSegment=0;
previousSegment+1<previousPassage.controlPoints.size();
++previousSegment)
{
const auto& previousA=previousPassage.controlPoints[previousSegment];
const auto& previousB=previousPassage.controlPoints[previousSegment+1];
const double ax=static_cast<double>(previousA.positionSubvoxels[0]);
const double ay=static_cast<double>(previousA.positionSubvoxels[1]);
const double az=static_cast<double>(previousA.positionSubvoxels[2]);
const double dx=static_cast<double>(previousB.positionSubvoxels[0])-ax;
const double dy=static_cast<double>(previousB.positionSubvoxels[1])-ay;
const double dz=static_cast<double>(previousB.positionSubvoxels[2])-az;
const double horizontalLengthSquared=dx*dx+dy*dy;
const double projection=horizontalLengthSquared>0.0
?std::clamp(((qx-ax)*dx+(qy-ay)*dy)
/horizontalLengthSquared,0.0,1.0):0.0;
const double centerX=ax+projection*dx;
const double centerY=ay+projection*dy;
const double centerZ=az+projection*dz
-(previousA.radiusSubvoxels+projection*(
static_cast<double>(previousB.radiusSubvoxels)
-previousA.radiusSubvoxels))
+1.13*qPerMetre;
const double vertical=std::max(0.0,
std::abs(qz-centerZ)-0.54*qPerMetre);
const double clearance=std::sqrt(
(qx-centerX)*(qx-centerX)+(qy-centerY)*(qy-centerY)
+vertical*vertical)-0.34*qPerMetre;
candidateTreadClearance=std::min(candidateTreadClearance,
clearance);
}
}
}
double previousTreadClearance=std::numeric_limits<double>::infinity();
const double entryXQ=entryX,entryYQ=entryY;
const double entryZQ=static_cast<double>(secondAxisZ-firstAxisZ);
const double entryLengthSquared=entryXQ*entryXQ+entryYQ*entryYQ
+entryZQ*entryZQ;
const double mouthStartT=std::clamp(
mouthRadius/entryLength,0.0,1.0);
for(const auto& previousPassage:layout.passages)
{
if(previousPassage.stableId==passage.stableId
||previousPassage.controlPoints.size()<2)continue;
for(std::size_t previousSegment=0;
previousSegment+1<previousPassage.controlPoints.size();
++previousSegment)
{
const auto& previousA=previousPassage.controlPoints[previousSegment];
const auto& previousB=previousPassage.controlPoints[previousSegment+1];
const double ax=static_cast<double>(previousA.positionSubvoxels[0]);
const double ay=static_cast<double>(previousA.positionSubvoxels[1]);
const double az=static_cast<double>(previousA.positionSubvoxels[2]);
const double dx=static_cast<double>(previousB.positionSubvoxels[0])-ax;
const double dy=static_cast<double>(previousB.positionSubvoxels[1])-ay;
const double dz=static_cast<double>(previousB.positionSubvoxels[2])-az;
for(int sample=0;sample<=8;++sample)
{
const double t=static_cast<double>(sample)/8.0;
const double px=ax+t*dx;
const double py=ay+t*dy;
const double pz=az+t*dz
-(previousA.radiusSubvoxels+t*(
static_cast<double>(previousB.radiusSubvoxels)
-previousA.radiusSubvoxels))
+0.20*qPerMetre;
const double projection=entryLengthSquared>0.0
?std::clamp(((px-upperMouth[0])*entryXQ
+(py-upperMouth[1])*entryYQ
+(pz-firstAxisZ)*entryZQ)/entryLengthSquared,
mouthStartT,1.0):mouthStartT;
const double ex=px-(upperMouth[0]+projection*entryXQ);
const double ey=py-(upperMouth[1]+projection*entryYQ);
const double ez=pz-(firstAxisZ+projection*entryZQ);
const double clearance=std::sqrt(ex*ex+ey*ey+ez*ez)
-mouthRadius;
previousTreadClearance=std::min(previousTreadClearance,
clearance);
}
}
}
const double clearanceScore=std::isfinite(candidateTreadClearance)
&&std::isfinite(previousTreadClearance)
?std::min(candidateTreadClearance,previousTreadClearance)/qPerMetre
:0.0;
if(phaseScoreCount<phaseScores.size())
phaseScores[phaseScoreCount++]={candidate,candidateDirection,
routeLength,clearanceScore,tangentAlignment};
}
}
if(phaseScoreCount>0)
{
double bestClearance=-std::numeric_limits<double>::infinity();
for(std::size_t index=0;index<phaseScoreCount;++index)
bestClearance=std::max(bestClearance,phaseScores[index].clearance);
constexpr double ClearanceToleranceMetres=0.10;
const FPhaseScore* selected=nullptr;
for(std::size_t index=0;index<phaseScoreCount;++index)
{
const auto& candidate=phaseScores[index];
if(candidate.clearance+ClearanceToleranceMetres<bestClearance)continue;
const bool shorter=!selected||candidate.routeLength<selected->routeLength;
const bool stableLengthTie=selected
&&candidate.routeLength==selected->routeLength
&&(candidate.clearance>selected->clearance+1.0e-6
||(std::abs(candidate.clearance-selected->clearance)<=1.0e-6
&&candidate.tangent>selected->tangent));
if(shorter||stableLengthTie)selected=&candidate;
}
if(selected)
{
phase=selected->phase;
direction=selected->direction;
clockwise=direction>0;
}
}
}
direction=clockwise?1:-1;
std::vector<std::array<std::int64_t,2>> xy;
const auto pushXY=[&](std::array<std::int64_t,2> p)
{if(xy.empty()||xy.back()!=p)xy.push_back(p);};
@@ -2703,6 +2987,12 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
}
++loops;
}
const std::int64_t mouthPlanSeparation=distanceQ(
lowerMouth[0]-upperMouth[0],lowerMouth[1]-upperMouth[1]);
const bool shortGeneratedClosure=!hasLowerTangent&&!hasAnchorHelix
&&pathStyle==1u&&loops<=1
&&mouthPlanSeparation<=2*mouthRadius
&&verticalDistance<=2*mouthRadius+qPerMetre/2;
if(hasLowerTangent)
{
std::uint32_t lowerPhase=static_cast<std::uint32_t>(PassageSeed(seed,
@@ -2761,6 +3051,48 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
}
pushXY(ringPoint(lowerPhase));
}
else if(shortGeneratedClosure)
{
// A one-turn generated route whose mouths share a plan location
// must leave and rejoin from different ring tangents. Returning on
// the entry chord creates a hairpin that overlaps its own tread.
double bestExitScore=-std::numeric_limits<double>::infinity();
std::uint32_t lowerPhase=phase;
const auto firstRing=ringPoint(phase,loops);
for(std::uint32_t candidate=0;candidate<16;++candidate)
{
if(candidate==phase)continue;
const std::uint32_t previous=static_cast<std::uint32_t>(
(static_cast<int>(candidate)-direction+16)%16);
const auto terminal=ringPoint(candidate,loops);
const auto beforeTerminal=ringPoint(previous,loops);
const double incomingX=static_cast<double>(terminal[0]-beforeTerminal[0]);
const double incomingY=static_cast<double>(terminal[1]-beforeTerminal[1]);
const double outgoingX=static_cast<double>(lowerMouth[0]-terminal[0]);
const double outgoingY=static_cast<double>(lowerMouth[1]-terminal[1]);
const double incomingLength=std::hypot(incomingX,incomingY);
const double outgoingLength=std::hypot(outgoingX,outgoingY);
if(incomingLength<=1.0e-6||outgoingLength<=1.0e-6)continue;
const double tangentAlignment=(incomingX*outgoingX+incomingY*outgoingY)
/(incomingLength*outgoingLength);
const double separation=std::hypot(
static_cast<double>(terminal[0]-firstRing[0]),
static_cast<double>(terminal[1]-firstRing[1]));
const double score=tangentAlignment
+0.20*std::clamp(separation/(2.0*qPerMetre),0.0,2.0);
if(score>bestExitScore)
{bestExitScore=score;lowerPhase=candidate;}
}
for(int step=1;step<=16;++step)
{
int directionIndex=static_cast<int>(phase)+direction*step;
directionIndex=(directionIndex%16+16)%16;
pushXY(ringPoint(static_cast<std::uint32_t>(directionIndex),loops));
if(static_cast<std::uint32_t>(directionIndex)==lowerPhase)break;
}
std::printf("[ResolvedPassageGeneratedExitApproach] id=%s turns=%u phase=%u exit_phase=%u tangent_score=%.3f status=PASS\n",
passage.stableId.c_str(),loops,phase,lowerPhase,bestExitScore);
}
pushXY({lowerMouth[0],lowerMouth[1]});totalHorizontal=measurePath();
std::size_t lowerMouthIndex=xy.size()-1;
std::vector<std::array<std::int64_t,3>> lowerLandingPoints;
@@ -2839,7 +3171,7 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
for(std::size_t i=0;i<xy.size();++i)
{
std::int64_t radius=(i<=1||i==lowerMouthIndex
||i+1>=xy.size()-1)?mouthRadius:midRadius;
||(i>lowerMouthIndex&&i+1>=xy.size()-1))?mouthRadius:midRadius;
if(surfaceGroundDeparture&&i<lowerMouthIndex)
{
const double t=std::clamp(static_cast<double>(cumulative[i])
@@ -2848,6 +3180,24 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
radius=surfacePortalRadius+static_cast<std::int64_t>(std::llround(
(midRadius-surfacePortalRadius)*smooth));
}
// Grow the lower bore into its mouth over fixed route distance. Assigning
// the full mouth radius to the penultimate point creates a segment-length
// flare whose cubic-detrended profile can read as a short-period wall beat.
if(i<=lowerMouthIndex)
{
constexpr std::int64_t LowerMouthRadiusBlendLengthMetres=12;
const std::uint64_t blendLength=static_cast<std::uint64_t>(
std::max<std::int64_t>(LowerMouthRadiusBlendLengthMetres*qPerMetre,
2*mouthRadius));
const std::uint64_t distanceToLowerMouth=mouthHorizontal-cumulative[i];
if(distanceToLowerMouth<blendLength)
{
const double t=static_cast<double>(distanceToLowerMouth)/blendLength;
const double smooth=t*t*(3.0-2.0*t);
radius=midRadius+static_cast<std::int64_t>(std::llround(
(mouthRadius-midRadius)*(1.0-smooth)));
}
}
std::int64_t playerZ=0;
if(i>lowerMouthIndex)
playerZ=lowerLandingPoints[i-lowerMouthIndex-1][2];
File diff suppressed because it is too large. Load diff
+148 -7
View File
@@ -2162,6 +2162,28 @@ namespace VoxelForgeCore
TileMesh out;out.key=key;
const auto CopyIslandCacheMetrics=[&]()
{
if(options.instrumentTileDrain)
{
const auto& QueryCounts=FStandaloneShapeWorld::GetThreadClassificationStats();
out.diagnostics.densityPointCalls=QueryCounts.DensityPointCalls;
out.diagnostics.densityFloatPointCalls=QueryCounts.DensityFloatPointCalls;
out.diagnostics.densityLatticePointCalls=QueryCounts.DensityLatticePointCalls;
out.diagnostics.densityUnclampedPointCalls=QueryCounts.DensityUnclampedPointCalls;
out.diagnostics.densityX8PointCalls=QueryCounts.DensityX8PointCalls;
out.diagnostics.densityFieldEvalNanoseconds=QueryCounts.DensityFieldEvalNanoseconds;
out.diagnostics.densityFieldEvalSamplePoints=QueryCounts.DensityFieldEvalSamplePoints;
out.diagnostics.legacyFieldEvalCalls=QueryCounts.LegacyFieldEvalCalls;
out.diagnostics.legacyFieldTermCalls=QueryCounts.LegacyFieldTermCalls;
out.diagnostics.legacyFieldTermNanoseconds=QueryCounts.LegacyFieldTermNanoseconds;
out.diagnostics.legacyFieldTermTimedCalls=QueryCounts.LegacyFieldTermTimedCalls;
out.diagnostics.legacyPrimitiveCandidateVisits=
QueryCounts.LegacyPrimitiveCandidateVisits;
out.diagnostics.legacyPrimitiveDistanceEvaluations=
QueryCounts.LegacyPrimitiveDistanceEvaluations;
out.diagnostics.legacySmoothMinCalls=QueryCounts.LegacySmoothMinCalls;
out.diagnostics.legacySmoothMinNanoseconds=QueryCounts.LegacySmoothMinNanoseconds;
out.diagnostics.legacySmoothMinTimedCalls=QueryCounts.LegacySmoothMinTimedCalls;
}
if(!options.instrumentTileDrain)return;
islandCacheInstrumentation.tileCycles=VoxelForgeCoreShared::ReadCpuCycles()
-islandCacheInstrumentation.tileStartCycles;
@@ -2200,16 +2222,25 @@ namespace VoxelForgeCore
line<<" native_"<<CallerNames[i]<<"="<<a.sampledNativeCalls[i]*256<<"/"
<<a.sampledNativeCalls[i]<<"/"
<<Estimate(a.nativeCycles[i],a.sampledNativeCalls[i]*256,
a.sampledNativeCalls[i])
<<" native_"<<CallerNames[i]<<"_ns_est="
<<Estimate(a.nativeNanoseconds[i],a.sampledNativeCalls[i]*256,
a.sampledNativeCalls[i]);
line<<" field_"<<CallerNames[i]<<"="<<a.sampledFieldCalls[i]*256<<"/"
<<a.sampledFieldCalls[i]<<"/"
<<Estimate(a.fieldCycles[i],a.sampledFieldCalls[i]*256,
a.sampledFieldCalls[i])
<<" field_"<<CallerNames[i]<<"_ns_est="
<<Estimate(a.fieldNanoseconds[i],a.sampledFieldCalls[i]*256,
a.sampledFieldCalls[i]);
}
for(std::size_t i=0;i<BucketNames.size();++i)
line<<" comp_"<<BucketNames[i]<<"="
<<Estimate(a.exclusiveCycles[i],a.scopeSamples[i]*256,
a.scopeSamples[i])
<<" comp_"<<BucketNames[i]<<"_ns_est="
<<Estimate(a.exclusiveNanoseconds[i],a.scopeSamples[i]*256,
a.scopeSamples[i])
<<"/"<<a.scopeSamples[i]<<"/"<<a.scopeSamples[i]*256;
const auto& shared=islandCacheInstrumentation.shared;
line<<" local_probe_calls="<<(islandCacheInstrumentation.localHits
@@ -2323,6 +2354,7 @@ namespace VoxelForgeCore
&&bHasNativeSurfaceHorizonPlan&&!bHasIslandPlanInTile;
const bool bSkipFarShellRepair=key.lod>=7&&bUseNativeSurfaceHorizon;
FStandaloneShapeWorld::ResetThreadClassificationStats();
FStandaloneShapeWorld::SetThreadDensityQueryProfileEnabled(options.instrumentTileDrain);
VoxelForgeCore::Heightfield2D::resetThreadColumnCacheStats(
options.captureColumnCoordinates);
if(options.captureColumnCoordinates)
@@ -2335,6 +2367,7 @@ namespace VoxelForgeCore
static_cast<std::int64_t>(origin.Y+extent)*SubvoxelsPerVoxel);
}
bool bNativeSurfaceTile=false;
bool bTileProofSkipSafe=false;
bool bNativeSurfaceBandSafe=false;
double classificationColumnComputeMilliseconds=0.0;
if(bHasWorldAssembly)
@@ -2342,10 +2375,15 @@ namespace VoxelForgeCore
const double columnMillisecondsBeforeClassification=
VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds;
const auto Classification=tileShape.ClassifyBoxDetailed(origin,step,cells);
out.diagnostics.tileProofVetoReason=Classification.LegacyProofVeto;
out.diagnostics.tileProofSafe=Classification.bSkipSafe;
out.diagnostics.tileLegacyBlockProofRelevant=
Classification.bLegacyBlockProofRelevant;
classificationColumnComputeMilliseconds=std::max(0.0,
VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds
-columnMillisecondsBeforeClassification);
bNativeSurfaceTile=Classification.bNativeSurface;
bTileProofSkipSafe=Classification.bSkipSafe;
if(options.profileMesherStages)
out.diagnostics.nativeSurfaceTile=bNativeSurfaceTile;
// NativeSurfaceDensityAt consumes only the bounded manifest heightfield and its
@@ -2353,7 +2391,7 @@ namespace VoxelForgeCore
// unknown; edits and cross-strate tiles are still excluded below.
// NativeSurfaceDensityAt ne consomme que le champ et les volumes bornes du manifeste.
bNativeSurfaceBandSafe=bNativeSurfaceTile;
if(bNativeSurfaceTile)
if(bNativeSurfaceTile||bTileProofSkipSafe)
{
out.diagnostics.tileWasClassified=true;
switch(Classification.bUnknown?EVoxelTileClass::Mixed:Classification.Verdict)
@@ -2362,12 +2400,30 @@ namespace VoxelForgeCore
case EVoxelTileClass::AllAir:out.diagnostics.tileClass=TileClass::allAir;break;
case EVoxelTileClass::Mixed:out.diagnostics.tileClass=TileClass::mixed;break;
}
if((out.diagnostics.tileClass==TileClass::allSolid
if(bTileProofSkipSafe&&(out.diagnostics.tileClass==TileClass::allSolid
||out.diagnostics.tileClass==TileClass::allAir)
&&!options.hashDensity&&!options.captureDensitySamples&&!bNeedsFarShell
&&(!options.hashDensity||options.allowUniformSkipsWhenHashing)
&&!options.captureDensitySamples&&!bNeedsFarShell
&&!options.auditUniformBlockProofs)
{
out.diagnostics.meshingSkipped=true;
if(options.hashDensity)
{
const FVoxelMeshData EmptyMesh;
out.densityHash=14695981039346656037ull;
out.meshHash=HashViewerMesh(EmptyMesh);
out.payloadHash=HashPayload(EmptyMesh);
out.topologyHash=HashTopology(EmptyMesh);
out.materialHash=HashMaterialSamples(out.vertices);
if(key.lod>=4)
{
out.meshHash=SaltFarHorizonHash(out.meshHash,options.farHorizonVersion);
out.payloadHash=SaltFarHorizonHash(out.payloadHash,options.farHorizonVersion);
out.topologyHash=SaltFarHorizonHash(out.topologyHash,options.farHorizonVersion);
}
out.hasDensityHash=out.hasMeshHash=out.hasPayloadHash=out.hasTopologyHash=true;
out.hasMaterialHash=true;
}
const auto Counts=FStandaloneShapeWorld::GetThreadClassificationStats();
const auto& Columns=VoxelForgeCore::Heightfield2D::threadColumnCacheStats();
out.diagnostics.solidBoxes=Counts.SolidBoxes;
@@ -2401,10 +2457,12 @@ namespace VoxelForgeCore
out.densitySampleCount=0;
return out;
}
// Native SurfaceWorld classifies each 8-cell block against its exact sample
// lattice. This lets the mesher avoid filling density grids for proven air/rock.
mesher.bUseBlockEarlyOut=true;
}
// Preserve the committed native-field block path. Legacy block certificates
// remain audit-only until their cost is repaid against the full-grid path.
if(bHasWorldAssembly&&step<=2
&&(bNativeSurfaceTile||options.auditUniformBlockProofs))
mesher.bUseBlockEarlyOut=true;
}
const auto MeshingStart=std::chrono::steady_clock::now();
int32 MeshBandMinZVoxel=options.bandMinZVoxel;
@@ -2620,7 +2678,22 @@ namespace VoxelForgeCore
// GenerateMesh n'échantillonne que origin..origin+extent et la marge de normale d'un pas ;
// queryMinimum/queryMaximum ci-dessus couvrent exactement ce domaine inclusif.
const FStandaloneShapeWorld::FScopedPassageTileQueries passageFeatureScope(
tileShape,queryMinimum,queryMaximum);
tileShape,queryMinimum,queryMaximum,
options.profilePassageComposer?&out.diagnostics:nullptr);
if(options.profilePassageComposer)
{
out.diagnostics.passageComposerProfiled=true;
out.diagnostics.passageTileFeatureCount=passageFeatureScope.featureCount();
if(impl_->resolvedLayout)
{
out.diagnostics.passageTileRouteSegmentCandidates=
passageFeatureScope.routeSegmentCandidateCount();
for(std::size_t passageIndex=0;
passageIndex<impl_->resolvedLayout->passages.size();++passageIndex)
out.diagnostics.passageTileMouthSegmentCandidates+=
passageFeatureScope.mouthSupportSegments(passageIndex).size();
}
}
const bool bDiagnosticColumnSampling=options.diagnosticGlobalOctaveDrop!=0
||options.diagnosticColumnInterpolationStride>1;
const std::uint32_t PreviousOctaveDrop=bDiagnosticColumnSampling
@@ -2959,6 +3032,74 @@ namespace VoxelForgeCore
const auto& values=options.captureDensitySamples?out.debugDensitySamples:densityValues;
for(const float value:values)
{ densityHash^=std::bit_cast<std::uint32_t>(value);densityHash*=1099511628211ull; }
// A stream-hash run is the full-grid reference path. Record the exact sign
// uniformity ceiling from the same captured lattice that feeds Marching Cubes.
// Blocks are disjoint sample cubes; partial cubes at tile edges are included.
const std::size_t gridWidth=static_cast<std::size_t>(
std::clamp(MeshCells,2,CHUNK_SIZE)+3);
const std::size_t xySamples=gridWidth*gridWidth;
const std::size_t zSamples=xySamples>0&&values.size()%xySamples==0
?values.size()/xySamples:0;
bool tileHasSign=false,tileUniform=true;
bool tileAir=false;
for(const float value:values)
{
if(!std::isfinite(value)||value==0.0f)
{tileUniform=false;continue;}
const bool air=value>0.0f;
if(!tileHasSign){tileHasSign=true;tileAir=air;}
else if(tileAir!=air)tileUniform=false;
}
const auto CountUniformBlocks=[&](std::size_t blockWidth)
{
std::uint64_t uniformBlocks=0,uniformSamples=0;
if(zSamples==0)return std::pair{uniformBlocks,uniformSamples};
for(std::size_t z0=0;z0<zSamples;z0+=blockWidth)
for(std::size_t y0=0;y0<gridWidth;y0+=blockWidth)
for(std::size_t x0=0;x0<gridWidth;x0+=blockWidth)
{
const std::size_t z1=std::min(zSamples,z0+blockWidth);
const std::size_t y1=std::min(gridWidth,y0+blockWidth);
const std::size_t x1=std::min(gridWidth,x0+blockWidth);
bool hasSign=false,uniform=true,air=false;
for(std::size_t z=z0;z<z1&&uniform;++z)
for(std::size_t y=y0;y<y1&&uniform;++y)
for(std::size_t x=x0;x<x1;++x)
{
const float value=values[(z*gridWidth+y)*gridWidth+x];
if(!std::isfinite(value)||value==0.0f)
{uniform=false;break;}
const bool sampleAir=value>0.0f;
if(!hasSign){hasSign=true;air=sampleAir;}
else if(air!=sampleAir){uniform=false;break;}
}
if(uniform&&hasSign)
{
++uniformBlocks;
uniformSamples+=static_cast<std::uint64_t>(z1-z0)
*static_cast<std::uint64_t>(y1-y0)
*static_cast<std::uint64_t>(x1-x0);
}
}
return std::pair{uniformBlocks,uniformSamples};
};
const auto [uniformBlocks8,uniformSamples8]=CountUniformBlocks(8);
const auto [uniformBlocks4,uniformSamples4]=CountUniformBlocks(4);
const double centerZ=(static_cast<double>(origin.Z)
+static_cast<double>(extent)*0.5)*static_cast<double>(world.CoreVoxelSizeMicrons)
/10000.0;
const int32 stratum=world.Manager
?world.Manager->GetStrateIndex(static_cast<float>(centerZ)):-1;
std::printf("[UniformGridCeiling] strate=%d lod=%d key=(%d,%d,%d) samples=%zu uniform=%d air=%d blocks8=%llu samples8=%llu blocks4=%llu samples4=%llu proof_safe=%d proof_veto=%u\n",
stratum,key.lod,key.coordinate.x,key.coordinate.y,key.coordinate.z,values.size(),
tileUniform&&tileHasSign?1:0,tileUniform&&tileHasSign&&tileAir?1:0,
static_cast<unsigned long long>(uniformBlocks8),
static_cast<unsigned long long>(uniformSamples8),
static_cast<unsigned long long>(uniformBlocks4),
static_cast<unsigned long long>(uniformSamples4),
out.diagnostics.tileProofSafe?1:0,
static_cast<unsigned>(out.diagnostics.tileProofVetoReason));
out.densityHash=densityHash;out.meshHash=HashViewerMesh(mesh);
out.payloadHash=HashPayload(mesh);out.topologyHash=HashTopology(mesh);
out.materialHash=HashMaterialSamples(out.vertices);
+3 -3
View File
@@ -394,9 +394,9 @@ VF_FIELD_INLINE float VF_FieldOwnerCellular(float x,float y,float z)
{
const VF_FieldI32 nx=cellX+dx,ny=cellY+dy,nz=cellZ+dz;
const VF_FieldU32 h=VF_FieldVoxelHashMix(
VF_FIELD_CAST_U32(nx+0x7FFFFFFF)
^VF_FieldVoxelHashMix(VF_FIELD_CAST_U32(ny+0x7FFFFFFF)*2654435761u)
^VF_FieldVoxelHashMix(VF_FIELD_CAST_U32(nz+0x7FFFFFFF)*374761393u));
(VF_FIELD_CAST_U32(nx)+0x7FFFFFFFu)
^VF_FieldVoxelHashMix((VF_FIELD_CAST_U32(ny)+0x7FFFFFFFu)*2654435761u)
^VF_FieldVoxelHashMix((VF_FIELD_CAST_U32(nz)+0x7FFFFFFFu)*374761393u));
const float fpx=VF_FIELD_CAST_F32(dx)
+VF_FIELD_CAST_F32(h&0xFFFFFFu)*(1.0f/16777216.0f)-fracX;
const VF_FieldU32 hy=VF_FieldVoxelHashMix(h^0x12345678u);
@@ -0,0 +1,133 @@
# VoxelForge — design review: how do we get a cold whole view from ~2.5–4.6 s to ~1 s?
You're reviewing a voxel terrain generator's performance architecture. **Read and reason only.** Don't edit,
build, run tests, commit or stash. You may read any file in this repository and search the web.
We've spent about 15 rounds finding and fixing hot spots, and the gains are shrinking. We want fresh eyes on the
*approach*, not another micro-optimisation. Question our framing where it's wrong.
## The system (summary, so you don't have to rediscover it)
**VoxelForge** is a UE5 plugin for underground density-field terrain. Generation lives in a standalone C++
library, "Core" (`Standalone/Core/`), that Unreal calls through an adapter
(`Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp`).
- **The field.** Density is a *function*, not stored data: negative = solid. Core voxels are 25 cm. Tiles hold
32³ voxels.
- **Clipmap.** LOD0–8; each LOD doubles the voxel step. A "whole view" is about 2,250–2,850 tiles across all LODs
around one camera.
- **Meshing.** Marching cubes per tile (about 35³ samples per LOD0/1 tile with borders), plus normals and skirts.
- **The world is a vertical stack of "strates".** Each is ceiling → content → bedrock, with its own generator:
- SurfaceWorld (SW): heightfield, native path;
- FloatingIslands (FI), TunnelNetwork (TN) and VerticalShafts (VS): the "legacy compiled-analytic" path.
- **The owner's test world** stacks four of them: SW 512 m, FI 240 m, TN 128 m, VS 128 m tall.
- **Passages.** Walkable, winding, natural-looking tunnels near (0,0) connect each strate to the next. They're
carved by a shared "composer" that also adds support treads, boundary bands and mouth shaping.
- **Multiplayer.** No geometry is replicated. Every peer regenerates from the seed, the layout and a diff of
player edits. That's why peer determinism is mandatory.
## The goal and constraints
- **Goal:** about 1 s cold whole view at any strate arrival on the owner's PC (Ryzen 5800X3D, 8 cores/16 threads,
7900 XTX). Streaming must stay smooth in VR (Quest 3) and at high travel speed.
- **Must hold:**
- peer determinism (same inputs → same world on every machine);
- no visible quality regression;
- passages stay walkable from arrival mouth to departure mouth (capsule, support and grade audits exist);
- no game-thread hitches.
- **Not required:** byte-identical output with earlier versions. The owner accepts that perf passes change the
world's shape slightly, as long as every peer gets the same result and quality holds. (We have tended to treat
hash equality as a gate anyway; it's a convenience, not a rule.)
- **On the shelf, not a speed excuse:** a disk mesh cache is an accepted feature, but perf is judged cold.
## Measurements (owner world, measured on an i9-12900H with 8 workers, headless unless stated)
**Whole view now** (it was 2.6 / 7.2 / 6.0 / 2.7 s a week ago):
| Strate | Whole view |
|---|---:|
| SW | 2.4 s |
| FI | 4.6 s |
| TN | 4.3 s |
| VS | 2.5 s |
**In Unreal (SW arrival):**
- Core work equals headless;
- initial set ready at 3.74 s vs headless 2.41 s, the extra being scheduling;
- walkable ring 0.4–1.8 s.
**Where the time goes:**
- **L73:** field evaluation inside grid fill is about 40 of FI's 46 worker-seconds (TN 33 of 39). LOD0/1 tiles
evaluate about 44k density samples each.
- **L74/L75:** sound whole-tile skip proofs now certify about 22–26% of tile visits.
- Ceiling: 46–82% of LOD0–2 tiles are truly uniform, and 90–97% of samples lie in uniform 8³ blocks.
- Dominant remaining veto: tiles crossing a strate boundary.
- Legacy sub-tile (block) proofs were unsound once (missed passage wall/floor relief), then sound but slower
than they saved.
- **L76, per legacy sample:**
- passage/support composer ≈ **1.05 µs**;
- primitive scan ≈ 0.19 µs (0.25 primitive distance evaluations per sample, binned);
- boundary band ≈ 0.06 µs;
- noise ≈ 0.01 µs.
**About 75% of field time is the passage composer, even far from passages.**
- Per-tile passage culling was tried and changed 612 density samples / 24 mesh tiles. It was rejected only for
breaking hash equality, which is a convenience, not a rule; nobody checked whether that change was harmless.
**Tried and rejected:**
- the native FI generator (fails passage walk audits on this plan);
- deeper Unreal queues (done: 4.78 → 3.74 s).
**Trap we fell into once:** a diagnostic hash mode silently disabled the skips and doubled the timings. Measure the
production path.
## What to reason about (open; don't limit yourself to these)
1. **Is "evaluate a density function at ~44k points per tile, then march cubes" the right shape** for this world?
Consider, for example:
- sparse/hierarchical evaluation (octree or brick refinement driven by interval or Lipschitz bounds, as in
libfive/Fidget/MPR-style implicit rendering);
- reusing samples across nested clipmap LODs;
- evaluating smooth large-scale terms coarsely and interpolating;
- narrow-band evaluation near the surface only.
2. **The passages are a few winding tubes.** Why does every sample pay 1 µs for them? What representation would
make "far from any passage" free and "near a passage" cheap? For example:
- bake each passage once at plan time into a sparse distance/brick structure;
- a spatial index of segments with a conservative distance early-out;
- a per-tile passage candidate list built once.
What would a correct per-tile culling need so that it doesn't change the result? And if it changes the result,
how should we judge whether that change is acceptable?
3. **Which work is needed for "ready in 1 s"?** Maybe the target should be redefined: walkable ring first,
progressive LOD refinement, far LODs from cheaper representations or impostors, fewer tiles. What do comparable
engines do (Voxel Plugin, Teardown, Dreams, No Man's Sky, Minecraft-style streaming, Nanite-era voxel work)?
4. **Hardware.** 8 CPU cores plus a 7900 XTX that's mostly idle during load. An earlier GPU port was closed, and
determinism across GPU vendors is a concern. Is there a deterministic way to use it, e.g. integer/fixed-point
evaluation or a CPU-authoritative plus GPU-display split? Is SIMD (AVX2) evaluation of the legacy field
realistic?
5. **Anything in our process or measurement** that hides the real bottleneck.
## How to work efficiently
- **Trust the numbers above; don't re-measure or re-derive them.** Spend your effort on thinking.
- **Read code only to check whether a specific idea fits.** Entry points:
- `CODEMAP.md` (navigation) and `ARCHITECTURE.md` §8.10 (performance invariants);
- `Standalone/Core/src/ShapeWorldInternal.h`: composer, `AnalyticDensityAt`, `ClassifyBoxDetailed`, passage
terms;
- `Standalone/Core/src/VoxelForgeCore.cpp`: tile generation and skips;
- reports in `Standalone/Research/WorldAssemblerM3/`: `Linux73`–`Linux76`, `Research36`, and R38–R40 notes
inside `Linux73/` and `Linux76/`.
- **Use the web** for prior art and techniques, and cite them.
- **Go broad first** (many approaches, one or two lines each), then deep on the best three.
## Output
At most ~2,500 words, plain language, **ranked recommendations first**. For each of the top three:
- what changes, and where in this codebase (file and function);
- expected gain, with arithmetic from the numbers above;
- what it risks: determinism, quality, walkability;
- effort;
- **a cheap experiment that would prove it wrong** before anyone builds it fully.
Then the shorter list of other ideas, then any questions only the owner can answer (e.g. what "ready" must mean).
Mark claims as [CODE] (read in this repo), [MEASURED] (from the numbers above), [PRIOR ART] (cited) or
[INFERENCE].
@@ -0,0 +1,137 @@
# Astra 1 — design review (gpt-6-astra, effort low, read-only), 2026-10-27
Brief: see BRIEF.md. Note from Claude: the brief's "~75% of field time is the passage composer" was WRONG — L76's term profiler covered ~6.0M of ~52M density calls per view (FI); measured passage cost is ~6.5 of ~40 worker-s (~15%). Astra flagged the scope mismatch itself. Its gain arithmetic in rec. 1 inherits the wrong share.
**[INFERENCE] Ranked recommendations:**
1. **Compile passages into spatially local operations.** Make most samples bypass the composer entirely; simplify the remaining work.
2. **Replace repeated block classification with a retained hierarchy of proofs and local field programs.** Keep marching cubes; stop densely evaluating regions that cannot produce geometry.
3. **Give arrival readiness its own deadline-driven pipeline.** Preserve whole-view completion as a separate benchmark; scheduling improvements alone cannot meet it.
**[INFERENCE]** These are complementary. Passage localization is the strongest first investment, but does not plausibly deliver one second alone. A wholesale mesher replacement, GPU rewrite, or native-generator migration is premature.
### 1. Make passage influence local by construction
**[CODE]** There is already a spatial index: `BuildPassageFeatureTileList` queries passage feature bounds. `FScopedPassageTileQueries` turns route candidates into per-passage flags, while retaining segment lists for mouth support. Nevertheless, `ApplyResolvedStratePassageCarves` still iterates all passages and scans crossings. After finding a route distance, it calculates relief and mouth-related terms. See [ShapeWorldInternal.h](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:989).
**[INFERENCE] What changes:** compile a small, immutable passage program for each spatial region:
- Empty region: no passage invocation.
- Route region: ordered candidate segments, precomputed geometry and distances along the route.
- Mouth/support region: explicit support, transition-fade and later-bore dependencies.
- Overlapping features: retain the original composition order.
Use `BuildPassageComposerConstants`, `BuildPassageFeatureTileList`, `FScopedPassageTileQueries`, and `ApplyResolvedStratePassageCarves`; connect the result through `World::generateTile` in [VoxelForgeCore.cpp](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:1997). This is stronger than filtering one loop: the region program should remove entire categories of work.
**[INFERENCE] Exact culling requires an identity proof**, not merely “the tube does not intersect this tile.” For every omitted operation, prove that it leaves the incoming density unchanged over the query domain. Include:
- Spline extrema, variable radius, wall/floor relief and smooth-blend reach.
- Mouth shaping, support treads, columns, boundary fade and dependencies on other passages.
- All sample positions used by normals, borders and skirts.
- Deterministic ordering and tie-breaking among retained features.
A feature can leave density signs unchanged while altering marching-cubes interpolation or normals. Conversely, changing far-away density magnitudes may be harmless. These are different correctness claims.
**[CODE]** L75 already corrected bounds for passage relief and spline extrema. The composer also conditionally restores support and defers support composition. Therefore, baking one distance to the nearest tube would not reproduce its semantics. [L75 report](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux75/REPORT.md:22)
**[INFERENCE]** If exact influence is awkward or effectively nonlocal, introduce a versioned, compact-support passage operator: identical in the protected walkable corridor and mouth regions, smoothly becoming identity outside an explicit envelope. That is a legitimate world-generation change under your constraints.
**[MEASURED → INFERENCE] Expected gain:** passage work is approximately `0.75 × 40 = 30` of FI’s 46 worker-seconds, and `0.75 × 33 = 24.75` of TN’s 39. Removing 80–90% gives proportional wall-time scenarios:
| View | 80% passage reduction | 90% passage reduction |
|---|---:|---:|
| FI | `4.6 × (46−24)/46 = 2.20 s` | `4.6 × (46−27)/46 = 1.90 s` |
| TN | `4.3 × (39−19.8)/39 = 2.12 s` | `4.3 × (39−22.275)/39 = 1.84 s` |
These assume critical-path time follows work reduction; they are not predictions for the Ryzen. Even eliminating passage cost entirely leaves approximately **1.6 seconds** in both views.
**[INFERENCE] Risks and effort:** medium effort for regional programs; larger for a new passage representation. Stable candidate ordering and versioned arithmetic protect determinism. Underestimated envelopes threaten clearance and support; overly generous envelopes merely lose speed. Run capsule, grade and support audits across entire passages, including landings and seams.
**[INFERENCE] Cheap falsifier:** resurrect the rejected culling candidate as an experiment. First inspect all 612 changed samples and 24 changed mesh tiles: location, zero crossings, surface displacement, normals, support and audit outcomes. Then time the production path including candidate construction. Reject this implementation if conservative lists remain large or composer savings are small. Hash differences alone are neither acceptance nor rejection.
### 2. Retain spatial proofs and simplify the field inside each region
**[INFERENCE]** A continuous density function remains a good world definition. Evaluating its complete program at every lattice point is the questionable implementation. Marching cubes is not currently the dominant problem.
**[CODE]** `ClassifyBoxDetailed` delegates to `ClassifyBoundsDetailed`. The latter already performs conditional strate slicing, but combines slices into a whole-box verdict and abandons the skip when a slice is unknown or has a different sign. L75’s legacy block certificates became audit-only after timing regressions. Simply enabling more block checks repeats a failed approach. [Classifier](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:6385), [L75 final disposition](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux75/REPORT.md:64)
**[INFERENCE] What changes:** introduce a compact region tree or brick table whose entries retain:
- A solid/air certificate, or an unresolved range.
- Relevant primitive and passage candidates.
- Terms proven inactive or dominated.
- Child regions requiring further evaluation.
Split first at known strate and transition-band boundaries, then refine unresolved regions spatially. Keep successful proofs even if a neighboring slice fails. Share candidate filtering and bounds down the hierarchy; do not repeatedly invoke the full classifier from scratch.
Start tile-local and retain the regular marching-cubes lattice. Extend `ClassifyBoundsDetailed`, the evaluation feeding `AnalyticDensityAt`, `World::generateTile`, and the grid/proof path in `UVoxelMarchingCubesMesher::GenerateMesh` in [VoxelMarchingCubesMesher.cpp](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp:102).
**[PRIOR ART]** Fidget combines interval evaluation with regional expression simplification: even unresolved regions can use shorter programs. That second benefit matters here as much as empty-space skipping. It also supports bulk SIMD evaluation. [Fidget’s author documentation](https://www.mattkeeter.com/projects/fidget/)
**[INFERENCE]** Do not assume the composed density is a signed distance field with Lipschitz constant one. Warps, blends and relief require their actual bounds. Uniform corners do not prove a uniform interior.
Similarly, do not fill certified interiors with arbitrary constants and then use those constants at surface edges or in normal stencils. Evaluate the exact endpoint and halo values wherever geometry or gradients consume them. A narrow band must be *discovered conservatively*, not guessed from sparse signs.
**[MEASURED → INFERENCE] Expected gain:** the 90–97% uniform-block sample census is an opportunity ceiling, not a realizable skip rate. A useful experiment target is **50–70% reduction of remaining field work, net of classification cost**.
After a 90% passage reduction, FI has approximately 13 field-work units plus 6 others. Reducing those 13 by 50–70% gives:
`4.6 × [6 + 13 × (0.5 to 0.3)] / 46 = 1.25 to 0.99 s`.
TN similarly gives approximately **1.25–1.02 seconds**. Thus the combination approaches the headless target, with little budget left for Unreal overhead. Do not multiply independent headline speedups: both techniques remove overlapping work.
**[INFERENCE] Risks and effort:** medium-to-large effort, substantially less than adaptive meshing with new topology. Incorrect proofs can silently delete geometry; edits must invalidate affected certificates, including their dependency halos. Quality and walkability can remain unchanged if surface samples and stencils remain exact. Unknown bounds must fall back.
**[INFERENCE] Cheap falsifier:** implement one tile-local hierarchy for representative boundary-crossing and passage-adjacent tiles, retaining the existing mesher. Compare total classification-plus-fill cost against dense evaluation, after passage localization. Reject expansion if net field savings fall well below 50%, or bookkeeping and conservative halos consume the benefit. The prior slow block experiment makes this an essential early gate.
### 3. Schedule for an explicit arrival deadline
**[MEASURED]** SW’s Unreal overhead is `3.74−2.41 = 1.33 s`; walkable readiness ranges from 0.4 to 1.8 seconds.
**[INFERENCE]** Keep two separate commitments:
- **Arrival ready:** safe collision and sufficient visible terrain for immediate movement.
- **Whole view complete:** the full requested set at final quality.
If the owner insists both happen within one second, say so explicitly. Progressive refinement cannot be counted as meeting the second commitment.
**[CODE]** §8.10 already specifies occupied/support-tile priority, heading priority, dynamic near reserve, independent region admission, cancellation, bounded result backlog and hot-swap. “Make nearby tiles higher priority” is therefore not a new recommendation. [Performance invariants](/home/jahni/VoxelM/Plugins/VoxelForge/ARCHITECTURE.md:572)
**[INFERENCE] What changes:** make readiness a dependency graph with deadlines: required region compilation → field/mesh → collision preparation → bounded publication. Prioritize prerequisites of the arrival set, not just its tile jobs. Reserve capacity through publication, and schedule travel-ahead work by time-to-arrival.
Inspect and change `AVoxelWorld::UpdateChunksAroundPosition`, `LoadTile`, and `ProcessPendingChunks` in [VoxelWorld.cpp](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelWorld.cpp:9400). Preserve the existing epoch, cancellation and collision-handoff rules. Refinement should retain covering geometry until replacements are ready.
**[PRIOR ART]** Voxel Plugin separates collision/navmesh demand through invokers. Geometry clipmaps use incremental updates, coarse-to-fine processing and continuity between levels. These support separate readiness domains, not a universal one-second guarantee. [Voxel Plugin](https://docs.voxelplugin.com/2.0p6/knowledgebase/blueprints/collision-navmesh-and-invokers), [Geometry clipmaps](https://developer.nvidia.com/gpugems/gpugems2/part-i-geometric-complexity/chapter-2-terrain-rendering-using-gpu-based-geometry)
**[MEASURED → INFERENCE] Expected gain:** removing half the observed scheduling gap saves about **0.67 seconds**; removing all of it still leaves SW at 2.41 seconds before Core changes. Improving a 1.8-second walkable arrival to one second requires eliminating or deferring 0.8 seconds from that critical path.
**[INFERENCE] Risks and effort:** small-to-medium for dependency scheduling; larger for new far representations. Timing must never change generation inputs. Coarse collision can violate walkability; coarse visuals can pop, particularly in VR. Preserve authoritative near collision and accept temporary visual substitutes only if they satisfy the owner’s quality criterion.
**[INFERENCE] Cheap falsifier:** first replay existing job timings against the dependency graph, then try an arrival-only admission experiment. If prerequisites plus bounded publication already exceed one second without competing work, scheduling cannot solve arrival latency. If the coarse cover visibly degrades the arrival, it cannot redefine “ready.”
**Other approaches, in priority order**
- **[INFERENCE] Bake passage geometry, not necessarily final density.** Precompute spline coefficients, arc lengths, frames and conservative segment bounds first. Sparse sampled bricks are a later option; retain separate carve/support operands and conditions. Count baking in cold latency. Interpolated distance alone can erase treads or alter mouths.
- **[INFERENCE] SIMD after localization.** AVX2 evaluation of batches sharing a local program is realistic; divergent all-passage loops are a poor starting point. Eight lanes do not imply eightfold speedup. Specify arithmetic and reduction order across scalar/vector paths.
- **[INFERENCE] Sample sharing.** Share same-LOD borders first; cross-LOD reuse requires identical coordinates *and field semantics*. Nested lattices overlap, but clipmap shells need not cover the same volume. Compare synchronization cost against duplicate evaluation before building a global cache.
- **[INFERENCE] Coarse smooth terms.** Interpolate bounded macro terms while evaluating fine relief near surfaces. Density error does not imply equal position error where gradients are weak. L76’s 0.01-µs noise row is too small to justify making noise approximation the main project.
- **[INFERENCE] GPU.** A specified integer/fixed-point evaluator can be deterministic: define rounding, overflow, square roots, interpolation and stable emission order. Quantizing floating-point results afterward is insufficient. CPU-authoritative collision plus GPU display is easier, provided visual disagreement is bounded and display results never affect gameplay. GPU readbacks and VR contention can erase gains.
- **[PRIOR ART → INFERENCE]** HLSL `precise` controls arithmetic refactoring; it is not a complete cross-vendor world-generation specification. Prototype one bounded integer passage kernel before reopening a full port. [Microsoft documentation](https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/precise)
- **[PRIOR ART → INFERENCE] Alternative renderers.** Dreams’ published architecture evaluates CSG into multiresolution point clouds; Teardown uses a voxel renderer without polygons. These demonstrate representation choices, not transferable cold-generation timings. Nanite addresses rendering downstream of field generation. [Dreams presentation](https://www.advances.realtimerendering.com/s2015/index.html), [Teardown FAQ](https://www.teardowngame.com/faq.html)
- **[PRIOR ART → INFERENCE] Continuous streaming.** No Man’s Sky’s architecture is explicitly organized around continuous generation. That supports travel-ahead planning, but does not prove cold teleport latency. Disk caching likewise remains useful without counting toward this target. [Hello Games’ GDC talk](https://www.youtube.com/watch?v=sCRzxEEcO2Y)
**Process changes that matter**
**[INFERENCE]** Separate three gates: peer determinism for the *new version*; gameplay/visual acceptance against the previous version; and performance on the production path. Old-version hash identity should only gate changes advertised as exact.
**[CODE]** L76’s term profiler covers selected legacy evaluations; its passage total is about 6.3 worker-seconds, whereas L73 reports broader grid-field totals. Those scopes must not be silently equated. The arithmetic above uses your supplied aggregate attribution as a scenario, not reconciled absolute profiling data. [L76 profiler scope](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux76/REPORT.md:5)
**[INFERENCE]** Report cold-start boundaries, critical-path stages, worker occupancy, publication latency and worst frame time alongside whole-view time. Include plan compilation and fresh in-memory structures. Validate the winning prototype on the 5800X3D; no reliable conversion follows from the laptop measurements alone.
**Questions only the owner can settle**
- Must every final-quality tile exist at one second, or can safe arrival precede complete refinement?
- What travel speed, teleport distance and VR frame budget must streaming sustain?
- Does “cold” include world planning, and must supported peers reproduce density bits, mesh bits, or an explicitly defined gameplay representation?
Read-only review completed; no edits, builds, tests, commits or stashes.
@@ -4,9 +4,11 @@ import argparse
import importlib.util
import math
from collections import defaultdict, deque
import os
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
BASELINES = Path(os.environ.get("VOXELFORGE_VERIFY_OUT", ROOT / "Saved/Linux/l37")) / "baselines"
PROFILE_PATH = ROOT / "Standalone/Research/WorldAssemblerM3/Linux32/AuditRingTerraceL32.py"
SPEC = importlib.util.spec_from_file_location("ring_profile_l37", PROFILE_PATH)
PROFILE = importlib.util.module_from_spec(SPEC)
@@ -502,8 +504,8 @@ def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--out", type=Path, default=ROOT / "Saved/Linux/l37")
parser.add_argument("--baseline-out", type=Path,
default=ROOT / "Saved/Linux/l37/no-tread-control")
parser.add_argument("--control-out", type=Path, default=ROOT / "Saved/Linux/l36")
default=BASELINES / "mouth/l37-no-tread")
parser.add_argument("--control-out", type=Path, default=BASELINES / "mouth/l36")
parser.add_argument("--scene", choices=tuple(SCENES) + ("all",), default="all")
args = parser.parse_args()
out = args.out if args.out.is_absolute() else ROOT / args.out
@@ -15,6 +15,7 @@ from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = Path(os.environ.get("VOXELFORGE_L37_OUT",
os.environ.get("VOXELFORGE_VERIFY_OUT", ROOT / "Saved/Linux/l37")))
BASELINES = OUT / "baselines"
BIN = OUT / "bin"
CAPTURES = Path(os.environ.get("VOXELFORGE_L37_CAPTURE_DIR",
OUT / "captures"))
@@ -23,10 +24,12 @@ HEADLESS = Path(os.environ.get("VOXELFORGE_L37_HEADLESS",
RENDERER = Path(os.environ.get("VOXELFORGE_L37_RENDERER",
BIN / "VoxelForgeOffscreenCapture-l37-final-clang"))
PNG_DIR = OUT / "capture-png"
OWNER = Path(os.environ.get("VOXELFORGE_OWNER_DATA",
ROOT / "Standalone/data/owner_world.json"))
RECIPE_INPUTS = (
("owner-fi", ROOT / "Standalone/data/owner_world.json",
("owner-fi", OWNER,
"floating-islands", "FloatingIslands", "upper"),
("owner-sw", ROOT / "Standalone/data/owner_world.json",
("owner-sw", OWNER,
"surface-world", "SurfaceWorld", "lower"),
("style-override-upper-sw-fixture-style0",
OUT / "fixtures/style-override-upper-sw-fixture-style0.json",
@@ -136,7 +139,7 @@ def fmt(values):
def load_reference_cameras():
log = ROOT / "Saved/Linux/l34/run-capture-standard-set-final-radius3.log"
log = BASELINES / "capture/l34-standard-set-cameras.baseline"
pattern = re.compile(
r"^\[L34CaptureCamera\] view=(\S+).*?camera_m=\(([^)]*)\) "
r"target_m=\(([^)]*)\) clip=(\S+)", re.M)
@@ -2,6 +2,7 @@
"""Run five interleaved whole-view cost pairs against the L36 Clang binary."""
import csv
import math
import os
import re
import statistics
import subprocess
@@ -10,6 +11,8 @@ from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l37"
OWNER = Path(os.environ.get("VOXELFORGE_OWNER_DATA",
ROOT / "Standalone/data/owner_world.json"))
BINARIES = {
"l36": ROOT / "Saved/Linux/l36/bin/VoxelForgeCoreHeadless-l36-final-clang",
"l37": OUT / "bin/VoxelForgeCoreHeadless-l37-final-clang",
@@ -30,7 +33,7 @@ for recipe in ("surface-world", "floating-islands"):
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "900s", str(BINARIES[build]),
str(ROOT / "Standalone/data/owner_world.json"),
str(OWNER),
"--workers=12", "--world-assembler-prototype", "--stream-benchmark",
"--stream-pipeline", "--force-generate", "--clip-radius=3",
"--max-clip-level=8", "--max-level=-1", f"--recipe={recipe}",
@@ -1,6 +1,7 @@
#!/usr/bin/env python3
"""Check L25, 24 m crossing and upper-SW fixture determinism for L37."""
import csv
import os
import re
import subprocess
import time
@@ -9,7 +10,8 @@ from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l37"
OWNER = ROOT / "Standalone/data/owner_world.json"
OWNER = Path(os.environ.get("VOXELFORGE_OWNER_DATA",
ROOT / "Standalone/data/owner_world.json"))
FIXTURE = ROOT / "Saved/Linux/l33/fixtures/sw-upper-tunnel-network-style0.json"
BINARIES = {
"clang": OUT / "bin/VoxelForgeCoreHeadless-l37-final-clang",
@@ -1,6 +1,7 @@
#!/usr/bin/env python3
"""Run L33 route/proof and transition invariants with recipe-stamped logs."""
import re
import os
import subprocess
import time
from pathlib import Path
@@ -48,7 +49,8 @@ def run(name, binary, data, recipe, extra, checks):
failed |= not ok
owner = ROOT / "Standalone/data/owner_world.json"
owner = Path(os.environ.get("VOXELFORGE_OWNER_DATA",
ROOT / "Standalone/data/owner_world.json"))
for recipe in ("surface-world", "floating-islands"):
for compiler, binary in BINARIES.items():
name = f"invariant-route-proof-{recipe}-{compiler}"
@@ -8,9 +8,11 @@ import json
import math
import re
import argparse
import os
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
BASELINES = Path(os.environ.get("VOXELFORGE_VERIFY_OUT", ROOT / "Saved/Linux/l37")) / "baselines"
BASE_PATH = ROOT / "Standalone/Research/WorldAssemblerM3/Linux37/RunMouthFragmentAuditL37.py"
SPEC = importlib.util.spec_from_file_location("mouth_fragment_l37", BASE_PATH)
BASE = importlib.util.module_from_spec(SPEC)
@@ -184,8 +186,8 @@ def main():
parser.add_argument("--capture-log-sw", type=Path)
args = parser.parse_args()
paths = {
"L35": ROOT / "Saved/Linux/l35/owner-fi-ground-mouth.vfmesh",
"L36": ROOT / "Saved/Linux/l36/owner-fi-ground-mouth.vfmesh",
"L35": BASELINES / "mouth/l35/owner-fi-ground-mouth.vfmesh",
"L36": BASELINES / "mouth/l36/owner-fi-ground-mouth.vfmesh",
"L37": args.candidate if args.candidate.is_absolute()
else ROOT / args.candidate,
}
@@ -11,9 +11,11 @@ from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = Path(os.environ.get("VOXELFORGE_VERIFY_OUT", ROOT / "Saved/Linux/l37"))
BASELINES = OUT / "baselines"
BIN = Path(os.environ.get("VOXELFORGE_VERIFY_HEADLESS",
OUT / "bin/VoxelForgeCoreHeadless-l37-final-clang"))
OWNER = ROOT / "Standalone/data/owner_world.json"
OWNER = Path(os.environ.get("VOXELFORGE_OWNER_DATA",
ROOT / "Standalone/data/owner_world.json"))
TERRACE = ROOT / "Standalone/Research/WorldAssemblerM3/Linux32/AuditRingTerraceL32.py"
RING_SET = ROOT / "Standalone/Research/WorldAssemblerM3/Linux32/AuditRingSetL32.py"
STAMP = re.compile(
@@ -176,9 +178,9 @@ for label, recipe, surface, window_min, window_max, camera0, camera1, centers in
# Keep the established L30 terrace and L31 flat-surface negative controls live.
controls = (
("l30-terrace", ROOT / "Saved/Linux/l32/fi-floor-l30-camera-negative.vfmesh",
("l30-terrace", BASELINES / "ring/l32-fi-floor-l30-camera-negative.vfmesh",
-130.0, -110.0),
("l31-flat", ROOT / "Saved/Linux/l31/fi-floor-v19.vfmesh", -122.0, -117.0),
("l31-flat", BASELINES / "ring/l31-fi-floor-v19.vfmesh", -122.0, -117.0),
)
for name, mesh, height_min, height_max in (() if OPTIONS.skip_controls else controls):
result, output, _ = run(f"control-{name}", ["python3", TERRACE, mesh,
@@ -14,6 +14,7 @@ import time
ROOT = Path(__file__).resolve().parents[4]
BASELINES = Path(os.environ.get("VOXELFORGE_VERIFY_OUT", ROOT / "Saved/Linux/l47")) / "baselines"
VERIFY = ROOT / "Standalone/Tools/verify"
LINUX37 = ROOT / "Standalone/Research/WorldAssemblerM3/Linux37"
sys.path.insert(0, str(VERIFY))
@@ -177,8 +178,8 @@ if not mesh.is_file() or mesh.stat().st_size == 0:
entrance_audit = ROOT / "Standalone/Research/WorldAssemblerM3/Linux47/MeasureEntranceL47.py"
run_logged("entrance-floor-control-l46", [sys.executable, str(entrance_audit),
"--mesh", str(ROOT / "Saved/Linux/l46/landmark-l46.vfmesh"),
"--layout", str(ROOT / "Saved/Linux/l46/layouts/landmark-reachability-clang.json"),
"--mesh", str(BASELINES / "landmarks/l46-entrance-control.vfmesh"),
"--layout", str(BASELINES / "landmarks/l46-entrance-control-layout.json"),
"--out", str(ARTIFACT_DIR / "entrance-profile-control-l46.json"), "--expect=fail"])
run_logged("entrance-floor-audit", [sys.executable, str(entrance_audit),
"--mesh", str(mesh), "--layout", str(LAYOUT_PATH),
@@ -6,11 +6,13 @@ import importlib.util
import argparse
import json
import math
import os
from pathlib import Path
import sys
ROOT = Path(__file__).resolve().parents[4]
BASELINES = Path(os.environ.get("VOXELFORGE_VERIFY_OUT", ROOT / "Saved/Linux/l47")) / "baselines"
PROFILE_PATH = ROOT / "Standalone/Research/WorldAssemblerM3/Linux32/AuditRingTerraceL32.py"
spec = importlib.util.spec_from_file_location("ring_profile", PROFILE_PATH)
if spec is None or spec.loader is None:
@@ -21,9 +23,9 @@ spec.loader.exec_module(PROFILE)
ARTIFACTS = ROOT / "Saved/Linux/l47"
PARSER = argparse.ArgumentParser(description=__doc__)
PARSER.add_argument("--layout", type=Path,
default=ROOT / "Saved/Linux/l46/layouts/landmark-reachability-clang.json")
default=BASELINES / "landmarks/l46-entrance-control-layout.json")
PARSER.add_argument("--mesh", type=Path,
default=ROOT / "Saved/Linux/l46/landmark-l46.vfmesh")
default=BASELINES / "landmarks/l46-entrance-control.vfmesh")
PARSER.add_argument("--out", type=Path, default=ARTIFACTS / "entrance-profile-before.json")
PARSER.add_argument("--expect", choices=("pass", "fail", "measure"), default="measure")
PARSER.add_argument("--lod", default="0", help="LOD level, or 'all' to include every exported level")
@@ -9,7 +9,8 @@ from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OWNER = ROOT / "Standalone/data/owner_world.json"
OWNER = Path(os.environ.get("VOXELFORGE_OWNER_DATA",
ROOT / "Standalone/data/owner_world.json"))
OUT_DEFAULT = Path(os.environ.get("VOXELFORGE_VERIFY_OUT", ROOT / "Saved/Linux/l56"))
@@ -0,0 +1,219 @@
#!/usr/bin/env python3
"""Render cave, motif, and lattice features from meshes streamed at LOD4-8."""
from __future__ import annotations
import argparse
import hashlib
import importlib.util
import json
import math
import os
from pathlib import Path
import re
import struct
import sys
ROOT = Path(__file__).resolve().parents[4]
CAPTURE54 = ROOT / "Standalone/Research/WorldAssemblerM3/Linux54/CaptureNativeCavesL54.py"
spec = importlib.util.spec_from_file_location("capture_native_caves_l54", CAPTURE54)
capture54 = importlib.util.module_from_spec(spec)
assert spec and spec.loader
spec.loader.exec_module(capture54)
run_step = capture54.run_step
camera_angles = capture54.camera_angles
fmt = capture54.fmt
QUALITY = ROOT / "Standalone/Tools/verify/capture_quality.py"
Q = 1_000_000.0
def sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as stream:
for block in iter(lambda: stream.read(1024 * 1024), b""):
digest.update(block)
return digest.hexdigest()
def midpoint(points: list[tuple[float, float, float]]) -> tuple[float, float, float]:
return tuple(sum(point[axis] for point in points) / len(points) for axis in range(3))
def read_feature(name: str, layout_path: Path):
layout = json.loads(layout_path.read_text(encoding="utf-8"))
if name == "caves":
edges = sorted(layout.get("cavern_edges", []), key=lambda row: row["stable_id"])
if len(edges) != 2:
raise ValueError("cave layout must have two resolved spine edges")
points = [[value / Q for value in point]
for point in edges[0]["control_points_um"]]
target = tuple(points[-1][axis] for axis in range(3))
return target, target[2] + 2.0, 28.0, "cave mouth"
if name == "motifs":
monolith = next(row for row in layout.get("stamps", []) if row.get("kind") == 5)
target = tuple(value / Q for value in monolith["position_um"])
floor = monolith["floor_um"] / Q
return (target[0], target[1], floor + 12.0), floor + 12.0, 32.0, "monolith"
edges = layout.get("cavern_edges", [])
helix = next((edge for edge in edges
if edge.get("stable_id") == "lattice/spine/01-helix-ramp-v1"), None)
if helix is None:
raise ValueError("lattice layout has no resolved helix")
points = [tuple(value / Q for value in point) for point in helix["polyline_um"]]
target = midpoint(points)
return target, target[2] + 4.0, 36.0, "helix"
def audit_roi_lods(mesh: Path, center: tuple[float, float, float], radius: float):
roi_counts: dict[int, int] = {}
roi_skirts: dict[int, int] = {}
with mesh.open("rb") as stream:
prefix = stream.read(60)
if len(prefix) != 60 or prefix[:8] != b"VFMSH001":
raise ValueError("far-LOD audit requires a VFMSH001 Core mesh")
version, voxel_um, tile_count = struct.unpack_from("<3I", prefix, 8)
if version != 1 or voxel_um <= 0:
raise ValueError("mesh version or voxel size is invalid")
for _ in range(tile_count):
header = stream.read(36)
if len(header) != 36:
raise ValueError("truncated far-LOD tile header")
key = struct.unpack_from("<4i", header, 0)
surface_count, skirt_count, vertex_count, index_count, mask_count = \
struct.unpack_from("<5I", header, 16)
positions = stream.read(vertex_count * 12)
normals = stream.read(vertex_count * 12)
index_bytes = stream.read(index_count * 4)
masks = stream.read(mask_count)
if (len(positions) != vertex_count * 12
or len(normals) != vertex_count * 12
or len(index_bytes) != index_count * 4
or len(masks) != mask_count):
raise ValueError("truncated far-LOD tile payload")
lod = key[3]
coords = list(struct.iter_unpack("<3f", positions))
indices = struct.unpack(f"<{index_count}I", index_bytes) if index_count else ()
for triangle in range(index_count // 3):
ids = indices[triangle * 3:triangle * 3 + 3]
if max(ids) >= vertex_count:
raise ValueError("far-LOD triangle references an invalid vertex")
is_skirt = bool(masks and triangle < len(masks) and masks[triangle])
if is_skirt:
continue
centroid = tuple(sum(coords[index][axis] for index in ids) / 3.0
for axis in range(3))
distance_squared = sum((centroid[axis] - center[axis]) ** 2
for axis in range(3))
if distance_squared <= radius * radius:
roi_counts[lod] = roi_counts.get(lod, 0) + 1
if skirt_count:
roi_skirts[lod] = roi_skirts.get(lod, 0) + skirt_count
if stream.read(1):
raise ValueError("far-LOD mesh has trailing bytes")
return roi_counts, roi_skirts
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--out", type=Path, required=True)
parser.add_argument("--fixture-dir", type=Path, required=True)
parser.add_argument("--layout-dir", type=Path, required=True)
parser.add_argument("--headless", type=Path, required=True)
parser.add_argument("--renderer", type=Path, required=True)
parser.add_argument("--artifact-dir", type=Path, required=True)
args = parser.parse_args()
out = args.out.resolve()
fixture_dir, layout_dir = args.fixture_dir.resolve(), args.layout_dir.resolve()
artifact_dir = args.artifact_dir.resolve()
mesh_dir, png_dir = out / "far-lod-meshes", out / "far-lod-png"
for directory in (mesh_dir, png_dir, artifact_dir):
directory.mkdir(parents=True, exist_ok=True)
env = os.environ.copy()
env["LP_NUM_THREADS"] = "12"
env["XDG_CACHE_HOME"] = str(out / "xdg-cache")
cases = (
("caves", fixture_dir / "caves.json",
layout_dir / "native-caves-field-clang.json", "caves", []),
("motifs", fixture_dir / "style-override-motifs-slice1.json",
layout_dir / "motif-slice1-capsule-reachability-clang.json", "surface-world", []),
("lattice", fixture_dir / "lattice-l58.json",
layout_dir / "native-lattice-field-clang.json", "lattice", ["--lattice-strate=1"]),
)
quality_rows = []
for name, fixture, layout, recipe, extra_flags in cases:
if not fixture.is_file() or not layout.is_file():
raise RuntimeError(f"missing {name} fixture or layout")
target, target_height, radius, label = read_feature(name, layout)
stream_camera = (target[0] + 1024.0, target[1], target[2], 0.0, 0.0)
stream_yaw, stream_pitch = camera_angles(stream_camera[:3], target)
stream_camera = (*stream_camera[:3], stream_yaw, stream_pitch)
mesh = mesh_dir / f"{name}-lod4-8.vfmesh"
mesh_layout = layout_dir / f"{name}-far-lod-layout.json"
command = [args.headless, fixture, "--workers=12", "--world-assembler-prototype",
"--stream-benchmark", "--stream-pipeline", "--force-generate",
"--clip-radius=3", "--max-clip-level=8", "--max-level=-1",
"--render-distance-chunks=0", f"--recipe={recipe}",
f"--camera={fmt(stream_camera)}", *extra_flags,
f"--resolved-layout-output={mesh_layout}", f"--mesh-export={mesh}"]
output = run_step(f"{name}-far-lod-export", command,
out / f"capture-{name}-far-lod-export.log", env=env, stamped=True)
worker_rows = {int(match.group(1)): (int(match.group(2)), int(match.group(3)))
for match in re.finditer(
r"^\[WorldAssemblerLODWorker\] lod=(\d+) tiles=(\d+) .*?skirt_triangles=(\d+)",
output, re.M)}
if (set(worker_rows) != set(range(9))
or any(worker_rows[level][0] <= 0 for level in range(9))
or sum(worker_rows[level][1] for level in range(4, 9)) <= 0):
raise RuntimeError(f"{name} far view lacks LOD0-8 tile/skirt work: {worker_rows}")
if not mesh.is_file() or mesh.stat().st_size == 0:
raise RuntimeError(f"{name} far LOD export produced no mesh")
roi, roi_skirts = audit_roi_lods(mesh, target, radius)
feature_lods = sorted(level for level, count in roi.items() if count > 0)
far_feature_lods = [level for level in feature_lods if level >= 4]
if not far_feature_lods:
raise RuntimeError(f"{name} {label} has no non-skirt surface triangles in LOD4-8 ROI: {roi}")
camera = (target[0] + 18.0, target[1] - 28.0, target_height + 10.0)
yaw, pitch = camera_angles(camera, target)
png = png_dir / f"{name}-far-lod.png"
image = artifact_dir / f"{name}-far-lod.jpg"
render = [args.renderer, mesh, png, "--size=1280x720", "--fov-deg=76",
f"--camera-m={fmt(camera)}", f"--yaw-deg={yaw:.6f}",
f"--pitch-deg={pitch:.6f}", "--tile-skirts=on", "--shade=readable",
"--headlamp=on", "--height-tint=on", f"--height-center-m={target_height:.6f}",
"--height-range-m=42", "--tile-edges=off",
f"--focus-region={fmt((*target, radius))}"]
run_step(f"{name}-far-lod-render", render,
out / f"capture-{name}-far-lod-render.log", env=env, stamped=True)
run_step(f"{name}-far-lod-jpeg", ["ffmpeg", "-nostdin", "-hide_banner",
"-loglevel", "error", "-y", "-i", png, "-frames:v", "1", "-q:v", "5", image],
out / f"capture-{name}-far-lod-jpeg.log", env=env)
quality_output = run_step(f"{name}-far-lod-quality", [sys.executable, QUALITY, image],
out / f"capture-{name}-far-lod-quality.log", env=env)
quality = [line for line in quality_output.splitlines()
if line.startswith("[CaptureQuality]")]
if not quality or not quality[-1].endswith("status=PASS"):
raise RuntimeError(f"{name} far LOD capture failed its image quality gate")
size = image.stat().st_size
if not 0 < size <= 300_000:
raise RuntimeError(f"{name} far LOD capture exceeds 300 KB: {size}")
print(f"[L60FarLODCapture] generator={name} feature={label} file={image} "
f"bytes={size} sha256={sha256(image)} feature_lods={feature_lods} "
f"feature_triangles={sum(roi.values())} far_skirt_triangles="
f"{sum(value for lod, value in roi_skirts.items() if lod >= 4)} status=PASS",
flush=True)
quality_rows.append({"generator": name, "feature": label, "file": str(image),
"bytes": size, "sha256": sha256(image), "feature_triangles_by_lod": roi,
"feature_skirts_by_lod": roi_skirts})
(out / "far-lod-captures.json").write_text(json.dumps(quality_rows, indent=2) + "\n",
encoding="utf-8")
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except Exception as error:
print(f"[L60FarLODCapture] status=FAIL error={error}", file=sys.stderr, flush=True)
raise SystemExit(1)
@@ -0,0 +1,77 @@
# Linux 60 report
## Result and root cause
The continuation is green. The full tier and speed matrix passed after fixing signed-overflow undefined behavior in the shared voxel hash.
The first differing density was LOD1 tile `(0,64,-512)`, step 2, sample order 23,658 at `(64,82,-476)`. Before the fix, Clang returned `-4.0078125` (`0xc0804000`) and GCC returned `-4.125` (`0xc0840000`). The sample follows the mesher's LOD X8/boundary scalar path into `DensityAtLattice` and the compiled analytic `TunnelNetwork` field. Its upstream cave passage placement comes from `UVoxelStrateManager::GeneratePassages` → `PassageRandom01` → `VoxelHash::Cell(i,c,seed)`.
`VoxelHash::Cell` added `0x7fffffff` to a signed cell coordinate before casting to `uint32`; connection `c=1` overflowed `int32`, so the compiler-dependent hash changed the passage coordinates and cave geometry. The trace showed passage pair `(0,1)` diverging, followed by all 21 control points of passage 1. The fix performs the bias in unsigned arithmetic in `Cell`, `Pair`, legacy `Cellular3D`, and the shared Core cellular hash. It does not quantize density. After the fix both compilers produced the same pair `(0,1)` coordinates, the same 44 passage points, and `0xc0804000` at the first sample. The evaluator version is now 211.
Before/after traces are in `Saved/Linux/l60/diag/caves-{clang,gcc}-probe.log` and `Saved/Linux/l60/diag/cache/fullcache-{clang,gcc}.log`.
## Far LOD support and seam checks
All full-view fixtures use max level `-1`, clip radius 3, and max clip level 8. All three generators streamed LOD0–8 with skirts.
- **Caves:** X8 and Z-run gates detect caves in manager and resolved stratum mappings, then use scalar lattice sampling for cave samples. Clang/GCC cave tile hashes match at W1/W8/W12, including random views at LOD1–2. The cave-mouth transition crossed levels 0–8 (432 entering tiles).
- **Motifs:** The fixed motif field streams through the existing rings, caps, and skirts. The monolith transition crossed levels 0–8 (295 entering tiles); whole-surface foot, cap, and platform-edge audits passed.
- **Lattice:** The fixture selects native lattice stratum 1 and uses the existing ring and skirt path. The helix transition crossed levels 0–8 (380 entering tiles); field, interval, reachability, and shape-free audits passed.
Hard, gradient, and interleaved stratum transitions passed with zero hole columns and zero air samples. Far-LOD cave, motif, and lattice JPEG captures passed their audits and are under 300 KB.
## Comparable speed matrix
`verify.py full --speed-matrix` ran five interleaved repetitions per generator, rotating generator order. Every run used camera `(-12,-118,-20 m)`, rotation `(0°,0°)`, clip radius 3, max clip level 8, max level `-1`, camera step 24 m, 12 workers, and shared column cache. The stage asserted the shared HeadlessConfig fields, whole-view and per-LOD tile-count ranges, and stable counts across repetitions. It passed for all eight generators. Every listed tile count occurred in all five runs; timings are medians from the final full run.
| Generator | Tiles | Whole view (s) | First tile (s) | Near ring (s) | Moving p50 (ms) | Moving p95 (ms) |
|---|---:|---:|---:|---:|---:|---:|
| Natural SW | 2,130 | 1.669 | 0.502 | 0.519 | 63.971 | 119.891 |
| periodicHills | 1,986 | 1.311 | 0.454 | 0.479 | 60.267 | 115.648 |
| mirroredRelief | 1,986 | 1.317 | 0.471 | 0.490 | 60.568 | 115.474 |
| flatField | 1,986 | 1.303 | 0.465 | 0.485 | 60.627 | 117.144 |
| Floating Islands | 2,130 | 4.926 | 0.817 | 0.977 | 528.939 | 871.290 |
| Caves | 2,130 | 1.408 | 0.489 | 0.507 | 61.099 | 118.508 |
| Motifs | 1,986 | 1.326 | 0.476 | 0.493 | 60.107 | 116.104 |
| Lattice | 2,130 | 1.701 | 0.539 | 0.561 | 69.572 | 137.585 |
Full run records and common config are in `Saved/Linux/l60/speed-matrix.json`.
## Captures
- `Saved/Linux/l60/captures/caves-far-lod.jpg` — 16,642 bytes; SHA-256 `55f8d3d2de76541e9f1c1d09b771af40bfbb37b1ce5afc0b2f70419b84ed71a3`
- `Saved/Linux/l60/captures/motifs-far-lod.jpg` — 24,926 bytes; SHA-256 `bfc5d8ddebc3706fedd6108653b4777fac4bac44e71f60c8e58b3508c14c3aa6`
- `Saved/Linux/l60/captures/lattice-far-lod.jpg` — 30,714 bytes; SHA-256 `c261728e48975f01c712e31594b4284eede289b73e793e6204b78cb8ff2a2947`
## Hashes and tiers
Clang 19 and GCC 14 builds used FP contraction off and 12 jobs. Final source stamp and source-manifest SHA-256: `902a0d8e45f895d1b53c0a86999adee5598ff77e1752aa7ea90397520cf7b82d`.
- Clang headless: `dcf6286bcecac382f9cce2458e7c327485e9b8b993c3d544e627a8fc964947fa`
- GCC headless: `be86f90f993ba27bfde235e0850ea783298396dc03456360cf4bb3fd4815988b`
- Offscreen capture: `b8f7a441c0a41be6ebd61721ff903832a51291df7a886e669b9084d84441d4c5`
| Tier | Status | Checks / runs | Elapsed |
|---|---|---:|---:|
| Fast | PASS before hash fix | 453 / 113 | 335.7 s |
| Medium | PASS before hash fix | 968 / 289 | 1,559.1 s |
| Full continuation trial 1 | FAIL: stale binary stamp; no checks ran | — | 0.0 s |
| Full continuation trial 2 | PASS on source stamp `914af3...` | 2,304 / 660 | 3,256.9 s |
| Full continuation trial 3 | PASS on final source stamp, including speed matrix | 2,304 / 660 | 3,260.6 s |
Fast and medium were not rerun as standalone tiers after the hash change; the passing full run executed their checks at the current source stamp.
## Trials and failed runs
The prior L60 work used 15 trials. This continuation used 3 of its 10-trial budget. Trial 1 stopped before checks because it found stale Clang/GCC executables. Trial 2 passed, then the final source review restored the mesher's existing `<bit>` include and changed the source stamp, so trial 3 rebuilt and reran full against the exact final source. The pre-fix trial 15 had failed peer determinism on the same six LOD1 and eight LOD2 tile keys across four seeded views. Trial 3 had no failed checks; negative controls reported expected failures.
## Orchestrator review (Claude)
Accepted. The stamp matches the final Clang and GCC builds (`902a0d8e…`); full PASS, including the speed matrix.
**The root cause is a real cross-platform determinism bug:** `VoxelHash::Cell`/`Pair`/`Cellular3D` added
`0x7FFFFFFF` to a signed `int32` (signed overflow is undefined behaviour), so passage placement became
compiler-dependent. **The same code lives in the Unreal plugin** (`Source/VoxelForge/Public/VoxelCaveMorphology.h`,
identical to the standalone copy before the fix), and only the standalone copy had been fixed. I applied the identical
fix to the plugin header and confirmed the two copies match. MSVC typically wraps there, so the owner's existing
worlds are most likely unchanged, but this needs his Windows build. No other signed-bias pattern remains in `Source/`
or Core. The comparable speed matrix is now a standing `verify.py full --speed-matrix` stage.
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

@@ -0,0 +1,65 @@
# L61 sanitizer sweep
## Builds and coverage
- Clang 19: `address,undefined,float-cast-overflow`; GCC: `address,undefined`. Both used `-fno-sanitize-recover=undefined`, `-ffp-contract=off`, and two build jobs.
- Clang's `integer` group was tried with unsigned overflow excluded. libstdc++ `std::rotl` emitted a non-actionable shift diagnostic, so the group is omitted from the standing stage.
- ASan runs used `taskset -c 0-11 prlimit --rss=6442450944` to allow its shadow address space to map. Linux RSS limits are advisory; the measured descendant-tree peak from the complete comparable sweep was 1.798 GiB. The corrected final rerun used the same limit mode.
- Each compiler ran 19 cases at LOD0–8: natural SW, FI, all three profiles, caves, motifs, lattice, both passage styles, and the live surface crossing. General generators used two camera views; the suite also covers seeded random cameras. Total: 38 runs, 46 passing checks, no ASan/UBSan reports. No edit fixture was present.
## Findings
The generator runs found no runtime sanitizer issue. The boundary findings below came from the requested static scan; they were fixed at the source.
| File:line | Kind and trigger | Fix |
|---|---|---|
| `VoxelClipmapDesiredTiles.h:35,49,57-68,129-171` (plugin and standalone) | Signed negation in floor/ceil division failed for `INT_MIN`; center/radius, ring, and tile-bound arithmetic could overflow at coordinate limits. | Quotient/remainder division, 64-bit intermediates and clamped bounds, bounded unsigned LOD shifts, and wide tile loops. |
| `VoxelWorld.cpp:85-93,6254-6267,7679-7693,10100-10104,10388-10394,11473-11497,12141-12147,12886-12888` (plugin only) | Signed LOD shifts, biased division, origin/band calculations, and coordinate differences were unsafe for invalid levels or edge coordinates. | Central bounded unsigned scale/extent helpers; 64-bit arithmetic and clamping; reject initial clipmaps outside the supported voxel range. |
| `VoxelContentManager.cpp:562-584,1109-1125,1476-1484,1533-1542` (plugin only) | `-INT_MIN`, player-cell ± radius, region-size products, and authored count spans could overflow. | Remainder-adjusted floor division, widened coordinates/count spans, capped reserve, and clamped count conversion. |
| `VoxelDensityVolume.cpp:156-162,185-186,638-639` (plugin only) | Floor division negated `INT_MIN`; signed LOD shifts depended on valid levels. | Remainder-adjusted division and bounded unsigned shifts. |
| `VoxelTypes.h:335-363` (plugin and standalone) | Signed shifts and coordinate-times-extent could overflow; `CenterCm` repeated the signed LOD shift. | Clamp LOD to 0–8, use unsigned shifts and 64-bit coordinate multiplication with clamping; derive `CenterCm` from the safe extent helper. |
## Unreal-only static scan
Scanned all 30 `Source/VoxelForge/Private/*.cpp` files. Standalone compiles 15; the other 15 were reviewed statically and are not compiled here: `VoxelAtmosphereManager.cpp`, `VoxelContentManager.cpp`, `VoxelDensityVolume.cpp`, `VoxelForgeCoreAdapter.cpp`, `VoxelForgeModule.cpp`, `VoxelRedefinedDensityField.cpp`, `VoxelSeasonAsset.cpp`, `VoxelSeasonManifest.cpp`, `VoxelStackSampler.cpp`, `VoxelStartupTrace.cpp`, `VoxelStats.cpp`, `VoxelStrateDefinition.cpp`, `VoxelStratePreview.cpp`, `VoxelStrateRecipe.cpp`, and `VoxelWorld.cpp`.
No signed-bias formulas or unbounded LOD shifts remain in that scan. Remaining private-source shifts are bounded (for example, `VoxelGenerator.cpp:900` clamps LOD to 0–5; marching-cubes shifts are fixed bit masks). The Unreal files could not be compiled here.
## Control, version, and hashes
The test-only `Saved/Linux/l61/sanitizer/signed-overflow-control.cpp` evaluates `INT_MAX + 1`. Both compilers report signed integer overflow, and the stage requires that diagnostic; logs are `signed-overflow-control-clang.log` and `signed-overflow-control-gcc.log`.
Version remains **211**. Supported-input output is unchanged; only invalid or out-of-range arithmetic is bounded.
- Source stamp: `c22b877c586423fbe67cdffb8ab762e3fe6e20e91897685447113fcb23944035`
- Clang headless: `400b4a66a4d3f76f584f57aefed882bcfc736db563815145518583ac007bd57c`
- GCC headless: `8f782ca4c73b6fc0a942fa06db4149426e58beaacd2bb9185974be4167a614b6`
- Clang sanitizer: `2a1618d25904b1c3620cfcd0e37d59133fccd349df347442396bd0be42f7b21c`
- GCC sanitizer: `6c70cc1c5c843b24de7f82e19a44947cdcd1d8bd4ba01a448027a230ebe5d3e7`
## Standing tier and results
Added a dedicated `verify.py sanitize` tier with a 1,200-second budget and the option to include it in full via `verify.py full --sanitize`.
| Tier | Timeout | Result |
|---|---:|---|
| Sanitize | 1,200 s | PASS, 787.3 s |
| Fast | 900 s | PASS, 251.4 s |
| Medium | 2,400 s | PASS, 1,554.2 s |
| Full + five-run speed matrix | 3,600 s | PASS, 1,709.3 s |
The full tier checked Clang/GCC and W1/W8/W12 determinism, random cameras, the 30-cell set, baseline/candidate cost pairs, and five interleaved timings for all eight generator views. Expected black-image negative-control exits were handled by passing control checks.
## Trials and failed attempts
Used all 15 verification attempts: seven discarded sanitizer attempts, the initial sanitizer pass, fast/medium/full, then the corrected sanitizer pass and final fast/medium/full. Discarded attempts were two missing-`/usr/bin/time` wrapper failures, the noisy integer-sanitizer diagnostic, a source-stamp change during a run, two stale-binary refusals, and a LeakSanitizer ptrace failure. A separate Clang release build briefly failed on a duplicate `ExtentVoxels()` declaration; it was corrected and rebuilt. Final tiers have no unexpected failed runs.
## Orchestrator review (Claude)
Accepted. The stamp matches the final Clang and GCC builds (`c22b877c…`); the sanitize, fast, medium and full tiers
all pass. The runtime sweep is clean (38 runs, 0 reports), and the planted signed-overflow control is caught by both
compilers. **The Unreal-only edits** (`VoxelWorld.cpp`, `VoxelContentManager.cpp`, `VoxelDensityVolume.cpp`) can't
be compiled here. I reviewed the `VoxelWorld.cpp` diff: the helpers are `static FORCEINLINE`, they use UE types
(`int32`, `int64`, `FMath::Clamp`, `MIN_int32`/`MAX_int32`), and their LOD clamp of 0–8 matches
`UVoxelSettings::MaxClipLevel` (`ClampMax = "8"`) and `CoreMaxClipLevel = 8`, so they don't change supported
behaviour. These go into the owner's Windows build list.
@@ -0,0 +1,130 @@
# L64 Linux baseline
## Empty top-strate result
**SurfaceWorld is not empty.** U2’s gate report says its sampled top window had 0 triangles, but does not give the camera/window coordinates. With the current owner file (SHA-256 `f42f96adca58…`), the correct SurfaceWorld windows mesh on Clang: the file `viewer_start` `(0, 0, -496 m)` gives 9/27 nonempty LOD0 tiles and 36,864 triangles; the resolved route arrival `(38.75, 70.75, -374.121 m)` gives 9/27 and 25,672; the departure mouth `(-6.75, 25.25, -376.307 m)` gives 11/27 and 33,582. The binding is `SurfaceWorld-native`; the 57 boundary probes had 0 dispatch disagreements. This points to the gate’s sample window placement, not an empty field or wrong generator binding. The precise U2 sample point cannot be confirmed from its report.
There is a separate owner-plan defect: strict layout validation rejects `passage/0-1/0` because its lower mouth is at z = −822.06 m, 78.56 m below FloatingIslands’ content band (bottom −743.5 m; that point is in TunnelNetwork). The plan’s passage audit also fails; details are below.
## Bisect
All 57 commits in `a176d07^..3202193` were exported and built with Clang 19, `-ffp-contract=off`, `-j12`, taskset CPUs 0–11, a 6 GiB address-space limit, and `TMPDIR` under `Saved/`. 56 built. `3e443bf` (L37/R1) fails to compile on an extra `)`; its following L37 fix `7f2d84d` builds. The timing matrix has 402 successful runs: three shuffled/interleaved passes per recipe and data/commit configuration. Every run used the same view `(−12, −118, −20 m)`, 12 workers, `--force-generate`, clip radius 3, max clip level 8, LOD8 step 0, hash off. The fixed data was `3202193` (`7b095f9b…`) for every commit; commits before L24 also ran the pre-L24 file (`289cd755…`).
**Not Core.** On fixed data, SurfaceWorld LOD0 worker time per tile is 10.05 ms at L14 and 12.78 ms at L61 (1.27×); whole-view is 4.31 s and 5.02 s (1.16×). The summed worker time across LOD0–8 is 31.2 s and 38.7 s (1.24×). This does not reproduce the Windows roughly 2× per-tile rise, so the cause is on the Unreal side: U1 or the adapter’s Core driving path. No Core fix was made; `Source/` was untouched.
`W/F/N/Σ/L0` below means whole-view seconds / first-tile seconds / near-ring seconds / summed worker-seconds across LOD0–8 / LOD0 worker-ms per tile. Each parenthesized percent is the delta against L14 for the same recipe and data file. L0 steps mark upward changes of at least 10% versus the previous successful commit in that dataset. For LOD0 p50/p95, builds before L55 lack tile-drain counters; later values are from separate `--instrument-tile-drain` passes on the same view/data (SW then FI, ms). Those opt-in passes have force-generation off, as required for the counters, and do not contribute to the timing medians.
| Round | Commit | Data | SW W/F/N/Σ/L0 (Δ%) | FI W/F/N/Σ/L0 (Δ%) | L0 step ≥10% | LOD0 p50/p95 SW; FI (ms) |
|---|---|---|---|---|---|---|
| L14 | `a176d07` | 3202193 | 4.31 (+0%) / 1.71 (+0%) / 1.76 (+0%) / 31.2 (+0%) / 10.05 (+0%) | 23.57 (+0%) / 3.21 (+0%) / 3.61 (+0%) / 236.6 (+0%) / 166.93 (+0%) | — | — |
| L14 | `a176d07` | pre-L24 | 4.53 (+0%) / 1.61 (+0%) / 1.67 (+0%) / 34.6 (+0%) / 11.71 (+0%) | 26.34 (+0%) / 3.91 (+0%) / 4.70 (+0%) / 291.3 (+0%) / 236.10 (+0%) | — | — |
| L15 | `a6cd7cf` | 3202193 | 4.26 (-1%) / 1.44 (-16%) / 1.49 (-15%) / 32.1 (+3%) / 10.50 (+5%) | 23.88 (+1%) / 3.67 (+15%) / 4.30 (+19%) / 249.3 (+5%) / 179.96 (+8%) | — | — |
| L15 | `a6cd7cf` | pre-L24 | 2.96 (-35%) / 1.33 (-17%) / 1.36 (-18%) / 19.4 (-44%) / 5.30 (-55%) | 27.50 (+4%) / 3.62 (-7%) / 4.33 (-8%) / 297.1 (+2%) / 247.25 (+5%) | — | — |
| L16 | `9a7c87e` | 3202193 | 3.59 (-17%) / 1.60 (-6%) / 1.65 (-6%) / 23.9 (-23%) / 8.47 (-16%) | 23.94 (+2%) / 3.92 (+22%) / 4.26 (+18%) / 256.3 (+8%) / 182.06 (+9%) | — | — |
| L16 | `9a7c87e` | pre-L24 | 4.49 (-1%) / 1.75 (+9%) / 1.81 (+9%) / 32.7 (-5%) / 10.08 (-14%) | 26.57 (+1%) / 3.66 (-6%) / 4.65 (-1%) / 271.7 (-7%) / 231.16 (-2%) | SW ↑90.2% | — |
| L17 | `8704129` | 3202193 | 3.95 (-8%) / 1.51 (-12%) / 1.56 (-11%) / 29.9 (-4%) / 9.06 (-10%) | 24.71 (+5%) / 3.95 (+23%) / 4.67 (+29%) / 264.2 (+12%) / 176.04 (+5%) | — | — |
| L17 | `8704129` | pre-L24 | 2.93 (-35%) / 0.87 (-46%) / 0.94 (-44%) / 26.0 (-25%) / 8.88 (-24%) | 25.58 (-3%) / 2.40 (-38%) / 3.11 (-34%) / 278.4 (-4%) / 231.14 (-2%) | — | — |
| L18 | `dcf0763` | 3202193 | 4.08 (-5%) / 1.55 (-9%) / 1.60 (-9%) / 31.4 (+1%) / 10.92 (+9%) | 29.75 (+26%) / 3.56 (+11%) / 4.42 (+22%) / 317.7 (+34%) / 257.73 (+54%) | SW ↑20.5%, FI ↑46.4% | — |
| L18 | `dcf0763` | pre-L24 | 4.02 (-11%) / 1.65 (+3%) / 1.70 (+2%) / 28.4 (-18%) / 6.94 (-41%) | 25.67 (-3%) / 4.14 (+6%) / 4.96 (+6%) / 258.9 (-11%) / 177.66 (-25%) | — | — |
| L19 | `811c4b7` | 3202193 | 4.79 (+11%) / 1.80 (+5%) / 1.85 (+5%) / 36.5 (+17%) / 12.45 (+24%) | 26.15 (+11%) / 2.54 (-21%) / 3.15 (-13%) / 283.8 (+20%) / 235.00 (+41%) | SW ↑14.1% | — |
| L19 | `811c4b7` | pre-L24 | 3.51 (-23%) / 0.80 (-50%) / 0.86 (-49%) / 32.4 (-6%) / 9.34 (-20%) | 25.38 (-4%) / 3.94 (+1%) / 4.64 (-1%) / 259.8 (-11%) / 213.94 (-9%) | SW ↑34.6%, FI ↑20.4% | — |
| L20 | `6f9c7bd` | 3202193 | 4.52 (+5%) / 1.74 (+2%) / 1.78 (+1%) / 33.3 (+7%) / 12.08 (+20%) | 23.30 (-1%) / 2.36 (-27%) / 3.22 (-11%) / 253.6 (+7%) / 173.02 (+4%) | — | — |
| L20 | `6f9c7bd` | pre-L24 | 4.32 (-5%) / 1.65 (+2%) / 1.69 (+1%) / 31.9 (-8%) / 10.91 (-7%) | 22.85 (-13%) / 2.18 (-44%) / 3.41 (-27%) / 229.8 (-21%) / 170.36 (-28%) | SW ↑16.8% | — |
| L20 review | `5d5447d` | 3202193 | 4.76 (+11%) / 1.63 (-4%) / 1.70 (-3%) / 35.1 (+13%) / 11.85 (+18%) | 25.93 (+10%) / 3.91 (+22%) / 4.78 (+33%) / 275.4 (+16%) / 227.96 (+37%) | FI ↑31.8% | — |
| L20 review | `5d5447d` | pre-L24 | 4.50 (-1%) / 1.65 (+3%) / 1.72 (+3%) / 34.5 (-0%) / 11.83 (+1%) | 27.01 (+3%) / 3.09 (-21%) / 3.87 (-18%) / 284.0 (-3%) / 225.37 (-5%) | FI ↑32.3% | — |
| L21 | `8753f6d` | 3202193 | 4.26 (-1%) / 1.50 (-12%) / 1.56 (-11%) / 33.0 (+6%) / 11.30 (+12%) | 26.21 (+11%) / 3.80 (+19%) / 5.09 (+41%) / 268.6 (+14%) / 192.22 (+15%) | — | — |
| L21 | `8753f6d` | pre-L24 | 3.78 (-17%) / 1.02 (-37%) / 1.12 (-33%) / 33.1 (-4%) / 10.18 (-13%) | 23.71 (-10%) / 3.73 (-5%) / 4.60 (-2%) / 260.3 (-11%) / 196.70 (-17%) | — | — |
| L22 | `55c26a0` | 3202193 | 4.01 (-7%) / 1.48 (-13%) / 1.55 (-12%) / 28.9 (-7%) / 10.23 (+2%) | 35.34 (+50%) / 4.74 (+48%) / 5.49 (+52%) / 369.9 (+56%) / 240.61 (+44%) | FI ↑25.2% | — |
| L22 | `55c26a0` | pre-L24 | 4.13 (-9%) / 1.58 (-2%) / 1.64 (-2%) / 30.4 (-12%) / 11.32 (-3%) | 33.67 (+28%) / 3.50 (-11%) / 4.07 (-13%) / 360.8 (+24%) / 273.30 (+16%) | SW ↑11.2%, FI ↑38.9% | — |
| L23 | `71d09a8` | 3202193 | 3.38 (-21%) / 1.54 (-10%) / 1.60 (-9%) / 22.0 (-29%) / 9.67 (-4%) | 33.92 (+44%) / 3.21 (+0%) / 3.77 (+4%) / 359.9 (+52%) / 234.50 (+40%) | — | — |
| L23 | `71d09a8` | pre-L24 | 4.34 (-4%) / 1.70 (+6%) / 1.76 (+5%) / 31.5 (-9%) / 10.93 (-7%) | 30.60 (+16%) / 4.29 (+10%) / 5.16 (+10%) / 316.1 (+9%) / 222.51 (-6%) | — | — |
| L24 | `cb9236c` | 3202193 | 2.83 (-34%) / 0.95 (-44%) / 1.03 (-42%) / 17.5 (-44%) / 6.50 (-35%) | 36.87 (+56%) / 4.38 (+37%) / 5.18 (+44%) / 404.1 (+71%) / 298.88 (+79%) | FI ↑27.5% | — |
| L25 | `ef789aa` | 3202193 | 4.29 (-0%) / 1.72 (+1%) / 1.78 (+1%) / 30.8 (-1%) / 10.86 (+8%) | 36.36 (+54%) / 2.18 (-32%) / 2.88 (-20%) / 387.8 (+64%) / 286.83 (+72%) | SW ↑67.0% | — |
| L26 | `37a2230` | 3202193 | 4.06 (-6%) / 1.69 (-1%) / 1.76 (-0%) / 32.4 (+4%) / 7.90 (-21%) | 43.11 (+83%) / 4.54 (+42%) / 5.28 (+46%) / 456.7 (+93%) / 325.35 (+95%) | FI ↑13.4% | — |
| L27 | `07b3fe2` | 3202193 | 4.10 (-5%) / 1.44 (-16%) / 1.49 (-16%) / 32.2 (+3%) / 9.12 (-9%) | 40.19 (+71%) / 3.68 (+15%) / 4.79 (+33%) / 441.3 (+87%) / 255.16 (+53%) | SW ↑15.4% | — |
| L28 | `f23ab7b` | 3202193 | 4.44 (+3%) / 1.72 (+1%) / 1.78 (+1%) / 32.1 (+3%) / 10.38 (+3%) | 32.25 (+37%) / 3.55 (+11%) / 4.26 (+18%) / 339.3 (+43%) / 248.36 (+49%) | SW ↑13.9% | — |
| L29 | `63c7e56` | 3202193 | 2.55 (-41%) / 0.72 (-58%) / 0.75 (-58%) / 21.5 (-31%) / 6.19 (-38%) | 34.47 (+46%) / 2.83 (-12%) / 3.64 (+1%) / 376.1 (+59%) / 265.76 (+59%) | — | — |
| L30 | `103d8b4` | 3202193 | 4.41 (+2%) / 1.59 (-7%) / 1.65 (-6%) / 32.1 (+3%) / 10.65 (+6%) | 36.40 (+54%) / 4.25 (+33%) / 4.96 (+37%) / 387.8 (+64%) / 303.13 (+82%) | SW ↑72.0%, FI ↑14.1% | — |
| L31 | `28069f0` | 3202193 | 3.12 (-27%) / 1.55 (-9%) / 1.61 (-8%) / 18.8 (-40%) / 7.23 (-28%) | 37.60 (+60%) / 4.14 (+29%) / 5.17 (+43%) / 407.7 (+72%) / 296.35 (+78%) | — | — |
| L32 | `d0158c6` | 3202193 | 4.88 (+13%) / 1.57 (-8%) / 1.71 (-3%) / 37.2 (+19%) / 12.07 (+20%) | 34.29 (+46%) / 2.85 (-11%) / 3.31 (-8%) / 362.5 (+53%) / 229.88 (+38%) | SW ↑66.9% | — |
| L33 | `b7a47be` | 3202193 | 2.65 (-38%) / 0.86 (-49%) / 0.89 (-50%) / 21.3 (-32%) / 9.38 (-7%) | 31.98 (+36%) / 2.91 (-9%) / 3.84 (+7%) / 350.3 (+48%) / 257.32 (+54%) | FI ↑11.9% | — |
| L34 | `49e29fc` | 3202193 | 4.40 (+2%) / 1.53 (-11%) / 1.58 (-10%) / 34.5 (+11%) / 10.81 (+8%) | 36.15 (+53%) / 4.22 (+31%) / 5.02 (+39%) / 378.7 (+60%) / 252.48 (+51%) | SW ↑15.3% | — |
| L35 | `ba01707` | 3202193 | 4.80 (+11%) / 1.77 (+4%) / 1.84 (+4%) / 36.0 (+15%) / 7.08 (-30%) | 35.01 (+49%) / 4.17 (+30%) / 4.96 (+37%) / 390.0 (+65%) / 289.50 (+73%) | FI ↑14.7% | — |
| L36 | `8be1c4d` | 3202193 | 3.67 (-15%) / 1.03 (-40%) / 1.08 (-39%) / 34.2 (+10%) / 10.92 (+9%) | 34.93 (+48%) / 2.72 (-15%) / 3.21 (-11%) / 384.8 (+63%) / 266.69 (+60%) | SW ↑54.2% | — |
| L37/R1 | `3e443bf` | 3202193 | — | — | compile fail | — |
| L37 fix/R2 | `7f2d84d` | 3202193 | 4.42 (+3%) / 1.73 (+1%) / 1.78 (+1%) / 32.2 (+3%) / 10.07 (+0%) | 38.96 (+65%) / 4.48 (+40%) / 5.25 (+46%) / 417.3 (+76%) / 259.39 (+55%) | — | — |
| R3 | `945225d` | 3202193 | 4.86 (+13%) / 1.85 (+8%) / 1.91 (+9%) / 35.3 (+13%) / 10.69 (+6%) | 33.96 (+44%) / 2.68 (-17%) / 3.43 (-5%) / 374.9 (+58%) / 266.08 (+59%) | — | — |
| L38/R4 | `cf57c87` | 3202193 | 3.14 (-27%) / 1.34 (-22%) / 1.36 (-23%) / 21.5 (-31%) / 5.96 (-41%) | 37.38 (+59%) / 4.72 (+47%) / 5.58 (+55%) / 396.3 (+67%) / 252.07 (+51%) | — | — |
| R5 | `88c54ea` | 3202193 | 3.37 (-22%) / 1.46 (-15%) / 1.56 (-11%) / 30.5 (-2%) / 11.09 (+10%) | 39.45 (+67%) / 3.52 (+10%) / 4.89 (+36%) / 416.6 (+76%) / 265.16 (+59%) | SW ↑85.8% | — |
| L39/R6 | `753cc9c` | 3202193 | 3.32 (-23%) / 0.86 (-50%) / 0.88 (-50%) / 21.7 (-31%) / 9.49 (-5%) | 37.03 (+57%) / 4.41 (+37%) / 5.56 (+54%) / 389.5 (+65%) / 232.40 (+39%) | — | — |
| L40/R7 | `efa9e89` | 3202193 | 4.57 (+6%) / 1.62 (-5%) / 1.68 (-4%) / 35.3 (+13%) / 10.78 (+7%) | 38.63 (+64%) / 3.39 (+6%) / 3.89 (+8%) / 412.4 (+74%) / 306.46 (+84%) | SW ↑13.6%, FI ↑31.9% | — |
| L41/R8 | `1abee91` | 3202193 | 4.30 (-0%) / 1.71 (+0%) / 1.81 (+3%) / 26.8 (-14%) / 8.08 (-20%) | 38.25 (+62%) / 2.88 (-10%) / 3.42 (-5%) / 413.8 (+75%) / 324.95 (+95%) | — | — |
| L42/R9 | `2566f53` | 3202193 | 4.37 (+1%) / 1.51 (-11%) / 1.56 (-11%) / 34.1 (+9%) / 10.05 (+0%) | 37.43 (+59%) / 2.15 (-33%) / 2.48 (-31%) / 427.7 (+81%) / 306.73 (+84%) | SW ↑24.4% | — |
| R9 | `497e974` | 3202193 | 4.55 (+6%) / 1.75 (+2%) / 1.79 (+2%) / 33.5 (+7%) / 10.31 (+3%) | 35.69 (+51%) / 4.87 (+52%) / 5.68 (+57%) / 372.0 (+57%) / 257.01 (+54%) | — | — |
| L43/R10 | `422dd77` | 3202193 | 3.54 (-18%) / 1.60 (-6%) / 1.69 (-4%) / 23.7 (-24%) / 8.14 (-19%) | 37.50 (+59%) / 2.92 (-9%) / 3.37 (-7%) / 401.3 (+70%) / 320.95 (+92%) | FI ↑24.9% | — |
| L44 | `036bd53` | 3202193 | 3.48 (-19%) / 0.95 (-44%) / 1.03 (-41%) / 30.2 (-3%) / 10.30 (+3%) | 36.78 (+56%) / 4.32 (+35%) / 5.20 (+44%) / 390.7 (+65%) / 239.12 (+43%) | SW ↑26.6% | — |
| L45/R12 | `f13b145` | 3202193 | 4.50 (+5%) / 1.67 (-2%) / 1.75 (-1%) / 33.9 (+9%) / 10.38 (+3%) | 38.59 (+64%) / 4.50 (+40%) / 5.26 (+46%) / 410.7 (+74%) / 300.28 (+80%) | FI ↑25.6% | — |
| L46/R13 | `e016f6a` | 3202193 | 4.15 (-4%) / 1.93 (+13%) / 2.01 (+14%) / 31.3 (+1%) / 9.64 (-4%) | 38.45 (+63%) / 4.10 (+28%) / 4.99 (+38%) / 406.5 (+72%) / 296.82 (+78%) | — | — |
| L47/R14 | `c091e44` | 3202193 | 4.70 (+9%) / 1.84 (+8%) / 1.90 (+8%) / 35.6 (+14%) / 10.76 (+7%) | 39.16 (+66%) / 4.03 (+26%) / 4.72 (+31%) / 420.2 (+78%) / 274.84 (+65%) | SW ↑11.6% | — |
| L48/R15 | `3073d1b` | 3202193 | 4.66 (+8%) / 1.54 (-10%) / 1.65 (-7%) / 37.4 (+20%) / 11.89 (+18%) | 37.86 (+61%) / 3.98 (+24%) / 4.82 (+33%) / 406.9 (+72%) / 289.92 (+74%) | SW ↑10.5% | — |
| R16 | `6110f11` | 3202193 | 4.72 (+10%) / 1.47 (-14%) / 1.59 (-10%) / 33.1 (+6%) / 10.39 (+3%) | 35.86 (+52%) / 2.42 (-25%) / 3.66 (+1%) / 400.1 (+69%) / 284.96 (+71%) | — | — |
| R17 | `b9ccff4` | 3202193 | 3.78 (-12%) / 1.33 (-22%) / 1.36 (-22%) / 34.4 (+10%) / 10.78 (+7%) | 38.70 (+64%) / 4.79 (+49%) / 5.58 (+55%) / 409.9 (+73%) / 282.19 (+69%) | — | — |
| L50/R18-R19 | `ca85e87` | 3202193 | 4.95 (+15%) / 1.76 (+3%) / 1.84 (+4%) / 38.2 (+22%) / 11.85 (+18%) | 36.53 (+55%) / 4.80 (+50%) / 5.20 (+44%) / 381.1 (+61%) / 251.21 (+50%) | — | — |
| L51/R20 | `7555025` | 3202193 | 4.44 (+3%) / 1.61 (-6%) / 1.67 (-5%) / 33.8 (+8%) / 10.63 (+6%) | 18.33 (-22%) / 2.32 (-28%) / 2.80 (-22%) / 174.2 (-26%) / 180.36 (+8%) | — | — |
| L51 | `c42c785` | 3202193 | 4.47 (+4%) / 1.61 (-6%) / 1.67 (-5%) / 32.2 (+3%) / 9.50 (-5%) | 14.36 (-39%) / 2.78 (-13%) / 3.17 (-12%) / 140.5 (-41%) / 131.17 (-21%) | — | — |
| L52/R21 | `408c14b` | 3202193 | 4.49 (+4%) / 1.58 (-7%) / 1.65 (-6%) / 35.5 (+14%) / 11.05 (+10%) | 12.50 (-47%) / 4.75 (+48%) / 5.20 (+44%) / 102.9 (-57%) / 125.17 (-25%) | SW ↑16.3% | — |
| L53/R22 | `20f6c98` | 3202193 | 4.42 (+3%) / 1.66 (-3%) / 1.83 (+4%) / 29.4 (-6%) / 9.54 (-5%) | 11.57 (-51%) / 3.77 (+17%) / 4.18 (+16%) / 112.4 (-52%) / 134.18 (-20%) | — | — |
| L54/R23 | `9458217` | 3202193 | 4.62 (+7%) / 1.50 (-12%) / 1.56 (-12%) / 35.6 (+14%) / 11.17 (+11%) | 14.71 (-38%) / 4.59 (+43%) / 5.11 (+42%) / 123.4 (-48%) / 137.96 (-17%) | SW ↑17.1% | — |
| L54 | `e35a77f` | 3202193 | 2.66 (-38%) / 0.77 (-55%) / 0.81 (-54%) / 22.6 (-27%) / 8.05 (-20%) | 12.72 (-46%) / 2.50 (-22%) / 3.18 (-12%) / 94.6 (-60%) / 92.67 (-44%) | — | — |
| L55/R24 | `aee1487` | 3202193 | 4.16 (-3%) / 1.73 (+1%) / 1.79 (+2%) / 27.7 (-11%) / 8.99 (-11%) | 10.74 (-54%) / 1.45 (-55%) / 1.65 (-54%) / 111.8 (-53%) / 146.17 (-12%) | SW ↑11.7%, FI ↑57.7% | 4.65/11.20; 94.70/122.83 |
| L56/R25 | `4e64b75` | 3202193 | 3.11 (-28%) / 0.76 (-55%) / 0.79 (-55%) / 28.0 (-10%) / 10.12 (+1%) | 9.68 (-59%) / 2.65 (-17%) / 3.23 (-10%) / 87.3 (-63%) / 95.93 (-43%) | SW ↑12.5% | 4.61/11.62; 97.07/124.52 |
| L57/R26 | `1e3e04b` | 3202193 | 5.11 (+19%) / 1.59 (-7%) / 1.67 (-5%) / 43.7 (+40%) / 14.12 (+41%) | 12.83 (-46%) / 2.48 (-23%) / 2.83 (-21%) / 125.4 (-47%) / 130.60 (-22%) | SW ↑39.6%, FI ↑36.1% | 4.77/11.47; 86.41/114.69 |
| L58/R27 | `6c6cc37` | 3202193 | 3.72 (-14%) / 0.93 (-46%) / 0.98 (-44%) / 33.5 (+7%) / 10.09 (+0%) | 9.00 (-62%) / 1.93 (-40%) / 2.13 (-41%) / 85.1 (-64%) / 95.95 (-43%) | — | 4.62/11.10; 86.08/114.90 |
| R28 | `fbae82e` | 3202193 | 4.85 (+13%) / 1.85 (+8%) / 1.90 (+8%) / 35.9 (+15%) / 11.24 (+12%) | 10.47 (-56%) / 2.18 (-32%) / 2.42 (-33%) / 99.6 (-58%) / 109.15 (-35%) | SW ↑11.5%, FI ↑13.8% | 4.71/11.40; 85.49/114.11 |
| L60 | `c0c204b` | 3202193 | 3.22 (-25%) / 1.46 (-14%) / 1.49 (-16%) / 21.0 (-33%) / 8.59 (-15%) | 11.36 (-52%) / 2.61 (-19%) / 2.99 (-17%) / 101.0 (-57%) / 96.22 (-42%) | — | 5.21/11.99; 85.26/114.07 |
| L61/R31 | `1359578` | 3202193 | 5.02 (+16%) / 1.79 (+5%) / 1.86 (+6%) / 38.7 (+24%) / 12.78 (+27%) | 13.38 (-43%) / 2.92 (-9%) / 3.26 (-10%) / 126.2 (-47%) / 131.47 (-21%) | SW ↑48.8%, FI ↑36.6% | 5.01/11.65; 85.11/113.95 |
| R32 | `3202193` | 3202193 | 3.69 (-14%) / 1.61 (-6%) / 1.71 (-3%) / 27.2 (-13%) / 11.06 (+10%) | 12.73 (-46%) / 2.60 (-19%) / 2.94 (-18%) / 124.2 (-48%) / 131.70 (-21%) | — | 4.89/11.24; 86.41/114.52 |
## Four-strate U2 baseline
The local mesh windows are all nonempty and deterministic at W1/W8/W12 on Clang and GCC. Each hash vector matched across all six compiler/worker configurations. The cost numbers are medians of three W12 full-view runs using the comparable clip-radius-3 speed-matrix configuration, with each camera in the named stratum. Because the owner plan fails strict ownership and passage validation, these full-view cost runs use a temporary diagnostic binary that continues after those validators; no in-tree Core or Unreal `Source/` file was changed; the bypass is confined to `Saved/Linux/l64/u2cost/export`. The cost cameras were SW `viewer_start` `(0, 0, -496 m)`, FI passage-1 upper mouth `(19.166, -14.200, -732.201 m)`, TN passage-1 lower mouth `(19.166, -14.200, -753.374 m)`, and VS passage-2 lower mouth `(−20.063, −5.068, −900.354 m)`. The SW local mesh window above is the resolved route arrival, not its cost camera.
| Strate (chunks; content band) | Core generator path | Local window (nonempty/27; triangles) | Determinism | Whole-view W/F/N/Σ/L0 | Arrival→departure audit |
|---|---|---|---|---|---|
| SW (0…−63; −503.5…7.5 m) | SurfaceWorld-native | route spawn: 9/27; 25,672 | W1/W8/W12 Clang/GCC: PASS | 6.34/0.63/0.80/68.8/60.39 | SW route fails: 24/33 points unsupported; SW→FI interface fails |
| FI (−64…−93; −743.5…−504.5 m) | compiled-analytic/FloatingIslands (legacy) | passage 1 upper mouth: 21/27; 93,149 | W1/W8/W12 Clang/GCC: PASS | 8.41/0.53/0.97/95.1/74.21 | SW→FI lower mouth out of band; FI→TN interface fails |
| TN (−94…−109; −871.5…−744.5 m) | compiled-analytic/TunnelNetwork (legacy) | passage 1 lower mouth: 13/27; 65,248 | W1/W8/W12 Clang/GCC: PASS | 6.50/0.53/0.79/71.6/56.33 | FI→TN and TN→VS interfaces fail |
| VS (−110…−125; −999.5…−872.5 m) | compiled-analytic/VerticalShafts (legacy) | passage 2 lower mouth: 5/27; 4,761 | W1/W8/W12 Clang/GCC: PASS | 3.39/0.52/0.54/34.6/14.51 | TN→VS: 3 unsupported; floor ripple 3.57 cm > 2.5 cm |
Across the three interface audits, 0/3 passages passed. In addition to the out-of-band 0–1 mouth, passage 1–2 has floor ripple 4.78 cm (2.5 cm limit), and 2–3 has 3.57 cm ripple; unsupported/blocked samples also remain. Native caves and lattice are not enabled in this plan; TN and VS use their legacy analytic evaluators.
## Failed runs
- Historical build `3e443bf` (L37/R1) failed at `ShapeWorldInternal.h:1554` with an extra `)`; the next L37 fix built and was measured. The corrected bisect has 402/402 successful timing runs. An earlier discarded invocation used unsupported `--data=` syntax and was replaced by the positional data path.
- `verify.py --tier fast --out Saved/Linux/l64` in the copied legacy-plan shadow reached the motif fixture and exited 1: `motif-slice1-capsule-reachability-clang` rejected its motif override because it did not resolve to a SurfaceWorld strate. Its hard-coded two-strata style map does not validate U2; U2 boundary, determinism, and passage audits are reported above. A first stale-source-stamp refusal was corrected by copying the L37 audit script into the shadow.
- The first U2 whole-view SW pass exited at the passage walkability gate (`65/7,729` samples solid). The cost table reruns use the same diagnostic binary for all four strata with route validation bypassed, and retain the audit failure. A first build attempt also used a nonexistent `TMPDIR`; it was corrected and the diagnostic build passed.
Evidence is under `Saved/Linux/l64`: `runs-spawn-correct.json` and `runs-spawn-correct/`, `profiles-spawn.json`, `u2probe/determinism.json`, `u2probe/passage-audit-clang.log`, `u2probe/boundary-dispatch.log`, `u2cost/speed-cost.json`, and `verify-fast.log`.
## Orchestrator review (Claude)
**The bisect timings are inflated by machine contention.** I was building Unreal Engine on cores 12–19 during this
round (memory bandwidth and disk I/O), and adjacent commits swing ±40%. A clean re-measure on a quiet machine
(6 interleaved runs each, same binaries, data `owner_world-3202193.json`, same command and camera):
| Build | Whole view (median of 6) | Worker-s, LOD0–8 | LOD0 ms/tile |
|---|---:|---:|---:|
| L14 `a176d07` | ≈1.55 s | 12.62 | 3.99 |
| L61 `1359578` | ≈1.72 s | 14.65 | 4.99 |
**Core regressed about +16% in worker time and +25% per LOD0 tile, not ×2.** The owner's Windows trace shows about
33 ms per LOD0 tile p95, roughly 6× Core's per-tile cost here, so most of the Windows regression is on the Unreal side
or isn't a like-for-like comparison (R33: U1's trace was not a controlled A/B, at 2,578 tiles). Core's own +16% is
plausibly R33 suspect #1: per-sample `ApplyResolvedStrateComposition` (L23) plus the per-sample passage loop (L37).
**The new four-strate plan has real defects:**
- passage 0–1's lower mouth is out of FI's band;
- all three interfaces fail their audits;
- FI, TN and VS run on legacy evaluators;
- the full view costs 3.4–8.4 s per strate.
Next: get the Linux UE build to compile, then trace the Unreal path itself for L14 vs HEAD here.
@@ -0,0 +1,93 @@
# Linux65 report
Date: 2026-10-26. No commit, push, or stash was made. The pre-existing untracked `Linux18/exports/` was left alone.
## Linux Unreal build
UE 5.7.4 / bundled Clang 20 builds pass with zero errors. The clean HEAD build completed 78 actions in 93.54 s; L14 (`a176d07`) completed 157 actions in 170.69 s. Final incremental builds after the Linux-only trace timing guards also pass. Logs are `Saved/Linux/l65/head-build5.log`, `l14-build1.log`, `head-build-final.log`, and `l14-build-final.log`.
Builds used CPUs 0–11, a 6 GiB RSS limit, at most 8 actions, AVX2, and `-NoUBA`. UBA stalled before starting compiler actions. Three existing RMC deprecation warnings remain: two `RemoveSectionGroup` calls and one `CreateSectionGroup` call in `VoxelWorld.cpp`.
Fixes:
- The Compat wrappers now forward to UE headers with `include_next` for generated Linux module TUs. `CoreNamespacePreamble.h` opts canonical Core TUs into the Compat definitions. `CoreMinimal.h` undefines `WITH_EDITOR` before defining the standalone value.
- `VoxelForgeCore.Build.cs` enables that forwarding and AVX2 only on Linux. `VoxelForge.Build.cs` disables Unity only on Linux to avoid duplicate anonymous test helpers.
- `VoxelContentManager.cpp` uses `std::int64_t` consistently; `VoxelRedefinedDensityField.cpp` groups the mixed `&&`/`||` expression.
- Linux-only trace fields measure Core `generateTile`, mesh copy, and RMC stream-set construction. The `PLATFORM_LINUX` guards keep Windows worker behavior and trace JSON unchanged.
### Files changed for the owner's Windows build review
All `Source/` changes:
- `Source/VoxelForge/Private/VoxelContentManager.cpp`
- `Source/VoxelForge/Private/VoxelRedefinedDensityField.cpp`
- `Source/VoxelForge/Private/VoxelStartupTrace.cpp`
- `Source/VoxelForge/Private/VoxelWorld.cpp`
- `Source/VoxelForge/Public/VoxelStartupTrace.h`
- `Source/VoxelForge/Public/VoxelWorld.h`
- `Source/VoxelForge/VoxelForge.Build.cs`
- `Source/VoxelForgeCore/VoxelForgeCore.Build.cs`
All changed `Standalone/Compat` headers:
- `AssetRegistry/AssetData.h`, `AssetRegistry/AssetRegistryModule.h`
- `Async/ParallelFor.h`, `CoreMinimal.h`
- `Dom/JsonObject.h`, `Dom/JsonValue.h`, `Engine/DataAsset.h`, `GameplayTagContainer.h`
- `HAL/CriticalSection.h`, `HAL/Event.h`, `HAL/FileManager.h`, `HAL/IConsoleManager.h`, `HAL/PlatformProcess.h`, `HAL/PlatformTime.h`
- `Json.h`, `Math/RandomStream.h`
- `Misc/CommandLine.h`, `Misc/Crc.h`, `Misc/FileHelper.h`, `Misc/Parse.h`, `Misc/Paths.h`, `Misc/ScopeLock.h`, `Misc/ScopeRWLock.h`, `Misc/SecureHash.h`
- `Modules/ModuleManager.h`, `ProfilingDebugging/CpuProfilerTrace.h`
- `Serialization/CustomVersion.h`, `Serialization/JsonSerializer.h`, `Serialization/JsonWriter.h`, `Stats/Stats.h`
- `Templates/SubclassOf.h`, `Templates/UniquePtr.h`
- `UObject/EnumProperty.h`, `UObject/FieldIterator.h`, `UObject/Object.h`, `UObject/UObjectGlobals.h`, `UObject/UnrealType.h`
Also changed: `Standalone/Core/src/CoreNamespacePreamble.h`.
## Unreal spawn trace
The U1 trace is `Tools/TestVoxelForgeCoreWorldHost.ps1 -TimeoutSeconds 900 -CoreVoxelSizeCm 25 -StreamingTraceOnly`. It runs `UnrealEditor-Cmd -run=VoxelForgeExplore` with `-coreworldtest=1`, `-coreworldstreamtrace=1`, `/Game/VoxelForge/DA_Settings.DA_Settings`, seed 0, 25 cm Core voxels, 600 cm/s movement, and the Legacy/default streaming flags. It creates a transient `UWorld`; no map is loaded.
The exact pre-U2 owner packages are unavailable in this Linux workspace. Both hosts used identical copies of the five current `/home/jahni/VoxelM/DA_*.uasset` files (post-U2), with matching SHA-256s in both projects:
| Asset | SHA-256 |
|---|---|
| `DA_Settings` | `8abe8243104980260feca0a6f4cf4927768e8e568221a6f6a2c2694f6d15d454` |
| `DA_Strate1` | `a9ca6cddd102250a893e379350b1d0f4aaf04c4f7fca70e0db46052a067d2cfb` |
| `DA_Strate2` | `8f9a12a3181635149e6f79f192e0ddaf6b6577442ee324071a7749dcc78b9a0c` |
| `DA_Strate3` | `5470d538c3c5e88237463e71bca9e2eb170dfe3a0543c0f33298d9fb010fd2f9` |
| `DA_Strate4` | `a98e33e1cd42f374d6cb70f5071e8ebba74d91ed59d2fd3201782dab84d28f5c` |
The minimal host lacked the two First Person Blueprints used by the commandlet's speed fixture. Stock UE template copies also lacked a valid sprint graph, so the isolated trace build trees used the test's established 600/6000 speed defaults only for `-StreamingTraceOnly`; no tracked source was changed for this workaround. The trace loop itself takes its 600 cm/s movement value from the command line.
| Measure | L14 | HEAD (`e157847`) |
|---|---:|---:|
| Tiles per LOD | Not reached | Not reached |
| Cells per tile | Not reached | Not reached |
| Initial worker-seconds | Not reached | Not reached |
| LOD0 tile worker p50 / p95 | Not reached | Not reached |
| Full initial set ready | Not reached | Not reached |
| Walkable ring count / ready time | Not reached | Not reached |
| `generateTile` / mesh copy / RMC stream build | Not reached | Not reached |
Both commandlets stop before spawning `AVoxelWorld` with the same error: `SurfaceWorld route candidates failed the capsule and support sweep.` No tile trace file was produced. Thus this run cannot identify the Windows worker-time regression or separate Core generation from Unreal packaging. The failure is consistent across L14 and HEAD, but whether the post-U2 asset set or the route audit itself is responsible is unresolved; no fix is justified from this run.
L14 was exported from `a176d07` to the requested `Saved/Linux/l65/l14tree/Plugins/VoxelForge`. The sandbox did not permit creating the sibling `/home/jahni/VFHostL14`, so its second host project is nested at `/home/jahni/VFHost/VFHostL14/VFHostL14.uproject`. The supplied `/home/jahni/VFHost/VFHost.uproject` was restored byte-for-byte after testing. RealtimeMeshComponent's original source tree was not modified.
## Failed runs
- Initial builds hit a read-only UBT config path, then a read-only RMC intermediate timestamp, then a UBA stall. The next compile found Linux Unity duplicate test helpers; disabling Unity on Linux fixed it. A final incremental attempt made after restoring the original host descriptor again hit the RMC read-only timestamp; rebuilding with the local RMC copy passed.
- Two early trace launches rejected an output directory outside the host project. The next launch stopped on the absent staged character Blueprint. Stock UE template Blueprints then failed to provide a sprint value; the trace-only defaults got past that gate. Both final revisions then stopped at the identical SurfaceWorld route audit above.
There were 12 build invocations, within the 15-attempt Part A limit.
## Orchestrator review (Claude)
Accepted. **The Linux UE build compiles VoxelForge with 0 errors** (HEAD and L14). I checked that the Compat and
`Source/` changes don't alter Core output: standalone Clang and GCC rebuilds produce **identical whole-view hashes**
to `3202193` on the pre-U2 data, for both SW and FI. The RealtimeMeshComponent tree is unmodified: its 203 git "M"
entries are CRLF-only (empty diff with whitespace and CR ignored, no file newer than this round outside
Intermediate/Binaries). The Unreal trace is blocked for both L14 and HEAD by the **owner's new assets**:
`SurfaceWorld route candidates failed the capsule and support sweep` before `AVoxelWorld` spawns (consistent with
L64: SW route 24/33 unsupported). The `verify.py` tiers still assume the old two-strate owner data and fail on the U2
export; they need a pinned regression dataset plus new-plan baselines. `Source/` files to include in the owner's next
Windows build are listed above.
@@ -0,0 +1,72 @@
# VoxelForge L66 — Linux report
## Findings and fixes
- The saved SW passage range is `ReachMax=5000` owner voxels, despite the C++ type default of 96; why the export carries 5000 is unresolved. At 0.125 m per owner voxel, the sampled lower reach was 318.539 m. With FI content top at −504.5 m, the old lower mouth resolved to −823.039 m, 79.539 m below FI content bottom. The planner selected the config from the upper SW stratum and did not cap that reach to FI’s band. This was not a metre migration error; L64’s prose value of −822.06 m was not reproduced by the saved layout. Core now caps each sampled mouth reach to its own usable content interval. The new 0–1 lower mouth is −737.000 m, 6.5 m above FI content bottom, and strict layout validation passes.
- The Unreal adapter’s old SW search tried 648 fixed 64 m candidates; all were rejected on grade. The adapter now tries a denser bounded search with shorter lengths for a single-mouthed SW route. In the four-strate HEAD spawn, 35 candidates were eligible and the selected route passed with 0 blocked, 0 unsupported and 0 grade failures. The 33-point SW arrival-to-departure audit also passes with 0 unsupported points. L14 still cannot run this SW route.
- The passage interfaces remain the main failure. Their unsupported samples are concentrated in the composed passage floor even though the underlying native fields report 5/5 support probes at the corresponding segments. A four-strate grounded-tread experiment did not reduce these counts and introduced blocked samples in controls, so it was reverted. The 0–1 rock column also falls short of its relief threshold. At 1–2 the sampled floor jumps upward by 0.2498 m at 109.280 m along the passage; the source of that one-Core-voxel offset is not resolved.
- Internal-chain floor/support blending reduced 2–3 floor ripple from 3.57 cm to 0.10 cm and 1–2 from 4.78 cm to 2.90 cm. The 1–2 floor and wall ripple gates still fail. No full quality pass is claimed.
## Four-strate audit
Live input SHA-256: `f42f96adca58a4ebd680b8553b2969d6cd9ca8a73f875f042bfbc7b8c649fdca`. The generator and style maps were derived from that export: `SurfaceWorld`, `FloatingIslands`, `TunnelNetwork`, `VerticalShafts`; passage styles `{0:1, 1:1, 2:1, 3:1}`.
| Audit | Before | After L66 |
|---|---|---|
| Strict layout | Rejected 0–1: lower mouth −823.039 m, below FI | Accepted; clamped reach 318.539 → 232.500 m; lower mouth −737.000 m |
| SW route | 24/33 points unsupported; Unreal spawn sweep failed | 33 points, 0 unsupported, 0 grade failures; mouth binding passes |
| Transitions | Not accepted with the rejected layout | 3/3 pass |
| Per-strate generation | Not audited | Each generator produced 27 tiles; W1/W8 each produced 12 hashes and matched |
| Passage chain | 0/3 walkable | 0/3 walkable; detailed results below |
| Interface | Unsupported / blocked / grade failures | Wall ripple | Floor ripple | Other result |
|---|---:|---:|---:|---|
| 0–1 SW→FI | 59 / 0 / 0 | 1.49 cm, pass | 0.08 cm, pass | Column relief 0.172 m < 0.20 m; route support 46/46, wall margin 1.75 m |
| 1–2 FI→TN | 3 / 0 / 0 | 6.78 cm, fail | 2.90 cm, fail | 0.2498 m floor offset begins at passage distance 109.280 m |
| 2–3 TN→VS | 2 / 0 / 0 | 2.28 cm, pass | 0.10 cm, pass | Ripple passes; unsupported support probes remain |
The fast tier checks arrival-to-departure for each stratum through its adjacent interfaces. Those four gates fail because the interfaces above are not yet walkable. The standalone route audit for strate 0 (SW) passes.
Per-strate W1 cost (`whole_view_ready`, worker-seconds): SW 1.015 s / 0.839 s; FI 1.424 s / 1.254 s; TN 0.558 s / 0.388 s; VS 0.208 s / 0.043 s. Each run generated 27 tiles.
## Unreal trace — L14 vs HEAD
L14 could not run the four-strate plan, and its compact 16-chunk SurfaceWorld route also failed the fixed-length search. Both versions therefore used the same transient 16-chunk FlatPlain trace plan copied from `DA_Strate1`; `DA_Settings` and `DA_Strate1`–`DA_Strate4` hashes matched between hosts. Three interleaved 45 s traces completed per version. No Unreal build ran during the timing trials.
| Measure | L14 | HEAD |
|---|---:|---:|
| Cells per tile | 16³ = 4,096 | 16³ = 4,096 |
| Desired tiles LOD0–8 | 512/448/320/208/144/96/96/96/306 | same |
| Unique generated keys LOD0–8 | 1264/896/464/284/156/96/96/96/306 | 1272/896/464/284/156/96/96/96/306 |
| Generated records LOD0–8 over trace | 1355/928/464/284/156/96/96/96/306 | 1363/928/464/284/156/96/96/96/306 |
| Worker-seconds by LOD0–8 | 4.962/2.985/1.078/0.751/0.441/0.323/0.322/0.316/1.063 | 5.168/3.007/1.138/0.786/0.454/0.262/0.326/0.320/1.118 |
| Total worker-seconds | 12.200 | 12.561 |
| LOD0 worker p50 / p95 | 3.324 / 7.351 ms | 3.604 / 7.313 ms |
| `generateTile` / mesh copy / RMC build | 11.980 / 0.036 / 0.179 s | 12.358 / 0.032 / 0.167 s |
| Initial walk gate | 9/9 ready at 0.244 s | 9/9 ready at 0.249 s |
| LOD0 ring | 512 tiles ready at 0.827 s | 512 tiles ready at 0.823 s |
| Full set | 2226/2226 at 3.326 s | 2226/2226 at 3.309 s |
| Median trace wall time | 45.037 s | 45.027 s |
Core `generateTile` accounts for 98.2% of measured L14 phase time and 98.4% for HEAD. Mesh copy is 0.29% / 0.25%; RMC build is 1.46% / 1.33%. Each version passed 12 ready gates through movement; the full-set trace had 125 collision tiles desired and 78 ready at the recorded completion point.
## Verification tiers
- `Standalone/data/regression/owner_world_2strate_l61.json` pins the L64 two-strate export; SHA-256 `7b095f9bcc6b90133d5a463f99f0b571133b2c549ce1ecf2d64fe9175e41e183`. All 60 fast-tier runs that consume owner data used this fixture. Legacy gates, including styles, passage audits, W1/W12 determinism, L52 hash comparisons, rings and controls, passed. L46 hash comparisons remain non-gating diagnostics and differ; L53 diagnostics and gated L52 hashes match.
- `verify.py` reads the live four-strate generator/style expectations from `owner_world.json`; legacy helper scripts receive `VOXELFORGE_OWNER_DATA` so they use the pinned fixture. The fast summary is **FAIL** only because the new-plan stage records nine failed interface/column/ripple gates and then fails its final stage gate. Generation, W1/W8 determinism, strict layout and transition checks pass.
- `Standalone/Tools/verify/README.md` documents the two input sets and deferred fast-tier result.
## Unreal source files for the owner's Windows build
- `Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp`
- `Source/VoxelForgeEditor/Private/VoxelForgeExploreCommandlet.cpp`
Both Linux L14 and HEAD host builds completed successfully. These are the two `Source/` files for the owner's Windows build.
## Failed runs
- An initial L14 trace used an output path outside the plugin `Saved` directory; the commandlet rejected it.
- The L14 compact SurfaceWorld attempt found no eligible 64 m candidate. The common FlatPlain plan was used for the paired timing traces.
- The first fast-tier rerun exposed hard-coded live-owner paths in the ring and motif fixture helpers. Both were switched to the pinned input; the subsequent run passed all legacy gates.
- The final fast tier remains red on the four-strate gates listed above.
- The grounded-tread support experiment compiled, but left unsupported counts at 59/3/2 and added blocked control samples; its source change was reverted.
@@ -0,0 +1,364 @@
#!/usr/bin/env python3
"""Render arrival, departure, and mid-tunnel views for the four-strate owner plan."""
import json
import importlib.util
import argparse
import math
import os
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l67/captures-run"
BIN = ROOT / "Saved/Linux/l67/bin-final"
CAPTURES = Path(__file__).resolve().parent / "captures"
HEADLESS = BIN / "VoxelForgeCoreHeadless-l67-final-clang"
RENDERER = BIN / "VoxelForgeOffscreenCapture-l67-final-clang"
PNG_DIR = OUT / "capture-png"
RECIPE_INPUTS = (
("passage-0-1", ROOT / "Standalone/data/owner_world.json",
"surface-world", "SurfaceWorld", "upper"),
("passage-1-2", ROOT / "Standalone/data/owner_world.json",
"surface-world", "FloatingIslands", "upper"),
("passage-2-3", ROOT / "Standalone/data/owner_world.json",
"surface-world", "TunnelNetwork", "upper"),
)
SUBVOXELS_PER_VOXEL = 256.0
RING_AUDIT = ROOT / "Standalone/Research/WorldAssemblerM3/Linux32/AuditRingTerraceL32.py"
spec = importlib.util.spec_from_file_location("ring_profile_l32", RING_AUDIT)
ring_profile = importlib.util.module_from_spec(spec)
spec.loader.exec_module(ring_profile)
for directory in (OUT, BIN, PNG_DIR, CAPTURES):
directory.mkdir(parents=True, exist_ok=True)
ENV = os.environ.copy()
ENV["LP_NUM_THREADS"] = "12"
ENV["XDG_CACHE_HOME"] = str(OUT / "xdg-cache")
PARSER = argparse.ArgumentParser(description=__doc__)
PARSER.add_argument("--scene", choices=[value[0] for value in RECIPE_INPUTS])
PARSER.add_argument("--view", choices=("arrival-mouth", "departure-mouth", "mid-tunnel"))
OPTIONS = PARSER.parse_args()
def run(name, command):
log = OUT / f"capture-{name}.log"
wrapped = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1800s", *map(str, command)]
started = time.monotonic()
with log.open("w", encoding="utf-8") as stream:
result = subprocess.run(wrapped, cwd=ROOT, env=ENV, stdout=stream,
stderr=subprocess.STDOUT, timeout=1800, check=False)
elapsed = time.monotonic() - started
print(f"[L35CaptureRun] step={name} elapsed={elapsed:.1f}s exit={result.returncode} log={log}",
flush=True)
if result.returncode != 0:
raise SystemExit(f"{name} failed; see {log}")
return log
def add(a, b):
return tuple(x + y for x, y in zip(a, b))
def sub(a, b):
return tuple(x - y for x, y in zip(a, b))
def mul(a, scale):
return tuple(x * scale for x in a)
def norm(a):
length = math.sqrt(sum(x * x for x in a))
if length <= 1.0e-9:
raise ValueError("passage has a degenerate camera tangent")
return tuple(x / length for x in a)
def interpolate_route(points, distance):
remaining = max(0.0, distance)
for a, b in zip(points, points[1:]):
delta = sub(b[0], a[0])
length = math.sqrt(sum(x * x for x in delta))
if length <= 1.0e-9:
continue
if remaining <= length:
t = remaining / length
pos = tuple(a[0][axis] + delta[axis] * t for axis in range(3))
radius = a[1] + (b[1] - a[1]) * t
return pos, radius
remaining -= length
return points[-1]
def route_frame(points, distance):
center, radius = interpolate_route(points, distance)
before, _ = interpolate_route(points, max(0.0, distance - 0.25))
after, _ = interpolate_route(points, distance + 0.25)
tangent = norm(sub(after, before))
horizontal = norm((tangent[0], tangent[1], 0.0))
side = (-horizontal[1], horizontal[0], 0.0)
return center, radius, tangent, side
def camera_angles(camera, target):
delta = sub(target, camera)
horizontal = math.hypot(delta[0], delta[1])
yaw = math.degrees(math.atan2(delta[0], delta[1]))
pitch = math.degrees(math.atan2(delta[2], horizontal))
return yaw, pitch
def fmt(values):
return ",".join(f"{value:.6f}" for value in values)
def load_reference_cameras():
log = ROOT / "Saved/Linux/l34/run-capture-standard-set-final-radius3.log"
pattern = re.compile(
r"^\[L34CaptureCamera\] view=(\S+).*?camera_m=\(([^)]*)\) "
r"target_m=\(([^)]*)\) clip=(\S+)", re.M)
values = {}
for name, camera, target, clip in pattern.findall(
log.read_text(encoding="utf-8", errors="replace")):
parse = lambda value: tuple(float(part) for part in value.split(","))
values[name] = (parse(camera), parse(target), None if clip == "none" else parse(clip))
return values
REFERENCE_CAMERAS = {}
def terrain_surface_near(mesh, x, y, expected_height):
bounds = (x - 2.0, x + 2.0, y - 2.0, y + 2.0)
_, triangles, _ = ring_profile.read_scene(
str(mesh), None, -1.0e9, 1.0e9, bounds)
buckets = ring_profile.build_buckets(triangles, True, bounds, cell_size=1.0)
hits = []
for dy in range(-4, 5):
for dx in range(-4, 5):
sample_x, sample_y = x + dx * 0.5, y + dy * 0.5
hit = ring_profile.sample_surface(buckets, sample_x, sample_y, True, 1.0,
preferred_height=expected_height)
if hit is not None:
hits.append((math.hypot(dx * 0.5, dy * 0.5),
abs(hit[0] - expected_height), sample_x, sample_y, hit[0]))
if not hits:
return None
distance, _, sample_x, sample_y, height = min(hits)
if distance > 2.0 or abs(height - expected_height) > 8.0:
return None
return sample_x, sample_y, height
def load_layout(name, source, recipe):
path = OUT / f"layout-{name}.json"
run(f"layout-{name}", [HEADLESS, source, "--workers=12", "--world-assembler-prototype",
"--stream-benchmark", "--stream-pipeline", "--force-generate", "--clip-radius=2",
"--max-clip-level=0", "--max-level=0", f"--recipe={recipe}",
f"--resolved-layout-output={path}", "--resolved-layout-only"])
return json.loads(path.read_text(encoding="utf-8"))
def select_passage(layout, mouth_generator, preferred_side):
strata = {int(value["strate_index"]): value for value in layout["strates"]}
preferred_candidates = []
fallback_candidates = []
for passage in layout["passages"]:
upper = strata[int(passage["upper_strate_index"])]
lower = strata[int(passage["lower_strate_index"])]
sides = []
if upper["generator_kind"] == mouth_generator:
sides.append("upper")
if lower["generator_kind"] == mouth_generator:
sides.append("lower")
if preferred_side in sides:
preferred_candidates.append((passage, preferred_side == "lower"))
elif sides:
fallback_candidates.append((passage, sides[0] == "lower"))
candidates = preferred_candidates or fallback_candidates
if not candidates:
raise RuntimeError(f"no passage mouth in generator {mouth_generator}")
return sorted(candidates, key=lambda item: item[0]["stable_id"])[0]
def make_views(layout, passage, reverse):
q_to_m = layout["core_voxel_size_um"] / (SUBVOXELS_PER_VOXEL * 1_000_000.0)
controls = [
(tuple(value * q_to_m for value in point["position_q"]),
point["radius_q"] * q_to_m)
for point in passage["control_points"]
]
lower_mouth = tuple(value * q_to_m for value in passage["lower_mouth_q"])
lower_index = min(range(len(controls)), key=lambda i: sum(
(controls[i][0][axis] - lower_mouth[axis]) ** 2 for axis in range(3)))
path = controls[:lower_index + 1]
if len(path) < 2:
raise RuntimeError(f"{passage['stable_id']} has no mouth-to-mouth route")
if reverse:
path.reverse()
lengths = [math.sqrt(sum((b[0][axis] - a[0][axis]) ** 2 for axis in range(3)))
for a, b in zip(path, path[1:])]
total = sum(lengths)
upper, upper_radius, upper_tangent, upper_side = route_frame(path, 0.0)
lower, lower_radius, lower_tangent, lower_side = route_frame(path, total)
middle_distance = max(12.0, min(total - 12.0, total * 0.4)) if total > 24.0 else total * 0.4
entry_distance = min(6.0, total * 0.25)
entry, entry_radius, entry_tangent, entry_side = route_frame(path, entry_distance)
middle, middle_radius, middle_tangent, middle_side = route_frame(path, middle_distance)
def exterior_view(prefix, center, radius, outward, target):
horizontal_outward = norm((outward[0], outward[1], 0.0))
eye_height = center[2] - radius + 1.6
eye = (center[0] + horizontal_outward[0] * 5.5,
center[1] + horizontal_outward[1] * 5.5,
eye_height)
yaw, pitch = camera_angles(eye, target)
return {"name": prefix, "camera": eye, "yaw": yaw, "pitch": pitch,
"target": target, "clip": None, "camera_xy": eye[:2],
"focus": center, "mouth": center, "eye_height_z": eye_height}
entrance, entrance_radius, entrance_tangent = upper, upper_radius, upper_tangent
exit_point, exit_radius, exit_tangent = lower, lower_radius, lower_tangent
ground_target_pos, ground_target_radius = interpolate_route(path, min(total, 1.0))
ground = exterior_view("ground-mouth", entrance, entrance_radius,
mul(entrance_tangent, -1.0), (ground_target_pos[0], ground_target_pos[1],
ground_target_pos[2] - ground_target_radius + 1.0))
ground["focus"] = entrance
ground["outside_camera"] = True
def cutaway_view(prefix, center, radius, tangent, side, distance, target_distance):
camera = (center[0] - side[0] * 7.0, center[1] - side[1] * 7.0,
center[2] - radius + 1.6)
target_pos, target_radius = interpolate_route(path, target_distance)
target = (target_pos[0], target_pos[1],
target_pos[2] - target_radius + 1.6)
yaw, pitch = camera_angles(camera, target)
plane = (side[0], side[1], 0.0,
-(side[0] * center[0] + side[1] * center[1]))
return {"name": prefix, "camera": camera, "yaw": yaw, "pitch": pitch,
"target": target, "clip": plane, "camera_xy": camera[:2],
"focus": center, "mouth": center}
# Place each interface view at its true route endpoint. The endpoint camera
# is a side cutaway so the solid terrain in front of an underground mouth
# cannot hide the floor-to-tunnel connection.
arrival_distance = 0.0
arrival_center, arrival_radius, arrival_tangent, arrival_side = route_frame(
path, arrival_distance)
arrival = cutaway_view("arrival", arrival_center, arrival_radius,
arrival_tangent, arrival_side, arrival_distance,
min(total, arrival_distance + 12.0))
arrival["camera_location"] = "passage-air"
arrival["focus_radius"] = 20.0
arrival["yaw"], arrival["pitch"] = camera_angles(
arrival["camera"], arrival["target"])
entry_camera_distance = min(8.0, total * 0.1)
entry_center, entry_radius, entry_tangent, entry_side = route_frame(
path, entry_camera_distance)
# Player-height view from just inside the passage, aimed through its actual
# interface mouth so the floor, bore, and opening share one frame.
entry_camera = (entry_center[0], entry_center[1],
entry_center[2] - entry_radius + 1.6)
entry_target_pos, entry_target_radius = interpolate_route(
path, min(total, entry_distance + 14.0))
entry_target = (entry_target_pos[0], entry_target_pos[1],
entry_camera[2] - 2.5)
entry_yaw, entry_pitch = camera_angles(entry_camera, entry_target)
entry_view = {"name": "rock-entry", "camera": entry_camera,
"yaw": entry_yaw, "pitch": entry_pitch, "target": entry_target,
"clip": None, "camera_xy": entry_camera[:2],
"focus": entry_center, "mouth": entrance, "outside_camera": False,
"camera_location": "passage-air", "focus_radius": 20.0,
"eye_height_z": entry_camera[2]}
middle_view = cutaway_view("mid-tunnel", middle, middle_radius, middle_tangent,
middle_side, middle_distance, min(total, middle_distance + 2.0))
ground["focus_radius"] = 20.0
middle_view["focus_radius"] = 20.0
middle_camera, _ = interpolate_route(path, max(0.0, middle_distance - 6.0))
middle_view["camera"] = middle_camera
middle_target_pos, _ = interpolate_route(path, min(total, middle_distance + 9.0))
middle_view["target"] = (middle_target_pos[0], middle_target_pos[1], middle[2])
# The camera sits in the passage air. Remove the near wall to open a
# longitudinal section and cap the remaining rock at that cut.
middle_cut_point = (middle[0] - middle_side[0] * 2.5,
middle[1] - middle_side[1] * 2.5, middle[2])
middle_view["clip"] = (*middle_side,
-sum(middle_side[axis] * middle_cut_point[axis] for axis in range(3)))
middle_view["camera_location"] = "passage-air"
middle_view["yaw"], middle_view["pitch"] = camera_angles(
middle_view["camera"], middle_view["target"])
return (ground, entry_view, middle_view, arrival), total, lower_index
def export_capture(scene_name, source, recipe, passage, endpoint, view):
stem = f"{passage['stable_id'].replace('/', '-')}-{endpoint}"
view["name"] = stem
mesh = OUT / f"{stem}.vfmesh"
png = PNG_DIR / f"{stem}.png"
jpg = CAPTURES / f"{stem}.jpg"
yaw, pitch = view["yaw"], view["pitch"]
camera = view["camera"]
export_camera = view.get("focus", camera)
run(f"{stem}-export", [HEADLESS, source, "--workers=12",
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
"--force-generate", "--clip-radius=3", "--max-clip-level=0", "--max-level=0",
"--render-distance-chunks=0", f"--recipe={recipe}",
f"--camera={fmt((*export_camera, yaw, pitch))}", f"--mesh-export={mesh}"])
render_clip = view["clip"]
surface = None
if view.get("outside_camera", False):
surface = terrain_surface_near(mesh, *view["camera_xy"], view["target"][2])
if surface is None:
camera = (view["camera_xy"][0], view["camera_xy"][1], view["eye_height_z"])
else:
camera = (surface[0], surface[1], surface[2] + 1.6)
elif render_clip is not None:
camera = view["camera"]
yaw, pitch = camera_angles(camera, view["target"])
print(f"[L67CaptureCamera] view={stem} camera_m=({fmt(camera)}) "
f"target_m=({fmt(view['target'])}) "
f"clip={fmt(render_clip) if render_clip is not None else 'none'} fov_deg=80",
flush=True)
render = [RENDERER, mesh, png, "--size=1600x900", "--fov-deg=80",
f"--camera-m={fmt(camera)}", f"--yaw-deg={yaw:.6f}", f"--pitch-deg={pitch:.6f}",
"--tile-skirts=off", "--shade=readable", "--headlamp=on", "--height-tint=on",
f"--height-center-m={view['target'][2]:.6f}", "--height-range-m=8", "--tile-edges=off"]
if view.get("focus_radius", 24.0) is not None:
render.append(f"--focus-region={fmt((*view['focus'], view.get('focus_radius', 24.0)))}")
if render_clip is not None:
render.append(f"--clip-plane={fmt(render_clip)}")
run(f"{stem}-render", render)
run(f"{stem}-jpeg", ["ffmpeg", "-nostdin", "-hide_banner", "-loglevel", "error",
"-y", "-i", png, "-frames:v", "1", "-q:v", "4", jpg])
size = jpg.stat().st_size if jpg.exists() else 0
print(f"[L67CaptureJPEG] file={jpg.relative_to(ROOT)} bytes={size} "
f"status={'PASS' if 0 < size <= 300_000 else 'FAIL'}", flush=True)
if not 0 < size <= 300_000:
raise SystemExit(f"{jpg.name} is missing or exceeds 300 KB")
for scene_name, source, recipe, mouth_generator, preferred_side in RECIPE_INPUTS:
if OPTIONS.scene and scene_name != OPTIONS.scene:
continue
layout = load_layout(scene_name, source, recipe)
passage, _ = select_passage(layout, mouth_generator, preferred_side)
forward_views, route_length, lower_index = make_views(layout, passage, False)
reverse_views, _, _ = make_views(layout, passage, True)
print(f"[L67CapturePassage] interface={scene_name.removeprefix('passage-')} "
f"id={passage['stable_id']} route_m={route_length:.3f} "
f"control_points={lower_index + 1}", flush=True)
endpoint_views = (
("arrival-mouth", forward_views[0] if scene_name == "passage-0-1" else forward_views[1]),
("departure-mouth", reverse_views[3]),
)
for endpoint, view in endpoint_views:
if OPTIONS.view and endpoint != OPTIONS.view:
continue
export_capture(scene_name, source, recipe, passage, endpoint, view)
if scene_name == "passage-1-2" and (OPTIONS.view is None or OPTIONS.view == "mid-tunnel"):
export_capture(scene_name, source, recipe, passage, "mid-tunnel", forward_views[2])
@@ -0,0 +1,81 @@
# L67 — four-strate passage walkability
## Cause and fix
The FI, TN, and VS paths use the compiled analytic base evaluator and then the shared strate composer. The “native” 5/5 result was the same composed field with resolved passages removed; it was not a raw-generator probe. The band stage is therefore not the difference. The passage carve and the support/capsule gates are.
- **0–1 SW→FI:** at the failed probe `(21.366, -11.480, -726.367)`, passage 0 first restored support, then passage 1’s bore changed density to `+1.613` (`carve_sdf=-0.292`), removing it. The shared composer now defers that support only when a physically overlapping later bore can reach it, then applies it after the later carve. The column’s natural relief is predictor-driven and tapers at the root; relief rose from `0.172 m` to `0.219 m`.
- **1–2 FI→TN:** at `(2.419, -13.433, -735.870)`, support restoration was rejected by an adjacent route segment’s player-capsule test (`clearance=-0.245 m`, required `0.045 m`). The segment-specific overlap candidates now keep that test local to physically relevant route geometry. The reported `0.2498 m` step came from the audit switching to a neighboring zero crossing on its `0.25 m` scan. Refining and following the same root shows a continuous floor; the step audit now reports `0.000 m`.
- **2–3 TN→VS:** at `(-1.365, -15.673, -897.088)`, the same capsule gate rejected support next to route segment 81 (`clearance=-0.329 m`). The shared segment-overlap handling fixes this link too.
The L66 `passageIndex>0` / `strates.size()>2` switches are gone. “Internal chain” now means both mouths lie inside their endpoint strata content bands and neither endpoint is a SurfaceWorld landing. Lower-mouth body overlap uses actual 3D distance to the mouth and tread, independent of plan length or passage index.
## Audit
All existing thresholds are unchanged. The L66 baseline binary reproduced the original failures.
| Passage | L66 negative control | L67 result |
|---|---|---|
| 0–1 SW→FI | 59 unsupported; column relief `0.172 m < 0.20 m` | 0 unsupported / 0 blocked / 0 grade failures; wall/floor ripple `1.49 / 0.06 cm`; column relief `0.219 m`; wall relief `0.267 m` |
| 1–2 FI→TN | 3 unsupported; wall/floor ripple `6.78 / 2.90 cm`; apparent `0.2498 m` step | 0 / 0 / 0; wall/floor ripple `2.29 / 1.23 cm`; refined step `0.000 m`; wall relief `0.257 m` |
| 2–3 TN→VS | 2 unsupported | 0 / 0 / 0; wall/floor ripple `1.48 / 0.47 cm`; wall relief `0.248 m` |
All three now pass support, blocked, grade, mouth-binding, ripple, and naturalness gates. The detailed final audit is in [`owner-four-strata-route4.log`](../../../../Saved/Linux/l67/logs/owner-four-strata-route4.log); the measured composition trace is in [`l66-root-refined-audit.log`](../../../../Saved/Linux/l67/logs/l66-root-refined-audit.log).
## Controls, hashes, and builds
- The L66 build remains a negative control: it reproduces `59 / 3 / 2` unsupported and the `0.172 m` column relief failure. The expected audit exit is 10.
- The pinned fast tier passes: **269 PASS, 5 diagnostic, overall PASS**. The prior L52 surface-world hash comparison is diagnostic because the route output changed; the changed style-1 baseline is [pinned at v212](hash-baselines/owner-hash-surface-world-style1-v212.log). See the [fast summary](../../../../Saved/Linux/l67/verify/fast-summary.json).
- Evaluator version is **212** (from 211), required because shared tread composition and route selection changed generated output. Source stamp: `d49aa8d941fb390818569243d7783f3e2799795244a8d7ca48e3e640d4a2876f`.
- All four strata produced 12 equal hash rows at each of W1/W8/W12 ([determinism summary](../../../../Saved/Linux/l67/verify/owner-w1-w8-w12-determinism.json)). Clang and GCC builds both pass.
- Changed implementation files are `Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h`, `Standalone/Core/src/ResolvedLayoutInternal.h`, `Standalone/Core/src/ShapeWorldInternal.h`, `Standalone/Source/OwnerWorldLoader.h`, `Standalone/Source/StreamBenchmarkPipeline.h`, and `Standalone/Tools/verify/verify.py`. No Unreal `Source/` files changed.
## Cost
Three interleaved whole-view pairs per strate, L66→L67 median seconds ([summary](../../../../Saved/Linux/l67/cost/cost-summary.json)):
| Strate | L66 | L67 | Change |
|---:|---:|---:|---:|
| 0 | 1.101 | 1.121 | +1.8% |
| 1 | 1.575 | 1.552 | −1.5% |
| 2 | 0.581 | 0.602 | +3.6% |
| 3 | 0.216 | 0.212 | −1.9% |
Strate 2’s samples overlap (`0.582–0.600 s`) and paired results are within observed run variation. The route chooser now selects the shortest candidate within `0.10 m` of maximum sampled tread clearance, with tangent alignment as a tie-breaker. It also skips an extra closure turn when mouth separation exceeds the tread envelope; the FI→TN route is `114.231 m` rather than the earlier `165.799 m` candidate.
## Captures
Seven JPEGs are in `captures/`; each is under 300 KB. The TN→VS endpoint meshes are sparse in these offscreen views, so those two frames show the nearby cave/strate surface more clearly than the tread.
- 0–1 arrival [26,342 B](captures/passage-0-1-0-arrival-mouth.jpg), departure [40,012 B](captures/passage-0-1-0-departure-mouth.jpg)
- 1–2 arrival [34,298 B](captures/passage-1-2-0-arrival-mouth.jpg), departure [42,450 B](captures/passage-1-2-0-departure-mouth.jpg)
- 2–3 arrival [23,937 B](captures/passage-2-3-0-arrival-mouth.jpg), departure [23,380 B](captures/passage-2-3-0-departure-mouth.jpg)
- Mid-tunnel, 1–2 [22,392 B](captures/passage-1-2-0-mid-tunnel.jpg)
## Failed runs
- The expected L66 negative-control audit exited 10 after reproducing the failures.
- One p0 clipped arrival-camera trial exited 3 because the clip plane left two open contours. It was replaced by the successful uncut SurfaceWorld entrance view.
- A p2 clipped arrival-camera trial also exited 3 on open contours. The final endpoint image uses the no-clip interior view. Both cap failures are recorded in [`failed-capture-trials.log`](../../../../Saved/Linux/l67/logs/failed-capture-trials.log).
- No final capture, Clang/GCC build, determinism run, fast-tier gate, or cost run failed. No run exceeded 30 minutes.
## Audit controls (follow-up)
This section supersedes the earlier wall-ripple values; the support, blocked, grade, mouth, and column results remain as reported. The residual-only gauge omitted `pose.radius` and hid an authored-radius beat:
| Field / gauge | Old gauge (authored radius, linear detrend) | Residual-only gauge |
|---|---:|---:|
| L66 four-strata field | 6.78 cm | 2.21 cm |
| L67 four-strata field | 14.52 cm | 2.29 cm |
On the old gauge, the geometry change raised 1–2 by 7.74 cm. On L67 geometry, the residual-only gauge then removed 12.23 cm; the reported 6.78→2.29 cm drop therefore masked the radius beat. The final gauge keeps `sampleRadius + 0.5*(left+right)` and cubic-detrends each 32 m window. The 0.18 m is the probe depth inside the authored radius; its former standalone subtraction is removed. The cubic baseline leaves the 0–1 and 2–3 taper profiles below 0.01 cm, while 1–2 retains a 4.77 cm authored-radius beat at 7.06 m. Final ripple results are 1.45 / 7.27 / 0.92 cm: 1–2 fails the unchanged 3.5 cm wall limit. Support, blocked, grade, and mouth-binding checks still pass for all three; overall passage audit is 2/3. See [`audit-control-l67-final-owner.log`](../../../../Saved/Linux/l67/logs/audit-control-l67-final-owner.log) and [`audit-control-authored-radius-all.log`](../../../../Saved/Linux/l67/logs/audit-control-authored-radius-all.log).
| Negative control under the updated gauge | Measured | Result |
|---|---:|---|
| L35 wall ribs | 9.75 cm at 6.50 m; limit 3.50 cm | Detected |
| Synthetic wall spheres / floor beads | 5.98 / 5.98 cm; limits 3.50 / 2.50 cm | Detected |
| Synthetic signed-distance floor step | 0.25 m step; 2.97 cm ripple vs 2.50 cm limit | Detected |
| L36 washboard field, five-step root refinement | 6.46 cm; limit 2.50 cm | Detected |
| L23 terrace profile | 2.00 m flat then 0.25 m step | Detected |
No control passed, so no old audit behavior was restored. Logs: [L35 ribs](../../../../Saved/Linux/l67/logs/audit-control-l35-wall-ribs-cubic-audit.log), [L36 washboard](../../../../Saved/Linux/l67/logs/audit-control-l36-washboard-l35field-bisection.log). The L67 synthetic controls are in the final owner log above. One direct link of the current headless code against archived Core libraries failed route setup and was discarded; the control runs used source/Core-matched L35 builds. The follow-up changed only `Standalone/Source/StreamBenchmarkPipeline.h`; Clang Linux build succeeded. No timing run, commit, push, or stash was made.
@@ -0,0 +1,110 @@
#!/usr/bin/env python3
"""Measure three interleaved L66/L67 whole-view pairs for each owner strate."""
import json
import os
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l67/cost"
DATA = ROOT / "Standalone/data/owner_world.json"
BASELINE = ROOT / "Saved/Linux/l67/bin-l66-baseline/VoxelForgeCoreHeadless-l67-l66-baseline-clang"
CANDIDATE = ROOT / "Saved/Linux/l67/bin-final/VoxelForgeCoreHeadless-l67-final-clang"
CAMERAS = {
0: "0.000000,0.000000,-374.939453,0.000000,0.000000",
1: "25.836914,3.199219,-737.000000,0.000000,0.000000",
2: "19.166016,-14.200195,-753.374023,0.000000,0.000000",
3: "-20.062500,-5.068359,-900.353516,0.000000,0.000000",
}
ORDER = ("l66", "l67", "l67", "l66", "l66", "l67")
PREFIXES = (
"[PipelineFullDetailHash]", "[PipelineWholeViewHash]",
"[PipelineLODHash]", "[PipelineExactQueryHash]",
)
ELAPSED_RE = re.compile(r"\belapsed_ms=([0-9.]+)")
WHOLE_VIEW_RE = re.compile(r"^\[StreamPipeline\].*?whole_view_ready=([0-9.]+)s", re.M)
ENV = os.environ.copy()
def run(strate, repetition, variant):
binary = BASELINE if variant == "l66" else CANDIDATE
name = f"strate{strate}-pair{repetition // 2 + 1}-{variant}"
log_path = OUT / f"{name}.log"
layout_path = OUT / f"{name}.json"
command = [
str(binary), str(DATA), "--workers=1", "--world-assembler-prototype",
"--stream-benchmark", "--stream-pipeline", "--force-generate",
"--clip-radius=1", "--max-clip-level=8", "--max-level=0",
"--recipe=surface-world", "--stream-hash", "--max-clip-level=0",
"--render-distance-chunks=0", f"--camera={CAMERAS[strate]}",
f"--resolved-layout-output={layout_path}",
]
wrapped = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1800s", *command,
]
start = time.monotonic()
with log_path.open("w", encoding="utf-8") as stream:
result = subprocess.run(wrapped, cwd=ROOT, env=ENV, stdout=stream,
stderr=subprocess.STDOUT, timeout=1800, check=False)
elapsed = time.monotonic() - start
lines = log_path.read_text(encoding="utf-8", errors="replace").splitlines()
hashes = [line for line in lines if line.startswith(PREFIXES)]
pipeline_elapsed = [float(value) for line in lines
if line.startswith("[PipelineWholeViewHash]")
for value in ELAPSED_RE.findall(line)]
whole_view_match = WHOLE_VIEW_RE.search("\n".join(lines))
record = {
"strate": strate, "pair_order": repetition, "variant": variant,
"elapsed_seconds": elapsed, "pipeline_elapsed_ms": pipeline_elapsed,
"whole_view_seconds": float(whole_view_match.group(1))
if whole_view_match else None,
"exit_code": result.returncode, "hash_rows": len(hashes),
"log": str(log_path.relative_to(ROOT)),
}
print(json.dumps(record, sort_keys=True), flush=True)
return record
def main():
OUT.mkdir(parents=True, exist_ok=True)
results = []
for strate in range(4):
for repetition, variant in enumerate(ORDER):
record = run(strate, repetition, variant)
results.append(record)
if record["exit_code"] != 0 or record["hash_rows"] != 12:
break
else:
continue
break
strata = {}
for strate in range(4):
values = {variant: [row["whole_view_seconds"] for row in results
if row["strate"] == strate and row["variant"] == variant
and row["whole_view_seconds"] is not None]
for variant in ("l66", "l67")}
if all(len(values[variant]) == 3 for variant in ("l66", "l67")):
l66 = sorted(values["l66"])[1]
l67 = sorted(values["l67"])[1]
strata[str(strate)] = {"l66_seconds": values["l66"],
"l67_seconds": values["l67"], "l66_median_seconds": l66,
"l67_median_seconds": l67,
"change_percent": (l67 / l66 - 1.0) * 100.0}
summary = {"pairs_per_strate": 3, "order": ORDER, "strata": strata,
"runs": results,
"complete": len(results) == 24
and all(x["exit_code"] == 0 and x["hash_rows"] == 12
for x in results)}
(OUT / "cost-summary.json").write_text(json.dumps(summary, indent=2) + "\n",
encoding="utf-8")
print(f"[L67CostSummary] complete={summary['complete']} runs={len(results)}",
flush=True)
return 0 if summary["complete"] else 1
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,68 @@
#!/usr/bin/env python3
"""Compare owner four-strata W12 hashes with the pinned W1/W8 cameras."""
import json
import re
import subprocess
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l67"
DATA = ROOT / "Standalone/data/owner_world.json"
BINARY = OUT / "bin-final/VoxelForgeCoreHeadless-l67-final-clang"
HASH_RE = re.compile(r"^\[Pipeline(?:FullDetailHash|WholeViewHash|LODHash|ExactQueryHash)\].*$", re.M)
CAMERA_RE = re.compile(r"camera_m=\(([-0-9.]+),([-0-9.]+),([-0-9.]+)\)")
RESULTS = []
def hashes(path):
return HASH_RE.findall(path.read_text(encoding="utf-8", errors="replace"))
def main():
logs = OUT / "logs"
layouts = OUT / "layouts"
logs.mkdir(parents=True, exist_ok=True)
layouts.mkdir(parents=True, exist_ok=True)
for strate in range(4):
w1_path = OUT / "verify" / f"owner-new-plan-clang-strate{strate}-w1.log"
w8_path = OUT / "verify" / f"owner-new-plan-clang-strate{strate}-w8.log"
match = CAMERA_RE.search(w1_path.read_text(encoding="utf-8", errors="replace"))
if not match:
raise RuntimeError(f"camera not found in {w1_path}")
camera = ",".join(match.groups()) + ",0.000000,0.000000"
log_path = logs / f"owner-new-plan-clang-strate{strate}-w12-final.log"
layout_path = layouts / f"owner-new-plan-clang-strate{strate}-w12-final.json"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1800s", str(BINARY), str(DATA), "--workers=12",
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
"--force-generate", "--clip-radius=1", "--max-clip-level=8",
"--max-level=0", "--recipe=surface-world", "--stream-hash",
"--max-clip-level=0", "--render-distance-chunks=0", f"--camera={camera}",
f"--resolved-layout-output={layout_path}",
]
with log_path.open("w", encoding="utf-8") as stream:
result = subprocess.run(command, cwd=ROOT, stdout=stream,
stderr=subprocess.STDOUT, timeout=1800, check=False)
current = hashes(log_path)
w1 = hashes(w1_path)
w8 = hashes(w8_path)
passed = result.returncode == 0 and len(current) == 12 \
and current == w1 == w8
row = {"strate": strate, "exit_code": result.returncode,
"w1_hashes": len(w1), "w8_hashes": len(w8),
"w12_hashes": len(current), "w1_w8_w12_equal": passed,
"log": str(log_path.relative_to(ROOT))}
RESULTS.append(row)
print(json.dumps(row, sort_keys=True), flush=True)
summary = {"status": "PASS" if all(row["w1_w8_w12_equal"] for row in RESULTS)
else "FAIL", "strata": RESULTS}
(OUT / "verify" / "owner-w1-w8-w12-determinism.json").write_text(
json.dumps(summary, indent=2) + "\n", encoding="utf-8")
print(f"[L67OwnerW1W8W12] status={summary['status']}", flush=True)
return 0 if summary["status"] == "PASS" else 1
if __name__ == "__main__":
raise SystemExit(main())
Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Loaded 100 of 244 files, more files were not shown because too many files have changed in this diff. Show more