Compare commits

...
Author SHA1 Message Date
Fr0zkaandClaude Opus 5.5 b09728354d wip: cave planner squash-merged into wip/combined (L173, RED - not for main)
Five conflicts resolved (CURRENT-STATE, ResolvedLayoutInternal.h, StreamBenchmarkHeadless.cpp,
StreamBenchmarkPipeline.h, WorldAssemblyPrototype.h) keeping liminal + cave planner; L156 band fit only; evaluator v239
with v239 baselines (18/18 Clang/GCC W1/W8/W12). Fast tier FAILS (FI upper arrival: 40 unsupported chamber samples,
10 chamber-floor shell violations); TN and VS composed sweeps 0/16 (passage, seam, shell gates). Living plane loads at
PostConfigInit (UE smoke passes). Source/: VoxelForgeCoreAdapter.cpp; VoxelForge.uplugin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-11-03 02:28:34 +01:00
Fr0zka 82eb5b1c66 Merge branch 'wip/l157-speed' into wip/combined 2026-11-02 20:27:26 +01:00
Fr0zkaandClaude Opus 5.5 52cb3d7a23 streaming: restore same-level refill; early admission on by default; fixed UE harness (L172)
Scheduler stable-sorts the whole level and checks capacity per admission (refill when slots free mid-pass); regression
test with the old partial-sort path as negative control. Harness repoints Source/Standalone/uplugin together, records
root HEAD/status, builds baselines from fresh worktrees, gates on DWARF struct sizes (480/480/480).
Frame-resolution arrival: A/A p90 36.8 ms; fixed-on vs fixed-off -12.2 ms median (4/10 slower). 30 m/s: near 43.3 ->
55.3%, far holes 1.8 -> 0%, p99 <= 7.46 ms, worst <= 12.3 ms.
Defaults: StreamingFastTravelWaitForAllCriticalTiles=0, StreamingFastTravelApplyBudgetMilliseconds=1.0,
StreamingIncrementalDesiredSet=1, CoreRegionConcurrentAdmission=1, StreamingFastTravelForwardFirst=0,
StreamingRebuildOnCoreRegionCompletion=1, StreamingCoreDesiredCacheByCenter=0, audits 0.
Source/: VoxelWorld.cpp, VoxelStreamingAdmissionOrder.h (new).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-11-02 20:27:24 +01:00
Fr0zka 4d7b470ebc Merge branch 'wip/living-plane' into wip/combined 2026-11-02 19:01:43 +01:00
Fr0zka c3a1610b22 Merge branch 'wip/l141-liminal' into wip/combined 2026-11-02 19:01:43 +01:00
Fr0zkaandClaude Opus 5.5 9d088ef96c research: UE arrival A/A could not run (L171)
Two identical ca0bf76 VFHost builds staged together crash at BeginPlay after snapshot parsing
(surface_strates=858993460 = 0x33333333, std::length_error). No arrival pairs measured.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-11-02 18:08:43 +01:00
Fr0zkaandClaude Opus 5.5 24803ab771 streaming: latch fast-travel early admission until the arrival set settles (L170, still opt-in)
Switches verified inactive before first settlement; moving gains hold (near 43.7 -> 55.5%, far holes 1.8 -> 0%, p99
7.21 -> 6.48 ms, worst 12.32 -> 12.27 ms). Arrival median +1.28% (4/7 pairs slower) though the switches did no work
then; defaults remain off (WaitForAllCriticalTiles=1, ApplyBudget=0). Source/: VoxelWorld.cpp, VoxelWorld.h.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-11-02 17:28:52 +01:00
Fr0zkaandClaude Opus 5.5 786ecc4cdf streaming: bounded early admission - partial level sort, apply preflight, sorted transition index (L169, opt-in)
With StreamingFastTravelWaitForAllCriticalTiles=0 and ApplyBudget=1 ms (test values; defaults stay 1 and 0), 7 pairs
vs ca0bf76: near 43.3 -> 56.1%, forward 15.5 -> 17.9%, far holes 1.8 -> 0.0%, p99 7.06 -> 6.52 ms, worst 12.2 ->
11.8 ms, every run p99 <= 7.4 and worst <= 13.8 ms; arrival 1.671 -> 1.683 s (slower in 7/7) so not promoted.
Source/: VoxelWorld.cpp, VoxelWorld.h.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-11-02 16:45:20 +01:00
Fr0zkaandClaude Opus 5.5 ca0bf767ca streaming: incremental desired set on by default (L168)
Defaults after this commit: StreamingIncrementalDesiredSet=1 (Core delta, overlay, priority merge);
StreamingCoreDesiredCacheByCenter=0, StreamingRebuildOnCoreRegionCompletion=1, StreamingFastTravelForwardFirst=0,
StreamingFastTravelWaitForAllCriticalTiles=1, StreamingFastTravelApplyBudgetMilliseconds=0 (L157/L160 stay off);
audits 0. Default-only 30 m/s UE, 5 pairs vs c6357b5: p99 10.07 -> 7.01 ms (all pairs), worst tick 13.24 -> 11.63 ms
(4/5), near +1.4 pp, far holes -0.19 pp, arrival flat. Audit run 307/307/302 equal. Source/: VoxelWorld.cpp.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-11-02 14:34:39 +01:00
Fr0zkaandClaude Opus 5.5 ec89b29853 research: re-test L157/L160 on the incremental base (L167, none promoted)
7 runs each: L157 near 55.4% but a 16.7 ms worst tick (UpdateChunks/level sort/overlay); L160 tick-safe but lower
all-around near and later arrival; both: forward cone 25.2% and p99 <= 11 ms, but worst tick up and a 14.3 ms run.
Also found: voxel.StreamingIncrementalDesiredSet defaults to 0, so L165/L166 gains need it enabled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-11-02 13:44:40 +01:00
Fr0zkaandClaude Opus 5.5 c6357b573f streaming: incremental overlay materialization and same-center priority merge (L166)
Persistent Core/overlay/candidate sets updated from deltas; same-center priority order merges changed keys (full sort
only on center change). Set and order equality audits 28/28 + 28/28, injected mismatch fails. 30 m/s UE, 7 pairs vs
9c70053: p99 10.52 -> 7.42 ms (all pairs), worst tick 12.98 -> 11.65 ms (6/7), near 42.0 -> 43.7% and forward cone
14.8 -> 16.1% (all pairs), far holes 3.20 -> 2.74% median (4/7), arrival flat. Source/: VoxelWorld.cpp, VoxelWorld.h.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-11-02 13:00:55 +01:00
Fr0zkaandClaude Opus 5.5 9c70053543 streaming: incremental clipmap desired-set delta for adjacent crossings (L165)
BuildDelta emits entering/leaving slabs per LOD (incl. coarser-ring coverage changes); cached Core key set updated
from the delta at one-tile crossings, full rebuild for larger jumps or LOD>=4 changes. Equality audit vs full rebuild
(45/45 crossings, 20/20 sampled; injected mismatch asserts) + 630 automation cases. 30 m/s UE, 5 pairs:
BuildDesiredTiles p95 7.80 -> 4.08 ms, p99 11.46 -> 7.97 ms, worst tick 13.73 -> 12.11 ms (all pairs), near and
forward readiness and arrival better in all pairs; far holes better in 4/5 (pair 5 +0.29 pp). Geometry unchanged.
Source/: VoxelClipmapDesiredTiles.h, VoxelWorld.cpp, VoxelWorld.h, Tests/VoxelForgeClipmapNoOverlapTest.cpp.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-11-02 12:01:25 +01:00
Fr0zkaandClaude Opus 5.5 3b71d35eba experiment: LivingPlane repeatable + standalone UE runtime module (L164)
Readback barrier fix (GL_TEXTURE_UPDATE_BARRIER_BIT before glGetTexImage); check_repeatability.py compares full-field
checksums across query/capture settings - Intel GPU PASS (3b9d45905c7aafff), llvmpipe PASS, mismatch control fails.
New runtime module VoxelForgeLivingPlane (separate from generation): RDG global shader (Shaders/LivingPlane.usf),
ping-pong RGBA32F render targets, actor with WPO plane and object inputs, no CPU readback. Compiles in Linux UE 5.7.
Windows build: Source/VoxelForgeLivingPlane/*, Shaders/LivingPlane.usf, VoxelForge.uplugin. Setup: UE-SETUP.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-11-02 11:10:43 +01:00
Fr0zkaandClaude Opus 5.5 e1d87eed26 experiment: LivingPlane readable + alive mode (L163)
RGBA32F displacement/velocity/imprint field; rolling base relief, grazing light, elevation colours, 7.5 cm contours,
follow camera; scenes: roll, rest and wobble, lift-off, second sphere; alive mode = travelling swells + viscoelastic
imprint. Intel ADL GT2: ~0.17 ms/step at 512^2. Open: run-to-run difference with queries on/off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-11-02 10:55:32 +01:00
Fr0zkaandClaude Opus 5.5 5b15595e4a experiment: LivingPlane - GPU wave-equation height field reacting to a moving sphere (L162)
Standalone EGL/OpenGL 4.3 compute runner (runtime GLSL, no shader compiler needed), ping-pong height/velocity field,
damped wave + foundation spring + Gaussian sphere push + optional breathing, vertex-displaced render to PNG.
Independent of generation. Intel ADL GT2: ~0.08 ms/step at 512^2, ~0.29 ms/step at 1024^2 (wall-time difference).
.gitignore now allows Experiments/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-11-02 10:35:37 +01:00
Fr0zkaandClaude Opus 5.5 ff52c9b576 proto: liminal looks - pool rooms, backrooms grid, staircase to nowhere, terraced hills (L161)
Stamp vocabulary v7 (opt-in fillBox, carveBox, fillSteps; carveArch reused); flat-field liminal stamp fixtures; capsule
walk audit with doorway-width gate (1.2 m control fails as expected). Clang/GCC W1/W8/W12 identical; owner canonical
hashes unchanged; fast tier 273/273. Terraced wide view not yet legible.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-11-02 10:11:37 +01:00
Fr0zkaandClaude Opus 5.5 aa458d3607 streaming: ahead-first admission, same-center rebuild suppression, Core key cache; off by default (L160)
30 m/s UE, 5 pairs: forward-cone readiness 14.2 -> 18.2% (+3.75 pp), all-around 39.5 -> 36.6%; p99 <= 11 ms in all
candidate runs, but worst tick, far holes and arrival regressed in some pairs. Rebuilds per crossing 7.2 -> 4.4; per
rebuild cost unchanged (not yet incremental). Source/: VoxelWorld.cpp, VoxelWorld.h (defaults unchanged).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-11-02 09:29:39 +01:00
Fr0zkaandClaude Opus 5.5 323fbe3736 streaming: early-admission + apply-cap experiment controls, off by default; forward-cone metric (L157)
30 m/s UE, 5 pairs: near coverage 36.1 -> 48.6% but 2/5 runs had p99 15.8/18.2 ms (worst 19.9 ms), so controls stay
off. Measured: only ~23% of desired LOD0-2 tiles lie in the 55 deg forward cone; forward readiness is 12.1% (base) /
16.9%, vs 50.9/68.5% behind. Slow frames are dominated by desired-set rebuild/UpdateChunks, not apply. Source/:
VoxelWorld.cpp (default behaviour unchanged).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-11-02 03:21:19 +01:00
Fr0zkaandClaude Opus 5.5 4290672d1c audit: passage interior must be air for every capsule sample (L146)
Root cause of liminal seed 5 (measured): passage carve makes the bore air (+3.52), then the liminal route fill stamp
re-solidifies it (-0.16) where the deck crosses the tube. Three fixes cleared the bore but left 28-29 road stations
unsupported; none kept. New gate: zero solid samples inside any passage interior, with no-passage negative controls.
Owner v227 hashes and fast tier pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-11-01 13:02:31 +01:00
Fr0zkaandClaude Opus 5.5 7d654ff861 wip: opt-in landing-pad contract + Core.Liminal.Field v2 pads (L145)
Layers may publish certified arrival/departure pads; the passage planner binds endpoints and tangents to them;
no-pad layouts serialize unchanged (owner canonical hashes match v227). Pads removed the endpoint grade failures;
seeds 0-4 passed at a 45 m margin, but seed 5 still has 289 blocked passage-capsule samples ~29 m before the
departure pad, where the composed field stays solid (-0.162) inside the tube over native ground (-3.6). Ripple
audit now keeps true route distance across masked gaps (with a synthetic control).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-11-01 12:19:30 +01:00
Fr0zkaandClaude Opus 5.5 a45ba9bb10 wip: Core.Liminal.Field v1 recipe, 4 seed-selected variants between fixed mouths (L144, fails mouth joins)
Plans and draws flat field+monolith, repeating hills, road over cloud floor and pillar grid in a 3-strata fixture,
binding passage/0-1 and 1-2 as fixed endpoints. Composed W1 probes: 3/4 seeds fail one passage interface, 4/4 fail the
liminal walk audit near the mouths (support itself passes). Stopped per AGENTS.md. Same mouth-ownership failure
class as FI (L133/L140) and VS seed 2 (L139).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-11-01 11:17:45 +01:00
Fr0zkaandClaude Opus 5.5 a4e214561f proto: continuous swept road deck; coarse-LOD proxies keep thin pillars intact (L143)
Road: one swept-deck stamp on a shared cubic-Hermite centerline (stamp vocabulary V5), 376.8 m, max 8.5 deg, walk
audit clean, short-road control fails as expected. Pillars: regular fillPillar stamps get deterministic 24-sided
capped-cylinder proxies once the LOD step exceeds their radius or half-height (applies to all worlds). Owner hashes
unchanged (v227); five-fixture Clang/GCC determinism; fast tier green.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-11-01 10:08:37 +01:00
Fr0zkaandClaude Opus 5.5 6cd9e4da76 proto: liminal pass 2 - hills to the horizon, swept road over a cloud floor, pillar grid (L142)
Captures stream LOD0-8 (hills repeat at 80 m to the horizon). Road: cubic swept solid deck, 376.8 m, 8 m wide, max
12.06 deg, 17 supports, cloud floor ~45 m below; walk audit 0 blocked/unsupported, 300 m negative control fails as
expected. 15x15 identical pillar grid at 76 m. Owner canonical hashes unchanged; fast tier, Clang/GCC green.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-11-01 09:31:56 +01:00
Fr0zkaandClaude Opus 5.5 9c6d261d01 proto: first liminal fixtures - flat field + monolith, periodic hills, raised road in fog (L141)
Standalone only: fixtures, liminal_road_override (fillDais), --audit-liminal-road-walk, capture-only distance fog.
Road 65 m x 6 m, 22 m up, capsule walk 127/127. Owner hashes unchanged (v227), fast tier, Clang/GCC green.
Hills repeat every 80 m but the capture reaches 56 m, so only one rise shows. Also R48 (who owns the tunnel mouth):
recommends a bounded joint plan of passage ports and layer landing candidates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-11-01 08:43:24 +01:00
Fr0zkaandClaude Opus 5.5 eb47d72258 research: R47 liminal/dreamcore style and mapping to Core (text only; images kept local)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-11-01 04:11:52 +01:00
Fr0zkaandClaude Opus 5.5 95bf654fbc docs: AGENTS.md for Codex (standing rules + working procedure)
Codex only auto-loads AGENTS.md; CodexGuidance.md was never read. Carries the per-brief standing rules (limits,
no commit/push, quiet-machine timing, same-world bar, units) and a short procedure distilled from systematic-
debugging/verification-before-completion (obra/superpowers) and ub-mode: count first, suspect the measurement,
root cause before fix, one change at a time, attack the premise / stop after three failed fixes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-11-01 01:37:58 +01:00
Fr0zkaandClaude Opus 5.5 0065ee68e6 tools: per-strate density dispatch report in the fast tier (L132)
--density-dispatch-report prints each strate's bound path and tile shares. Owner world: SW native heightfield;
FI, TN, VS on compiled-analytic legacy fallback (FI lacks an imported FloatingIslandPlan; TN/VS have legacy recipes).
A native FI candidate broke the composed owner walk/shell gates, so the owner stays on its path. Output unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-31 02:40:58 +01:00
Fr0zkaandClaude Opus 5.5 ebae71b142 research: R46 generator capability matrix (read-only)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-31 02:11:29 +01:00
Fr0zkaandClaude Opus 5.5 81071969fc docs+cleanup: CURRENT-STATE.md and dead L86/L88 ablation switches removed (L131)
One current-state page (shipping behaviour, references with sources, gaps, verification, closed lines), linked from
CLAUDE.md. Removes the L86/L88 passage/boundary ablation switches and the unused legacy route-grade audit; inventory
of every kept switch in the report. Output identical: 24/24 + 18/18 hashes, fast tier, walk/seam audits, UE host
build; timing unchanged (-0.2%, CI spans 0). No Source/ changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-31 02:11:10 +01:00
Fr0zkaandClaude Opus 5.5 cbbfd46bd3 research: Astra low open project review — what to focus on now (read-only)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-31 01:19:41 +01:00
Fr0zkaandClaude Opus 5.5 044cd6d9c5 core: one passage influence reach in metres, converted once (L130, evaluator v227)
Reach = 0.75 shell + 0.21 relief + 0.50/6 smooth-max support + 0.01 = 1.0533 m, used by proof, feature, fallback,
off-grid bounds and the sweep cutoff. Removes the dead 4 m term (v225 added metres to core-voxel coordinates).
Captures and mouth close-ups byte-identical; proof audit 0 misses over 25,728 shell/crest samples; floor 7.884 mm;
seams, walkability, fast tier, Clang/GCC W1/W8/W12 green; timing equal within noise.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-31 00:43:44 +01:00
413 changed files with 43039 additions and 610 deletions

No files matched your search

+8
View File
@@ -9,12 +9,20 @@
!/Source/ !/Source/
!/Source/** !/Source/**
# Plugin runtime shader source.
!/Shaders/
!/Shaders/**
# Portable CPU generator and D3D11 viewer. # Portable CPU generator and D3D11 viewer.
!/Standalone/ !/Standalone/
!/Standalone/** !/Standalone/**
/Standalone/Build/ /Standalone/Build/
/Standalone/bin/ /Standalone/bin/
# Research and prototype experiments.
!/Experiments/
!/Experiments/**
# The guarded single-entry test harness is source, not generated output. # The guarded single-entry test harness is source, not generated output.
!/Tools/ !/Tools/
!/Tools/** !/Tools/**
+57
View File
@@ -0,0 +1,57 @@
# AGENTS.md — VoxelForge (read automatically by Codex)
You are the implementation agent. The orchestrator (Claude) writes each round's brief, reviews your diff against the
code, and commits. The owner decides direction. Be warm and direct; credit what worked; say plainly what didn't.
Read first, as the task needs: `CURRENT-STATE.md` (what ships, reference numbers, **closed experiments: don't reopen
them without new evidence**), then `CODEMAP.md` and `ARCHITECTURE.md`. Round reports live in
`Standalone/Research/WorldAssemblerM3/Linux*/REPORT.md`.
## Standing rules (every round, unless the brief says otherwise)
- Run as user `jahni`. Write only inside this plugin, plus `/home/jahni/VFHost` and `/home/jahni/UnrealEngine` when
the brief asks for a UE build. No installs, no system changes.
- **No `git commit`, `push` or `stash`.** Leave changes in the working tree; the orchestrator reviews and commits.
- Headless processes and builds: `taskset -c 0-11 prlimit --as=6442450944` (12 P-core threads, 6 GiB). Never use all
cores.
- Unreal (UBT, the editor, commandlets): `taskset -c 0-11` only, **never `prlimit`** (CoreCLR fails). UE builds use
`-NoUBA -MaxParallelActions=4`, never while timing.
- Stop any single run that passes 30 minutes. Logs go in `Saved/Linux/l<round>/`.
- **Timing:** quiet machine only (check `uptime` first; load < ~1.5, nothing else building). L80 protocol: one warm-up
per binary, ≥5 interleaved runs, W8 and W12, all four owner layers. Every number states the window that produced it.
- **Same world for everyone:** a seed must give the same world on every machine and worker count (Clang and GCC,
W1/W8/W12). Byte-identity with older versions is NOT required. If the output changes: bump
`WorldAssemblyEvaluatorVersion`, add baselines, and show captures.
- Density sign: negative = solid, positive = air. Core voxels are 25 cm. Never mix metres and voxels without an
explicit conversion (v225 shipped a reach bug from exactly that).
- The fast tier (`Standalone/Tools/verify/verify.py fast`) must stay green. Don't loosen an audit to make a change pass.
## How to work
1. **Count before you build.** Measure where the time or the failure actually is (stack sampler, census, counters).
State which parts are measured and which are inferred.
2. **Suspect the measurement first.** An alarming or contradictory number is the instrument until a control rules it
out (A/A runs, an ablation that verifiably removes the work).
3. **No fix without a root cause.** Reproduce it, trace it to its source, fix the cause. A guard that hides the symptom
is not a fix.
4. **One change at a time,** each with its own check.
5. **Subtract before you add.** Prefer removing work to reorganising it. The smallest edit that solves the problem.
6. **Attack the premise.** When two fixes sharing one premise fail the same gate, write that premise in one sentence
and question it before trying a third. **After three failed fixes, stop:** it is probably the design, not the
hypothesis. Report it instead of trying fix #4.
7. **Tests must be able to fail.** Name what would make each check fail. Add a negative control when you add an audit.
8. **Refactors:** pin the behaviour (hashes), change the structure, prove nothing changed.
9. **Verify before you claim.** Don't report a check as passed unless it ran on the final source. Say which checks
didn't run and why.
10. Changes that speed things up go in the shared layer, so every generator benefits.
## Report
Write the round's `REPORT.md`, short and plain:
- the result first;
- what changed and why;
- the measurements, with their windows;
- the checks run, and those not run;
- remaining risks.
List any changed Unreal `Source/` files, because the owner compiles them on Windows.
+51 -11
View File
@@ -1522,11 +1522,12 @@ collision ring became walkable at 0.632 s. `voxel.StreamingCoarseFirst=1` with
residency by about one third on the staged plan, but null-RHI residency does not prove rendered residency by about one third on the staged plan, but null-RHI residency does not prove rendered
enclosure. Keep coarse-first opt-in until that view is checked in a rendered owner-plan run. enclosure. Keep coarse-first opt-in until that view is checked in a rendered owner-plan run.
The separate `voxel.StreamingStartupApplyBoost` switch stays default-off. At `1`, it temporarily allows The separate `voxel.StreamingStartupApplyBoost` switch is default-on in the current code. At `1`, it
12 visible mesh submissions / 14 ms while the initial desired set and walkable ring are incomplete, temporarily allows up to 24 visible mesh submissions / 14 ms while the initial desired set and
with a 30 second ceiling; at `0`, the authored budget is unchanged. On the matched legacy-order test, walkable ring are incomplete, with a 30 second ceiling; at `0`, the authored budget is unchanged.
the boost changed full-set readiness from 17.412 s to 17.346 s and removed count-cap frames, but did The historical matched S6 run changed full-set readiness from 17.412 s to 17.346 s and removed
not improve end-to-end residency. Keep the normal 4 / 2 ms guard for play. count-cap frames, but did not improve end-to-end residency. These current defaults are defined in
`Source/VoxelForge/Private/VoxelWorld.cpp`; the S6 measurement remains historical context.
The S6 trace found the shared pool was not saturated: its 10-slot idle estimate was about 132 The S6 trace found the shared pool was not saturated: its 10-slot idle estimate was about 132
slot-seconds on legacy order and 73 on gated coarse-first, while game-thread tile applies totaled only slot-seconds on legacy order and 73 on gated coarse-first, while game-thread tile applies totaled only
@@ -1556,8 +1557,8 @@ by its RAII guard when the worker lambda exits. This still counts queued and run
results do not consume an admission slot. `voxel.StreamingLegacyPendingResultCap=1` restores the old results do not consume an admission slot. `voxel.StreamingLegacyPendingResultCap=1` restores the old
`PendingTiles` accounting; the test harness's `NearReserveMode=Legacy` also sets `PendingTiles` accounting; the test harness's `NearReserveMode=Legacy` also sets
`voxel.StreamingDynamicNearReserve=0`, reproducing the S6 path. `StreamingDynamicNearReserve` now `voxel.StreamingDynamicNearReserve=0`, reproducing the S6 path. `StreamingDynamicNearReserve` now
defaults to `1`; `StreamingCoarseFirst` remains `0`, and the apply boost remains `0` in the matched defaults to `1`; `StreamingCoarseFirst` remains `0`. That historical S6 matched measurement disabled
measurement. the startup apply boost (`voxel.StreamingStartupApplyBoost=0`); the current default is on (§8.27).
On the 2,578-key owner SurfaceWorld trace, the old-path control reached the full set in 17.527 s, On the 2,578-key owner SurfaceWorld trace, the old-path control reached the full set in 17.527 s,
walkable ring in 0.650 s, and full LOD0 in 2.281 s. Dynamic admission reached them in 7.780 s, walkable ring in 0.650 s, and full LOD0 in 2.281 s. Dynamic admission reached them in 7.780 s,
@@ -2298,16 +2299,55 @@ results; the code path is not promoted to legacy Maze/VerticalShafts generation.
rooms, or tunnels. The Unreal adapter preserves the established passage planner, then resolves 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 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. 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 Core profile heightfields are restricted to SurfaceWorld. Native caves require the TunnelNetwork,
Underwater archetype; native lattice requires Maze or VerticalShafts. Core recipe/version fields Underwater, or VerticalShafts archetype; native lattice requires Maze or VerticalShafts. Core
enter the resolved layout identity when selected. An imported manifest cannot be mixed with a recipe/version fields enter the resolved layout identity when selected. An imported manifest cannot
per-strate profiled heightfield. 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 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. 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. Research17 names V2, but the source API and motif vocabulary require V3; source was followed.
Research27 water remains out of scope for U1. Research27 water remains out of scope for U1.
### 8.56 Native Vertical Shafts graph field — World Assembler M3 L139
Vertical Shafts uses the same `PlanResolvedCavernNetworkV1` and `Core.Caves.PrimitiveField` as
Tunnel Network. There is no separate shaft planner. The existing room/tunnel shapes do not express
a capped bore spanning the strate height, so the shared cave field gains a shaft-cylinder record
kind; resolved records then add access tunnels, shaft cross-connects, and internal ledges.
`Core.Lattice.PrimitiveField` is not used: its L58 VerticalShafts route read as a corkscrew, so it
does not satisfy this archetype's open-shaft identity.
The basal owner strate has an arrival mouth from passage 2–3 and deliberately has no departure
mouth. Here the primordial law's route is the arrival mouth to a walkable terminal landing above the
bedrock seal. The shared planner extends its walk-only spine to that terminal with the same grade
limit used for a two-mouth cave; it does not invent a second passage. Route landings and connectors
are built as part of the graph. Shaft bores and their ledges are a separate exploration tier: an
open vertical drop or a climb between ledges is not walk-only traversal. Climb and mine remain
reporting tiers; they never relax the walk-only guarantee.
`FVerticalShaftParams` mapping:
| Field | Core behavior |
|---|---|
| `ShaftSpacing` | Planner room spacing, converting owner voxels to integer micrometres. |
| `ShaftDensity` | `roomDensityQ16`; seeds shaft placement, with the route's initial shaft guaranteed when nonzero. |
| `ShaftMinRadius`, `ShaftMaxRadius` | Minimum and maximum shaft radii. |
| `CrossConnectChance` | `tunnelDensityQ16` for cross-shaft links; generic cave branches are disabled in shaft mode. |
| `ConnectorRadius` | Fixed minimum/maximum radius for access and connector bores. |
| `LedgeSpacing`, `LedgeDepth` | Shaft shelf spacing and intrusion depth; zero spacing disables ledges, while zero depth leaves them nonintrusive. |
| `SurfaceRoughness` | Shaft-wall roughness amplitude. Frequency is derived as `max(0.5 m, 4 owner voxels)` because the legacy params have no frequency field. |
| `BoundarySealThickness`, `BaseDensity` | Continue to feed the resolved top and basal boundary bands through `sealAndDensity`, not the planner settings. |
L139 increments `WorldAssemblyEvaluatorVersion` to 237 and extends the versioned cavern graph
vocabulary for shaft cylinders. The fast-tier dispatch report binds S3 to
`Core.Caves.PrimitiveField` / `Caves-native`. The 16-seed composed acceptance gate is not green:
seed 2 fails the passage-shell audit at the incoming mouth. Three geometry variants failed that
same gate, so the remaining issue is recorded for a new design premise rather than another local
patch. L139 does not claim promoted shaft generation; its L80 run is recorded as characterization
of the current worktree. See
[`Standalone/Research/WorldAssemblerM3/Linux139/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux139/REPORT.md).
The shared VoxelForge custom asset version is 2: version 1 records the tunnel-floor slope migration; 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 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 saved strate-layout width is used, currently 8 m. Old packages are converted in memory and marked
+1
View File
@@ -10,6 +10,7 @@ then wait for his results / pasted errors. Same for in-editor checks — ask for
try to run anything. try to run anything.
## Read first (in order, load only what the task needs) ## Read first (in order, load only what the task needs)
- **[CURRENT-STATE.md](CURRENT-STATE.md) — current shipping snapshot and verification entry point.**
- **[CODEMAP.md](CODEMAP.md) — ALWAYS.** Navigation: what it is, data-flow (§2), symbol→`file:line` - **[CODEMAP.md](CODEMAP.md) — ALWAYS.** Navigation: what it is, data-flow (§2), symbol→`file:line`
index (§3), "I want to change X → go here" (§5), conventions & gotchas (§6). Trust **symbol names index (§3), "I want to change X → go here" (§5), conventions & gotchas (§6). Trust **symbol names
over line numbers** (lines drift). over line numbers** (lines drift).
+17 -3
View File
@@ -174,7 +174,7 @@ remains `0`, and lateral regions remain gated off.
| `../../VoxelForgeCore/Private/ModuleEntry/` | Unreal module entry and an isolated `CoreMinimal.h` shim for the actual engine header. | | `../../VoxelForgeCore/Private/ModuleEntry/` | Unreal module entry and an isolated `CoreMinimal.h` shim for the actual engine header. |
| `../../Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` | Standard-library-only public Core API. `VOXELFORGECORE_API` exports the API from the Unreal module and is empty for standalone builds. | | `../../Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` | Standard-library-only public Core API. `VOXELFORGECORE_API` exports the API from the Unreal module and is empty for standalone builds. |
| `../../Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` / `../../Standalone/Core/src/ResolvedLayoutInternal.h` | `ResolvedLayoutSnapshotV3`: physical layout-chunk width, versioned micrometre intervals, owned ceiling/content/bedrock bands, honored transition settings, deterministic per-column transition bands, recipe anchors, director-planned passages, canonical JSON/FNV identity, compatibility/strict validators, and negative-control audit. Built once from the director-resolved plan and consumed by bounds planning, Core dispatch, composition, and validation. §8.40–8.42, §8.46. | | `../../Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` / `../../Standalone/Core/src/ResolvedLayoutInternal.h` | `ResolvedLayoutSnapshotV3`: physical layout-chunk width, versioned micrometre intervals, owned ceiling/content/bedrock bands, honored transition settings, deterministic per-column transition bands, recipe anchors, director-planned passages, canonical JSON/FNV identity, compatibility/strict validators, and negative-control audit. Built once from the director-resolved plan and consumed by bounds planning, Core dispatch, composition, and validation. §8.40–8.42, §8.46. |
| `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. | | `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` remains Maze/VerticalShafts recipe version 1, and `Core.Caves.PrimitiveField` also accepts VerticalShafts in L139. Lattice seeds include world seed, strate, recipe version and stable feature ID; explicit polyline records keep their endpoints and receive conservative bounds. §8.54, §8.56. |
| `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. | | `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. | | `../../Standalone/Core/src/FloatingIslandsInternal.h` | Canonical standalone/Core floating-island planner, geometry-versioned field, manifest serializer, influence bounds, capsule-route checks, and thickness audit. |
| `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. | | `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. |
@@ -388,7 +388,7 @@ casse cette arithmetique *structurellement* — `BeginPlay` loggue une **Error**
| `AdvanceHeadlessDigMotionTest` / `RecordHeadlessDigMotionFrame` | 3060 / 3217 | Explicit `-voxel.TestDigMotion=1` repro: optional near digs, then repeats underfoot digs and samples `Generator->GetDensityAt` immediately after each edit, on its first subsequent simulated frame, and on every following observation frame. `-voxel.TestDigMotionFieldOverride=1` is a measurement-only prototype that clears `CurrentFloor` and switches CMC to falling when the sampled post-edit density is air; it is inactive by default. The test counts false support, false air and downward velocity beyond one gravity step. `-voxel.TestDigMotionUnderfootDigCount` is clamped to 1–100; `-voxel.TestDigMotionUnderfootIntervalSeconds=0` performs per-frame rapid edits. The accompanying gap audit is scoped to the dig session. | | `AdvanceHeadlessDigMotionTest` / `RecordHeadlessDigMotionFrame` | 3060 / 3217 | Explicit `-voxel.TestDigMotion=1` repro: optional near digs, then repeats underfoot digs and samples `Generator->GetDensityAt` immediately after each edit, on its first subsequent simulated frame, and on every following observation frame. `-voxel.TestDigMotionFieldOverride=1` is a measurement-only prototype that clears `CurrentFloor` and switches CMC to falling when the sampled post-edit density is air; it is inactive by default. The test counts false support, false air and downward velocity beyond one gravity step. `-voxel.TestDigMotionUnderfootDigCount` is clamped to 1–100; `-voxel.TestDigMotionUnderfootIntervalSeconds=0` performs per-frame rapid edits. The accompanying gap audit is scoped to the dig session. |
| `MaybeFinishHeadlessStreamingTest` | — | The existing `voxel.TestExitSeconds` diagnostic emits `VoxelForgeStreamingExitDiag` at most every 30 elapsed seconds, including threshold and early-return test flags; no output when the timer is unconfigured. | | `MaybeFinishHeadlessStreamingTest` | — | The existing `voxel.TestExitSeconds` diagnostic emits `VoxelForgeStreamingExitDiag` at most every 30 elapsed seconds, including threshold and early-return test flags; no output when the timer is unconfigured. |
| `UpdateVisualStalenessAudit` | 5034 | Test-only camera-frustum coverage audit: tracks up to 24 real pre-skirt isosurface points per visible component, then counts frames where no visible tile component covers a previously visible, still-desired point (excluding edited bounds). `voxel.TestVisualGapInjectEarlyHide=1` hides a valid component for three frames and must be caught. `voxel.TestGapAuditLegacyHandoff=1` restores old handoff culling for A/B baselines. | | `UpdateVisualStalenessAudit` | 5034 | Test-only camera-frustum coverage audit: tracks up to 24 real pre-skirt isosurface points per visible component, then counts frames where no visible tile component covers a previously visible, still-desired point (excluding edited bounds). `voxel.TestVisualGapInjectEarlyHide=1` hides a valid component for three frames and must be caught. `voxel.TestGapAuditLegacyHandoff=1` restores old handoff culling for A/B baselines. |
| `ProcessPendingChunks` | 7435 | Drains `CriticalProcessQueue`, `EditedProcessQueue`, `NearProcessQueue`, then `ProcessQueue`; applies under the authored 4-mesh/2 ms budget during normal play and the bounded shared startup budget (12 meshes/14 ms) until the initial desired set and walkable ring are ready. `voxel.StreamingStartupApplyBoost=0` restores the authored budget. `startup_apply_config` traces the actor-read CVar and both budgets; `startup_apply_budget` records activation/end. Reads per-request cancellation before removing its map entry, traces stale/obsolete/aborted discards with epoch and ordinal, then applies via `ApplyTileResult`. Step 1 prequeue still releases here and emits `VFStep1UploadCorpusReleased`. | | `ProcessPendingChunks` | 7435 | Drains `CriticalProcessQueue`, `EditedProcessQueue`, `NearProcessQueue`, then `ProcessQueue`; applies under the authored 4-mesh/2 ms budget during normal play and the default-on bounded startup budget (24 meshes/14 ms, at most 30 s) until the initial desired set and walkable ring are ready. `voxel.StreamingStartupApplyBoost=0` restores the authored budget. `startup_apply_config` traces the actor-read CVar and both budgets; `startup_apply_budget` records activation/end. Reads per-request cancellation before removing its map entry, traces stale/obsolete/aborted discards with epoch and ordinal, then applies via `ApplyTileResult`. Step 1 prequeue still releases here and emits `VFStep1UploadCorpusReleased`. |
| `ApplyTileResult` | — | **Shared game-thread apply** for one `FChunkResult` (async drain + sync carve): discards stale generation epochs and keys no longer desired, marks loaded, ingests capture, EMPTY releases the tile's existing component (a re-gen can flip content→empty on band change — old geometry must not linger), else `ApplyMeshToTile`. Returns true iff a visible mesh uploaded (counts the budget). Doesn't touch `PendingTiles` (caller's). | | `ApplyTileResult` | — | **Shared game-thread apply** for one `FChunkResult` (async drain + sync carve): discards stale generation epochs and keys no longer desired, marks loaded, ingests capture, EMPTY releases the tile's existing component (a re-gen can flip content→empty on band change — old geometry must not linger), else `ApplyMeshToTile`. Returns true iff a visible mesh uploaded (counts the budget). Doesn't touch `PendingTiles` (caller's). |
| `FVoxelStackSampler` / `FScopedVoxelStackRegistration` (`VoxelStackSampler.h/.cpp`) | — | **Diagnostic raw stack sampler, off by default.** A sampler thread captures registered generation workers' stacks (`CaptureThreadStackBackTrace`) every N µs and tags each with the tile's LOD. It writes bounded raw PCs to CSV plus a symbolization-free summary under `Saved/`; symbol resolution is deferred until after process exit so the measured process never initializes/calls DbgHelp for this diagnostic. Lifecycle teardown is serialized and registration TLS is session-tagged. Registration is per tile TASK in `LoadTile` (and the export's tasks), never per sample; disabled costs one relaxed atomic load. Game `-voxel.SampleStacks=<us>`, commandlet `-samplestacks[=us]`. | | `FVoxelStackSampler` / `FScopedVoxelStackRegistration` (`VoxelStackSampler.h/.cpp`) | — | **Diagnostic raw stack sampler, off by default.** A sampler thread captures registered generation workers' stacks (`CaptureThreadStackBackTrace`) every N µs and tags each with the tile's LOD. It writes bounded raw PCs to CSV plus a symbolization-free summary under `Saved/`; symbol resolution is deferred until after process exit so the measured process never initializes/calls DbgHelp for this diagnostic. Lifecycle teardown is serialized and registration TLS is session-tagged. Registration is per tile TASK in `LoadTile` (and the export's tasks), never per sample; disabled costs one relaxed atomic load. Game `-voxel.SampleStacks=<us>`, commandlet `-samplestacks[=us]`. |
| Room-cache performance (2026-09-13): `VF_FindPlayerFitPointForRoomMemoized` / `GPlayerFitMemo` (`VoxelCaveMorphology.cpp`), `voxel.TileCacheWindow` + `VoxelGenLOD::TileOriginVoxels/TileCellsPerAxis` (`VoxelGenerator.h`), `FChunkSDFSpatialIndex` | — | **Player-fit memo:** exact 97-word key, process-wide, `voxel.PlayerFitMemo`. **Tile cache window:** the room cache covers the requesting tile's footprint plus a fixed collect margin. It is set via `TGuardValue` in `GenerateMesh`/classify, and is window-invariant by construction (the margin does not grow with the tile). **LOD-gated:** fused path at LOD ≥ `voxel.TileCacheWindowMinLOD` (3), op-stack at LOD ≥ `voxel.TileCacheWindowOpStackMinLOD` (4); lower LODs keep the 4-chunk worker window, because the tile window slowed LOD0 evaluation ~6%. Strates whose authored terrain ops exceed the proven 256-voxel extent (or are malformed/unloaded) fall back to the legacy window (`VF_ComputeTerrainOpEnvelope`). `voxel.SpatialIndex` is -1 auto (off at fused LOD0, on elsewhere), 0 or 1. **Spatial index:** ordered XY buckets over cache primitives, exact rejection. The `static_assert` in `VoxelStrateTypes.h` ties `VF_STRATE_PARAM_FIELDS` to `FStrateGenerationParams` (83 fields + 8 B padding). | | Room-cache performance (2026-09-13): `VF_FindPlayerFitPointForRoomMemoized` / `GPlayerFitMemo` (`VoxelCaveMorphology.cpp`), `voxel.TileCacheWindow` + `VoxelGenLOD::TileOriginVoxels/TileCellsPerAxis` (`VoxelGenerator.h`), `FChunkSDFSpatialIndex` | — | **Player-fit memo:** exact 97-word key, process-wide, `voxel.PlayerFitMemo`. **Tile cache window:** the room cache covers the requesting tile's footprint plus a fixed collect margin. It is set via `TGuardValue` in `GenerateMesh`/classify, and is window-invariant by construction (the margin does not grow with the tile). **LOD-gated:** fused path at LOD ≥ `voxel.TileCacheWindowMinLOD` (3), op-stack at LOD ≥ `voxel.TileCacheWindowOpStackMinLOD` (4); lower LODs keep the 4-chunk worker window, because the tile window slowed LOD0 evaluation ~6%. Strates whose authored terrain ops exceed the proven 256-voxel extent (or are malformed/unloaded) fall back to the legacy window (`VF_ComputeTerrainOpEnvelope`). `voxel.SpatialIndex` is -1 auto (off at fused LOD0, on elsewhere), 0 or 1. **Spatial index:** ordered XY buckets over cache primitives, exact rejection. The `static_assert` in `VoxelStrateTypes.h` ties `VF_STRATE_PARAM_FIELDS` to `FStrateGenerationParams` (83 fields + 8 B padding). |
@@ -396,7 +396,7 @@ casse cette arithmetique *structurellement* — `BeginPlay` loggue une **Error**
| `RecordTileHash` / `WriteTileHashDump` | — | Headless `-voxel.TileHashDump=<absolute path>` parity instrument. Records the exact `FVoxelMeshData` used by the streaming result, including empty/classified tiles, then writes sorted absolute tile keys, LOD step/cells, content-band limits, settings digest context, and the shared `VoxelGeometryHash` at `EndPlay`. | | `RecordTileHash` / `WriteTileHashDump` | — | Headless `-voxel.TileHashDump=<absolute path>` parity instrument. Records the exact `FVoxelMeshData` used by the streaming result, including empty/classified tiles, then writes sorted absolute tile keys, LOD step/cells, content-band limits, settings digest context, and the shared `VoxelGeometryHash` at `EndPlay`. |
| `SyncRemeshTile` | — | **INSTANT DIG**: level-0 same-frame re-mesh on the game thread. Uses `PreEditDensityCache` when the tile is resident in the bounded local cache, replays the current DiffLayer over that exact procedural lattice, then meshes/applies inline so a carve is visible THIS frame. A cache miss uses normal generation; it captures the unedited lattice if the tile needs meshing, while uniform-tile proofs remain enabled. | | `SyncRemeshTile` | — | **INSTANT DIG**: level-0 same-frame re-mesh on the game thread. Uses `PreEditDensityCache` when the tile is resident in the bounded local cache, replays the current DiffLayer over that exact procedural lattice, then meshes/applies inline so a carve is visible THIS frame. A cache miss uses normal generation; it captures the unedited lattice if the tile needs meshing, while uniform-tile proofs remain enabled. |
| `PreEditDensityCache` / `VoxelForgePreEditDensityCache::BuildEditedGrid` | — | Per-world cache of the pawn's current L0 tile: exact 35³ pre-edit samples, max **171,500 bytes**. It is pruned when the pawn crosses chunk boundaries and cleared on `RegenerateAllChunks`. `BuildEditedGrid` snapshots touched diff chunks once and reproduces `GetDensityAt`'s final `pre-edit - EvaluateMods` operation before the per-call shared-grid mesher path. | | `PreEditDensityCache` / `VoxelForgePreEditDensityCache::BuildEditedGrid` | — | Per-world cache of the pawn's current L0 tile: exact 35³ pre-edit samples, max **171,500 bytes**. It is pruned when the pawn crosses chunk boundaries and cleared on `RegenerateAllChunks`. `BuildEditedGrid` snapshots touched diff chunks once and reproduces `GetDensityAt`'s final `pre-edit - EvaluateMods` operation before the per-call shared-grid mesher path. |
| `UpdateChunksAroundPosition` / `GetGenerationAdmissionCount` | 10990 / 10971 | Shared Core/legacy scheduler. Builds the desired set and delta-culls leavers; admits the occupied/support tile plus collision-required LOD0 tiles in the pawn's 3×3 XY ring as `CollisionCritical`. S5 coarse-first admission stays opt-in (`voxel.StreamingCoarseFirst=1`). `voxel.StreamingDynamicNearReserve=1` is now the default: it keeps the authored near reserve while moving or until the stationary walkable ring is ready, then makes those slots available to coarse work. Admission counts submitted queued/running tasks, not completed results waiting in the MPSC result queues; `voxel.StreamingLegacyPendingResultCap=1` restores the old `PendingTiles`-based cap for the negative control. `PendingTiles` still lasts through game-thread apply for duplicate suppression and result bookkeeping. The clipmap stays non-overlapping and load-before-unload / `TransitionHold` reconciliation remains authoritative. Standalone has no mirror of this actor scheduler. §8.10 + §8.26–8.28. | | `UpdateChunksAroundPosition` / `GetGenerationAdmissionCount` | 10990 / 10971 | Shared Core/legacy scheduler. Builds the desired set and delta-culls leavers; admits the occupied/support tile plus collision-required LOD0 tiles in the pawn's 3×3 XY ring as `CollisionCritical`. S5 coarse-first admission stays opt-in (`voxel.StreamingCoarseFirst=1`). `voxel.StreamingDynamicNearReserve=1` is the default: it keeps the authored near reserve while moving or until the stationary walkable ring is ready, then makes those slots available to coarse work. Admission counts submitted queued/running tasks, not completed results waiting in the MPSC result queues; `voxel.StreamingLegacyPendingResultCap=1` restores the old `PendingTiles`-based cap for the negative control. `voxel.StreamingFarApplySupplement=1` is the default L121 lane: after the visible-apply cap, at most one far result is admitted when under half the apply-time budget and startup boost is inactive. `PendingTiles` still lasts through game-thread apply for duplicate suppression and result bookkeeping. The clipmap stays non-overlapping and load-before-unload / `TransitionHold` reconciliation remains authoritative. Standalone has no mirror of this actor scheduler. §8.10 + §8.26–8.28. |
| `AdvanceHeadlessCollisionGateStressTest` / `ObserveHeadlessCollisionGateStressTest` / `MaybeFinishHeadlessCollisionGateStressTest` / `MeasureHeadlessCollisionGateStressTerrain` | — | Explicit `-voxel.TestCollisionGateStress=1` harness only. The normal spawn-height cvar offsets from the measured density surface; `voxel.TestCollisionGateStressDeepDrop=1` carves a process-local box shaft from above the top to just above the floor tile, extending along the post-landing route so the pawn does not run into the shaft sidewall, temporarily raises only the transient test DiffLayer brush-radius allowance for that fixture, removes only the local pawn's redundant streaming anchor for this isolated repro, proves every sample in two complete outside-ring level-0 tiles is air before seeding their known-empty status, prewarms the known-empty in-ring column, and asserts that the support walk reaches the in-layout floor outside the aligned level-0 render ring before the pawn drops. The log reports whether the fixed gate retains that otherwise out-of-ring target. Other streaming anchors remain active. The fixture rejects a target or floor outside the layout, a non-air empty-tile proof, or a floor inside the aligned level-0 ring. The `GetDensityAt` column at capsule feet defines the local air/solid surface; the deep-drop column crossing is cached after its first exact scan. Each frame records actor position, velocity, direct foot tile and gate support readiness, gate state, signed feet clearance, and a ≤0.5-voxel sweep between positions. PASS requires no measured clearance below −5 cm; deep-drop gate-on also requires horizontal travel during a hold. Result logs include the terminal density column, first crossing below spawn height, hold count/time, and gated horizontal progress. | | `AdvanceHeadlessCollisionGateStressTest` / `ObserveHeadlessCollisionGateStressTest` / `MaybeFinishHeadlessCollisionGateStressTest` / `MeasureHeadlessCollisionGateStressTerrain` | — | Explicit `-voxel.TestCollisionGateStress=1` harness only. The normal spawn-height cvar offsets from the measured density surface; `voxel.TestCollisionGateStressDeepDrop=1` carves a process-local box shaft from above the top to just above the floor tile, extending along the post-landing route so the pawn does not run into the shaft sidewall, temporarily raises only the transient test DiffLayer brush-radius allowance for that fixture, removes only the local pawn's redundant streaming anchor for this isolated repro, proves every sample in two complete outside-ring level-0 tiles is air before seeding their known-empty status, prewarms the known-empty in-ring column, and asserts that the support walk reaches the in-layout floor outside the aligned level-0 render ring before the pawn drops. The log reports whether the fixed gate retains that otherwise out-of-ring target. Other streaming anchors remain active. The fixture rejects a target or floor outside the layout, a non-air empty-tile proof, or a floor inside the aligned level-0 ring. The `GetDensityAt` column at capsule feet defines the local air/solid surface; the deep-drop column crossing is cached after its first exact scan. Each frame records actor position, velocity, direct foot tile and gate support readiness, gate state, signed feet clearance, and a ≤0.5-voxel sweep between positions. PASS requires no measured clearance below −5 cm; deep-drop gate-on also requires horizontal travel during a hold. Result logs include the terminal density column, first crossing below spawn height, hold count/time, and gated horizontal progress. |
| `BuildDesiredTiles` | 6482 | Builds `DesiredSorted`+`DesiredStamped` (player clipmap shells), then adds streaming anchors and the pending player's collision-only support column before the leaver sweep. It orders from the true local pawn position and builds `CriticalDesiredTiles` for the unresolved support target, occupied/support and heading tiles. Also rebuilds `DesiredTransitionDescendants` (ancestor → desired descendants, including face/edge-touching replacements) for the reconcile pass. | | `BuildDesiredTiles` | 6482 | Builds `DesiredSorted`+`DesiredStamped` (player clipmap shells), then adds streaming anchors and the pending player's collision-only support column before the leaver sweep. It orders from the true local pawn position and builds `CriticalDesiredTiles` for the unresolved support target, occupied/support and heading tiles. Also rebuilds `DesiredTransitionDescendants` (ancestor → desired descendants, including face/edge-touching replacements) for the reconcile pass. |
| `CollisionGateDesiredTiles` / `RequestCollisionGateSupportTile` | 5062 | Retains an unresolved support target and up to three level-0 tiles below it across desired-set rebuilds. The additional keys are hidden collision-only tiles, queued ahead of ordinary streaming, and pruned once resolved or outside the active column. | | `CollisionGateDesiredTiles` / `RequestCollisionGateSupportTile` | 5062 | Retains an unresolved support target and up to three level-0 tiles below it across desired-set rebuilds. The additional keys are hidden collision-only tiles, queued ahead of ordinary streaming, and pruned once resolved or outside the active column. |
@@ -1352,6 +1352,20 @@ tunnel-slope version in `VoxelStrateDefinition.cpp`, plus the standalone `VoxelF
in `VoxelForgeExploreCommandlet.cpp`. `GetStrateForChunk` still receives layout-chunk Z, not a Core in `VoxelForgeExploreCommandlet.cpp`. `GetStrateForChunk` still receives layout-chunk Z, not a Core
tile index. See `Standalone/Research/WorldAssemblerM3/Unreal1/REPORT.md` for BuildHost evidence. tile index. See `Standalone/Research/WorldAssemblerM3/Unreal1/REPORT.md` for BuildHost evidence.
### 3.38 Native Vertical Shafts graph field — World Assembler M3 L139
| Symbol | Canonical file | Role |
|---|---|---|
| `ResolvedCavernPlannerSettingsV1::verticalShaftMode`, basal terminal route planning | `Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h`, `Standalone/Core/src/ResolvedLayoutInternal.h` | Extends shared `PlanResolvedCavernNetworkV1` for an arrival-only basal strate; its walk-only spine terminates at a landing above bedrock. Shaft mode disables generic branches and uses dedicated shaft nodes. |
| `ResolvedCavernRoomV1::shapeKind=1`, `ApplyResolvedNativeCavesField` | `Standalone/Core/src/ShapeWorldInternal.h` | Evaluates capped vertical shaft cylinders with shelves alongside shared access and cross-connect tunnels. Uses `Core.Caves.PrimitiveField`, not the L58 lattice spiral. |
| VerticalShafts settings mapping | `Standalone/Source/WorldAssemblyPrototype.h` | Maps the 11 legacy params into shared spacing, room density/radii, bore radius, cross-connect density, ledges, roughness, and existing boundary seal/density bands. |
| `IsNativeCaveFieldStratum`, S3 binding and `FCavesFieldAdapter` | `Standalone/Core/src/ShapeWorldInternal.h`, `Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp` | Reports `VerticalShafts` as `Core.Caves.PrimitiveField` v1 / `Caves-native`; leaves the legacy generator archetype identity intact. |
| `RunL139SeedSweep.py`, `RunL139Determinism.py`, `RunL139Captures.py`, `RunL139WholeLayerCapture.py`, `RunL139FinalTiming.py` | `Standalone/Research/WorldAssemblerM3/Linux139/` | Reproducible composed 16-seed audit, Clang/GCC W1/W8/W12 identity check, five before/after eye-height views plus density-cutaway and plan-projection outside views, and L80 characterization. The seed-2 passage-shell failure blocks promotion. |
See ARCHITECTURE §8.56 and
[`Standalone/Research/WorldAssemblerM3/Linux139/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux139/REPORT.md)
for the planner semantics, field mapping, audit result, captures, and outstanding shell failure.
## 4. The density pipeline (most-edited hot path) ## 4. The density pipeline (most-edited hot path)
### 4.1 `GetDensityWithParams` (TunnelNetwork) — VoxelGenerator.cpp:3749 ### 4.1 `GetDensityWithParams` (TunnelNetwork) — VoxelGenerator.cpp:3749
+59
View File
@@ -0,0 +1,59 @@
# VoxelForge current state
As of 2026-11-01. L139 is being developed on `wip/l134-cave-planner`. Current-state claims below point to their code or measured report; L139's Vertical Shafts implementation has not passed composed acceptance and is not promoted.
## What ships today
- **Core:** evaluator version **237** in the L139 worktree. Unreal's `AVoxelWorld::bUseVoxelForgeCore` remains opt-in and defaults to `false`; the legacy generator remains the default runtime path. [Core version](Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h), [runtime switch](Source/VoxelForge/Public/VoxelWorld.h), [L139 report](Standalone/Research/WorldAssemblerM3/Linux139/REPORT.md).
- **Verified owner4 generator dispatch:** S0 SurfaceWorld binds `SurfaceWorld-native`; S1 FloatingIslands remains `compiled-analytic/FloatingIslands`; S2 TunnelNetwork binds `Core.Caves.PrimitiveField`; and the L139 fast-tier report binds S3 VerticalShafts to `Core.Caves.PrimitiveField` / `Caves-native`. This proves dispatch, not the full shaft gate: the composed 16-seed sweep fails the passage-shell audit at seed 2. [Dispatch](Standalone/Core/src/ShapeWorldInternal.h), [adapter recipe checks](Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h), [L139 report](Standalone/Research/WorldAssemblerM3/Linux139/REPORT.md).
- **Passages:** continuous smooth swept tubes with a continuous floor profile. L125 stamps conservative route slabs and outside sentinels into the tile cache; exact route geometry is still evaluated. All 21,425 route samples passed the open-air walk check; the nine-station floor probe's maximum root offset was 7.884 mm. L130 sets the shared influence reach to **1.0533 m** (0.75 m shell + 0.21 m relief + 0.50 m smooth-max support / 6 + 0.01 m margin), converted once from metres to Core voxels for the proof, feature, fallback, and off-grid bounds. [L125](Standalone/Research/WorldAssemblerM3/Linux125/REPORT.md), [L130](Standalone/Research/WorldAssemblerM3/Linux130/REPORT.md).
- **Streaming:** arrival uses a bounded startup apply burst: `voxel.StreamingStartupApplyBoost=1` by default, up to 24 visible meshes or 14 ms per frame for at most 30 seconds, until the initial desired set and walkable ring are ready. `voxel.StreamingFarApplySupplement=1` is also default-on: after the normal visible apply cap, at most one far result applies when under half the apply-time budget; it is inactive during the startup burst. Dynamic near reserve defaults on; coarse-first remains off; the legacy pending-result cap defaults off. The L165/L166 incremental desired-set path now defaults on (`voxel.StreamingIncrementalDesiredSet=1`): Core clipmap keys, overlay membership, and priority order update incrementally, with same-center Core keys cached. No separate overlay or priority incremental switches are required. Audits stay off (`StreamingIncrementalDesiredAuditEvery=0`, `StreamingIncrementalDesiredMaterializationAuditEvery=0`, mismatch injection `=0`). L157 early admission and apply-cap controls stay off (`StreamingFastTravelWaitForAllCriticalTiles=1`, `StreamingFastTravelApplyBudgetMilliseconds=0`). L160 ahead-first and same-center rebuild suppression stay off (`StreamingFastTravelForwardFirst=0`, `StreamingRebuildOnCoreRegionCompletion=1`). Its separate Core desired-cache switch stays off (`StreamingCoreDesiredCacheByCenter=0`); the new incremental path enables its own same-center Core cache. The far-apply lane is Unreal-only. [CVar source](Source/VoxelForge/Private/VoxelWorld.cpp), [L121 report](Standalone/Research/WorldAssemblerM3/Linux121/REPORT.md), [architecture](ARCHITECTURE.md).
- **Streaming:** arrival uses a bounded startup apply burst and a far-result supplement; dynamic near reserve and incremental desired sets are default-on. L172 made `voxel.StreamingFastTravelWaitForAllCriticalTiles=0` and `voxel.StreamingFastTravelApplyBudgetMilliseconds=1.0` the defaults. Forward-first and same-center rebuild suppression remain off; audit CVars remain off. The far-apply lane is Unreal-only. [L172 report](Standalone/Research/WorldAssemblerM3/Linux172/REPORT.md), [CVar source](Source/VoxelForge/Private/VoxelWorld.cpp), [architecture](ARCHITECTURE.md).
- **Determinism:** L131's unchanged-output comparison covers owner4 SW/FI/TN/VS views in 24 compiler/worker cases, and the canonical owner4/owner2 recipe matrix passes. The L139 Tunnel Network and Vertical Shafts cave plans matched across Clang/GCC W1/W8/W12 (six cases); their composed passage sweep still fails seed 2. Windows determinism is unverified. [L131 checks](Standalone/Research/WorldAssemblerM3/Linux131/REPORT.md), [L139 checks](Standalone/Research/WorldAssemblerM3/Linux139/REPORT.md), [canonical hashes](Standalone/Tools/verify/baselines/owner-hash/canonical/).
## Current performance references
Headless medians in seconds, five interleaved L80-style measurements per version in each cell. Near-ready means the complete LOD0–2 set; whole-view is the full requested view. Values below are the latest complete L131 measurements; L139 characterization is in the [L139 report](Standalone/Research/WorldAssemblerM3/Linux139/REPORT.md) and [timing summary](Saved/Linux/l139/timing/summary.json). The paired pre-cleanup comparison is in the [L131 report](Standalone/Research/WorldAssemblerM3/Linux131/REPORT.md) and [timing summary](Saved/Linux/l131/timing/summary.json).
| Stratum | Near-ready W8 / W12 | Whole-view W8 / W12 |
|---|---:|---:|
| S0 | 0.804 / 0.848 | 1.811 / 1.832 |
| S1 | 1.014 / 1.002 | 1.993 / 1.886 |
| S2 | 1.060 / 1.012 | 1.960 / 1.865 |
| S3 | 0.917 / 0.884 | 1.887 / 1.782 |
Across 40 paired runs per metric, the mean relative change from pre-cleanup L130 was −0.22% near-ready (95% CI −1.02% to +0.57%) and −0.29% whole-view (−1.04% to +0.46%); neither indicates a measurable slowdown. The machine gate was load 0.81 and 99.2% idle. [L80 protocol](Standalone/Research/WorldAssemblerM3/Linux80/REPORT.md), [runner and raw runs](Standalone/Research/WorldAssemblerM3/Linux131/RunL131Timing.py).
The latest recorded Linux Unreal start-to-LOD0–2 values are L127's one run per stratum: S0 2.456 s, S1 2.231 s, S2 2.514 s, S3 2.221 s. L130 did not rerun UE startup or movement. Five L127 runs at 30 m/s, 12 s moving plus 5 s dwell, had median moving near coverage **40.22%**, far holes **3.46%**, tick p99 **10.80 ms**, and worst tick **13.27 ms**. After stopping, near coverage reached 100% and far holes reached 0%. These are the latest UE references, not v227 UE measurements. [L127 report](Standalone/Research/WorldAssemblerM3/Linux127/REPORT.md), [L127 movement summary](Saved/Linux/l127/ue/movement-pairs-summary.json), [L127 start summaries](Saved/Linux/l127/ue/merge/start-summary.json).
## Open gaps
- Multiplayer world lifecycle is unfinished: seed/layout replication, authoritative modification RPCs, and serialized diff replay for late join are still open. See [ARCHITECTURE §9.5 and §9.7](ARCHITECTURE.md).
- L139 Vertical Shafts is not accepted: the composed audit's seed 2 fails the incoming passage shell after three geometry variants failed that same gate. L80 characterization and the Linux host build passed, but neither changes the composed acceptance result. See [L139 report](Standalone/Research/WorldAssemblerM3/Linux139/REPORT.md).
- Determinism has not been verified on Windows. Linux Clang/GCC agreement does not establish MSVC agreement.
- NullRHI measures residency and apply timing, not the first presented pixel or rendered enclosure. L127 also found four low-contrast views among 23 otherwise verified captures. [L121](Standalone/Research/WorldAssemblerM3/Linux121/REPORT.md), [L127](Standalone/Research/WorldAssemblerM3/Linux127/REPORT.md).
- UE startup/movement numbers above predate L130's v227 reach change; there is no L130/L131 UE movement comparison.
## How to verify
- Build standalone Clang or GCC with [`Standalone/BuildLinux.sh`](Standalone/BuildLinux.sh), for example `bash Standalone/BuildLinux.sh --compiler=clang++-19 --variant=l139-clang --fp-contract=off --jobs=4`. The script pins CPUs 0–11 and applies `prlimit --as=6442450944` itself.
- Fast tier: `python3 Standalone/Tools/verify/verify.py fast --out Saved/Linux/l139 --overall-timeout 1740`. It includes the four-strate owner density-dispatch report; per-run logs and the summary stay under `Saved/Linux/l139`.
- The L139 v237 canonical Surface World and Floating Islands owner-hash baselines are in `Standalone/Tools/verify/baselines/owner-hash/canonical/`; both values match v236. L131 also provides reproducible pre-cleanup identity matrices for owner4 tile hashes and owner4/owner2 canonical hashes under `Standalone/Research/WorldAssemblerM3/Linux131/`.
- Timing follows L80: quiet machine, no overlapping build, one warm-up per version, five interleaved runs per stratum and worker count, W8/W12. The L139 run recorded the L137 A/A noise floor of ±1%. Each headless run uses `taskset -c 0-11`, `prlimit --as=6442450944`, and a 1,740 s timeout. See [`RunL139FinalTiming.py`](Standalone/Research/WorldAssemblerM3/Linux139/RunL139FinalTiming.py).
- Linux host build: `/home/jahni/UnrealEngine/Engine/Build/BatchFiles/Linux/Build.sh VFHostEditor Linux Development -Project=/home/jahni/VFHost/VFHost.uproject -NoUBA -MaxParallelActions=4 -Log=/home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l139/ubt.log`. Run Unreal without `prlimit`.
## Closed experiments
These rejected speed/streaming proposals stay closed unless new evidence changes their measured failure. Reports and surviving patches are linked in the [L131 inventory](Standalone/Research/WorldAssemblerM3/Linux131/REPORT.md).
| Line | Result |
|---|---|
| L116 tunnel chord bound and z cache | Pair count fell 20.8%, but stayed 1.96× L109 and readiness was 15–30% slower. Patch: [`chord-bound-zcolumn-not-merged.patch`](Standalone/Research/WorldAssemblerM3/Linux116/patches/chord-bound-zcolumn-not-merged.patch), commit `89504ec`; see [report](Standalone/Research/WorldAssemblerM3/Linux116/REPORT.md). |
| L118 pair pruning / deferred floor work | Pruned 17–18% of candidate pairs but remained 17.2–25.7% behind L109 near-ready. [Report](Standalone/Research/WorldAssemblerM3/Linux118/REPORT.md); patch: [`l118-designs.patch`](Standalone/Research/WorldAssemblerM3/Linux118/l118-designs.patch). |
| L126 floor-call filters | Cut floor calls 20–23% without reducing pair counts; all eight cells missed L109. Candidate source was restored and no standalone patch was kept; see [report](Standalone/Research/WorldAssemblerM3/Linux126/REPORT.md) and commit `01a5094`. |
| L128 half-open slab ownership | Ownership audit passed, but readiness was 14.8–28.0% behind L109 and moving coverage dropped. Report and patch `l128-slab-ownership.patch` are in archived commit `7022d02`. |
| L129 2 m physical reach | v225's metre/Core-voxel bounds mismatch made the correctly bounded candidate do more work; it was slower and failed UE movement gates. Report and patch `l129-reach-2m.patch` are in archived commit `0d1140c`. |
| L120 priority split / extra worker slots | Neither met coverage and tick gates; 10 slots reached a 16.67 ms worst tick against 15.43 ms baseline. [Report](Standalone/Research/WorldAssemblerM3/Linux120/REPORT.md); no production patch retained. |
| L122 admission relaxation | Early admission missed p99 or far-hole gates; no behavior retained. [Report](Standalone/Research/WorldAssemblerM3/Linux122/REPORT.md); patch: [`l122-trace.patch`](Standalone/Research/WorldAssemblerM3/Linux122/l122-trace.patch). |
| L123 lookahead / completion gate | Lookahead failed p99 and worsened near/far coverage; queue-only also failed p99. [Report](Standalone/Research/WorldAssemblerM3/Linux123/REPORT.md); patch: [`l123-queue-lookahead.patch`](Standalone/Research/WorldAssemblerM3/Linux123/l123-queue-lookahead.patch). |
| L124 early LOD0 pages | Optimistic gain stayed below 100 ms except negative cases; no publication/scheduling implementation was made. [Report](Standalone/Research/WorldAssemblerM3/Linux124/REPORT.md). |
File diff suppressed because it is too large. Load diff
+201
View File
@@ -0,0 +1,201 @@
# L162 — Living Plane
The EGL/OpenGL 4.3 runner builds and passes its llvmpipe correctness and frame checks. The sandbox has no accessible DRM render node, so it did not run or report Intel GPU performance. The runner refuses `--device=gpu` here instead of falling back to software.
## Implementation
`LivingPlane.cpp` runs a ping-pong `RG32F` height/velocity field through a runtime-compiled GLSL compute shader. The damped wave update includes a weak foundation spring, a Gaussian sphere push, and an optional slow breathing force. The spring is needed because a persistent local push otherwise accelerates the zero-frequency height mode and sinks the whole plane. The sphere moves across the plane, stops, and bobs. A vertex-displaced mesh and lit sphere render to an EGL pbuffer-backed framebuffer and save as PNGs.
The timestep is selected from the largest 2D grid eigenfrequency:
`dt = 1.30 / sqrt(8*c*c/(dx*dx) + foundationStiffness)`
The resulting spectral step ratio is 1.30, below the explicit update's 2.0 stability limit. The runner warms the compute kernel for eight unmeasured dispatches, then records one `GL_TIME_ELAPSED` query per requested simulation step in `timing.json`.
The first-step equation audit also caught a GL state error during bring-up: frame rendering left the graphics program active before the next compute dispatch. `Dispatch` now binds the compute program before every step, so the audit fails if a frame silently stops simulation.
Device selection enumerates EGL devices and uses `EGL_EXT_platform_device` for an accessible Intel render node first. On this host, the known integrated device is Intel on `renderD128`. Automatic mode falls back to a surfaceless Mesa context with `LIBGL_ALWAYS_SOFTWARE=1`; explicit GPU mode fails when no accessible hardware node is present. EGL does not expose Vulkan's integrated/discrete enum, so Intel vendor/render-node identity is the selection heuristic.
The Unreal handoff is in [UNREAL-MAPPING.md](UNREAL-MAPPING.md). It keeps the prototype render-only and describes the density mapping if the surface later needs to affect voxel queries. No Unreal `Source/` files changed.
## Measurements and checks
- Built with `Experiments/LivingPlane/build.sh` using the installed EGL, desktop GL, and libpng development files; no shader compiler or package install was needed.
- Final llvmpipe run: 128² field, 360 steps, `dt=9.56814 ms`, spectral ratio 1.30. The first-step equation audit and finite-field/dynamics audit passed. Final field range was −0.139533 m to +0.0366704 m. It wrote 16 128×128 PNGs and a JSON file containing 360 per-step query samples. The frames were inspected; the moving sphere and wake are visible.
- Auto-selection smoke check: 32², 12 steps; selected llvmpipe and passed both audits. Breathing smoke check: 64², 180 steps with `--breathing=on`; passed both audits.
- Negative device-selection control: `--device=gpu` returned an error because the sandbox has no accessible EGL DRM render node.
- Existing fast verification tier passed: 278.9 seconds, ending in `SUMMARY PASS`. Its build and logs are under `Saved/Linux/l162/verify/` and `Saved/Linux/l162/`.
- The llvmpipe `GL_TIME_ELAPSED` samples are preserved in JSON but are software-driver query values, not GPU timings. The requested hardware timing pass remains to be run outside the sandbox.
- No Unreal build was run; no Unreal `Source/` files changed.
## Intel GPU pass
Run these from the plugin root as `jahni`. The frame run writes 512×512 PNGs to `Saved/Linux/l162/gpu-frames-512/`; the two timing runs write `timing.json` with one GPU query per step and skip PNG capture. They still read the field for the first-step and final-state correctness audits.
Before the timing runs, check `uptime` and run them on a quiet machine (load below about 1.5, with no other build active).
```sh
uptime
taskset -c 0-11 prlimit --as=6442450944 -- timeout 1740s ./Saved/Linux/l162/living-plane --device=gpu --size=512 --steps=1800 --capture-every=12 --out=Saved/Linux/l162/gpu-frames-512
taskset -c 0-11 prlimit --as=6442450944 -- timeout 1740s ./Saved/Linux/l162/living-plane --device=gpu --size=512 --steps=1800 --capture-every=0 --out=Saved/Linux/l162/gpu-timing-512
taskset -c 0-11 prlimit --as=6442450944 -- timeout 1740s ./Saved/Linux/l162/living-plane --device=gpu --size=1024 --steps=1800 --capture-every=0 --out=Saved/Linux/l162/gpu-timing-1024
```
Encode the captured sequence as MP4 with:
```sh
ffmpeg -y -framerate 30 -i Saved/Linux/l162/gpu-frames-512/frame_%06d.png -c:v libx264 -pix_fmt yuv420p Saved/Linux/l162/living-plane-512.mp4
```
## Files and remaining risk
The only changed file outside `Experiments/LivingPlane/` is `.gitignore`, which now allows `Experiments/` and its contents. GPU timings and Intel render-node selection remain unverified until the commands above run on the host outside this sandbox.
## Intel GPU pass (run by the orchestrator outside the sandbox, 2026-11-02)
- The device was selected correctly: `Mesa Intel(R) Graphics (ADL GT2)`, OpenGL 4.6, `/dev/dri/renderD128`. 512²,
1,800 steps, 151 frames; the equation audit passes. MP4: `Saved/Linux/l162/living-plane-512.mp4` (local).
- **The `GL_TIME_ELAPSED` per-dispatch values (~0.0001 ms) are not credible** for 262k cells, so they aren't
reported as timings.
- Wall-time difference between 1,800 and 200 steps, divided by the 1,600 steps (start-up cancels out; this includes
submission, so it's an upper bound): **0.080 ms/step at 512²** and **0.292 ms/step at 1024²**.
- Visual read: the effect is very subtle in the current render (a slight dip under the sphere, faint rings). It needs
a larger push amplitude, grazing light or height colouring, and a closer camera to read.
## L163 — readable, living plane
The plane now reads as rolling terrain, and the objects leave visible troughs and wakes. `alive` adds travelling
breathing and a viscoelastic imprint that remains after lift-off. The simulation still runs in one GPU dispatch per
step; CPU work only supplies the two object states.
`LivingPlane.cpp` now keeps the dynamic displacement, velocity, and imprint in an `RGBA32F` ping-pong field. The
surface shader adds broad static terrain relief, calculates normals from relief plus displacement, and uses grazing
light, elevation colours, slope tint, and 7.5 cm contour bands. The camera follows the first sphere at a closer angle.
The wake scene rolls the first sphere, pauses and wobbles it, lifts it, then sends a second sphere through the wake.
The alive scene runs that same sequence with low-frequency travelling swells and a decaying contact imprint. Its
defaults are breathing strength 4.8 m/s², imprint capture 0.15 m/s, recovery time 6 s, and maximum imprint depth
0.34 m. These values have command-line overrides.
The timer query check found no early read or reuse in L162: it allocated a unique query per step, called `glFinish`,
then read every result. The saved Intel/Mesa 25.0.7 samples are 0 or 52 ns for most dispatches (512² median 0,
p95 52 ns; largest sample 17.552 µs), which cannot be a credible per-dispatch cost. The old usage did not reuse
query objects, so lifetime is not the cause; the saved data point to Intel/Mesa's timer-query reporting or timing
scope, but the sandbox cannot isolate which. L163 uses a 64-query ring, reads each query before reusing its slot,
checks availability after `glFinish`, and records `GL_QUERY_COUNTER_BITS`. On llvmpipe the availability check passed
with 64 counter bits and zero waits. The Intel query result still needs the GPU diagnostic command below.
### Measurements and checks
- llvmpipe wake: 128², 600 steps (`dt=9.56814 ms`, 5.741 s simulation window), 151 frames at 30 fps. First-step
equation audit and timer-query lifetime check passed; final displacement range was −0.177488 m to +0.104854 m.
- llvmpipe alive: same grid, steps, and window; equation audit and timer-query lifetime check passed. Final
displacement range was −0.200437 m to +0.115904 m, with a −0.111667 m stored imprint.
- llvmpipe cost: 512², 1,800 steps, `--queries=off`, no frame capture: **0.767473 ms/step** in one timed
simulation-loop window. `timing.json` includes dispatch submission, the step-0 equation readback, and final
`glFinish`, but excludes setup and frame capture. This is a software-renderer measurement, not the Intel GPU cost.
- Both five-second llvmpipe previews are in `Saved/Linux/l163/llvmpipe-wake.mp4` and
`Saved/Linux/l163/llvmpipe-alive.mp4`. They are 128² review previews; use the GPU commands below for the owner
videos.
- Equation-audit negative control: a temporary copy with its expected first-step height forced to zero exited FAIL
at actual −0.0488777 m. The final source passes that same check.
- The fast verification tier passed in 278.5 s. Build and logs are under `Saved/Linux/l163/verify/`.
- No Unreal `Source/` files changed and no Unreal build ran.
### Intel GPU run
Run from the plugin root as `jahni`. First build the runner. For timed runs, check `uptime` and keep the machine
quiet. The wall-time field is valid only when `--capture-every=0`; use `--queries=off` for the step-cost measurement.
```sh
bash Experiments/LivingPlane/build.sh
uptime
taskset -c 0-11 prlimit --as=6442450944 -- timeout 1740s ./Saved/Linux/l163/living-plane --device=gpu --scene=wake --size=512 --steps=2400 --capture-every=16 --queries=off --out=Saved/Linux/l163/gpu-wake-512
taskset -c 0-11 prlimit --as=6442450944 -- timeout 1740s ./Saved/Linux/l163/living-plane --device=gpu --scene=alive --size=512 --steps=2400 --capture-every=16 --queries=off --out=Saved/Linux/l163/gpu-alive-512
ffmpeg -y -framerate 30 -i Saved/Linux/l163/gpu-wake-512/frame_%06d.png -c:v libx264 -pix_fmt yuv420p Saved/Linux/l163/living-plane-wake-512.mp4
ffmpeg -y -framerate 30 -i Saved/Linux/l163/gpu-alive-512/frame_%06d.png -c:v libx264 -pix_fmt yuv420p Saved/Linux/l163/living-plane-alive-512.mp4
taskset -c 0-11 prlimit --as=6442450944 -- timeout 1740s ./Saved/Linux/l163/living-plane --device=gpu --scene=alive --size=512 --steps=1800 --capture-every=0 --queries=off --out=Saved/Linux/l163/gpu-timing-512
taskset -c 0-11 prlimit --as=6442450944 -- timeout 1740s ./Saved/Linux/l163/living-plane --device=gpu --scene=alive --size=512 --steps=1800 --capture-every=0 --queries=on --out=Saved/Linux/l163/gpu-query-check-512
```
`gpu-timing-512/timing.json` reports the 512² batch wall time per step, including dispatch submission and completion.
`gpu-query-check-512/timing.json` contains query counter bits, waits before slot reuse, availability status, and raw
per-step query samples. The sandbox has no accessible Intel DRM node, so the current Intel step cost and new query
diagnostic remain unmeasured here.
### L163 Intel GPU pass (run by the orchestrator, 2026-11-02)
- Intel ADL GT2 (Mesa 25.0.7, `renderD128`): wake and alive scenes, 512², 2,400 steps, 151 frames each; equation
audits PASS. Videos (local): `Saved/Linux/l163/living-plane-wake-512.mp4`, `living-plane-alive-512.mp4`.
- Step cost, alive scene, 512², 1,800 steps, no capture: **0.172 ms/step** with queries off, 0.152 ms/step with
queries on (batch wall time per step).
- The Intel `GL_TIME_ELAPSED` median is still 0 with 64 counter bits and the ring/availability checks passing. That
looks like a Mesa/Intel reporting issue; use wall time.
- **Open issue:** the same scene with the same steps gave different final fields (min −0.637 m with queries off vs
−0.327 m with queries on). Something depends on more than the step index (e.g. wall-clock time); investigate.
## L164 — repeatable field readback and UE runtime actor
**Result:** the repeatability check passes on llvmpipe, and the new `VoxelForgeLivingPlane` runtime module compiles
in the Linux UE 5.7 host. The surface actor is independent of VoxelForge generation and sends no field readback to
the CPU.
### Repeatability finding
`uTime` is `Step * dt`, and `GetBodiesAt` receives that same value. Neither the object schedule nor the breathing
force uses wall time or a rendered-frame counter. Every compute step already issued an image-access barrier before
the next ping-pong image read. The defect was on the diagnostic side: the step-zero and final `glGetTexImage`
readbacks followed shader image writes without `GL_TEXTURE_UPDATE_BARRIER_BIT`. The Khronos OpenGL specification
requires that barrier for `GetTexImage` to observe earlier shader writes ([§7.12](https://registry.khronos.org/OpenGL/specs/gl/glspec44.compatibility.pdf)). Query/capture pacing could therefore affect the reported field values without changing any simulation input. The barrier is now issued before both readbacks.
`check_repeatability.py` runs the same 512² alive scene for 1,800 steps twice: queries off with no captures, then
queries on with captures every 900 steps. The checksum covers every final RGBA32F field value using canonical
little-endian float bytes.
- llvmpipe, queries off/capture off: field range −0.637382 m to +0.109308 m; checksum
`1ba08f5dafc01f82`.
- llvmpipe, queries on/capture every 900: the same range and checksum; equation and timer-query audits pass; three
frames written.
- Negative control: changing one saved checksum made the comparison exit 1 with `FAIL field checksum mismatch`.
- The llvmpipe runs were each one 1,800-step simulation-loop window. Logs and JSON are under
`Saved/Linux/l164/repeatability/`.
The Intel run is not available in this sandbox. The owner should repeat the checksum comparison on the GPU to
confirm that the missing transfer-readback barrier accounts for the earlier Intel min mismatch:
```sh
python3 Experiments/LivingPlane/check_repeatability.py --device=gpu --out=Saved/Linux/l164/gpu-repeatability
```
### Unreal implementation and checks
The plugin descriptor adds a separate `VoxelForgeLivingPlane` runtime module. Its RDG global shader is in
`Shaders/LivingPlane.usf`; module startup maps `/Plugin/VoxelForgeLivingPlane` to the plugin shader directory. The
actor owns two UAV-capable RGBA32F render targets, advances them at the stable fixed step, and supplies the current
texture to a movable plane mesh material. Its Blueprint/C++ object input includes world position, radius, velocity,
base push, and active state. The CPU converts those values to plane-local metres and uploads uniforms; it does not
read the field back. The ignore rules now include `Shaders/` so that the shader ships in the worktree diff.
`Experiments/LivingPlane/UE-SETUP.md` gives level setup, the full material graph, and object driving steps.
- Linux UE 5.7 `VFHostEditor Linux Development`: **PASS**, final incremental build compiled
`VoxelForgeLivingPlaneActor.cpp` and linked `libUnrealEditor-VoxelForgeLivingPlane.so`. Command used
`-NoUBA -MaxParallelActions=4`, `taskset -c 0-11`, and no `prlimit`. Log:
`Saved/Linux/l164/ue-build-final.log`. The initial attempt exposed an incorrect RDG texture registration overload;
it had already compiled `VoxelForgeLivingPlaneModule.cpp` and the generated module code. The corrected final
build passed.
- The host `PluginDeps/VoxelForge/Source` and `.uplugin` links were temporarily pointed at this worktree for the
build and restored to `/home/jahni/VoxelM/Plugins/VoxelForge` afterward. The temporary shader link was removed.
- Standalone build, final repeatability comparison, checksum-mismatch negative control, plugin JSON parse, and
`git diff --check` passed.
- Not run: fast generation verification (generation sources are untouched), Intel GPU checksum comparison, or a UE
editor render/shader-runtime check. The Linux UBT build verifies C++ and module integration, not first-use shader
compilation or material appearance.
New Unreal source and shader files for the owner's Windows build:
- `Source/VoxelForgeLivingPlane/VoxelForgeLivingPlane.Build.cs`
- `Source/VoxelForgeLivingPlane/Public/VoxelForgeLivingPlaneActor.h`
- `Source/VoxelForgeLivingPlane/Private/VoxelForgeLivingPlaneActor.cpp`
- `Source/VoxelForgeLivingPlane/Private/VoxelForgeLivingPlaneModule.cpp`
- `Shaders/LivingPlane.usf`
No VoxelForge generation module files changed. Remaining risks are the Intel checksum confirmation and first-use UE
shader/material rendering, which require the owner's GPU/editor run.
+55
View File
@@ -0,0 +1,55 @@
# Living Plane in Unreal Engine 5.7
`VoxelForgeLivingPlane` is a separate runtime module. It does not call the VoxelForge terrain generators and it does not read the simulated texture back to the CPU. The actor owns two transient `RGBA32f` render targets and advances them with an RDG compute pass. The CPU sends object state and fixed-step uniforms only.
## Add it to a level
1. Enable the VoxelForge plugin and restart the editor so the `VoxelForgeLivingPlane` runtime module and its shader mapping load.
2. Create a material named `M_LivingPlane` using the graph below.
3. Place `VoxelForgeLivingPlaneActor` in the level. Assign `M_LivingPlane` to **Plane Material**.
4. Keep the actor on the world XY plane with unit actor scale. `Plane Length Meters` controls the mesh width and simulation width; the built-in plane mesh is scaled to that size.
5. The actor creates and clears its two GPU state targets on `BeginPlay`. The mesh has collision disabled; this surface is render-only and does not change VoxelForge density or collision queries.
## Material graph
Use a Surface, Opaque material. The actor binds these parameters on its dynamic material instance:
- `LivingField`: Texture Object Parameter. The texture is the current RGBA32f simulation state; **R** is displacement in metres.
- `PlaneLengthMeters`: Scalar Parameter.
- `FieldSize`: Scalar Parameter, equal to the render target width and height.
For World Position Offset:
1. Read `LivingField` at `TextureCoordinate[0]` with a Texture Sample.
2. Multiply its **R** output by `100` to convert metres to Unreal centimetres.
3. Append `(0, 0)` to that value, making `(0, 0, heightCm)`, and connect it to **World Position Offset**.
For the displaced normal, sample the height at four UV offsets using the same texture object:
- Left/right UV: `TexCoord + (-1/FieldSize, 0)` and `TexCoord + (1/FieldSize, 0)`.
- Down/up UV: `TexCoord + (0, -1/FieldSize)` and `TexCoord + (0, 1/FieldSize)`.
- `dHdx = (Right.R - Left.R) * FieldSize / (2 * PlaneLengthMeters)`.
- `dHdy = (Up.R - Down.R) * FieldSize / (2 * PlaneLengthMeters)`.
- Make and normalize the tangent-space vector `(-dHdx, -dHdy, 1)`, then connect it to **Normal**.
The built-in plane's U/V tangent directions align with local X/Y. Use a neutral base colour and roughness around `0.7` to start. The graph assumes a horizontal plane; add a local-to-world transform if the plane needs arbitrary rotation.
## Drive objects
`ObjectInputs` has two slots. Each `FVFLivingPlaneObjectInput` contains world position in centimetres, world velocity in centimetres per second, radius in centimetres, base push acceleration in metres per second squared, and an active flag. Blueprint can use **Set Object State** or set the array entry; C++ can call the same method. Set the object's center about one radius above the plane while it contacts it, and mark the slot inactive after lift-off.
The compute shader uses the radius to scale the Gaussian contact footprint. The original wave equation keeps the L163 wave speed, damping, foundation spring, travelling breathing force, and recovering imprint. The base push acceleration supplies the object force; lateral speed adds `0.25 m/s²` per `1 m/s`, and downward speed adds `0.5 m/s²` per `1 m/s`. Upward velocity does not add force. Object positions and velocities are transformed into the plane's local frame and converted from centimetres to metres before upload.
Each step uses `dt = 1.30 / sqrt(8*c*c/(dx*dx) + foundationStiffness)`, with `dx = PlaneLengthMeters / FieldSize`. Simulation time passed to breathing is the fixed step index times `dt`. Game-thread delta time only determines how many fixed steps are queued; no GPU field data is read back in the normal path.
## Build files
The owner's Windows build needs every added source and shader file:
- `Source/VoxelForgeLivingPlane/VoxelForgeLivingPlane.Build.cs`
- `Source/VoxelForgeLivingPlane/Public/VoxelForgeLivingPlaneActor.h`
- `Source/VoxelForgeLivingPlane/Private/VoxelForgeLivingPlaneActor.cpp`
- `Source/VoxelForgeLivingPlane/Private/VoxelForgeLivingPlaneModule.cpp`
- `Shaders/LivingPlane.usf`
The `VoxelForge.uplugin` runtime-module entry must ship with them.
+33
View File
@@ -0,0 +1,33 @@
# Unreal mapping note
The prototype stores height, vertical velocity, a recovering imprint, and a spare
channel in a pair of `RGBA32F` textures. A world-owned Unreal component can keep
equivalent transient render graph textures and advance them with a compute pass. It
should own simulation time and clear or restore its buffers when the world is recreated.
The sphere path becomes a world-space interaction event: position, contact radius,
and impulse strength are supplied to the compute pass. For multiplayer or deterministic
replay, replicate the compact events and simulation tick, or record the resulting field;
do not assume that independent GPU floating-point runs will produce byte-identical
textures.
The rendered top surface can remain a render-only displaced mesh. If it must participate
in voxel collision or queries, its height must also reach the density evaluator. For a
height `h(x,y)`, the plugin's density convention is negative inside solid and positive
in air, so the plane's signed density is `z - h(x,y)`. Keep this calculation in metres
until the existing voxel/Core boundary converts with the active voxel size (25 cm by
default); do not mix metre distances and voxel counts in the wave solver.
The current sample is an isolated visual field. Wiring this into `AVoxelWorld` would
need an explicit owner for simulation lifetime, collision/update cadence, save/load,
and the replication policy before it could affect shipped terrain.
## L164 render-only Unreal implementation
`Source/VoxelForgeLivingPlane/` now implements this render-only path as a separate
runtime module. `AVoxelForgeLivingPlaneActor` owns two UAV-capable RGBA32F render
targets, advances the wave, breathing, and imprint equations with an RDG global shader,
and binds the current texture to its plane material. Blueprint/C++ supplies object
position, radius, velocity, push, and active state. The actor does not call the
generation modules and does not read the field back. Level setup and the material graph
are in [UE-SETUP.md](UE-SETUP.md).
+22
View File
@@ -0,0 +1,22 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
PLUGIN_ROOT="$(cd -- "${SCRIPT_DIR}/../.." && pwd)"
OUTPUT_DIR="${PLUGIN_ROOT}/Saved/Linux/l164"
CXX="${CXX:-g++}"
command -v "${CXX}" >/dev/null || { echo "C++ compiler not found: ${CXX}" >&2; exit 2; }
pkg-config --exists egl gl libpng || {
echo "EGL, desktop OpenGL, and libpng development files are required" >&2
exit 2
}
mkdir -p "${OUTPUT_DIR}"
read -r -a GRAPHICS_FLAGS <<<"$(pkg-config --cflags --libs egl gl libpng)"
taskset -c 0-11 prlimit --as=6442450944 -- \
"${CXX}" -std=c++20 -O2 -g -Wall -Wextra -Wpedantic \
"${SCRIPT_DIR}/LivingPlane.cpp" -o "${OUTPUT_DIR}/living-plane" \
"${GRAPHICS_FLAGS[@]}"
echo "built ${OUTPUT_DIR}/living-plane"
+132
View File
@@ -0,0 +1,132 @@
#!/usr/bin/env python3
"""Compare final field checksums across different query and capture settings."""
from __future__ import annotations
import argparse
import json
import pathlib
import shutil
import subprocess
import sys
PLUGIN_ROOT = pathlib.Path(__file__).resolve().parents[2]
def read_checksum(path: pathlib.Path) -> str:
try:
with path.open(encoding="utf-8") as source:
result = json.load(source)
except (OSError, json.JSONDecodeError) as error:
raise RuntimeError(f"could not read {path}: {error}") from error
checksum = result.get("field_checksum_fnv1a64")
if not isinstance(checksum, str) or len(checksum) != 16:
raise RuntimeError(f"{path} has no 16-digit field checksum")
return checksum
def compare(left: pathlib.Path, right: pathlib.Path) -> bool:
left_checksum = read_checksum(left)
right_checksum = read_checksum(right)
if left_checksum != right_checksum:
print(
"FAIL field checksum mismatch: "
f"{left}={left_checksum}, {right}={right_checksum}",
file=sys.stderr,
)
return False
print(f"PASS field checksum matches: {left_checksum}")
return True
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--binary",
type=pathlib.Path,
default=PLUGIN_ROOT / "Saved/Linux/l164/living-plane",
)
parser.add_argument(
"--out",
type=pathlib.Path,
default=PLUGIN_ROOT / "Saved/Linux/l164/repeatability",
)
parser.add_argument("--device", choices=("auto", "cpu", "gpu"), default="cpu")
parser.add_argument("--size", type=int, default=512)
parser.add_argument("--steps", type=int, default=1800)
parser.add_argument(
"--compare-only",
nargs=2,
type=pathlib.Path,
metavar=("LEFT_JSON", "RIGHT_JSON"),
help="compare existing timing.json files; used to exercise the failure path",
)
options = parser.parse_args()
if options.compare_only:
return 0 if compare(*options.compare_only) else 1
if not options.binary.is_file():
parser.error(f"binary not found: {options.binary}; run Experiments/LivingPlane/build.sh")
options.out.mkdir(parents=True, exist_ok=True)
off_dir = options.out / "queries-off_capture-off"
on_dir = options.out / "queries-on_capture-every-900"
for directory in (off_dir, on_dir):
if directory.exists():
shutil.rmtree(directory)
common = [
f"--device={options.device}",
"--scene=alive",
f"--size={options.size}",
f"--steps={options.steps}",
]
runs = (
("--capture-every=0", "--queries=off", off_dir),
("--capture-every=900", "--queries=on", on_dir),
)
for capture, queries, output in runs:
command = [
"taskset",
"-c",
"0-11",
"prlimit",
"--as=6442450944",
"--",
"timeout",
"1740s",
str(options.binary.resolve()),
*common,
capture,
queries,
f"--out={output}",
]
print("RUN " + " ".join(command), flush=True)
log_path = options.out / f"{output.name}.log"
with log_path.open("w", encoding="utf-8") as log_file:
try:
subprocess.run(
command,
cwd=PLUGIN_ROOT,
check=True,
stdout=log_file,
stderr=subprocess.STDOUT,
)
except subprocess.CalledProcessError:
print(log_path.read_text(encoding="utf-8"), file=sys.stderr)
raise
print(log_path.read_text(encoding="utf-8").rstrip())
left = off_dir / "timing.json"
right = on_dir / "timing.json"
return 0 if compare(left, right) else 1
if __name__ == "__main__":
try:
raise SystemExit(main())
except (OSError, RuntimeError, subprocess.CalledProcessError) as error:
print(f"FAIL repeatability check: {error}", file=sys.stderr)
raise SystemExit(1)
+85
View File
@@ -0,0 +1,85 @@
Texture2D<float4> SourceState;
RWTexture2D<float4> TargetState;
int2 GridSize;
float Dt;
float Dx;
float PlaneLength;
float WaveSpeed;
float Damping;
float FoundationStiffness;
float TimeSeconds;
float2 BreathingSettings;
float4 ImprintSettings;
float4 ObjectPositionRadius[2];
float4 ObjectVelocityPush[2];
[numthreads(8, 8, 1)]
void MainCS(uint3 DispatchThreadId : SV_DispatchThreadID)
{
int2 Pixel = int2(DispatchThreadId.xy);
if (Pixel.x >= GridSize.x || Pixel.y >= GridSize.y)
{
return;
}
int2 Lo = int2(0, 0);
int2 Hi = GridSize - 1;
float4 State = SourceState.Load(int3(Pixel, 0));
float Height = State.r;
float Velocity = State.g;
float Imprint = State.b;
float Left = SourceState.Load(int3(max(Pixel + int2(-1, 0), Lo), 0)).r;
float Right = SourceState.Load(int3(min(Pixel + int2(1, 0), Hi), 0)).r;
float Down = SourceState.Load(int3(max(Pixel + int2(0, -1), Lo), 0)).r;
float Up = SourceState.Load(int3(min(Pixel + int2(0, 1), Hi), 0)).r;
float Laplacian = (Left + Right + Down + Up - 4.0 * Height) / max(Dx * Dx, 1.0e-8);
float2 World = (float2(Pixel) + 0.5) * Dx - PlaneLength * 0.5;
float Interaction = 0.0;
float Contact = 0.0;
for (int ObjectIndex = 0; ObjectIndex < 2; ++ObjectIndex)
{
float4 PositionRadius = ObjectPositionRadius[ObjectIndex];
if (PositionRadius.w <= 0.0)
{
continue;
}
float4 VelocityPush = ObjectVelocityPush[ObjectIndex];
float Radius = PositionRadius.w;
float Sigma = max(Radius * (0.58 / 0.43), Dx);
float2 Offset = World - PositionRadius.xy;
float Weight = exp(-dot(Offset, Offset) / max(2.0 * Sigma * Sigma, 1.0e-8));
float HeightAboveContact = max(PositionRadius.z - Radius, 0.0);
float ContactHeight = 1.0 - smoothstep(0.0, max(Radius, Dx), HeightAboveContact);
float ImpactSpeed = length(VelocityPush.xy) * 0.25 + max(-VelocityPush.z, 0.0) * 0.5;
float Push = max(VelocityPush.w, 0.0) + ImpactSpeed;
Interaction -= Push * Weight * ContactHeight;
if (Push > 0.0)
{
Contact = max(Contact, Weight * ContactHeight);
}
}
float Breathing = 0.0;
if (BreathingSettings.x != 0.0)
{
float Swell = sin(World.x * 0.48 - TimeSeconds * 0.22)
* cos(World.y * 0.42 + TimeSeconds * 0.16);
Breathing = BreathingSettings.y * Swell * sin(TimeSeconds * 0.48);
}
float NextImprint = Imprint;
if (ImprintSettings.x != 0.0)
{
NextImprint = clamp(Imprint * exp(-Dt / max(ImprintSettings.z, 1.0e-4))
- Contact * ImprintSettings.y * Dt, -ImprintSettings.w, 0.0);
}
float FoundationTarget = ImprintSettings.x != 0.0 ? Imprint : 0.0;
float Acceleration = WaveSpeed * WaveSpeed * Laplacian
- FoundationStiffness * (Height - FoundationTarget) + Interaction + Breathing;
float NextVelocity = (Velocity + Acceleration * Dt) * exp(-Damping * Dt);
float NextHeight = Height + NextVelocity * Dt;
TargetState[Pixel] = float4(NextHeight, NextVelocity, NextImprint, 0.0);
}
@@ -322,6 +322,108 @@ bool FVoxelForgeClipmapNoOverlapTest::RunTest(const FString& Parameters)
FirstFailure.IsEmpty() ? TEXT("none") : *FirstFailure)); FirstFailure.IsEmpty() ? TEXT("none") : *FirstFailure));
TestEqual(TEXT("all swept desired sets have zero overlapping pairs"), OverlapCases, 0); TestEqual(TEXT("all swept desired sets have zero overlapping pairs"), OverlapCases, 0);
TestEqual(TEXT("all swept desired sets cover the legacy footprint"), CoverageFailures, 0); TestEqual(TEXT("all swept desired sets cover the legacy footprint"), CoverageFailures, 0);
static const FIntVector DeltaCenters[] = {
FIntVector(-5, -2, -1), FIntVector(-3, 1, 2), FIntVector(-1, -3, 0),
FIntVector(0, 0, 0), FIntVector(1, 3, -2), FIntVector(3, -1, 1),
FIntVector(5, 2, -3),
};
static const FIntVector DeltaSteps[] = {
FIntVector(-1, 0, 0), FIntVector(1, 0, 0),
FIntVector(0, -1, 0), FIntVector(0, 1, 0),
FIntVector(0, 0, -1), FIntVector(0, 0, 1),
};
int32 DeltaCases = 0;
int32 DeltaFailures = 0;
FString FirstDeltaFailure;
for (const FIntVector& Center : DeltaCenters)
for (const FSweepSettings& Sweep : SweepSettings)
for (int32 ClampCase = 0; ClampCase < 3; ++ClampCase)
{
FParameters P;
P.ClipRadius = Sweep.Radius;
P.MaxClipLevel = Sweep.MaxLevel;
P.RenderDistanceChunks = Sweep.RenderDistance;
const auto GetVerticalBounds = [&](int32 CenterZ, int32& OutMinZ, int32& OutMaxZ)
{
OutMinZ = MIN_int32;
OutMaxZ = MAX_int32;
if (ClampCase == 1)
{
OutMinZ = CenterZ - 2;
OutMaxZ = CenterZ + 3;
}
else if (ClampCase == 2)
{
const int32 Band = VoxelClipmapDesiredTiles::FloorDiv(
CenterZ, 1 << Sweep.MaxLevel);
const int64 BandStart = static_cast<int64>(Band)
* (int64{1} << static_cast<uint32>(Sweep.MaxLevel));
OutMinZ = static_cast<int32>(BandStart - 1);
OutMaxZ = OutMinZ + 6;
}
};
int32 PreviousMinZ = 0;
int32 PreviousMaxZ = 0;
GetVerticalBounds(Center.Z, PreviousMinZ, PreviousMaxZ);
TArray<FVoxelTileKey> PreviousTiles;
VoxelClipmapDesiredTiles::Build(Center, PreviousMinZ, PreviousMaxZ, P, PreviousTiles);
for (const FIntVector& Step : DeltaSteps)
{
const FIntVector NextCenter = Center + Step;
int32 NextMinZ = 0;
int32 NextMaxZ = 0;
GetVerticalBounds(NextCenter.Z, NextMinZ, NextMaxZ);
TArray<FVoxelTileKey> Entering;
TArray<FVoxelTileKey> Leaving;
VoxelClipmapDesiredTiles::BuildDelta(
Center, PreviousMinZ, PreviousMaxZ,
NextCenter, NextMinZ, NextMaxZ, P, Entering, Leaving);
TSet<FVoxelTileKey> Updated;
for (const FVoxelTileKey& Tile : PreviousTiles) Updated.Add(Tile);
for (const FVoxelTileKey& Tile : Leaving) Updated.Remove(Tile);
for (const FVoxelTileKey& Tile : Entering) Updated.Add(Tile);
TArray<FVoxelTileKey> FullNextTiles;
VoxelClipmapDesiredTiles::Build(
NextCenter, NextMinZ, NextMaxZ, P, FullNextTiles);
TSet<FVoxelTileKey> FullNextSet;
for (const FVoxelTileKey& Tile : FullNextTiles) FullNextSet.Add(Tile);
bool bMatches = Updated.Num() == FullNextSet.Num();
if (bMatches)
{
for (const FVoxelTileKey& Tile : Updated)
{
if (!FullNextSet.Contains(Tile))
{
bMatches = false;
break;
}
}
}
if (!bMatches)
{
++DeltaFailures;
if (FirstDeltaFailure.IsEmpty())
{
FirstDeltaFailure = FString::Printf(
TEXT("center=(%d,%d,%d) step=(%d,%d,%d) radius=%d level=%d clamp=%d entering=%d leaving=%d full=%d updated=%d"),
Center.X, Center.Y, Center.Z, Step.X, Step.Y, Step.Z,
Sweep.Radius, Sweep.MaxLevel, ClampCase,
Entering.Num(), Leaving.Num(), FullNextSet.Num(), Updated.Num());
}
}
++DeltaCases;
}
}
AddInfo(FString::Printf(
TEXT("clipmap delta cases=%d failures=%d first=%s"), DeltaCases, DeltaFailures,
FirstDeltaFailure.IsEmpty() ? TEXT("none") : *FirstDeltaFailure));
TestEqual(TEXT("clipmap delta update equals a full rebuild on every tested crossing"),
DeltaFailures, 0);
return true; return true;
} }
@@ -182,4 +182,218 @@ namespace VoxelClipmapDesiredTiles
PreviousRingMax = RingMax; PreviousRingMax = RingMax;
} }
} }
// Compute the exact key difference between two adjacent clipmap centers without visiting
// unchanged ring cells. A coarser ring can change membership when its finer child ring moves,
// even when the coarser ring bounds themselves stay fixed; the cover-box delta below includes
// those boundary keys as well.
FORCEINLINE void BuildDelta(
const FIntVector& PreviousCenter, int32 PreviousMinChunkZ, int32 PreviousMaxChunkZ,
const FIntVector& Center, int32 MinChunkZ, int32 MaxChunkZ,
const FParameters& Parameters,
TArray<FVoxelTileKey>& OutEntering, TArray<FVoxelTileKey>& OutLeaving)
{
struct FBox
{
FIntVector Min = FIntVector::ZeroValue;
FIntVector Max = FIntVector::ZeroValue;
bool bValid = false;
};
OutEntering.Reset();
OutLeaving.Reset();
const int32 Radius = FMath::Max(1, Parameters.ClipRadius);
const int32 MaxLevel = FMath::Clamp(Parameters.MaxClipLevel, 0, 8);
const int32 OuterRadius = OuterShellRadius(Parameters, Radius, MaxLevel);
FBox PreviousValid[9];
FBox CurrentValid[9];
FBox PreviousCovered[9];
FBox CurrentCovered[9];
const auto BuildRings = [&](const FIntVector& RingCenter, int32 RingMinChunkZ,
int32 RingMaxChunkZ, FBox* OutValid, FBox* OutCovered)
{
FIntVector ChildRingMin = FIntVector::ZeroValue;
FIntVector ChildRingMax = FIntVector::ZeroValue;
for (int32 Level = 0; Level <= MaxLevel; ++Level)
{
const int32 Pow = static_cast<int32>(uint32{1} << static_cast<uint32>(Level));
const FIntVector CenterLevel = FloorDiv(RingCenter, Pow);
const int32 LevelRadius = (Level == MaxLevel) ? OuterRadius : Radius;
const int32 RingAlignment = Level < MaxLevel ? 2 : 1;
FIntVector RingMin, RingMax;
AlignedRingBounds(CenterLevel, LevelRadius, RingAlignment, RingMin, RingMax);
FBox Valid;
Valid.Min = RingMin;
Valid.Max = RingMax;
Valid.Min.Z = FMath::Max(Valid.Min.Z, FloorDiv(RingMinChunkZ, Pow));
Valid.Max.Z = FMath::Min(Valid.Max.Z, FloorDiv(RingMaxChunkZ, Pow));
Valid.bValid = Valid.Min.X <= Valid.Max.X
&& Valid.Min.Y <= Valid.Max.Y && Valid.Min.Z <= Valid.Max.Z;
OutValid[Level] = Valid;
FBox Covered;
if (Level > 0)
{
Covered.Min = FIntVector(
CeilDiv(ChildRingMin.X, 2), CeilDiv(ChildRingMin.Y, 2),
CeilDiv(ChildRingMin.Z, 2));
Covered.Max = FIntVector(
ClampInt32(FloorDivWide(static_cast<int64>(ChildRingMax.X) - 1, 2)),
ClampInt32(FloorDivWide(static_cast<int64>(ChildRingMax.Y) - 1, 2)),
ClampInt32(FloorDivWide(static_cast<int64>(ChildRingMax.Z) - 1, 2)));
Covered.bValid = Covered.Min.X <= Covered.Max.X
&& Covered.Min.Y <= Covered.Max.Y && Covered.Min.Z <= Covered.Max.Z;
}
OutCovered[Level] = Covered;
ChildRingMin = RingMin;
ChildRingMax = RingMax;
}
};
BuildRings(PreviousCenter, PreviousMinChunkZ, PreviousMaxChunkZ,
PreviousValid, PreviousCovered);
BuildRings(Center, MinChunkZ, MaxChunkZ, CurrentValid, CurrentCovered);
const auto Intersect = [](const FBox& A, const FBox& B)
{
FBox Result;
if (!A.bValid || !B.bValid) return Result;
Result.Min = FIntVector(
FMath::Max(A.Min.X, B.Min.X), FMath::Max(A.Min.Y, B.Min.Y),
FMath::Max(A.Min.Z, B.Min.Z));
Result.Max = FIntVector(
FMath::Min(A.Max.X, B.Max.X), FMath::Min(A.Max.Y, B.Max.Y),
FMath::Min(A.Max.Z, B.Max.Z));
Result.bValid = Result.Min.X <= Result.Max.X
&& Result.Min.Y <= Result.Max.Y && Result.Min.Z <= Result.Max.Z;
return Result;
};
const auto AppendDifference = [&Intersect](const FBox& A, const FBox& B, auto& OutBoxes)
{
if (!A.bValid) return;
const FBox Common = Intersect(A, B);
if (!Common.bValid)
{
OutBoxes.Add(A);
return;
}
// Six disjoint slabs cover A minus Common. The later slabs are narrowed to the
// common extent on each already-partitioned axis, so no key is emitted twice.
FBox Slab = A;
if (A.Min.X < Common.Min.X)
{
Slab.Max.X = Common.Min.X - 1;
Slab.bValid = Slab.Min.X <= Slab.Max.X;
if (Slab.bValid) OutBoxes.Add(Slab);
}
Slab = A;
if (Common.Max.X < A.Max.X)
{
Slab.Min.X = Common.Max.X + 1;
Slab.bValid = Slab.Min.X <= Slab.Max.X;
if (Slab.bValid) OutBoxes.Add(Slab);
}
Slab = A;
Slab.Min.X = Common.Min.X;
Slab.Max.X = Common.Max.X;
if (A.Min.Y < Common.Min.Y)
{
Slab.Max.Y = Common.Min.Y - 1;
Slab.bValid = Slab.Min.Y <= Slab.Max.Y;
if (Slab.bValid) OutBoxes.Add(Slab);
}
Slab = A;
Slab.Min.X = Common.Min.X;
Slab.Max.X = Common.Max.X;
if (Common.Max.Y < A.Max.Y)
{
Slab.Min.Y = Common.Max.Y + 1;
Slab.bValid = Slab.Min.Y <= Slab.Max.Y;
if (Slab.bValid) OutBoxes.Add(Slab);
}
Slab = A;
Slab.Min.X = Common.Min.X;
Slab.Max.X = Common.Max.X;
Slab.Min.Y = Common.Min.Y;
Slab.Max.Y = Common.Max.Y;
if (A.Min.Z < Common.Min.Z)
{
Slab.Max.Z = Common.Min.Z - 1;
Slab.bValid = Slab.Min.Z <= Slab.Max.Z;
if (Slab.bValid) OutBoxes.Add(Slab);
}
Slab = A;
Slab.Min.X = Common.Min.X;
Slab.Max.X = Common.Max.X;
Slab.Min.Y = Common.Min.Y;
Slab.Max.Y = Common.Max.Y;
if (Common.Max.Z < A.Max.Z)
{
Slab.Min.Z = Common.Max.Z + 1;
Slab.bValid = Slab.Min.Z <= Slab.Max.Z;
if (Slab.bValid) OutBoxes.Add(Slab);
}
};
const auto Contains = [](const FBox& Box, const FIntVector& Point)
{
return Box.bValid && Point.X >= Box.Min.X && Point.X <= Box.Max.X
&& Point.Y >= Box.Min.Y && Point.Y <= Box.Max.Y
&& Point.Z >= Box.Min.Z && Point.Z <= Box.Max.Z;
};
const auto ForEachBox = [](const FBox& Box, const auto& Callback)
{
if (!Box.bValid) return;
for (int64 Z = Box.Min.Z; Z <= Box.Max.Z; ++Z)
for (int64 Y = Box.Min.Y; Y <= Box.Max.Y; ++Y)
for (int64 X = Box.Min.X; X <= Box.Max.X; ++X)
{
Callback(FIntVector(static_cast<int32>(X), static_cast<int32>(Y),
static_cast<int32>(Z)));
}
};
for (int32 Level = 0; Level <= MaxLevel; ++Level)
{
TArray<FBox, TInlineAllocator<16>> ChangedBoxes;
AppendDifference(CurrentValid[Level], PreviousValid[Level], ChangedBoxes);
AppendDifference(PreviousValid[Level], CurrentValid[Level], ChangedBoxes);
if (Level > 0)
{
const FBox CommonValid = Intersect(CurrentValid[Level], PreviousValid[Level]);
if (CommonValid.bValid)
{
TArray<FBox, TInlineAllocator<16>> CoverDifferences;
AppendDifference(CurrentCovered[Level], PreviousCovered[Level], CoverDifferences);
AppendDifference(PreviousCovered[Level], CurrentCovered[Level], CoverDifferences);
for (const FBox& CoverDifference : CoverDifferences)
{
const FBox ChangedCover = Intersect(CoverDifference, CommonValid);
if (ChangedCover.bValid) ChangedBoxes.Add(ChangedCover);
}
}
}
for (const FBox& ChangedBox : ChangedBoxes)
{
ForEachBox(ChangedBox, [&](const FIntVector& Coord)
{
const bool bCurrentMember = Contains(CurrentValid[Level], Coord)
&& !Contains(CurrentCovered[Level], Coord);
const bool bPreviousMember = Contains(PreviousValid[Level], Coord)
&& !Contains(PreviousCovered[Level], Coord);
if (bCurrentMember && !bPreviousMember)
{
OutEntering.Emplace(Coord, Level);
}
else if (bPreviousMember && !bCurrentMember)
{
OutLeaving.Emplace(Coord, Level);
}
});
}
}
}
} }
@@ -144,13 +144,15 @@ bool ConfigureCoreGeometryStratum(const FStrateSlot& Slot, ECaveGeneratorType Ge
return true; return true;
case EVoxelForgeCoreGeneratorRecipe::NativeCaves: case EVoxelForgeCoreGeneratorRecipe::NativeCaves:
if (GeneratorType != ECaveGeneratorType::TunnelNetwork if (GeneratorType != ECaveGeneratorType::TunnelNetwork
&& GeneratorType != ECaveGeneratorType::Underwater) && GeneratorType != ECaveGeneratorType::Underwater
&& GeneratorType != ECaveGeneratorType::VerticalShafts)
{ {
OutError = FString::Printf(TEXT("Strate %d selects NativeCaves but its generator archetype is not TunnelNetwork/Underwater."), OutError = FString::Printf(TEXT("Strate %d selects NativeCaves but its generator archetype is not TunnelNetwork/Underwater/VerticalShafts."),
Slot.StrateIndex); Slot.StrateIndex);
return false; return false;
} }
Item.generatorKind = "Caves"; Item.generatorKind = GeneratorType == ECaveGeneratorType::VerticalShafts
? "VerticalShafts" : "Caves";
Item.recipeId = "Core.Caves.PrimitiveField"; Item.recipeId = "Core.Caves.PrimitiveField";
Item.recipeVersion = 1; Item.recipeVersion = 1;
Item.boundDispatchPath = "Core native cave graph uses resolved room and tunnel records"; Item.boundDispatchPath = "Core native cave graph uses resolved room and tunnel records";
@@ -0,0 +1,28 @@
#pragma once
#include <algorithm>
namespace VoxelStreamingAdmissionOrder
{
template <typename Iterator, typename Less>
void StableSortCandidates(Iterator First, Iterator Last, Less Compare)
{
std::stable_sort(First, Last, Compare);
}
template <typename Range, typename IsAtCapacity, typename SubmitCandidate,
typename OnCapacityReached>
void AdmitInOrder(const Range& Candidates, IsAtCapacity AtCapacity,
SubmitCandidate Submit, OnCapacityReached CapacityReached)
{
for (const auto& Candidate : Candidates)
{
if (AtCapacity())
{
CapacityReached();
break;
}
Submit(Candidate);
}
}
}
File diff suppressed because it is too large. Load diff
+18 -1
View File
@@ -1247,6 +1247,8 @@ public:
bool bSampleStacksStarted = false; bool bSampleStacksStarted = false;
bool bStartupApplyBoostStarted = false; bool bStartupApplyBoostStarted = false;
bool bStartupApplyBoostEnded = false; bool bStartupApplyBoostEnded = false;
// Fast-travel admission stays disabled until the initial near LOD and collision set is ready.
bool bInitialArrivalSetSettled = false;
uint64 StartupApplyBoostStartCycles = 0; uint64 StartupApplyBoostStartCycles = 0;
// Trace the resolved startup budget once per world. / Tracer une fois le budget de demarrage resolu par monde. // Trace the resolved startup budget once per world. / Tracer une fois le budget de demarrage resolu par monde.
bool bStartupApplyConfigTraced = false; bool bStartupApplyConfigTraced = false;
@@ -1254,6 +1256,21 @@ public:
float StartupTraceSpeedCmPerSecond = 0.0f; float StartupTraceSpeedCmPerSecond = 0.0f;
bool bSpeedAwareTravelMode = false; bool bSpeedAwareTravelMode = false;
bool bSpeedAwareFastMode = false; bool bSpeedAwareFastMode = false;
TArray<FVoxelTileKey> CachedCoreDesiredTiles;
TSet<FVoxelTileKey> CachedCoreDesiredTileSet;
FIntVector CachedCoreDesiredCenter = FIntVector(MAX_int32, MAX_int32, MAX_int32);
bool bHasCachedCoreDesiredTiles = false;
TArray<FVoxelTileKey> CachedDesiredCandidateKeys;
TSet<FVoxelTileKey> CachedDesiredCandidateSet;
TSet<FVoxelTileKey> CachedDesiredBaseVisualTiles;
TSet<FVoxelTileKey> CachedDesiredOverlayTiles;
FIntVector CachedDesiredMaterializationCenter = FIntVector(MAX_int32, MAX_int32, MAX_int32);
bool bHasCachedDesiredMaterialization = false;
TArray<FVoxelTileKey> CachedDesiredPriorityOrder;
FIntVector CachedDesiredPriorityCenter = FIntVector(MAX_int32, MAX_int32, MAX_int32);
bool bHasCachedDesiredPriorityOrder = false;
int32 IncrementalDesiredCenterCrossings = 0;
int32 IncrementalDesiredMaterializationUpdates = 0;
TArray<FVoxelTileKey> DesiredSorted; // critical floor prefix, then true-pawn distance-first TArray<FVoxelTileKey> DesiredSorted; // critical floor prefix, then true-pawn distance-first
TArray<FVoxelTileKey> CriticalDesiredTiles; // occupied/support tile, then heading tile TArray<FVoxelTileKey> CriticalDesiredTiles; // occupied/support tile, then heading tile
// Ancestor -> desired descendants, rebuilt with DesiredSorted. Reconciliation uses this // Ancestor -> desired descendants, rebuilt with DesiredSorted. Reconciliation uses this
@@ -1602,7 +1619,7 @@ public:
// ONE frame → a game-thread teardown spike. Drained by ProcessUnloadQueue (catch-up scaled). // ONE frame → a game-thread teardown spike. Drained by ProcessUnloadQueue (catch-up scaled).
TSet<FVoxelTileKey> PendingUnload; TSet<FVoxelTileKey> PendingUnload;
void ProcessPendingChunks(); void ProcessPendingChunks(uint64 TickStartCycles);
void ProcessUnloadQueue(); // budgeted teardown drain (see PendingUnload) void ProcessUnloadQueue(); // budgeted teardown drain (see PendingUnload)
void QueuePendingUnload(const FVoxelTileKey& Tile); void QueuePendingUnload(const FVoxelTileKey& Tile);
void HideOverlappingDesiredVisuals(const FVoxelTileKey& RetiredTile); void HideOverlappingDesiredVisuals(const FVoxelTileKey& RetiredTile);
@@ -0,0 +1,303 @@
#include "VoxelForgeLivingPlaneActor.h"
#include "Engine/TextureRenderTarget2D.h"
#include "Engine/TextureRenderTarget.h"
#include "Engine/StaticMesh.h"
#include "Engine/World.h"
#include "Materials/MaterialInstanceDynamic.h"
#include "Materials/MaterialInterface.h"
#include "RenderGraphBuilder.h"
#include "RenderGraphUtils.h"
#include "RHICommandList.h"
#include "RHIResources.h"
#include "DataDrivenShaderPlatformInfo.h"
#include "GlobalShader.h"
#include "ShaderParameterStruct.h"
#include "UObject/ConstructorHelpers.h"
#include "Components/StaticMeshComponent.h"
namespace
{
constexpr int32 LivingPlaneThreadGroupSize = 8;
struct FLivingPlaneStepInput
{
FVector4f ObjectPositionRadius[2] = {FVector4f(0, 0, 0, -1), FVector4f(0, 0, 0, -1)};
FVector4f ObjectVelocityPush[2] = {FVector4f(0, 0, 0, 0), FVector4f(0, 0, 0, 0)};
float TimeSeconds = 0.0f;
};
class FVFLivingPlaneStepCS final : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(FVFLivingPlaneStepCS);
SHADER_USE_PARAMETER_STRUCT(FVFLivingPlaneStepCS, FGlobalShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER(FIntPoint, GridSize)
SHADER_PARAMETER(float, Dt)
SHADER_PARAMETER(float, Dx)
SHADER_PARAMETER(float, PlaneLength)
SHADER_PARAMETER(float, WaveSpeed)
SHADER_PARAMETER(float, Damping)
SHADER_PARAMETER(float, FoundationStiffness)
SHADER_PARAMETER(float, TimeSeconds)
SHADER_PARAMETER(FVector2f, BreathingSettings)
SHADER_PARAMETER(FVector4f, ImprintSettings)
SHADER_PARAMETER_ARRAY(FVector4f, ObjectPositionRadius, [2])
SHADER_PARAMETER_ARRAY(FVector4f, ObjectVelocityPush, [2])
SHADER_PARAMETER_RDG_TEXTURE_SRV(Texture2D<float4>, SourceState)
SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D<float4>, TargetState)
END_SHADER_PARAMETER_STRUCT()
public:
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5);
}
};
IMPLEMENT_GLOBAL_SHADER(FVFLivingPlaneStepCS,
"/Plugin/VoxelForgeLivingPlane/LivingPlane.usf", "MainCS", SF_Compute)
UTextureRenderTarget2D* CreateFieldTarget(UObject* Outer, int32 Size, FName Name)
{
UTextureRenderTarget2D* Target = NewObject<UTextureRenderTarget2D>(Outer, Name, RF_Transient);
Target->RenderTargetFormat = RTF_RGBA32f;
Target->bCanCreateUAV = true;
Target->bAutoGenerateMips = false;
Target->ClearColor = FLinearColor::Transparent;
Target->Filter = TF_Bilinear;
Target->AddressX = TA_Clamp;
Target->AddressY = TA_Clamp;
Target->InitAutoFormat(Size, Size);
Target->UpdateResourceImmediate(true);
return Target;
}
}
AVoxelForgeLivingPlaneActor::AVoxelForgeLivingPlaneActor()
{
PrimaryActorTick.bCanEverTick = true;
PlaneMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("LivingPlaneMesh"));
SetRootComponent(PlaneMesh);
PlaneMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
PlaneMesh->SetMobility(EComponentMobility::Movable);
static ConstructorHelpers::FObjectFinder<UStaticMesh> PlaneAsset(
TEXT("/Engine/BasicShapes/Plane.Plane"));
if (PlaneAsset.Succeeded())
{
PlaneMesh->SetStaticMesh(PlaneAsset.Object);
}
ObjectInputs.SetNum(2);
}
void AVoxelForgeLivingPlaneActor::OnConstruction(const FTransform& Transform)
{
Super::OnConstruction(Transform);
if (PlaneMesh)
{
// /Engine/BasicShapes/Plane is 100 cm square; one Unreal scale unit
// therefore corresponds to one metre of authored plane length.
PlaneMesh->SetRelativeScale3D(FVector(PlaneLengthMeters, PlaneLengthMeters, 1.0));
}
}
float AVoxelForgeLivingPlaneActor::GetStableStepSeconds() const
{
const float Dx = PlaneLengthMeters / static_cast<float>(FMath::Max(GridSize, 16));
const float EigenFrequencySquared = 8.0f * WaveSpeedMetersPerSecond * WaveSpeedMetersPerSecond
/ FMath::Max(Dx * Dx, 1.0e-8f) + FoundationStiffnessPerSecondSquared;
return 1.30f / FMath::Sqrt(FMath::Max(EigenFrequencySquared, 1.0e-8f));
}
void AVoxelForgeLivingPlaneActor::BeginPlay()
{
Super::BeginPlay();
GridSize = FMath::Clamp(GridSize, 16, 2048);
PlaneLengthMeters = FMath::Max(PlaneLengthMeters, 1.0f);
FixedStepSeconds = GetStableStepSeconds();
CreateFieldTargets();
if (PlaneMaterial && PlaneMesh)
{
DynamicPlaneMaterial = PlaneMesh->CreateDynamicMaterialInstance(0, PlaneMaterial);
if (DynamicPlaneMaterial)
{
DynamicPlaneMaterial->SetScalarParameterValue(TEXT("PlaneLengthMeters"), PlaneLengthMeters);
DynamicPlaneMaterial->SetScalarParameterValue(TEXT("FieldSize"), static_cast<float>(GridSize));
}
}
UpdateMaterialField();
}
void AVoxelForgeLivingPlaneActor::CreateFieldTargets()
{
FieldTargets[0] = CreateFieldTarget(this, GridSize, TEXT("LivingPlaneStateA"));
FieldTargets[1] = CreateFieldTarget(this, GridSize, TEXT("LivingPlaneStateB"));
CurrentFieldIndex = 0;
SimulationStepCount = 0;
SimulationAccumulatorSeconds = 0.0;
}
void AVoxelForgeLivingPlaneActor::SetObjectState(int32 ObjectIndex,
const FVFLivingPlaneObjectInput& State)
{
if (!ObjectInputs.IsValidIndex(ObjectIndex) || ObjectIndex >= 2)
{
return;
}
ObjectInputs[ObjectIndex] = State;
}
void AVoxelForgeLivingPlaneActor::ResetSimulation()
{
if (!IsInGameThread() || !FieldTargets[0] || !FieldTargets[1])
{
return;
}
CreateFieldTargets();
FixedStepSeconds = GetStableStepSeconds();
UpdateMaterialField();
}
void AVoxelForgeLivingPlaneActor::UpdateMaterialField()
{
if (DynamicPlaneMaterial && FieldTargets[CurrentFieldIndex])
{
DynamicPlaneMaterial->SetTextureParameterValue(TEXT("LivingField"), FieldTargets[CurrentFieldIndex]);
}
}
void AVoxelForgeLivingPlaneActor::EnqueueSimulationSteps(int32 NumSteps)
{
if (NumSteps <= 0 || !FieldTargets[0] || !FieldTargets[1])
{
return;
}
FTextureRenderTargetResource* ResourceA = FieldTargets[0]->GameThread_GetRenderTargetResource();
FTextureRenderTargetResource* ResourceB = FieldTargets[1]->GameThread_GetRenderTargetResource();
if (!ResourceA || !ResourceB)
{
return;
}
TArray<FLivingPlaneStepInput> Steps;
Steps.Reserve(NumSteps);
const FTransform PlaneTransform = GetActorTransform();
for (int32 Step = 0; Step < NumSteps; ++Step)
{
FLivingPlaneStepInput& Input = Steps.AddDefaulted_GetRef();
Input.TimeSeconds = static_cast<float>(SimulationStepCount + Step) * FixedStepSeconds;
for (int32 ObjectIndex = 0; ObjectIndex < 2; ++ObjectIndex)
{
if (!ObjectInputs.IsValidIndex(ObjectIndex) || !ObjectInputs[ObjectIndex].bActive)
{
continue;
}
const FVFLivingPlaneObjectInput& Object = ObjectInputs[ObjectIndex];
const FVector LocalPosition = PlaneTransform.InverseTransformPosition(Object.WorldPositionCm);
const FVector LocalVelocity = PlaneTransform.InverseTransformVector(Object.WorldVelocityCmPerSecond);
const float RadiusMeters = FMath::Max(Object.RadiusCm * 0.01f, 0.001f);
Input.ObjectPositionRadius[ObjectIndex] = FVector4f(
static_cast<float>(LocalPosition.X * 0.01),
static_cast<float>(LocalPosition.Y * 0.01),
static_cast<float>(LocalPosition.Z * 0.01),
RadiusMeters);
Input.ObjectVelocityPush[ObjectIndex] = FVector4f(
static_cast<float>(LocalVelocity.X * 0.01),
static_cast<float>(LocalVelocity.Y * 0.01),
static_cast<float>(LocalVelocity.Z * 0.01),
FMath::Max(Object.PushAccelerationMetersPerSecondSquared, 0.0f));
}
}
const int32 Resolution = GridSize;
const int32 InitialFieldIndex = CurrentFieldIndex;
const float Dt = static_cast<float>(FixedStepSeconds);
const float Dx = PlaneLengthMeters / static_cast<float>(Resolution);
const float PlaneLength = PlaneLengthMeters;
const float WaveSpeed = WaveSpeedMetersPerSecond;
const float Damping = DampingPerSecond;
const float Foundation = FoundationStiffnessPerSecondSquared;
const float BreathingStrength = bEnableBreathing ? BreathingStrengthMetersPerSecondSquared : 0.0f;
const FVector2f BreathingSettings(bEnableBreathing ? 1.0f : 0.0f, BreathingStrength);
const FVector4f ImprintSettings(bEnableImprint ? 1.0f : 0.0f,
ImprintCaptureMetersPerSecond, FMath::Max(ImprintRecoverySeconds, 1.0e-4f),
FMath::Max(ImprintMaxDepthMeters, 0.0f));
ENQUEUE_RENDER_COMMAND(VoxelForgeLivingPlaneStep)(
[ResourceA, ResourceB, Steps = MoveTemp(Steps), InitialFieldIndex, Resolution, Dt, Dx, PlaneLength,
WaveSpeed, Damping, Foundation, BreathingSettings, ImprintSettings]
(FRHICommandListImmediate& RHICmdList) mutable
{
FRDGBuilder GraphBuilder(RHICmdList, RDG_EVENT_NAME("VoxelForgeLivingPlane"));
FRDGTextureRef Fields[2] = {
RegisterExternalTexture(GraphBuilder, ResourceA->GetRenderTargetTexture().GetReference(), TEXT("LivingPlaneStateA")),
RegisterExternalTexture(GraphBuilder, ResourceB->GetRenderTargetTexture().GetReference(), TEXT("LivingPlaneStateB"))};
TShaderMapRef<FVFLivingPlaneStepCS> ComputeShader(GetGlobalShaderMap(GMaxRHIFeatureLevel));
int32 CurrentIndex = InitialFieldIndex;
for (const FLivingPlaneStepInput& StepInput : Steps)
{
const int32 TargetIndex = 1 - CurrentIndex;
FVFLivingPlaneStepCS::FParameters* PassParameters =
GraphBuilder.AllocParameters<FVFLivingPlaneStepCS::FParameters>();
PassParameters->GridSize = FIntPoint(Resolution, Resolution);
PassParameters->Dt = Dt;
PassParameters->Dx = Dx;
PassParameters->PlaneLength = PlaneLength;
PassParameters->WaveSpeed = WaveSpeed;
PassParameters->Damping = Damping;
PassParameters->FoundationStiffness = Foundation;
PassParameters->TimeSeconds = StepInput.TimeSeconds;
PassParameters->BreathingSettings = BreathingSettings;
PassParameters->ImprintSettings = ImprintSettings;
for (int32 ObjectIndex = 0; ObjectIndex < 2; ++ObjectIndex)
{
PassParameters->ObjectPositionRadius[ObjectIndex] = StepInput.ObjectPositionRadius[ObjectIndex];
PassParameters->ObjectVelocityPush[ObjectIndex] = StepInput.ObjectVelocityPush[ObjectIndex];
}
PassParameters->SourceState = GraphBuilder.CreateSRV(Fields[CurrentIndex]);
PassParameters->TargetState = GraphBuilder.CreateUAV(Fields[TargetIndex]);
FComputeShaderUtils::AddPass(GraphBuilder,
RDG_EVENT_NAME("LivingPlaneStep"),
ComputeShader, PassParameters,
FIntVector(FMath::DivideAndRoundUp(Resolution, LivingPlaneThreadGroupSize),
FMath::DivideAndRoundUp(Resolution, LivingPlaneThreadGroupSize), 1));
CurrentIndex = TargetIndex;
}
GraphBuilder.Execute();
});
CurrentFieldIndex = (InitialFieldIndex + NumSteps) & 1;
SimulationStepCount += NumSteps;
UpdateMaterialField();
}
void AVoxelForgeLivingPlaneActor::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);
if (FixedStepSeconds <= 0.0 || !FieldTargets[0] || !FieldTargets[1])
{
return;
}
SimulationAccumulatorSeconds += FMath::Max(static_cast<double>(DeltaSeconds), 0.0);
const int32 StepsToRun = FMath::Min(
static_cast<int32>(SimulationAccumulatorSeconds / FixedStepSeconds), 32);
if (StepsToRun > 0)
{
SimulationAccumulatorSeconds -= StepsToRun * FixedStepSeconds;
EnqueueSimulationSteps(StepsToRun);
}
}
void AVoxelForgeLivingPlaneActor::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
Super::EndPlay(EndPlayReason);
DynamicPlaneMaterial = nullptr;
FieldTargets[0] = nullptr;
FieldTargets[1] = nullptr;
}
@@ -0,0 +1,25 @@
#include "Modules/ModuleManager.h"
#include "Interfaces/IPluginManager.h"
#include "Misc/Paths.h"
#include "ShaderCore.h"
class FVoxelForgeLivingPlaneModule final : public IModuleInterface
{
public:
virtual void StartupModule() override
{
const TSharedPtr<IPlugin> Plugin = IPluginManager::Get().FindPlugin(TEXT("VoxelForge"));
if (!Plugin.IsValid())
{
UE_LOG(LogTemp, Error, TEXT("VoxelForgeLivingPlane could not find its owning plugin."));
return;
}
AddShaderSourceDirectoryMapping(
TEXT("/Plugin/VoxelForgeLivingPlane"),
FPaths::Combine(Plugin->GetBaseDir(), TEXT("Shaders")));
}
};
IMPLEMENT_MODULE(FVoxelForgeLivingPlaneModule, VoxelForgeLivingPlane)
@@ -0,0 +1,114 @@
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "VoxelForgeLivingPlaneActor.generated.h"
class UMaterialInterface;
class UMaterialInstanceDynamic;
class UStaticMeshComponent;
class UTextureRenderTarget2D;
USTRUCT(BlueprintType)
struct FVFLivingPlaneObjectInput
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Living Plane|Object")
FVector WorldPositionCm = FVector(0.0, 0.0, 43.0);
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Living Plane|Object")
FVector WorldVelocityCmPerSecond = FVector::ZeroVector;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Living Plane|Object", meta=(ClampMin="0.1"))
float RadiusCm = 43.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Living Plane|Object", meta=(ClampMin="0.0"))
float PushAccelerationMetersPerSecondSquared = 36.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Living Plane|Object")
bool bActive = false;
};
/** A render-only, GPU-stepped heightfield plane. Object state is uploaded; the field is never read back. */
UCLASS(BlueprintType, Blueprintable)
class VOXELFORGELIVINGPLANE_API AVoxelForgeLivingPlaneActor : public AActor
{
GENERATED_BODY()
public:
AVoxelForgeLivingPlaneActor();
virtual void BeginPlay() override;
virtual void Tick(float DeltaSeconds) override;
virtual void OnConstruction(const FTransform& Transform) override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
UFUNCTION(BlueprintCallable, Category="Living Plane|Object")
void SetObjectState(int32 ObjectIndex, const FVFLivingPlaneObjectInput& State);
UFUNCTION(BlueprintCallable, Category="Living Plane")
void ResetSimulation();
UFUNCTION(BlueprintPure, Category="Living Plane")
int64 GetSimulationStepCount() const { return SimulationStepCount; }
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Living Plane")
TObjectPtr<UStaticMeshComponent> PlaneMesh;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Living Plane|Rendering")
TObjectPtr<UMaterialInterface> PlaneMaterial;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Living Plane|Simulation", meta=(ClampMin="16", ClampMax="2048"))
int32 GridSize = 256;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Living Plane|Simulation", meta=(ClampMin="1.0", ClampMax="100.0"))
float PlaneLengthMeters = 12.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Living Plane|Simulation", meta=(ClampMin="0.0"))
float WaveSpeedMetersPerSecond = 4.5f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Living Plane|Simulation", meta=(ClampMin="0.0"))
float DampingPerSecond = 1.35f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Living Plane|Simulation", meta=(ClampMin="0.0"))
float FoundationStiffnessPerSecondSquared = 28.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Living Plane|Simulation")
bool bEnableBreathing = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Living Plane|Simulation", meta=(ClampMin="0.0", ClampMax="30.0"))
float BreathingStrengthMetersPerSecondSquared = 4.8f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Living Plane|Imprint")
bool bEnableImprint = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Living Plane|Imprint", meta=(ClampMin="0.0", ClampMax="1.0"))
float ImprintCaptureMetersPerSecond = 0.15f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Living Plane|Imprint", meta=(ClampMin="0.1", ClampMax="60.0"))
float ImprintRecoverySeconds = 6.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Living Plane|Imprint", meta=(ClampMin="0.0", ClampMax="1.0"))
float ImprintMaxDepthMeters = 0.34f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Living Plane|Object")
TArray<FVFLivingPlaneObjectInput> ObjectInputs;
private:
void CreateFieldTargets();
void EnqueueSimulationSteps(int32 NumSteps);
void UpdateMaterialField();
float GetStableStepSeconds() const;
UPROPERTY(Transient)
TObjectPtr<UTextureRenderTarget2D> FieldTargets[2];
UPROPERTY(Transient)
TObjectPtr<UMaterialInstanceDynamic> DynamicPlaneMaterial;
int32 CurrentFieldIndex = 0;
int64 SimulationStepCount = 0;
double SimulationAccumulatorSeconds = 0.0;
double FixedStepSeconds = 0.0;
};
@@ -0,0 +1,28 @@
using UnrealBuildTool;
public class VoxelForgeLivingPlane : ModuleRules
{
public VoxelForgeLivingPlane(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
if (Target.Platform == UnrealTargetPlatform.Linux)
{
bUseUnity = false;
}
PublicDependencyModuleNames.AddRange(new string[]
{
"Core",
"CoreUObject",
"Engine"
});
PrivateDependencyModuleNames.AddRange(new string[]
{
"Projects",
"RenderCore",
"RHI"
});
}
}
@@ -119,6 +119,48 @@ namespace VoxelForgeCore::PassageSpline
return std::lerp(plannedFloor,receivingFloor,blend); return std::lerp(plannedFloor,receivingFloor,blend);
} }
inline void BlendEndpointFloorToRoute(std::span<const FPoint> controls,
std::vector<double>& floorProfile,std::size_t endpoint,
std::span<const std::array<double,3>> receivingRoute,double unitsPerMetre,
double playerCenterAboveFloorMetres=1.13,double maximumGradeDegrees=12.0)
{
if(controls.size()!=floorProfile.size()||endpoint>=floorProfile.size()
||receivingRoute.empty()||!(unitsPerMetre>0.0)
||!(maximumGradeDegrees>0.0&&maximumGradeDegrees<45.0))return;
const double maximumGrade=std::tan(maximumGradeDegrees
*3.14159265358979323846/180.0);
const double routeReach=controls[endpoint][3]/unitsPerMetre+0.5;
constexpr double MouthProfileBlendDistanceMetres=10.0;
double routeHeight=0.0,routeDistanceSquared=0.0;
if(!ClosestRouteHeightAtXY(receivingRoute,controls[endpoint][0],
controls[endpoint][1],routeHeight,routeDistanceSquared)
||routeDistanceSquared>std::pow(routeReach*unitsPerMetre,2.0))return;
floorProfile[endpoint]=routeHeight
-playerCenterAboveFloorMetres*unitsPerMetre;
double pathDistance=0.0;
for(std::size_t step=1;step<controls.size();++step)
{
if(endpoint>0&&step>endpoint)break;
const std::size_t index=endpoint==0?step:endpoint-step;
const std::size_t previous=endpoint==0?index-1:index+1;
const double dx=(controls[index][0]-controls[previous][0])/unitsPerMetre;
const double dy=(controls[index][1]-controls[previous][1])/unitsPerMetre;
const double horizontal=std::hypot(dx,dy);
pathDistance+=horizontal;
double localRouteHeight=0.0,localRouteDistanceSquared=0.0;
const bool followsRoute=pathDistance<=MouthProfileBlendDistanceMetres
&&ClosestRouteHeightAtXY(receivingRoute,
controls[index][0],controls[index][1],localRouteHeight,localRouteDistanceSquared)
&&localRouteDistanceSquared<=std::pow(routeReach*unitsPerMetre,2.0);
double target=floorProfile[index];
if(followsRoute)
target=localRouteHeight-playerCenterAboveFloorMetres*unitsPerMetre;
const double maximumStep=maximumGrade*horizontal*unitsPerMetre;
floorProfile[index]=std::clamp(target,
floorProfile[previous]-maximumStep,floorProfile[previous]+maximumStep);
}
}
inline std::vector<FSegment> BuildSegments(std::span<const FPoint> controls, inline std::vector<FSegment> BuildSegments(std::span<const FPoint> controls,
std::size_t spanCount,double spacingMetres,double unitsPerMetre=1.0, std::size_t spanCount,double spacingMetres,double unitsPerMetre=1.0,
std::span<const double> plannedFloorProfile={}, std::span<const double> plannedFloorProfile={},
@@ -138,6 +138,9 @@ namespace VoxelForgeCore
bool passageComposerProfiled = false; bool passageComposerProfiled = false;
std::uint64_t passageTileFeatureCount = 0; std::uint64_t passageTileFeatureCount = 0;
std::uint64_t passageTileRouteSegmentCandidates = 0; std::uint64_t passageTileRouteSegmentCandidates = 0;
std::uint64_t nativeCaveFieldSamples = 0;
std::uint64_t cavernRoomCandidates = 0;
std::uint64_t cavernEdgeCandidates = 0;
std::uint64_t resolvedBoundaryBandQueries = 0; std::uint64_t resolvedBoundaryBandQueries = 0;
std::uint64_t passageComposerQueries = 0; std::uint64_t passageComposerQueries = 0;
std::uint64_t passageIterations = 0; std::uint64_t passageIterations = 0;
@@ -494,7 +497,22 @@ namespace VoxelForgeCore
// Version 223 projects the floor onto one continuous arc-length profile and groups // Version 223 projects the floor onto one continuous arc-length profile and groups
// dense passage samples into coarse tile candidates. Version 224 keeps the same // dense passage samples into coarse tile candidates. Version 224 keeps the same
// continuous floor while relaxing the spline chord tolerance and bounding shell work. // continuous floor while relaxing the spline chord tolerance and bounding shell work.
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 225; // Version 226 unifies the passage influence reach in metres and converts it to core
// voxels for every broadphase and proof bound. Version 227 uses the exact smooth-max
// support width/6 when deriving that reach. Version 228 composes deterministic cave
// graphs from fixed passage mouths and the owner Tunnel Network settings. Version 229
// budgets cave floor support; 230 records a shared backbone floor profile and joint cap;
// 231 carries passage tangents and floor slopes through cave portal lead-ins and keeps
// floor backing local to the walk footprint, with depth measured along the floor normal;
// 233 bounds passage ring jitter to the authored placement disk; 234 aligns route mouth
// anchors with capsule foot height and carries support through the SurfaceWorld mouth;
// 235 plans the cave backbone as a sampled curve with both passage mouth tangents and
// preserves open cave lanes through passage shell overlaps; 236 binds passage mouths to
// the planned graph route, preserves its walk corridor through shell overlaps, and keeps
// boundary-blended host rock from widening the authored passage wall.
// v238 plans one resolved passage spline with explicit landing chambers.
// v239 plans their floor planes as the passage's continuous floor profile.
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 239;
enum class FieldChannel : std::uint32_t enum class FieldChannel : std::uint32_t
{ {
@@ -594,6 +612,62 @@ namespace VoxelForgeCore
} }
} }
inline constexpr std::string_view CoreLiminalFieldRecipeIdV1 = "Core.Liminal.Field";
inline constexpr std::uint32_t CoreLiminalFieldRecipeVersionV1 = 1;
inline constexpr std::uint32_t CoreLiminalFieldRecipeVersionV2 = 2;
inline constexpr std::uint32_t CoreLiminalFieldVariantCountV1 = 4;
enum class WorldAssemblyLiminalVariantV1 : std::uint32_t
{
flatFieldMonolith = 0,
repeatingHills = 1,
roadOverCloudFloor = 2,
pillarGrid = 3
};
struct WorldAssemblyLiminalVariantDefinitionV1
{
WorldAssemblyLiminalVariantV1 variant = WorldAssemblyLiminalVariantV1::flatFieldMonolith;
std::string_view name;
WorldAssemblySurfaceProfileKindV1 surfaceKind = WorldAssemblySurfaceProfileKindV1::flatField;
std::int64_t hillAmplitudeMicrometres = 0;
std::int64_t hillWavelengthMicrometres = 0;
std::int64_t routeHalfWidthMicrometres = 0;
std::int64_t routeHalfThicknessMicrometres = 0;
std::int64_t supportSpacingMicrometres = 0;
std::int64_t supportRadiusMicrometres = 0;
std::int64_t monolithRadiusMicrometres = 0;
std::int64_t monolithHeightMicrometres = 0;
std::uint32_t pillarRows = 0;
std::uint32_t pillarColumns = 0;
std::int64_t pillarSpacingMicrometres = 0;
std::int64_t pillarRadiusMicrometres = 0;
std::int64_t pillarHeightMicrometres = 0;
};
inline constexpr std::array<WorldAssemblyLiminalVariantDefinitionV1,
CoreLiminalFieldVariantCountV1> CoreLiminalFieldVariantsV1{{
{WorldAssemblyLiminalVariantV1::flatFieldMonolith, "flatField-monolith",
WorldAssemblySurfaceProfileKindV1::flatField, 0, 0, 1750000, 500000,
36000000, 550000, 1600000, 26000000, 0, 0, 0, 0, 0},
{WorldAssemblyLiminalVariantV1::repeatingHills, "periodicHills",
WorldAssemblySurfaceProfileKindV1::periodicHills, 2200000, 80000000,
1750000, 500000, 36000000, 550000, 0, 0, 0, 0, 0, 0, 0},
{WorldAssemblyLiminalVariantV1::roadOverCloudFloor, "road-over-cloud-floor",
WorldAssemblySurfaceProfileKindV1::flatField, 0, 0, 2000000, 500000,
24000000, 550000, 0, 0, 0, 0, 0, 0, 0},
{WorldAssemblyLiminalVariantV1::pillarGrid, "pillar-grid",
WorldAssemblySurfaceProfileKindV1::flatField, 0, 0, 1750000, 500000,
36000000, 550000, 0, 0, 15, 15, 76000000, 5500000, 40000000}
}};
inline constexpr WorldAssemblyLiminalVariantV1 SelectCoreLiminalFieldVariantV1(
std::uint32_t seed) noexcept
{
return static_cast<WorldAssemblyLiminalVariantV1>(
seed % CoreLiminalFieldVariantCountV1);
}
struct WorldAssemblyRecipeV1 struct WorldAssemblyRecipeV1
{ {
std::uint32_t schemaVersion = WorldAssemblySchemaVersion; std::uint32_t schemaVersion = WorldAssemblySchemaVersion;
@@ -651,6 +725,23 @@ namespace VoxelForgeCore
std::array<std::int64_t, 4> parametersSubvoxels{}; std::array<std::int64_t, 4> parametersSubvoxels{};
}; };
struct WorldAssemblyLiminalFieldPlanV1
{
std::uint32_t version = 0;
WorldAssemblyLiminalVariantV1 variant = WorldAssemblyLiminalVariantV1::flatFieldMonolith;
std::uint32_t seed = 0;
std::array<std::int64_t, 3> arrivalMouthSubvoxels{};
std::array<std::int64_t, 3> departureMouthSubvoxels{};
// Fixed distance from each passage player-center anchor to its walk surface.
std::int64_t mouthFloorOffsetSubvoxels = 0;
std::int64_t floorHeightSubvoxels = 0;
std::int64_t routeHalfWidthSubvoxels = 0;
std::int64_t routeHalfThicknessSubvoxels = 0;
std::int64_t routeOffsetSubvoxels = 0;
std::int64_t supportSpacingSubvoxels = 0;
std::int64_t supportRadiusSubvoxels = 0;
};
struct WorldAssemblyStratumPlanV1 struct WorldAssemblyStratumPlanV1
{ {
std::int32_t strateIndex = -1; std::int32_t strateIndex = -1;
@@ -675,6 +766,7 @@ namespace VoxelForgeCore
std::vector<WorldAssemblyFeatureV1> features; std::vector<WorldAssemblyFeatureV1> features;
WorldAssemblyRouteConstraintsV1 routeConstraints; WorldAssemblyRouteConstraintsV1 routeConstraints;
std::vector<WorldAssemblyRoutePointV1> route; std::vector<WorldAssemblyRoutePointV1> route;
WorldAssemblyLiminalFieldPlanV1 liminalField;
std::uint64_t contentHash = 0; std::uint64_t contentHash = 0;
}; };
@@ -888,7 +980,13 @@ namespace VoxelForgeCore
inline constexpr std::uint32_t ResolvedLayoutStampVocabularyVersionV1 = 1; inline constexpr std::uint32_t ResolvedLayoutStampVocabularyVersionV1 = 1;
inline constexpr std::uint32_t ResolvedLayoutStampVocabularyVersionV2 = 2; inline constexpr std::uint32_t ResolvedLayoutStampVocabularyVersionV2 = 2;
inline constexpr std::uint32_t ResolvedLayoutStampVocabularyVersionV3 = 3; inline constexpr std::uint32_t ResolvedLayoutStampVocabularyVersionV3 = 3;
inline constexpr std::uint32_t ResolvedLayoutStampVocabularyVersionV4 = 4;
inline constexpr std::uint32_t ResolvedLayoutStampVocabularyVersionV5 = 5;
inline constexpr std::uint32_t ResolvedLayoutStampVocabularyVersionV6 = 6;
inline constexpr std::uint32_t ResolvedLayoutStampVocabularyVersionV7 = 7;
inline constexpr std::uint32_t ResolvedLayoutCavernGraphVocabularyVersionV1 = 1; inline constexpr std::uint32_t ResolvedLayoutCavernGraphVocabularyVersionV1 = 1;
inline constexpr std::uint32_t ResolvedLayoutCavernGraphVocabularyVersionV2 = 2;
inline constexpr std::uint32_t ResolvedLayoutCavernGraphVocabularyVersionV3 = 3;
inline constexpr std::uint32_t ResolvedLayoutOreVeinVocabularyVersionV1 = 1; inline constexpr std::uint32_t ResolvedLayoutOreVeinVocabularyVersionV1 = 1;
enum class ResolvedLayoutValidationModeV3 : std::uint8_t { compatibility, strict }; enum class ResolvedLayoutValidationModeV3 : std::uint8_t { compatibility, strict };
@@ -901,7 +999,11 @@ namespace VoxelForgeCore
fillPillar = 3, fillPillar = 3,
fillDais = 4, fillDais = 4,
fillMonolith = 5, fillMonolith = 5,
fillPlatform = 6 fillPlatform = 6,
fillSweptDeckSegment = 7,
fillBox = 8,
carveBox = 9,
fillSteps = 10
}; };
struct ResolvedLayoutStampRequestV1 struct ResolvedLayoutStampRequestV1
@@ -911,6 +1013,12 @@ namespace VoxelForgeCore
ResolvedSdfStampKindV1 kind = ResolvedSdfStampKindV1::carveEllipsoid; ResolvedSdfStampKindV1 kind = ResolvedSdfStampKindV1::carveEllipsoid;
// A preferred center and a bounded deterministic search radius. // A preferred center and a bounded deterministic search radius.
std::array<std::int64_t, 3> anchorPositionMicrometres{}; std::array<std::int64_t, 3> anchorPositionMicrometres{};
// Present only for fillSweptDeckSegment; defines its directed centerline.
std::array<std::array<std::int64_t, 3>, 2> sweepEndpointsMicrometres{};
// V5 permits one continuous swept deck to carry its complete sampled centerline.
// V6 also permits a path whose X coordinates are not strictly increasing.
// Empty retains the V4 single-segment interpretation.
std::vector<std::array<std::int64_t, 3>> sweepPathMicrometres;
std::int64_t placementRadiusMicrometres = 0; std::int64_t placementRadiusMicrometres = 0;
std::array<std::int64_t, 3> halfExtentsMicrometres{}; std::array<std::int64_t, 3> halfExtentsMicrometres{};
std::int64_t smoothWidthMicrometres = 0; std::int64_t smoothWidthMicrometres = 0;
@@ -918,6 +1026,9 @@ namespace VoxelForgeCore
std::int64_t irregularityWavelengthMicrometres = 0; std::int64_t irregularityWavelengthMicrometres = 0;
std::int64_t roundingRadiusMicrometres = 0; std::int64_t roundingRadiusMicrometres = 0;
std::int64_t minimumSpacingMicrometres = 0; std::int64_t minimumSpacingMicrometres = 0;
// Vocabulary 7: fillSteps divides the X and Z extents into this many
// identical treads and risers. Other stamp kinds keep this at zero.
std::uint16_t stepCount = 0;
// 0 = free placement, 1 = seated on the authored flat profile floor. // 0 = free placement, 1 = seated on the authored flat profile floor.
std::uint8_t attachmentPolicy = 0; std::uint8_t attachmentPolicy = 0;
std::int64_t floorMicrometres = 0; std::int64_t floorMicrometres = 0;
@@ -930,6 +1041,8 @@ namespace VoxelForgeCore
std::int32_t strateIndex = -1; std::int32_t strateIndex = -1;
ResolvedSdfStampKindV1 kind = ResolvedSdfStampKindV1::carveEllipsoid; ResolvedSdfStampKindV1 kind = ResolvedSdfStampKindV1::carveEllipsoid;
std::array<std::int64_t, 3> positionMicrometres{}; std::array<std::int64_t, 3> positionMicrometres{};
std::array<std::array<std::int64_t, 3>, 2> sweepEndpointsMicrometres{};
std::vector<std::array<std::int64_t, 3>> sweepPathMicrometres;
std::array<std::int64_t, 3> boundsMinimumMicrometres{}; std::array<std::int64_t, 3> boundsMinimumMicrometres{};
std::array<std::int64_t, 3> boundsMaximumMicrometres{}; std::array<std::int64_t, 3> boundsMaximumMicrometres{};
std::array<std::int64_t, 3> halfExtentsMicrometres{}; std::array<std::int64_t, 3> halfExtentsMicrometres{};
@@ -938,6 +1051,7 @@ namespace VoxelForgeCore
std::int64_t irregularityWavelengthMicrometres = 0; std::int64_t irregularityWavelengthMicrometres = 0;
std::int64_t roundingRadiusMicrometres = 0; std::int64_t roundingRadiusMicrometres = 0;
std::int64_t minimumSpacingMicrometres = 0; std::int64_t minimumSpacingMicrometres = 0;
std::uint16_t stepCount = 0;
std::uint8_t attachmentPolicy = 0; std::uint8_t attachmentPolicy = 0;
std::int64_t floorMicrometres = 0; std::int64_t floorMicrometres = 0;
std::uint64_t irregularitySeed = 0; std::uint64_t irregularitySeed = 0;
@@ -950,9 +1064,18 @@ namespace VoxelForgeCore
{ {
std::string stableId; std::string stableId;
std::int32_t strateIndex = -1; std::int32_t strateIndex = -1;
// 0 = floor-backed room; 1 = a true vertical cylindrical shaft. The
// shaft form is part of the shared cave graph so other recipes can reuse
// the same graph, indexing, interval and density machinery.
std::uint8_t shapeKind = 0;
std::array<std::int64_t, 3> centerMicrometres{}; std::array<std::int64_t, 3> centerMicrometres{};
std::array<std::int64_t, 3> halfExtentsMicrometres{}; std::array<std::int64_t, 3> halfExtentsMicrometres{};
std::int64_t floorMicrometres = 0; std::int64_t floorMicrometres = 0;
// Optional shared backbone profile reference. The room floor plane is
// floorMicrometres plus this Q16 XY gradient from centerMicrometres.
std::string floorProfileStableId;
std::int64_t floorProfileDistanceMicrometres = -1;
std::array<std::int32_t, 2> floorGradientQ16{};
std::int64_t smoothWidthMicrometres = 0; std::int64_t smoothWidthMicrometres = 0;
// Optional feature-to-feature union width. Zero preserves the historical // Optional feature-to-feature union width. Zero preserves the historical
// behavior where smoothWidthMicrometres also controls the graph union. // behavior where smoothWidthMicrometres also controls the graph union.
@@ -960,6 +1083,11 @@ namespace VoxelForgeCore
std::int64_t irregularityAmplitudeMicrometres = 0; std::int64_t irregularityAmplitudeMicrometres = 0;
std::int64_t irregularityWavelengthMicrometres = 0; std::int64_t irregularityWavelengthMicrometres = 0;
std::uint64_t irregularitySeed = 0; std::uint64_t irregularitySeed = 0;
// Used only by shapeKind=1. floorMicrometres is the shaft top; the
// bottom is center.z - halfExtents.z. Ledges face one cardinal side.
std::int64_t ledgeSpacingMicrometres = 0;
std::int64_t ledgeDepthMicrometres = 0;
std::uint8_t ledgeSide = 0;
std::array<std::int64_t, 3> boundsMinimumMicrometres{}; std::array<std::int64_t, 3> boundsMinimumMicrometres{};
std::array<std::int64_t, 3> boundsMaximumMicrometres{}; std::array<std::int64_t, 3> boundsMaximumMicrometres{};
}; };
@@ -972,6 +1100,11 @@ namespace VoxelForgeCore
std::int32_t strateIndex = -1; std::int32_t strateIndex = -1;
// A single cubic Bezier in XY. Its four Z values are the cubic floor profile. // A single cubic Bezier in XY. Its four Z values are the cubic floor profile.
std::array<std::array<std::int64_t, 3>, 4> controlPointsMicrometres{}; std::array<std::array<std::int64_t, 3>, 4> controlPointsMicrometres{};
// Edges with the same profile ID are adjacent pieces of one shared h(s).
// Distances are signed along that profile, so a terminal edge may be reversed.
std::string floorProfileStableId;
std::int64_t floorProfileStartDistanceMicrometres = -1;
std::int64_t floorProfileEndDistanceMicrometres = -1;
// Resolve-time, error-bounded approximation of the cubic centerline. Each // Resolve-time, error-bounded approximation of the cubic centerline. Each
// consecutive pair is a physical segment used by the native cave field. // consecutive pair is a physical segment used by the native cave field.
std::vector<std::array<std::int64_t, 3>> polylineMicrometres; std::vector<std::array<std::int64_t, 3>> polylineMicrometres;
@@ -988,6 +1121,43 @@ namespace VoxelForgeCore
std::array<std::int64_t, 3> boundsMaximumMicrometres{}; std::array<std::int64_t, 3> boundsMaximumMicrometres{};
}; };
// Inputs to the reusable, plan-time cave graph composer. All distances are
// integer micrometres and unit-interval values are Q16 so the resolved graph
// does not depend on worker count or floating-point random distributions.
struct ResolvedCavernPlannerSettingsV1
{
std::int64_t roomSpacingMicrometres = 16000000;
std::int64_t minimumRoomRadiusMicrometres = 2000000;
std::int64_t maximumRoomRadiusMicrometres = 5000000;
std::int64_t destinationRadiusMicrometres = 6000000;
// Solid floor backing below the planned edge floor, preserved after graph
// unions and passage composition. This is separate from bore height.
std::int64_t floorSupportDepthMicrometres = 750000;
std::int64_t minimumTunnelRadiusMicrometres = 750000;
std::int64_t maximumTunnelRadiusMicrometres = 1000000;
std::int64_t maximumTunnelLengthMicrometres = 45000000;
std::int64_t tunnelWarpMicrometres = 0;
std::int64_t caveWarpMicrometres = 0;
std::int64_t smoothWidthMicrometres = 500000;
std::int64_t irregularityAmplitudeMicrometres = 0;
std::int64_t irregularityWavelengthMicrometres = 0;
std::int32_t roomDensityQ16 = 22938;
std::int32_t tunnelDensityQ16 = 26214;
std::int32_t roomShapeVarietyQ16 = 32768;
std::int32_t roomHeightRatioQ16 = 55706;
std::int32_t roomFloorCutMinimumQ16 = 45875;
std::int32_t roomFloorCutMaximumQ16 = 58982;
std::int32_t tunnelHorizontalBiasQ16 = 32768;
std::int32_t originRoomMaximumConnections = 4;
bool tunnelsFlowTowardOrigin = true;
// Vertical Shafts uses this same planner and graph field. It binds its
// incoming passage and resolves a bedrock terminal when no departure
// mouth exists, then adds shaft records beside the walk-only backbone.
bool verticalShaftMode = false;
std::int64_t ledgeSpacingMicrometres = 8000000;
std::int64_t ledgeDepthMicrometres = 1000000;
};
// Immutable material-only deposits. The capsule and its conservative bounds are in // Immutable material-only deposits. The capsule and its conservative bounds are in
// micrometres; bounds include irregularity and the blend envelope. // micrometres; bounds include irregularity and the blend envelope.
struct ResolvedLayoutOreVeinRequestV1 struct ResolvedLayoutOreVeinRequestV1
@@ -1057,6 +1227,27 @@ namespace VoxelForgeCore
bool surfaceToTunnelLandingSupport = false; bool surfaceToTunnelLandingSupport = false;
}; };
enum class ResolvedLayoutLandingPadRoleV1 : std::uint32_t
{
arrival = 0,
departure = 1
};
struct ResolvedLayoutLandingPadV1
{
std::string stableId;
ResolvedLayoutLandingPadRoleV1 role = ResolvedLayoutLandingPadRoleV1::arrival;
// Capsule-center position. floorHeightSubvoxels is the supported floor below it.
std::array<std::int64_t, 3> positionSubvoxels{};
std::int64_t floorHeightSubvoxels = 0;
// Unit horizontal travel direction, signed Q16, oriented into the layer route.
std::array<std::int32_t, 2> approachDirectionQ16{};
// Certified horizontal free radius around the capsule center and support below the floor.
std::int64_t clearanceSubvoxels = 0;
std::int64_t supportDepthSubvoxels = 0;
bool certifiedWalkableWithSupport = false;
};
struct ResolvedLayoutStratumV2 struct ResolvedLayoutStratumV2
{ {
std::string stableId; std::string stableId;
@@ -1086,13 +1277,15 @@ namespace VoxelForgeCore
std::int64_t managerChunkWidthMicrometres = 0; std::int64_t managerChunkWidthMicrometres = 0;
std::int64_t dispatchChunkWidthMicrometres = 0; std::int64_t dispatchChunkWidthMicrometres = 0;
std::vector<ResolvedLayoutAnchorV2> anchors; std::vector<ResolvedLayoutAnchorV2> anchors;
std::vector<ResolvedLayoutLandingPadV1> landingPads;
ResolvedLayoutStratePassageCapabilitiesV1 passageCapabilities; ResolvedLayoutStratePassageCapabilitiesV1 passageCapabilities;
ResolvedLayoutInterfaceCapabilitiesV1 lowerInterfaceCapabilities; ResolvedLayoutInterfaceCapabilitiesV1 lowerInterfaceCapabilities;
}; };
inline bool IsNativeCaveFieldStratum(const ResolvedLayoutStratumV2& stratum) noexcept inline bool IsNativeCaveFieldStratum(const ResolvedLayoutStratumV2& stratum) noexcept
{ {
return stratum.generatorKind=="Caves" return (stratum.generatorKind=="Caves"||stratum.generatorKind=="TunnelNetwork"
||stratum.generatorKind=="VerticalShafts")
&&stratum.recipeId=="Core.Caves.PrimitiveField" &&stratum.recipeId=="Core.Caves.PrimitiveField"
&&stratum.recipeVersion==1; &&stratum.recipeVersion==1;
} }
@@ -1133,6 +1326,16 @@ namespace VoxelForgeCore
std::int64_t radiusSubvoxels = 0; std::int64_t radiusSubvoxels = 0;
}; };
struct ResolvedLayoutPassageLandingChamberV1
{
// The chamber floor plane passes through this point. Gradient is dz/dx,
// dz/dy in Q16 so the chamber continues the passage's authored h(s) grade.
std::array<std::int64_t, 3> floorCenterSubvoxels{};
std::array<std::int64_t, 2> floorGradientQ16{};
std::int64_t radiusSubvoxels = 0;
std::int64_t clearHeightSubvoxels = 0;
};
struct ResolvedLayoutPassageV1 struct ResolvedLayoutPassageV1
{ {
std::string stableId; std::string stableId;
@@ -1147,6 +1350,12 @@ namespace VoxelForgeCore
std::int64_t midRadiusSubvoxels = 0; std::int64_t midRadiusSubvoxels = 0;
// Derived from the mouth binding; the main descent is monotone through this point. // Derived from the mouth binding; the main descent is monotone through this point.
std::size_t lowerMouthControlIndex = 0; std::size_t lowerMouthControlIndex = 0;
ResolvedLayoutPassageLandingChamberV1 upperLandingChamber;
ResolvedLayoutPassageLandingChamberV1 lowerLandingChamber;
// When true, each control point's position.z - radius is the planned
// floor h(s), and both chamber planes continue that same endpoint profile.
// False preserves older resolved snapshots' runtime floor composition.
bool floorProfileIncludesLandingChambers = false;
std::vector<ResolvedLayoutPassagePointV1> controlPoints; std::vector<ResolvedLayoutPassagePointV1> controlPoints;
}; };
@@ -1225,6 +1434,15 @@ namespace VoxelForgeCore
ResolvedLayoutSnapshotV3& layout, std::int32_t seed, ResolvedLayoutSnapshotV3& layout, std::int32_t seed,
const std::vector<ResolvedLayoutPassageConfigV1>& configs, const std::vector<ResolvedLayoutPassageConfigV1>& configs,
std::string& error); std::string& error);
VFCORE_API bool PlanCoreLiminalFieldV1(
WorldAssemblyRecipeV1& recipe, WorldAssemblyStratumPlanV1& stratum,
const ResolvedLayoutSnapshotV3& layout, std::string& error);
VFCORE_API bool PrepareCoreLiminalLandingPadsV2(
WorldAssemblyRecipeV1& recipe, WorldAssemblyStratumPlanV1& stratum,
ResolvedLayoutSnapshotV3& layout, std::string& error);
VFCORE_API bool PlanCoreLiminalFieldV2(
WorldAssemblyRecipeV1& recipe, WorldAssemblyStratumPlanV1& stratum,
const ResolvedLayoutSnapshotV3& layout, std::string& error);
struct ResolvedLayoutStampPlacementAuditV1 struct ResolvedLayoutStampPlacementAuditV1
{ {
@@ -1253,6 +1471,17 @@ namespace VoxelForgeCore
std::span<const ResolvedCavernRoomV1> rooms, std::span<const ResolvedCavernRoomV1> rooms,
std::span<const ResolvedCavernEdgeV1> edges, std::span<const ResolvedCavernEdgeV1> edges,
std::string& error); std::string& error);
// Build a deterministic, walkable room-and-tunnel graph between the two
// passage mouths that terminate in strateIndex. Compound rooms are emitted
// as overlapping room primitives; the route is planned below a 15 degree
// walking grade and every corridor carries a 4.5 m vertical clearance.
VFCORE_API bool PlanResolvedCavernNetworkV1(
const ResolvedLayoutSnapshotV3& layout, std::int32_t seed,
std::int32_t strateIndex,
const ResolvedCavernPlannerSettingsV1& settings,
std::vector<ResolvedCavernRoomV1>& rooms,
std::vector<ResolvedCavernEdgeV1>& edges,
std::string& error);
VFCORE_API bool ResolveResolvedLayoutOreVeinsV1( VFCORE_API bool ResolveResolvedLayoutOreVeinsV1(
ResolvedLayoutSnapshotV3& layout, std::int32_t seed, ResolvedLayoutSnapshotV3& layout, std::int32_t seed,
std::span<const ResolvedLayoutOreVeinRequestV1> requests, std::span<const ResolvedLayoutOreVeinRequestV1> requests,
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+12 -1
View File
@@ -2889,12 +2889,16 @@ namespace VoxelForgeCore
VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds; VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds;
const auto MarchingCubesStart=std::chrono::steady_clock::now(); const auto MarchingCubesStart=std::chrono::steady_clock::now();
FVoxelUniformBlockProofAuditStats BlockProofAudit; FVoxelUniformBlockProofAuditStats BlockProofAudit;
FVoxelMeshData mesh=mesher.GenerateMesh(origin,MeshStep,MeshCells,nullptr, FVoxelMeshData mesh;
{
FStandaloneShapeWorld::FScopedStampSamplingStep stampSamplingStep(MeshStep);
mesh=mesher.GenerateMesh(origin,MeshStep,MeshCells,nullptr,
MeshBandMinZVoxel,MeshBandMaxZVoxel,&sampleCount,nullptr,nullptr, MeshBandMinZVoxel,MeshBandMaxZVoxel,&sampleCount,nullptr,nullptr,
(options.hashDensity||options.captureDensitySamples)?&densityValues:nullptr, (options.hashDensity||options.captureDensitySamples)?&densityValues:nullptr,
options.profileMesherStages?&McStageTimings:nullptr, options.profileMesherStages?&McStageTimings:nullptr,
options.auditUniformBlockProofs?&BlockProofAudit:nullptr, options.auditUniformBlockProofs?&BlockProofAudit:nullptr,
options.forceFullDensityCapture); options.forceFullDensityCapture);
}
passageFeatureScope.copySweptTubeRuntimeCounts(); passageFeatureScope.copySweptTubeRuntimeCounts();
if(options.auditUniformBlockProofs) if(options.auditUniformBlockProofs)
{ {
@@ -3107,6 +3111,13 @@ namespace VoxelForgeCore
AppendStratumBoundaryFloors(world,farShape,nativeKey,step,mesh); AppendStratumBoundaryFloors(world,farShape,nativeKey,step,mesh);
AppendStratumBoundaryCeilings(world,farShape,nativeKey,step,mesh); AppendStratumBoundaryCeilings(world,farShape,nativeKey,step,mesh);
} }
const std::uint32_t coarsePillarProxyCount=tileShape.AppendCoarsePillarProxies(
mesh,origin,extent,MeshStep);
if(coarsePillarProxyCount>0)
std::printf("[CoarsePillarProxy] key=(%d,%d,%d,lod%d) sampling_step_vox=%d "
"pillars=%u mode=exact_capped_cylinder status=EMITTED\n",
key.coordinate.x,key.coordinate.y,key.coordinate.z,key.lod,MeshStep,
coarsePillarProxyCount);
l77RefineStage.Restore(); l77RefineStage.Restore();
out.densitySampleCount=static_cast<std::uint64_t>(std::max<int64>(0,sampleCount)); out.densitySampleCount=static_cast<std::uint64_t>(std::max<int64>(0,sampleCount));
@@ -539,6 +539,38 @@ namespace world_assembly_detail
if (squared == 0) if (squared == 0)
{ error = "world assembly route contains a zero-length segment"; return false; } { error = "world assembly route contains a zero-length segment"; return false; }
} }
const auto& liminal = stratum.liminalField;
if (liminal.version != 0)
{
if ((liminal.version != CoreLiminalFieldRecipeVersionV1
&& liminal.version != CoreLiminalFieldRecipeVersionV2)
|| static_cast<std::uint32_t>(liminal.variant) >= CoreLiminalFieldVariantCountV1
|| liminal.seed != effectiveRecipe.seed
|| liminal.variant != SelectCoreLiminalFieldVariantV1(liminal.seed)
|| stratum.route.size() < 2
|| liminal.mouthFloorOffsetSubvoxels <= 0
|| stratum.route.front().positionSubvoxels[0] != liminal.arrivalMouthSubvoxels[0]
|| stratum.route.front().positionSubvoxels[1] != liminal.arrivalMouthSubvoxels[1]
|| stratum.route.front().positionSubvoxels[2]
!= liminal.arrivalMouthSubvoxels[2]-liminal.mouthFloorOffsetSubvoxels
|| stratum.route.back().positionSubvoxels[0] != liminal.departureMouthSubvoxels[0]
|| stratum.route.back().positionSubvoxels[1] != liminal.departureMouthSubvoxels[1]
|| stratum.route.back().positionSubvoxels[2]
!= liminal.departureMouthSubvoxels[2]-liminal.mouthFloorOffsetSubvoxels
|| liminal.routeHalfWidthSubvoxels < stratum.routeConstraints.capsuleRadiusSubvoxels
|| liminal.routeHalfThicknessSubvoxels <= 0
|| liminal.routeOffsetSubvoxels <= 0
|| liminal.supportSpacingSubvoxels <= 0
|| liminal.supportRadiusSubvoxels <= 0
|| stratum.surfaceProfile.version != 1)
{ error = "Core.Liminal.Field plan has invalid version, seed-selected variant, route mouths or dimensions"; return false; }
const std::int64_t bottom = static_cast<std::int64_t>(stratum.bottomChunkZ)
* layoutChunkWidthSubvoxels;
const std::int64_t top = (static_cast<std::int64_t>(stratum.topChunkZ) + 1)
* layoutChunkWidthSubvoxels;
if (liminal.floorHeightSubvoxels <= bottom || liminal.floorHeightSubvoxels >= top)
{ error = "Core.Liminal.Field floor escapes its finite stratum bounds"; return false; }
}
const auto& constraints = stratum.routeConstraints; const auto& constraints = stratum.routeConstraints;
if (constraints.version == 0) if (constraints.version == 0)
{ {
@@ -682,6 +714,24 @@ namespace world_assembly_detail
for (const auto value : point.positionSubvoxels) HashWord(hash, static_cast<std::uint64_t>(value), 8); for (const auto value : point.positionSubvoxels) HashWord(hash, static_cast<std::uint64_t>(value), 8);
HashWord(hash, static_cast<std::uint64_t>(point.radiusSubvoxels), 8); HashWord(hash, static_cast<std::uint64_t>(point.radiusSubvoxels), 8);
} }
if (stratum.liminalField.version != 0)
{
const auto& liminal = stratum.liminalField;
HashWord(hash, 0x4c494d494e414c31ull, 8);
HashWord(hash, liminal.version, 4);
HashWord(hash, static_cast<std::uint32_t>(liminal.variant), 4);
HashWord(hash, liminal.seed, 4);
for (const auto value : liminal.arrivalMouthSubvoxels)
HashWord(hash, static_cast<std::uint64_t>(value), 8);
for (const auto value : liminal.departureMouthSubvoxels)
HashWord(hash, static_cast<std::uint64_t>(value), 8);
for (const auto value : {liminal.mouthFloorOffsetSubvoxels,
liminal.floorHeightSubvoxels,
liminal.routeHalfWidthSubvoxels, liminal.routeHalfThicknessSubvoxels,
liminal.routeOffsetSubvoxels, liminal.supportSpacingSubvoxels,
liminal.supportRadiusSubvoxels})
HashWord(hash, static_cast<std::uint64_t>(value), 8);
}
if (stratum.surfaceProfile.version != 0) if (stratum.surfaceProfile.version != 0)
HashSurfaceProfile(hash, stratum.surfaceProfile); HashSurfaceProfile(hash, stratum.surfaceProfile);
return hash; return hash;
@@ -1059,6 +1109,30 @@ namespace world_assembly_detail
{ error = "world assembly route point is missing a fixed-scale field"; return false; } { error = "world assembly route point is missing a fixed-scale field"; return false; }
stratum.route.push_back(point); stratum.route.push_back(point);
} }
const VFJson::Value& liminalValue = item.Get("liminal_field");
if (!std::holds_alternative<std::nullptr_t>(liminalValue.Data))
{
auto& liminal = stratum.liminalField;
std::uint32_t variant = 0;
if (!liminalValue.IsObject()
|| !ReadU32(liminalValue.Get("version"), liminal.version)
|| !ReadU32(liminalValue.Get("variant"), variant)
|| !ReadU32(liminalValue.Get("seed"), liminal.seed)
|| !ReadArray(liminalValue.Get("arrival_mouth_q"),
liminal.arrivalMouthSubvoxels.data(), 3)
|| !ReadArray(liminalValue.Get("departure_mouth_q"),
liminal.departureMouthSubvoxels.data(), 3)
|| !ReadInteger(liminalValue.Get("mouth_floor_offset_q"),
liminal.mouthFloorOffsetSubvoxels)
|| !ReadInteger(liminalValue.Get("floor_height_q"), liminal.floorHeightSubvoxels)
|| !ReadInteger(liminalValue.Get("route_half_width_q"), liminal.routeHalfWidthSubvoxels)
|| !ReadInteger(liminalValue.Get("route_half_thickness_q"), liminal.routeHalfThicknessSubvoxels)
|| !ReadInteger(liminalValue.Get("route_offset_q"), liminal.routeOffsetSubvoxels)
|| !ReadInteger(liminalValue.Get("support_spacing_q"), liminal.supportSpacingSubvoxels)
|| !ReadInteger(liminalValue.Get("support_radius_q"), liminal.supportRadiusSubvoxels))
{ error = "Core.Liminal.Field plan is missing a fixed-scale field"; return false; }
liminal.variant = static_cast<WorldAssemblyLiminalVariantV1>(variant);
}
plan.strates.push_back(std::move(stratum)); plan.strates.push_back(std::move(stratum));
} }
return true; return true;
@@ -2324,6 +2398,724 @@ bool WorldAssemblyManifest::validate(std::string& error) const
return true; return true;
} }
bool PlanCoreLiminalFieldV1(WorldAssemblyRecipeV1& recipe,
WorldAssemblyStratumPlanV1& stratum,const ResolvedLayoutSnapshotV3& layout,
std::string& error)
{
error.clear();
if(layout.coreVoxelSizeMicrometres==0||stratum.strateIndex<0
||stratum.topChunkZ<stratum.bottomChunkZ)
{error="Core.Liminal.Field needs a finite stratum and resolved Core voxel size";return false;}
const auto resolvedStratum=std::find_if(layout.strates.begin(),layout.strates.end(),
[&](const auto& value){return value.strateIndex==stratum.strateIndex
&&value.generatorKind=="SurfaceWorld";});
if(resolvedStratum==layout.strates.end()||!resolvedStratum->content.presentInCurrentField)
{error="Core.Liminal.Field must target a resolved SurfaceWorld content band";return false;}
const ResolvedLayoutPassageV1* arrival=nullptr;
const ResolvedLayoutPassageV1* departure=nullptr;
for(const auto& passage:layout.passages)
{
if(passage.lowerStrateIndex==stratum.strateIndex)
{
if(arrival){error="Core.Liminal.Field requires one fixed arrival passage mouth";return false;}
arrival=&passage;
}
if(passage.upperStrateIndex==stratum.strateIndex)
{
if(departure){error="Core.Liminal.Field requires one fixed departure passage mouth";return false;}
departure=&passage;
}
}
if(!arrival||!departure)
{error="Core.Liminal.Field requires an adjacent passage on both sides";return false;}
constexpr std::int64_t Q=WorldAssemblySubvoxelsPerVoxel;
const std::int64_t coreMicrons=layout.coreVoxelSizeMicrometres;
const auto micrometresToQ=[&](std::int64_t micrometres)
{
const std::int64_t numerator=micrometres*Q;
return numerator>=0?(numerator+coreMicrons/2)/coreMicrons
:(numerator-coreMicrons/2)/coreMicrons;
};
const std::int64_t qPerMetre=micrometresToQ(1000000);
if(qPerMetre<=0){error="Core.Liminal.Field could not resolve fixed-scale metres";return false;}
const std::uint32_t seed=recipe.seed;
const WorldAssemblyLiminalVariantV1 variant=SelectCoreLiminalFieldVariantV1(seed);
const auto& definition=CoreLiminalFieldVariantsV1[static_cast<std::size_t>(variant)];
const auto& start=arrival->lowerMouthSubvoxels;
const auto& finish=departure->upperMouthSubvoxels;
constexpr std::int64_t MaximumMouthCoordinateQ=1000000000ll;
if(std::abs(start[0])>MaximumMouthCoordinateQ||std::abs(start[1])>MaximumMouthCoordinateQ
||std::abs(start[2])>MaximumMouthCoordinateQ||std::abs(finish[0])>MaximumMouthCoordinateQ
||std::abs(finish[1])>MaximumMouthCoordinateQ||std::abs(finish[2])>MaximumMouthCoordinateQ)
{error="Core.Liminal.Field passage mouth exceeds the supported fixed-coordinate range";return false;}
const std::int64_t mouthPlayerCenterHeight=micrometresToQ(1130000);
auto routeStart=start;
auto routeFinish=finish;
routeStart[2]-=mouthPlayerCenterHeight;
routeFinish[2]-=mouthPlayerCenterHeight;
const auto findMouthControl=[&](const ResolvedLayoutPassageV1& passage,
const std::array<std::int64_t,3>& mouth)
{
std::size_t found=passage.controlPoints.size();
for(std::size_t i=0;i<passage.controlPoints.size();++i)
{
const auto& point=passage.controlPoints[i];
if(point.positionSubvoxels[0]==mouth[0]
&&point.positionSubvoxels[1]==mouth[1]
&&point.positionSubvoxels[2]-point.radiusSubvoxels
+mouthPlayerCenterHeight==mouth[2])
{
if(found!=passage.controlPoints.size())return passage.controlPoints.size();
found=i;
}
}
return found;
};
const std::size_t arrivalControl=findMouthControl(*arrival,start);
const std::size_t departureControl=findMouthControl(*departure,finish);
if(arrival->controlPoints.size()<2||departure->controlPoints.size()<2
||arrivalControl>=arrival->controlPoints.size()
||departureControl>=departure->controlPoints.size())
{error="Core.Liminal.Field needs exact passage-control bindings at both fixed mouths";return false;}
const auto horizontalLengthQ=[](std::int64_t dx,std::int64_t dy)
{
const auto absX=static_cast<std::uint64_t>(std::abs(dx));
const auto absY=static_cast<std::uint64_t>(std::abs(dy));
return world_assembly_detail::IntegerSqrt(absX*absX+absY*absY);
};
const auto roundRatio=[](std::int64_t numerator,std::uint64_t denominator)
{
if(denominator==0)return std::int64_t{0};
const auto positiveDenominator=static_cast<std::int64_t>(denominator);
return numerator>=0
?(numerator+positiveDenominator/2)/positiveDenominator
:-((-numerator+positiveDenominator/2)/positiveDenominator);
};
const auto passageSplineControls=[](const ResolvedLayoutPassageV1& passage)
{
std::vector<PassageSpline::FPoint> controls;
controls.reserve(passage.controlPoints.size());
for(const auto& point:passage.controlPoints)
controls.push_back({static_cast<double>(point.positionSubvoxels[0]),
static_cast<double>(point.positionSubvoxels[1]),
static_cast<double>(point.positionSubvoxels[2]-point.radiusSubvoxels),
static_cast<double>(point.radiusSubvoxels)});
return controls;
};
const auto arrivalSpline=passageSplineControls(*arrival);
const auto departureSpline=passageSplineControls(*departure);
constexpr double MouthTangentProbeParameter=0.5;
const std::size_t arrivalSegment=arrivalControl==0?0:arrivalControl-1;
const double arrivalAtT=arrivalControl==0?0.0:1.0;
const double arrivalNearT=arrivalControl==0?MouthTangentProbeParameter
:1.0-MouthTangentProbeParameter;
const auto arrivalNear=PassageSpline::EvaluateRaw(arrivalSpline,arrivalSegment,
arrivalNearT,static_cast<double>(qPerMetre));
const auto arrivalAt=PassageSpline::EvaluateRaw(arrivalSpline,arrivalSegment,
arrivalAtT,static_cast<double>(qPerMetre));
const std::size_t departureSegment=departureControl+1==departure->controlPoints.size()
?departureControl-1:departureControl;
const double departureAtT=departureControl+1==departure->controlPoints.size()?1.0:0.0;
const double departureNearT=departureControl+1==departure->controlPoints.size()
?1.0-MouthTangentProbeParameter:MouthTangentProbeParameter;
const auto departureAt=PassageSpline::EvaluateRaw(departureSpline,departureSegment,
departureAtT,static_cast<double>(qPerMetre));
const auto departureNear=PassageSpline::EvaluateRaw(departureSpline,departureSegment,
departureNearT,static_cast<double>(qPerMetre));
const auto roundedDelta=[](double startValue,double endValue)
{return static_cast<std::int64_t>(std::llround(endValue-startValue));};
const std::int64_t arrivalDx=roundedDelta(arrivalNear[0],arrivalAt[0]);
const std::int64_t arrivalDy=roundedDelta(arrivalNear[1],arrivalAt[1]);
const std::int64_t arrivalFloorDelta=roundedDelta(arrivalNear[2],arrivalAt[2]);
const std::uint64_t arrivalLength=horizontalLengthQ(arrivalDx,arrivalDy);
const std::int64_t departureDx=roundedDelta(departureAt[0],departureNear[0]);
const std::int64_t departureDy=roundedDelta(departureAt[1],departureNear[1]);
const std::int64_t departureFloorDelta=roundedDelta(departureAt[2],departureNear[2]);
const std::uint64_t departureLength=horizontalLengthQ(departureDx,departureDy);
if(arrivalLength==0||departureLength==0)
{error="Core.Liminal.Field passage mouth tangent is too short for a stable connector";return false;}
const auto extendMouth=[&](const std::array<std::int64_t,3>& mouth,
std::int64_t dx,std::int64_t dy,std::int64_t dz,std::uint64_t horizontalLength,
std::int64_t distance,bool forward)
{
const std::int64_t sign=forward?1:-1;
return std::array<std::int64_t,3>{
mouth[0]+roundRatio(sign*dx*distance,horizontalLength),
mouth[1]+roundRatio(sign*dy*distance,horizontalLength),
mouth[2]+roundRatio(sign*dz*distance,horizontalLength)};
};
constexpr std::int64_t MouthConnectorMidMetres=5;
constexpr std::int64_t MouthConnectorFullMetres=10;
const auto arrivalConnectorMid=extendMouth(routeStart,arrivalDx,arrivalDy,
arrivalFloorDelta,arrivalLength,MouthConnectorMidMetres*qPerMetre,true);
const auto arrivalConnector=extendMouth(routeStart,arrivalDx,arrivalDy,
arrivalFloorDelta,arrivalLength,MouthConnectorFullMetres*qPerMetre,true);
const auto departureConnector=extendMouth(routeFinish,departureDx,departureDy,
departureFloorDelta,departureLength,MouthConnectorFullMetres*qPerMetre,false);
const auto departureConnectorMid=extendMouth(routeFinish,departureDx,departureDy,
departureFloorDelta,departureLength,MouthConnectorMidMetres*qPerMetre,false);
const std::int64_t bottom=static_cast<std::int64_t>(stratum.bottomChunkZ)
*recipe.layoutChunkWidthSubvoxels;
const std::int64_t top=(static_cast<std::int64_t>(stratum.topChunkZ)+1)
*recipe.layoutChunkWidthSubvoxels;
const std::int64_t minRouteFloor=std::min({routeStart[2],routeFinish[2],
arrivalConnectorMid[2],arrivalConnector[2],departureConnector[2],
departureConnectorMid[2]});
const std::int64_t contentBottom=micrometresToQ(
resolvedStratum->content.bottomMicrometres);
const std::int64_t profileRelief=variant==WorldAssemblyLiminalVariantV1::repeatingHills
?2*micrometresToQ(definition.hillAmplitudeMicrometres):0;
const std::int64_t floorHeight=std::max(contentBottom+profileRelief+qPerMetre/4,
minRouteFloor-8*qPerMetre);
if(floorHeight>=minRouteFloor-2*qPerMetre||floorHeight<=bottom||top<=floorHeight+8*qPerMetre)
{error="Core.Liminal.Field has no room for its floor and passage route";return false;}
stratum.hasRecipeOverride=true;
stratum.recipeOverride=recipe;
WorldAssemblySurfaceProfileV1 profile;
profile.version=1;
profile.kind=definition.surfaceKind;
profile.h0Subvoxels=floorHeight;
if(profile.kind==WorldAssemblySurfaceProfileKindV1::periodicHills)
{
profile.amplitudeXSubvoxels=micrometresToQ(definition.hillAmplitudeMicrometres);
profile.amplitudeYSubvoxels=profile.amplitudeXSubvoxels;
profile.wavelengthXSubvoxels=micrometresToQ(definition.hillWavelengthMicrometres);
profile.wavelengthYSubvoxels=profile.wavelengthXSubvoxels;
ResolveWorldAssemblySurfaceProfilePhasesV1(profile,seed);
}
stratum.recipeOverride.surfaceProfile=profile;
stratum.recipeOverride.contentHash=0;
stratum.surfaceProfile=profile;
stratum.capHeightSubvoxels=top-4*qPerMetre;
stratum.capUndulationSubvoxels=0;
stratum.capDetailAmplitudeSubvoxels=0;
stratum.capRidgeAmplitudeSubvoxels=0;
stratum.waterEnabled=false;
stratum.waterSurfaceSubvoxels=0;
WorldAssemblyLiminalFieldPlanV1 liminal;
liminal.version=CoreLiminalFieldRecipeVersionV1;
liminal.variant=variant;
liminal.seed=seed;
liminal.arrivalMouthSubvoxels=start;
liminal.departureMouthSubvoxels=finish;
liminal.floorHeightSubvoxels=floorHeight;
liminal.routeHalfWidthSubvoxels=micrometresToQ(definition.routeHalfWidthMicrometres);
liminal.routeHalfThicknessSubvoxels=micrometresToQ(definition.routeHalfThicknessMicrometres);
liminal.supportSpacingSubvoxels=micrometresToQ(definition.supportSpacingMicrometres);
liminal.supportRadiusSubvoxels=micrometresToQ(definition.supportRadiusMicrometres);
liminal.mouthFloorOffsetSubvoxels=mouthPlayerCenterHeight;
constexpr std::int64_t GradeQ16=17560;
constexpr std::int64_t MinimumPathOffsetMetres=150;
constexpr std::int64_t ConnectorSpanMetres=16;
const std::int64_t dx=departureConnector[0]-arrivalConnector[0];
const std::int64_t dy=departureConnector[1]-arrivalConnector[1];
const std::uint64_t chord=horizontalLengthQ(dx,dy);
std::int64_t directionX=dx,directionY=dy;
std::uint64_t directionLength=chord;
if(directionLength<static_cast<std::uint64_t>(qPerMetre/4))
{directionX=Q;directionY=0;directionLength=static_cast<std::uint64_t>(Q);}
const std::int64_t normalX=-directionY;
const std::int64_t normalY=directionX;
const std::int64_t vertical=std::abs(departureConnector[2]-arrivalConnector[2]);
const std::int64_t requiredHorizontal=(vertical*65536+GradeQ16-1)/GradeQ16;
std::int64_t routeOffset=std::max(MinimumPathOffsetMetres*qPerMetre,
(requiredHorizontal+1)/2+20*qPerMetre);
if(chord<ConnectorSpanMetres*static_cast<std::uint64_t>(qPerMetre))
routeOffset=std::max(routeOffset,MinimumPathOffsetMetres*qPerMetre);
liminal.routeOffsetSubvoxels=routeOffset;
const auto normalOffset=[&](std::int64_t scale)
{
return std::array<std::int64_t,2>{
normalX*scale/static_cast<std::int64_t>(directionLength),
normalY*scale/static_cast<std::int64_t>(directionLength)};
};
const auto directionOffset=[&](std::int64_t scale)
{
return std::array<std::int64_t,2>{
directionX*scale/static_cast<std::int64_t>(directionLength),
directionY*scale/static_cast<std::int64_t>(directionLength)};
};
const auto normal=normalOffset(routeOffset);
const auto connector=chord<ConnectorSpanMetres*static_cast<std::uint64_t>(qPerMetre)
?directionOffset(ConnectorSpanMetres*qPerMetre):std::array<std::int64_t,2>{0,0};
std::vector<std::array<std::int64_t,3>> horizontalPath;
horizontalPath.reserve(8);
horizontalPath.push_back(routeStart);
horizontalPath.push_back(arrivalConnectorMid);
horizontalPath.push_back(arrivalConnector);
horizontalPath.push_back({arrivalConnector[0]+normal[0],
arrivalConnector[1]+normal[1],0});
horizontalPath.push_back({departureConnector[0]+normal[0]+connector[0],
departureConnector[1]+normal[1]+connector[1],0});
horizontalPath.push_back(departureConnector);
horizontalPath.push_back(departureConnectorMid);
horizontalPath.push_back(routeFinish);
std::vector<WorldAssemblyRoutePointV1> route;
route.reserve(horizontalPath.size());
std::vector<std::uint64_t> cumulative(horizontalPath.size(),0);
std::uint64_t totalHorizontal=0;
for(std::size_t i=1;i<horizontalPath.size();++i)
{
const std::int64_t sx=horizontalPath[i][0]-horizontalPath[i-1][0];
const std::int64_t sy=horizontalPath[i][1]-horizontalPath[i-1][1];
const std::uint64_t segment=horizontalLengthQ(sx,sy);
if(segment==0){error="Core.Liminal.Field route contains a zero-horizontal segment";return false;}
totalHorizontal+=segment;
cumulative[i]=totalHorizontal;
}
if(totalHorizontal<static_cast<std::uint64_t>(requiredHorizontal))
{error="Core.Liminal.Field route cannot meet the 15-degree grade bound";return false;}
const std::uint64_t centralFirst=horizontalLengthQ(
horizontalPath[3][0]-horizontalPath[2][0],
horizontalPath[3][1]-horizontalPath[2][1]);
const std::uint64_t centralMiddle=horizontalLengthQ(
horizontalPath[4][0]-horizontalPath[3][0],
horizontalPath[4][1]-horizontalPath[3][1]);
const std::uint64_t centralLast=horizontalLengthQ(
horizontalPath[5][0]-horizontalPath[4][0],
horizontalPath[5][1]-horizontalPath[4][1]);
const std::uint64_t centralTotal=centralFirst+centralMiddle+centralLast;
if(centralTotal==0)
{error="Core.Liminal.Field central route has no horizontal span";return false;}
const std::int64_t centralVertical=departureConnector[2]-arrivalConnector[2];
horizontalPath[3][2]=arrivalConnector[2]+roundRatio(
centralVertical*static_cast<std::int64_t>(centralFirst),centralTotal);
horizontalPath[4][2]=arrivalConnector[2]+roundRatio(
centralVertical*static_cast<std::int64_t>(centralFirst+centralMiddle),centralTotal);
for(std::size_t i=0;i<horizontalPath.size();++i)
{
auto point=horizontalPath[i];
WorldAssemblyRoutePointV1 routePoint;
routePoint.positionSubvoxels=point;
routePoint.radiusSubvoxels=std::max<std::int64_t>(liminal.routeHalfWidthSubvoxels,
micrometresToQ(500000));
std::uint64_t id=14695981039346656037ull;
const auto add=[&](std::uint64_t value,std::uint32_t bytes)
{for(std::uint32_t byte=0;byte<bytes;++byte){id^=static_cast<std::uint8_t>(value>>(byte*8));id*=1099511628211ull;}};
add(seed,4);add(static_cast<std::uint32_t>(stratum.strateIndex),4);add(i,4);
routePoint.stableFeatureId=id?id:1;
route.push_back(routePoint);
}
stratum.route=std::move(route);
stratum.routeConstraints={1,micrometresToQ(340000),micrometresToQ(1760000),
GradeQ16,micrometresToQ(125000),micrometresToQ(250000),
micrometresToQ(1000000),micrometresToQ(120000),3};
stratum.liminalField=liminal;
std::string validationError;
if(!world_assembly_detail::ValidateSurfaceProfile(stratum.surfaceProfile,seed,validationError)
||!world_assembly_detail::ValidateStratum(stratum,recipe,validationError))
{error=validationError.empty()?"Core.Liminal.Field produced an invalid plan":validationError;return false;}
std::printf("[CoreLiminalFieldPlan] recipe=%.*s version=%u variant=%.*s seed=%u arrival=%s departure=%s route_points=%zu route_offset_m=%.3f route_grade_limit_deg=15.000 status=PASS\n",
static_cast<int>(CoreLiminalFieldRecipeIdV1.size()),CoreLiminalFieldRecipeIdV1.data(),
liminal.version,static_cast<int>(definition.name.size()),definition.name.data(),seed,
arrival->stableId.c_str(),departure->stableId.c_str(),stratum.route.size(),
static_cast<double>(routeOffset)/qPerMetre);
return true;
}
std::array<std::array<std::int64_t,2>,6> CoreLiminalRouteXYV2(
std::int64_t startX,std::int64_t startY,std::int64_t finishX,
std::int64_t finishY,std::int64_t qPerMetre)
{
return {{{{startX,startY}},{{-40*qPerMetre,40*qPerMetre}},
{{-40*qPerMetre,-40*qPerMetre}},{{40*qPerMetre,-40*qPerMetre}},
{{40*qPerMetre,40*qPerMetre}},{{finishX,finishY}}}};
}
std::vector<WorldAssemblyRoutePointV1> BuildCoreLiminalRouteV2(
const WorldAssemblySurfaceProfileV1& profile,
const ResolvedLayoutLandingPadV1& arrivalPad,
const ResolvedLayoutLandingPadV1& departurePad,
const WorldAssemblyLiminalVariantDefinitionV1& definition,
std::uint32_t seed,std::int32_t strateIndex,std::int64_t coreMicrons,
std::int64_t qPerMetre,
std::uint64_t& totalHorizontal,std::string& error)
{
error.clear();totalHorizontal=0;
const auto groundHeightAt=[&](std::int64_t x,std::int64_t y)
{
if(profile.kind==WorldAssemblySurfaceProfileKindV1::flatField)
return profile.h0Subvoxels;
const std::int64_t turnX=world_assembly_detail::SurfaceProfileTurnAtCoordinate(
x,profile.wavelengthXSubvoxels,profile.phaseXTurnsQ32);
const std::int64_t turnY=world_assembly_detail::SurfaceProfileTurnAtCoordinate(
y,profile.wavelengthYSubvoxels,profile.phaseYTurnsQ32);
return profile.h0Subvoxels
+world_assembly_detail::MulQ30(profile.amplitudeXSubvoxels,
world_assembly_detail::CosineQ30(turnX))
+world_assembly_detail::MulQ30(profile.amplitudeYSubvoxels,
world_assembly_detail::CosineQ30(turnY));
};
const auto horizontalLengthQ=[](std::int64_t dx,std::int64_t dy)
{return world_assembly_detail::IntegerSqrt(static_cast<std::uint64_t>(dx*dx+dy*dy));};
const auto micrometresToQ=[&](std::int64_t micrometres)
{return (micrometres*WorldAssemblySubvoxelsPerVoxel+coreMicrons/2)/coreMicrons;};
const auto routeXY=CoreLiminalRouteXYV2(arrivalPad.positionSubvoxels[0],
arrivalPad.positionSubvoxels[1],departurePad.positionSubvoxels[0],
departurePad.positionSubvoxels[1],qPerMetre);
std::vector<WorldAssemblyRoutePointV1> route;
const std::int64_t pointSpacing=12*qPerMetre;
const std::int64_t routeRadius=std::max<std::int64_t>(
micrometresToQ(definition.routeHalfWidthMicrometres),micrometresToQ(500000));
const auto routeId=[&](std::size_t ordinal)
{
std::uint64_t id=14695981039346656037ull;
const auto add=[&](std::uint64_t value,std::uint32_t bytes)
{for(std::uint32_t byte=0;byte<bytes;++byte)
{id^=static_cast<std::uint8_t>(value>>(byte*8));id*=1099511628211ull;}};
add(seed,4);add(static_cast<std::uint32_t>(strateIndex),4);add(ordinal,4);
return id?id:1;
};
const auto appendRoutePoint=[&](std::int64_t x,std::int64_t y)
{
WorldAssemblyRoutePointV1 point;
point.positionSubvoxels={x,y,groundHeightAt(x,y)};
point.radiusSubvoxels=routeRadius;
point.stableFeatureId=routeId(route.size());
route.push_back(point);
};
const auto interpolate=[](std::int64_t start,std::int64_t delta,
std::uint64_t step,std::uint64_t steps)
{
const std::int64_t numerator=delta*static_cast<std::int64_t>(step);
const std::int64_t divisor=static_cast<std::int64_t>(steps);
return start+(numerator>=0?(numerator+divisor/2)/divisor
:(numerator-divisor/2)/divisor);
};
appendRoutePoint(routeXY.front()[0],routeXY.front()[1]);
for(std::size_t segment=1;segment<routeXY.size();++segment)
{
const std::int64_t dx=routeXY[segment][0]-routeXY[segment-1][0];
const std::int64_t dy=routeXY[segment][1]-routeXY[segment-1][1];
const std::uint64_t length=horizontalLengthQ(dx,dy);
if(length==0){error="Core.Liminal.Field v2 route contains a zero-horizontal segment";return {};}
totalHorizontal+=length;
const std::uint64_t steps=std::max<std::uint64_t>(1,
(length+static_cast<std::uint64_t>(pointSpacing)-1)
/static_cast<std::uint64_t>(pointSpacing));
for(std::uint64_t step=1;step<=steps;++step)
appendRoutePoint(interpolate(routeXY[segment-1][0],dx,step,steps),
interpolate(routeXY[segment-1][1],dy,step,steps));
}
if(route.size()<2||totalHorizontal<300*static_cast<std::uint64_t>(qPerMetre)
||route.front().positionSubvoxels[2]!=arrivalPad.floorHeightSubvoxels
||route.back().positionSubvoxels[2]!=departurePad.floorHeightSubvoxels)
{error="Core.Liminal.Field v2 pad-to-pad route is incomplete or too short";return {};}
return route;
}
bool PrepareCoreLiminalLandingPadsV2(WorldAssemblyRecipeV1& recipe,
WorldAssemblyStratumPlanV1& stratum,ResolvedLayoutSnapshotV3& layout,
std::string& error)
{
error.clear();
if(layout.coreVoxelSizeMicrometres==0||stratum.strateIndex<0
||stratum.topChunkZ<stratum.bottomChunkZ)
{error="Core.Liminal.Field v2 needs a finite stratum and resolved Core voxel size";return false;}
auto resolved=std::find_if(layout.strates.begin(),layout.strates.end(),
[&](const auto& value){return value.strateIndex==stratum.strateIndex
&&value.generatorKind=="SurfaceWorld";});
if(resolved==layout.strates.end()||!resolved->content.presentInCurrentField)
{error="Core.Liminal.Field v2 must target a resolved SurfaceWorld content band";return false;}
constexpr std::int64_t Q=WorldAssemblySubvoxelsPerVoxel;
const std::int64_t coreMicrons=layout.coreVoxelSizeMicrometres;
const auto micrometresToQ=[&](std::int64_t micrometres)
{
const std::int64_t numerator=micrometres*Q;
return numerator>=0?(numerator+coreMicrons/2)/coreMicrons
:(numerator-coreMicrons/2)/coreMicrons;
};
const std::int64_t qPerMetre=micrometresToQ(1000000);
if(qPerMetre<=0){error="Core.Liminal.Field v2 could not resolve fixed-scale metres";return false;}
const std::uint32_t seed=recipe.seed;
const WorldAssemblyLiminalVariantV1 variant=SelectCoreLiminalFieldVariantV1(seed);
const auto& definition=CoreLiminalFieldVariantsV1[static_cast<std::size_t>(variant)];
const std::int64_t bottom=static_cast<std::int64_t>(stratum.bottomChunkZ)
*recipe.layoutChunkWidthSubvoxels;
const std::int64_t top=(static_cast<std::int64_t>(stratum.topChunkZ)+1)
*recipe.layoutChunkWidthSubvoxels;
const std::int64_t contentBottom=micrometresToQ(resolved->content.bottomMicrometres);
const std::int64_t profileRelief=variant==WorldAssemblyLiminalVariantV1::repeatingHills
?2*micrometresToQ(definition.hillAmplitudeMicrometres):0;
const std::int64_t deckSweepRadius=static_cast<std::int64_t>(std::ceil(std::hypot(
static_cast<double>(definition.routeHalfWidthMicrometres+250000),
static_cast<double>(definition.routeHalfThicknessMicrometres+250000))));
const std::int64_t floorClearance=definition.routeHalfThicknessMicrometres
+deckSweepRadius+250000;
// Keep the field above the lower tunnel's mouth transition so the routed
// departure has a clean open section before it reaches the cave wall.
constexpr std::int64_t MinimumFieldGroundMarginMicrometres=20000000;
const std::int64_t fieldGroundMargin=std::max(floorClearance,
MinimumFieldGroundMarginMicrometres);
const std::int64_t floorHeight=contentBottom+profileRelief
+micrometresToQ(fieldGroundMargin);
if(floorHeight<=bottom||top<=floorHeight+8*qPerMetre)
{error="Core.Liminal.Field v2 has no room for its supported field and cap";return false;}
WorldAssemblySurfaceProfileV1 profile;
profile.version=1;
profile.kind=definition.surfaceKind;
profile.h0Subvoxels=floorHeight;
if(profile.kind==WorldAssemblySurfaceProfileKindV1::periodicHills)
{
profile.amplitudeXSubvoxels=micrometresToQ(definition.hillAmplitudeMicrometres);
profile.amplitudeYSubvoxels=profile.amplitudeXSubvoxels;
profile.wavelengthXSubvoxels=micrometresToQ(definition.hillWavelengthMicrometres);
profile.wavelengthYSubvoxels=profile.wavelengthXSubvoxels;
ResolveWorldAssemblySurfaceProfilePhasesV1(profile,seed);
}
if(!world_assembly_detail::ValidateSurfaceProfile(profile,seed,error))return false;
stratum.hasRecipeOverride=true;
stratum.recipeOverride=recipe;
stratum.recipeOverride.surfaceProfile=profile;
stratum.recipeOverride.contentHash=0;
stratum.surfaceProfile=profile;
stratum.capHeightSubvoxels=top-4*qPerMetre;
stratum.capUndulationSubvoxels=0;
stratum.capDetailAmplitudeSubvoxels=0;
stratum.capRidgeAmplitudeSubvoxels=0;
stratum.waterEnabled=false;
stratum.waterSurfaceSubvoxels=0;
const auto groundHeightAt=[&](std::int64_t x,std::int64_t y)
{
if(profile.kind==WorldAssemblySurfaceProfileKindV1::flatField)
return profile.h0Subvoxels;
const std::int64_t turnX=world_assembly_detail::SurfaceProfileTurnAtCoordinate(
x,profile.wavelengthXSubvoxels,profile.phaseXTurnsQ32);
const std::int64_t turnY=world_assembly_detail::SurfaceProfileTurnAtCoordinate(
y,profile.wavelengthYSubvoxels,profile.phaseYTurnsQ32);
return profile.h0Subvoxels
+world_assembly_detail::MulQ30(profile.amplitudeXSubvoxels,
world_assembly_detail::CosineQ30(turnX))
+world_assembly_detail::MulQ30(profile.amplitudeYSubvoxels,
world_assembly_detail::CosineQ30(turnY));
};
const auto routeXY=CoreLiminalRouteXYV2(-12*qPerMetre,0,12*qPerMetre,0,qPerMetre);
const auto squareRoot=[](std::uint64_t value)
{return world_assembly_detail::IntegerSqrt(value);};
const auto directionQ16=[&](std::int64_t dx,std::int64_t dy)
{
const std::uint64_t length=squareRoot(static_cast<std::uint64_t>(dx*dx+dy*dy));
if(length==0)return std::array<std::int32_t,2>{{0,0}};
const auto rounded=[&](std::int64_t value)
{return value>=0?(value+static_cast<std::int64_t>(length/2))
/static_cast<std::int64_t>(length)
:(value-static_cast<std::int64_t>(length/2))
/static_cast<std::int64_t>(length);};
return std::array<std::int32_t,2>{{
static_cast<std::int32_t>(rounded(dx*65536)),
static_cast<std::int32_t>(rounded(dy*65536))}};
};
const auto arrivalDirection=directionQ16(routeXY[1][0]-routeXY[0][0],
routeXY[1][1]-routeXY[0][1]);
const auto departureDirection=directionQ16(routeXY.back()[0]-routeXY[routeXY.size()-2][0],
routeXY.back()[1]-routeXY[routeXY.size()-2][1]);
const std::int64_t playerCenterAboveFloor=micrometresToQ(1130000);
const std::int64_t landingClearance=micrometresToQ(6000000);
const std::int64_t supportDepth=micrometresToQ(2000000);
const auto makePad=[&](std::string id,ResolvedLayoutLandingPadRoleV1 role,
const std::array<std::int64_t,2>& xy,const std::array<std::int32_t,2>& direction)
{
ResolvedLayoutLandingPadV1 pad;
pad.stableId=std::move(id);pad.role=role;
pad.floorHeightSubvoxels=groundHeightAt(xy[0],xy[1]);
pad.positionSubvoxels={xy[0],xy[1],pad.floorHeightSubvoxels+playerCenterAboveFloor};
pad.approachDirectionQ16=direction;
pad.clearanceSubvoxels=landingClearance;
pad.supportDepthSubvoxels=supportDepth;
pad.certifiedWalkableWithSupport=true;
return pad;
};
resolved->recipeId=std::string(CoreLiminalFieldRecipeIdV1);
resolved->recipeVersion=CoreLiminalFieldRecipeVersionV2;
resolved->landingPads.clear();
resolved->landingPads.push_back(makePad("core/liminal-field/landing/arrival/000",
ResolvedLayoutLandingPadRoleV1::arrival,routeXY.front(),arrivalDirection));
resolved->landingPads.push_back(makePad("core/liminal-field/landing/departure/000",
ResolvedLayoutLandingPadRoleV1::departure,routeXY.back(),departureDirection));
std::uint64_t routeLength=0;
stratum.route=BuildCoreLiminalRouteV2(profile,resolved->landingPads[0],
resolved->landingPads[1],definition,seed,stratum.strateIndex,coreMicrons,
qPerMetre,routeLength,error);
if(!error.empty())return false;
stratum.routeConstraints={1,micrometresToQ(340000),micrometresToQ(1760000),
17560,micrometresToQ(125000),micrometresToQ(250000),
micrometresToQ(1000000),micrometresToQ(120000),3};
resolved->anchors.erase(std::remove_if(resolved->anchors.begin(),resolved->anchors.end(),
[](const auto& anchor){return anchor.kind=="route_point"
||anchor.kind=="liminal_route_point"||anchor.kind=="liminal_variant"
||anchor.kind=="heightfield_profile";}),resolved->anchors.end());
ResolvedLayoutAnchorV2 profileAnchor;
profileAnchor.kind="heightfield_profile";
profileAnchor.stableId="heightfield-profile/liminal/"
+std::to_string(static_cast<std::uint32_t>(profile.kind));
profileAnchor.units="profile kind enum";
profileAnchor.values={1,static_cast<std::int64_t>(profile.kind)};
resolved->anchors.push_back(std::move(profileAnchor));
std::uint64_t routeAnchorDistance=0,lastRouteAnchorDistance=0;
for(std::size_t pointIndex=0;pointIndex<stratum.route.size();++pointIndex)
{
if(pointIndex>0)
{
const auto& previous=stratum.route[pointIndex-1].positionSubvoxels;
const auto& current=stratum.route[pointIndex].positionSubvoxels;
const auto dx=static_cast<std::uint64_t>(current[0]-previous[0]);
const auto dy=static_cast<std::uint64_t>(current[1]-previous[1]);
routeAnchorDistance+=world_assembly_detail::IntegerSqrt(dx*dx+dy*dy);
}
const bool first=pointIndex==0;
const bool last=pointIndex+1==stratum.route.size();
const bool spaced=routeAnchorDistance-lastRouteAnchorDistance
>=static_cast<std::uint64_t>(8*qPerMetre);
if(!first&&!last&&!spaced)continue;
const auto& point=stratum.route[pointIndex];
ResolvedLayoutAnchorV2 routeAnchor;
routeAnchor.kind="route_point";
routeAnchor.stableId="route/"+std::to_string(pointIndex);
routeAnchor.units="1/256 Core voxel";
routeAnchor.values={point.positionSubvoxels[0],point.positionSubvoxels[1],
point.positionSubvoxels[2]+playerCenterAboveFloor,point.radiusSubvoxels};
resolved->anchors.push_back(std::move(routeAnchor));
lastRouteAnchorDistance=routeAnchorDistance;
}
if(!layout.finalize(error))return false;
const auto reportPad=[&](const ResolvedLayoutLandingPadV1& pad)
{
std::printf("[ResolvedLandingPad] strate=%d id=%s role=%s position_m=(%.3f,%.3f,%.3f) "
"floor_m=%.3f approach_q16=(%d,%d) clearance_m=%.3f support_m=%.3f "
"certified_walkable_with_support=true status=PASS\n",stratum.strateIndex,
pad.stableId.c_str(),pad.role==ResolvedLayoutLandingPadRoleV1::arrival?"arrival":"departure",
pad.positionSubvoxels[0]/static_cast<double>(qPerMetre),
pad.positionSubvoxels[1]/static_cast<double>(qPerMetre),
pad.positionSubvoxels[2]/static_cast<double>(qPerMetre),
pad.floorHeightSubvoxels/static_cast<double>(qPerMetre),
pad.approachDirectionQ16[0],pad.approachDirectionQ16[1],
pad.clearanceSubvoxels/static_cast<double>(qPerMetre),
pad.supportDepthSubvoxels/static_cast<double>(qPerMetre));
};
reportPad(resolved->landingPads[0]);reportPad(resolved->landingPads[1]);
std::printf("[LiminalPadRoute] points=%zu path_length_m=%.3f spacing_max_m=4.000 "
"profile_ground_following=true status=PASS\n",stratum.route.size(),
routeLength/static_cast<double>(qPerMetre));
return true;
}
bool PlanCoreLiminalFieldV2(WorldAssemblyRecipeV1& recipe,
WorldAssemblyStratumPlanV1& stratum,const ResolvedLayoutSnapshotV3& layout,
std::string& error)
{
error.clear();
if(layout.coreVoxelSizeMicrometres==0||stratum.strateIndex<0
||stratum.topChunkZ<stratum.bottomChunkZ)
{error="Core.Liminal.Field v2 needs a finite stratum and resolved Core voxel size";return false;}
const auto resolved=std::find_if(layout.strates.begin(),layout.strates.end(),
[&](const auto& value){return value.strateIndex==stratum.strateIndex
&&value.recipeId==CoreLiminalFieldRecipeIdV1
&&value.recipeVersion==CoreLiminalFieldRecipeVersionV2;});
if(resolved==layout.strates.end()||!resolved->content.presentInCurrentField
||stratum.surfaceProfile.version!=1)
{error="Core.Liminal.Field v2 needs its prepared profile and resolved landing pads";return false;}
const ResolvedLayoutLandingPadV1* arrivalPad=nullptr;
const ResolvedLayoutLandingPadV1* departurePad=nullptr;
for(const auto& pad:resolved->landingPads)
{
if(pad.role==ResolvedLayoutLandingPadRoleV1::arrival)
{if(arrivalPad){error="Core.Liminal.Field v2 has multiple arrival pads";return false;}arrivalPad=&pad;}
if(pad.role==ResolvedLayoutLandingPadRoleV1::departure)
{if(departurePad){error="Core.Liminal.Field v2 has multiple departure pads";return false;}departurePad=&pad;}
}
if(!arrivalPad||!departurePad||!arrivalPad->certifiedWalkableWithSupport
||!departurePad->certifiedWalkableWithSupport)
{error="Core.Liminal.Field v2 requires certified arrival and departure pads";return false;}
const ResolvedLayoutPassageV1* arrival=nullptr;
const ResolvedLayoutPassageV1* departure=nullptr;
for(const auto& passage:layout.passages)
{
if(passage.lowerStrateIndex==stratum.strateIndex)
{if(arrival){error="Core.Liminal.Field v2 requires one arrival passage";return false;}arrival=&passage;}
if(passage.upperStrateIndex==stratum.strateIndex)
{if(departure){error="Core.Liminal.Field v2 requires one departure passage";return false;}departure=&passage;}
}
if(!arrival||!departure||arrival->lowerMouthSubvoxels!=arrivalPad->positionSubvoxels
||departure->upperMouthSubvoxels!=departurePad->positionSubvoxels)
{error="Core.Liminal.Field v2 passage mouths are not bound to the certified layer pads";return false;}
constexpr std::int64_t Q=WorldAssemblySubvoxelsPerVoxel;
const std::int64_t coreMicrons=layout.coreVoxelSizeMicrometres;
const auto micrometresToQ=[&](std::int64_t micrometres)
{
const std::int64_t numerator=micrometres*Q;
return numerator>=0?(numerator+coreMicrons/2)/coreMicrons
:(numerator-coreMicrons/2)/coreMicrons;
};
const std::int64_t qPerMetre=micrometresToQ(1000000);
const std::int64_t playerCenterAboveFloor=micrometresToQ(1130000);
if(qPerMetre<=0||arrivalPad->positionSubvoxels[2]
!=arrivalPad->floorHeightSubvoxels+playerCenterAboveFloor
||departurePad->positionSubvoxels[2]
!=departurePad->floorHeightSubvoxels+playerCenterAboveFloor)
{error="Core.Liminal.Field v2 pad floor and capsule-center heights differ";return false;}
const std::uint32_t seed=recipe.seed;
const WorldAssemblyLiminalVariantV1 variant=SelectCoreLiminalFieldVariantV1(seed);
const auto& definition=CoreLiminalFieldVariantsV1[static_cast<std::size_t>(variant)];
const auto& profile=stratum.surfaceProfile;
std::uint64_t totalHorizontal=0;
std::vector<WorldAssemblyRoutePointV1> route=BuildCoreLiminalRouteV2(profile,
*arrivalPad,*departurePad,definition,seed,stratum.strateIndex,coreMicrons,
qPerMetre,totalHorizontal,error);
if(!error.empty())return false;
if(route.size()!=stratum.route.size())
{error="Core.Liminal.Field v2 route differs from the path used to certify its pads";return false;}
for(std::size_t index=0;index<route.size();++index)
if(route[index].stableFeatureId!=stratum.route[index].stableFeatureId
||route[index].positionSubvoxels!=stratum.route[index].positionSubvoxels
||route[index].radiusSubvoxels!=stratum.route[index].radiusSubvoxels)
{error="Core.Liminal.Field v2 route differs from the path used to certify its pads";return false;}
WorldAssemblyLiminalFieldPlanV1 liminal;
liminal.version=CoreLiminalFieldRecipeVersionV2;
liminal.variant=variant;liminal.seed=seed;
liminal.arrivalMouthSubvoxels=arrivalPad->positionSubvoxels;
liminal.departureMouthSubvoxels=departurePad->positionSubvoxels;
liminal.floorHeightSubvoxels=profile.h0Subvoxels;
liminal.routeHalfWidthSubvoxels=micrometresToQ(definition.routeHalfWidthMicrometres);
liminal.routeHalfThicknessSubvoxels=micrometresToQ(definition.routeHalfThicknessMicrometres);
liminal.routeOffsetSubvoxels=150*qPerMetre;
liminal.supportSpacingSubvoxels=micrometresToQ(definition.supportSpacingMicrometres);
liminal.supportRadiusSubvoxels=micrometresToQ(definition.supportRadiusMicrometres);
liminal.mouthFloorOffsetSubvoxels=playerCenterAboveFloor;
stratum.route=std::move(route);
stratum.routeConstraints={1,micrometresToQ(340000),micrometresToQ(1760000),
17560,micrometresToQ(125000),micrometresToQ(250000),
micrometresToQ(1000000),micrometresToQ(120000),3};
stratum.liminalField=liminal;
std::string validationError;
if(!world_assembly_detail::ValidateSurfaceProfile(stratum.surfaceProfile,seed,validationError)
||!world_assembly_detail::ValidateStratum(stratum,recipe,validationError))
{error=validationError.empty()?"Core.Liminal.Field v2 produced an invalid plan":validationError;return false;}
std::printf("[CoreLiminalFieldPlan] recipe=%.*s version=%u variant=%.*s seed=%u "
"arrival_pad=%s departure_pad=%s route_points=%zu path_length_m=%.3f "
"profile_ground_following=true route_grade_limit_deg=15.000 status=PASS\n",
static_cast<int>(CoreLiminalFieldRecipeIdV1.size()),CoreLiminalFieldRecipeIdV1.data(),
liminal.version,static_cast<int>(definition.name.size()),definition.name.data(),seed,
arrivalPad->stableId.c_str(),departurePad->stableId.c_str(),stratum.route.size(),
totalHorizontal/static_cast<double>(qPerMetre));
return true;
}
std::string WorldAssemblyManifest::ToJson() const std::string WorldAssemblyManifest::ToJson() const
{ {
std::string error; std::string error;
@@ -2391,6 +3183,27 @@ std::string WorldAssemblyManifest::ToJson() const
<< ",\"minimum_clearance_q\":" << constraints.minimumClearanceSubvoxels << ",\"minimum_clearance_q\":" << constraints.minimumClearanceSubvoxels
<< ",\"minimum_support_probes\":" << constraints.minimumSupportProbes << '}'; << ",\"minimum_support_probes\":" << constraints.minimumSupportProbes << '}';
} }
if (stratum.liminalField.version != 0)
{
const auto& liminal = stratum.liminalField;
out << ",\"liminal_field\":{\"version\":" << liminal.version
<< ",\"variant\":" << static_cast<std::uint32_t>(liminal.variant)
<< ",\"seed\":" << liminal.seed << ",\"arrival_mouth_q\":["
<< liminal.arrivalMouthSubvoxels[0] << ','
<< liminal.arrivalMouthSubvoxels[1] << ','
<< liminal.arrivalMouthSubvoxels[2] << "],\"departure_mouth_q\":["
<< liminal.departureMouthSubvoxels[0] << ','
<< liminal.departureMouthSubvoxels[1] << ','
<< liminal.departureMouthSubvoxels[2]
<< "],\"mouth_floor_offset_q\":" << liminal.mouthFloorOffsetSubvoxels
<< ",\"floor_height_q\":"
<< liminal.floorHeightSubvoxels
<< ",\"route_half_width_q\":" << liminal.routeHalfWidthSubvoxels
<< ",\"route_half_thickness_q\":" << liminal.routeHalfThicknessSubvoxels
<< ",\"route_offset_q\":" << liminal.routeOffsetSubvoxels
<< ",\"support_spacing_q\":" << liminal.supportSpacingSubvoxels
<< ",\"support_radius_q\":" << liminal.supportRadiusSubvoxels << '}';
}
out << ",\"route\":["; out << ",\"route\":[";
for (std::size_t p = 0; p < stratum.route.size(); ++p) for (std::size_t p = 0; p < stratum.route.size(); ++p)
{ {
@@ -0,0 +1,37 @@
# VoxelForge — open read-only review: what should be done and focused on now?
You are an outside reviewer. **Do not edit, create or delete any file, and do not build or run anything.** Read the
repository and its reports and reason. Be thorough; mark each claim as [CODE] (with file:line), [MEASURED] (from a
report), or [INFERENCE].
## The project, in the owner's words and goals
- VoxelForge is a UE5 density-field voxel plugin for a **multiplayer** game. The world is made of stacked "strates"
(layers) linked by tunnels. The standalone C++ generator is "Core" (`Standalone/Core`), and it is also compiled into
Unreal. Read `CLAUDE.md`, `CODEMAP.md`, `ARCHITECTURE.md`, `GDD.md`, `COMPOSER-NOTES.md` and `fable-idea.md` at the
plugin root for the game and design intent. The research reports are in
`Standalone/Research/WorldAssemblerM3/Linux*/REPORT.md` and the `Astra*/` reviews.
- Owner goals stated over time:
- the near terrain (LOD0–2) ready in ~1 s on arrival, then far terrain keeps filling in at low priority;
- same seed gives the same world on every machine (byte-identity with older versions is NOT required);
- smooth for VR and high-speed travel;
- all generators (surface, floating islands, caves, dreamcore/liminal, etc.) working in Core;
- the system should eventually *invent* strates and worlds from tagged authored content plus a seed;
- keep things simple and avoid over-complication.
- The current main line is branch `assembler/world`.
## Your task
Look at the whole: code, reports, design docs, and the direction of the last few dozen rounds. Tell the owner what
should be done and focused on now, whatever it is. That may be performance, features, generation quality, gameplay
foundations, architecture, technical debt, tooling, process, or stopping something. Don't assume the recent focus is
the right one; question it if the evidence says so.
Answer:
1. **Where the project actually is**: what works, what's fragile, what's missing for the game to exist.
2. **The top 3–5 things to focus on now**, ranked, each with why, a rough size, what "done" looks like, and how to
tell it worked.
3. **What to stop or deprioritise**, and why.
4. **Risks or debts** that will bite later if ignored.
Write plain Markdown, at most ~1,500 words, readable by a non-specialist owner (explain jargon briefly).
@@ -0,0 +1,72 @@
[INFERENCE] **Focus now on a small, persistent multiplayer descent, supported by end-to-end Unreal performance work.** VoxelForge has substantial terrain technology. The larger uncertainty is whether it delivers a reliable, interesting shared expedition. Another sequence of tunnel optimizations will not resolve that.
Read-only review of `assembler/world` at `044cd6d`; no files changed and no builds, tests, or project execution. [CODE] identifies repository evidence; design-document references establish intent, not implementation. [MEASURED] means reported results, not independently reproduced measurements.
**1. Where the project actually is**
[CODE] **Core is real and integrated, but generator implementation remains mixed.** Unreal compiles the canonical standalone sources through validated wrappers. Core dispatches to surface, floating-island and native graph evaluators, with a legacy-generator fallback when appropriate. That is useful coverage, but does not mean every family has a complete native implementation. See [VoxelForgeCore.Build.cs:15](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForgeCore/VoxelForgeCore.Build.cs:15) and [ShapeWorldInternal.h:600](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:600).
[MEASURED] **Passage correctness and speed have improved substantially.** L109 simplified tunnels and removed support-restoration machinery, reducing floating-island and tunnel-network near-generation times by approximately 31–38%. L125 reports 21,425/21,425 open route samples, a maximum sampled floor offset of 7.884 mm, and matching cross-compiler/worker hashes. These are meaningful engineering achievements. [L109](Standalone/Research/WorldAssemblerM3/Linux109/REPORT.md), [L125](Standalone/Research/WorldAssemblerM3/Linux125/REPORT.md).
[MEASURED] **The one-second player experience is not achieved.** L125’s standalone near-ready medians were approximately 0.78–1.03 seconds. L127’s Unreal startup took 2.22–2.51 seconds; at 30 m/s, median near coverage fell from 43.36% to 40.22% after the smooth-tunnel merge. Both versions recovered after stopping. L130 found no measurable timing improvement and skipped Unreal movement. These are different workloads; subtracting their times would not isolate a particular overhead. [L125](Standalone/Research/WorldAssemblerM3/Linux125/REPORT.md), [L127](Standalone/Research/WorldAssemblerM3/Linux127/REPORT.md), [L130](Standalone/Research/WorldAssemblerM3/Linux130/REPORT.md).
[CODE] **Multiplayer foundations exist; the multiplayer lifecycle remains unfinished here.** There are season hashes, local terrain-edit entry points, and streaming-anchor infrastructure. The architecture explicitly leaves join replication, authoritative edit messages, late-join snapshots and networked season changes open. The edit entry point directly dispatches to local modification paths. [VoxelWorld.h:574](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Public/VoxelWorld.h:574), [VoxelWorld.cpp:15771](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelWorld.cpp:15771), [ARCHITECTURE.md:2408](/home/jahni/VoxelM/Plugins/VoxelForge/ARCHITECTURE.md:2408).
[CODE] **The composer is more than a proposal, but less than the full content vision.** It rolls candidates, measures them and rejects failures. The intended system additionally combines tagged geometry, materials, decorations, creatures and audio; theme coherence remains an open design item. [VoxelSeasonManifest.cpp:2523](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelSeasonManifest.cpp:2523), [COMPOSER-NOTES.md:1512](/home/jahni/VoxelM/Plugins/VoxelForge/COMPOSER-NOTES.md:1512).
[INFERENCE] **This repository demonstrates a capable terrain platform, not yet a validated game loop.** I cannot establish what gameplay exists outside the plugin. The GDD itself identifies “Is the descent fun?” as its first unresolved question. [GDD.md:441](/home/jahni/VoxelM/Plugins/VoxelForge/GDD.md:441).
**2. Ranked priorities**
All sizes below are [INFERENCE]: rough focused engineering estimates, excluding substantial art production.
**1 — Deliver one multiplayer expedition that survives reconnecting. Size: 3–6 weeks.**
[INFERENCE] Use two contrasting strates and their connecting tunnel. Two players descend, find one destination, uncover or collect something, return, and retain their changes after restart. Include a third player joining after terrain has changed. Avoid expanding immediately into the city economy or community backend.
[INFERENCE] The necessary foundation is a server-approved world identity, ordered authoritative edits, a saved snapshot plus subsequent edits, and collision around separated players. Give edits stable identifiers so retransmission cannot apply them twice. Reuse the existing local edit and anchor systems.
[INFERENCE] **Done:** host/client edits agree; a late joiner reconstructs the same terrain; restart preserves it; incompatible world versions fail clearly; remote players have ground when away from the host.
[INFERENCE] **Success:** a complete shared expedition without terrain disagreement, lost progress or collision failures—and players can explain what made them want to descend again. This tests the project’s central assumptions much earlier than a complete RPG would.
**2 — Close the Unreal arrival and movement gap. Size: 1–3 weeks initially.**
[MEASURED] L122 found idle generation capacity while admission was blocked; L123 measured approximately 19 ms from region request to game-thread observation despite sub-millisecond median compilation. Simple lookahead and admission relaxations failed their combined coverage/frame-time gates. L121’s modest extra far-apply lane did improve far coverage. [L121](Standalone/Research/WorldAssemblerM3/Linux121/REPORT.md), [L122](Standalone/Research/WorldAssemblerM3/Linux122/REPORT.md), [L123](Standalone/Research/WorldAssemblerM3/Linux123/REPORT.md).
[INFERENCE] First establish one current-build, rendered Unreal baseline on intended hardware. Measure arrival through **visible terrain and usable collision**, then walking, 30 m/s travel, turns, teleporting and edits. Record near coverage, far gaps, frame-time spikes and memory separately. NullRHI measurements, which omit rendering, cannot certify VR comfort.
[INFERENCE] Investigate region completion, admission and publication before another broad density rewrite. A game-thread continuation is a candidate experiment, not a proven solution; keep world mutation on its owning thread.
[INFERENCE] **Done:** approximately one-second LOD0–2 readiness on the agreed hardware, safe collision, steadily filling far terrain, and an explicit movement-coverage target without worsening frame spikes. **Success:** repeated rendered runs meet those limits, including with remote-player collision work. Do not redefine “ready” as only the nearest handful of tiles.
**3 — Turn generator breadth into playable variety. Size: 2–4 weeks for the first content slice.**
[INFERENCE] Establish a compact capability matrix for each family: Core dispatch, entrances/exits, interior connectivity, edits, materials/content, far terrain and performance. Include dreamcore/liminal explicitly; motif generation alone does not establish a convincing anomalous place.
[MEASURED] L58 illustrates the distinction: its lattice route passed reachability checks, but the review described a corkscrew tunnel rather than the intended open vertical shaft. Technical validity and identity are separate acceptance criteria. [L58](Standalone/Research/WorldAssemblerM3/Linux58/REPORT.md).
[INFERENCE] Extend the existing offline composer with a small tagged library: a grounded cave, an island environment and one liminal identity, each with coherent materials, sound and a destination. Preserve owner review before publishing a season.
[INFERENCE] **Done:** several seeds produce recognizably different, coherent places with findable progression and usable encounter spaces. **Success:** players navigate them without developer guidance and distinguish their identities without reading labels. Expand families after this works.
**4 — Consolidate the evidence and remove obsolete complexity incrementally. Size: 2–4 days, then ongoing.**
[INFERENCE] Maintain one short current-state page: shipped behavior, open defects, active baseline, hardware and next acceptance test. Keep historical reports as history. Remove rejected experimental paths when they no longer serve a specific comparison; extract stable responsibilities when touching oversized files.
[INFERENCE] **Done:** a contributor can identify the current evaluator, runtime path, multiplayer gaps and performance limits without reconstructing 130 rounds. **Success:** new work no longer repeats rejected experiments or treats old measurements as current.
**3. Stop or deprioritise**
- [INFERENCE] **Pause open-ended tunnel micro-optimization.** L125/L126 missed the older speed bar; L130’s reach correction produced no measurable speedup. Keep correctness fixes, but require a current cost account and a bounded experiment before further optimization. [MEASURED: L126](Standalone/Research/WorldAssemblerM3/Linux126/REPORT.md), [L130](Standalone/Research/WorldAssemblerM3/Linux130/REPORT.md).
- [INFERENCE] **Do not implement Astra recommendations mechanically.** Astra1’s support-heavy model predates L109’s simplification. Astra2/3 offer hypotheses, not measured wins for today’s code. Re-profile before adopting them.
- [INFERENCE] **Defer GPU generation, a mesher replacement, elaborate discovery systems and large-scale backend work.** None currently resolves the missing shared expedition.
- [INFERENCE] **Stop requiring old-version byte identity for deliberate generation changes.** Preserve peer agreement within a version and explicit versioned saves; permit reviewed geometry improvements.
**4. Debts that will bite later**
[INFERENCE] **Determinism needs a deployment contract.** Linux Clang/GCC and worker-count agreement is strong evidence, not proof across every intended machine. Include Windows/compiler/CPU combinations actually supported. World identity must cover evaluator, content, layout, scale and edit state—not merely seed. [MEASURED: L130](Standalone/Research/WorldAssemblerM3/Linux130/REPORT.md).
[CODE] **Scale and fallback complexity remain active integration risks.** Core is opt-in, and runtime queries bridge different coordinate conventions. [VoxelWorld.h:300](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Public/VoxelWorld.h:300), [VoxelWorld.cpp:15612](/home/jahni/VoxelM/Plugins/VoxelForge/Source/VoxelForge/Private/VoxelWorld.cpp:15612). [INFERENCE] Test edits, content placement and collision across those boundaries before broadening authoring.
[INFERENCE] **A passing route audit does not prove a whole strate is enjoyable, connected or suitable for six-player combat.** Retain geometric checks, but add actual traversal, destination discovery and encounter-space evaluation. Those are now more valuable than another polished tunnel benchmark.
@@ -0,0 +1,33 @@
# Design49 — flat landing pad at tunnel ends (owner idea, reviewed 2026-11-02)
Owner: "the end of the tunnel should be flat; flatten the arrival with a fade-out that blends to the surrounding
density; a cheap stamp applied at arrival, no matter what's there."
A read-only design review (Claude Opus 5.5, medium effort) of `wip/l134-cave-planner` recommends a **clamp, not a
lerp**. Each tunnel end gets a level disc at the tunnel's end floor P. Out to a blend radius, the native ground is
squeezed between two gentle cones (12°) starting at P: a lower one that fills solid, an upper one that carves air.
Inside the disc the floor is exactly flat; outside it, the slope is never steeper than max(cone, native), with no
overhang or cliff. The full note is in the session transcript; key points:
- **Placement:** the last step of `ApplyResolvedStrateComposition`, after the passage composer, on both the native and
non-native branches. The fill is masked by the tube-only distance (bore stays open). It replaces the landing-chamber
box, shell and floor-shell special cases that caused L152–L156.
- **Formula** (metres; ×cos α for 1-Lipschitz). d = max(0, r−R), t = tan α.
- Fill: `Lo = P − t·d`, `F = max(z−Lo, (Lo−Ds)−z, r−(R+B))`.
- Air: `Hi = P + t·d`, `A = min(z−Hi, (Hi+H)−z, (R+B)−r)`.
- Result: `c = max(min(native, max(F, −tubeDist)), A)`.
- Hard min/max inside R; smooth-min width ramps 0 → 0.5 m over the first metre outside it.
- Why not lerp: native density saturates at about ±4.125 voxels (`DetailSupremum`), so a lerp makes cliffs and
films.
- **Parameters:** α = 12°, R = max(mouthRadius + 1 m, 3 m), B = min(8 m, |Δh_ring|/t + 1 m), H = 3 m, Ds = 1.25 m.
- **Planner:**
- P = h(end), with h′ = 0 over the last R.
- Sample the edge ring; move P into the feasible band or take the next deterministic site.
- Rewrite route heights inside R+B to the clamped ground.
- Content-band check including P−tB−Ds and P+tB+H.
- **Bounds:** the pad influence box goes into the proof bounds, the fallback overlap test, the **tile feature
bounds** (critical: the composer early-returns on an empty route list) and the proof-miss sampler.
- **Audits:** new `LandingPadAudit` (flatness ±2 cm, support, headroom, 36 radial blend transects ≤ 15° with one
crossing) plus negative controls (pad off, 2% tilt, B halved, bore mask removed).
- **Risks:** caves under pads (connectivity), low ceilings, shaft footprints, island edges and their air gap, liminal
decks; off-route edges on non-heightfield layers are a metric only.
@@ -0,0 +1,9 @@
# Linux130 — passage reach
On `wip/l130-reach-units` from `b96dd73`, passage influence is now **1.0533 m**: 0.75 m shell + 0.21 m relief + 0.50 m smooth-max support (`width/6`) + 0.01 m margin. One metres-to-core-voxels conversion feeds proof, feature, fallback and off-grid bounds; the sweep cutoff uses the same metre value. The dead 4 m cutoff is removed. Evaluator version is 227.
The debug shell/crest audit sampled 25,728 points, including 5 mm inside and outside the reach; 18,993 had sign-relevant passage contribution. Proof and feature bounds missed none. The nine-station floor probe still peaks at **7.884 mm**, matching L125. The 23 L125 views and six L129 mouth close-ups are byte-identical. Fast verification, walkability and shell audits pass; the seam audit found 0 mismatches across 4 pairs / 4,900 shared samples.
Clang/GCC W1/W8/W12 hashes agree across all 18 runs. Versus v225, hashes changed for owner4 full-detail, whole-view and LOD0–3; owner2 surface-world and floating-islands whole-view and LOD2–3. Exact-query hashes stayed unchanged. The v227 canonical baselines are recorded; changed hashes reflect the shorter density influence halo, while sampled sign-relevant bounds and captured surfaces remain covered.
L80 timing used 10 interleaved pairs per stratum/worker count. Across the eight case medians, the weighted deltas were +0.33% for LOD0–2 and +0.31% whole-view. Across 80 paired runs, mean deltas were +0.26% (95% interval −0.44% to +0.96%) and +0.47% (−0.06% to +1.00%); both intervals include zero. No measurable timing change; UE movement was skipped. The Linux UE host build succeeded in 76.30 s with `-NoUBA -MaxParallelActions=4`, without `prlimit`. `ARCHITECTURE.md` and `CODEMAP.md` had no note for this optimized influence reach, so they were unchanged.
@@ -0,0 +1,95 @@
#!/usr/bin/env python3
"""Run Clang/GCC owner hashes at W1/W8/W12 and compare with b96dd73."""
from __future__ import annotations
import json
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l130/hashes"
BASELINE = json.loads((ROOT / "Saved/Linux/l125/hashes/summary.json").read_text())
DATASETS = {
"owner4": ROOT / "Standalone/data/owner_world.json",
"owner2": ROOT / "Standalone/data/regression/owner_world_2strate_l61.json",
}
RECIPES = {"owner4": ("surface-world",),
"owner2": ("surface-world", "floating-islands")}
BINARIES = {
"clang": ROOT / "Saved/Linux/l130/bin/VoxelForgeCoreHeadless-l130-clang",
"gcc": ROOT / "Saved/Linux/l130/bin/VoxelForgeCoreHeadless-l130-gcc",
}
HASH_RE = re.compile(r"^\[Pipeline(?:FullDetailHash|WholeViewHash|LODHash|ExactQueryHash)\].*$", re.M)
STAMP_RE = re.compile(r"^\[VoxelForgeSourceStamp\] sha256=(\S+)$", re.M)
def run(dataset: str, recipe: str, workers: int, compiler: str) -> dict:
OUT.mkdir(parents=True, exist_ok=True)
label = f"{dataset}-{recipe}-w{workers}-{compiler}"
log = OUT / f"{label}.log"
layout = OUT / f"{label}.layout.json"
command = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1740s", str(BINARIES[compiler]), str(DATASETS[dataset]),
f"--workers={workers}", "--world-assembler-prototype", "--stream-benchmark",
"--stream-pipeline", "--force-generate", "--clip-radius=3",
"--max-clip-level=8", "--max-level=8", f"--recipe={recipe}",
"--stream-hash", f"--resolved-layout-output={layout}"]
started = time.monotonic()
with log.open("w", encoding="utf-8") as stream:
result = subprocess.run(command, cwd=ROOT, stdout=stream,
stderr=subprocess.STDOUT, check=False, timeout=1740)
output = log.read_text(encoding="utf-8", errors="replace")
hashes = HASH_RE.findall(output)
if result.returncode or len(hashes) != 12:
raise SystemExit(f"hash run failed: {label}; exit={result.returncode} "
f"hashes={len(hashes)} log={log}")
stamp = STAMP_RE.search(output)
row = {"dataset": dataset, "recipe": recipe, "workers": workers,
"compiler": compiler, "exit_code": result.returncode, "hashes": hashes,
"source_stamp": stamp.group(1) if stamp else None,
"elapsed_s": round(time.monotonic() - started, 3),
"log": str(log.relative_to(ROOT))}
print("[L130HashRun] " + json.dumps(row, sort_keys=True), flush=True)
return row
def main() -> int:
if any(not path.is_file() for path in (*DATASETS.values(), *BINARIES.values())):
raise SystemExit("missing L130 hash input")
old = {(row["dataset"], row["recipe"], row["workers"], row["compiler"]): row
for row in BASELINE["runs"]}
rows = []
for dataset, recipes in RECIPES.items():
for recipe in recipes:
for workers in (1, 8, 12):
for compiler in ("clang", "gcc"):
rows.append(run(dataset, recipe, workers, compiler))
(OUT / "runs-partial.json").write_text(
json.dumps(rows, indent=2) + "\n", encoding="utf-8")
groups = {}
for dataset, recipes in RECIPES.items():
for recipe in recipes:
group = [row for row in rows if row["dataset"] == dataset
and row["recipe"] == recipe]
key = f"{dataset}/{recipe}"
stable = all(row["hashes"] == group[0]["hashes"] for row in group)
baseline = old[(dataset, recipe, 12, "clang")]["hashes"]
changed = [i for i, (before, after) in enumerate(zip(baseline,
group[0]["hashes"])) if before != after]
groups[key] = {"all_workers_and_compilers_match": stable,
"matches_b96dd73_v225": not changed,
"changed_hash_rows_from_v225": changed,
"source_stamps": sorted({row["source_stamp"] for row in group})}
if not stable:
raise SystemExit(f"worker/compiler hash mismatch: {key}")
(OUT / "summary.json").write_text(
json.dumps({"baseline_source": "b96dd73", "runs": rows,
"groups": groups}, indent=2) + "\n", encoding="utf-8")
print("[L130HashSummary] " + json.dumps(groups, sort_keys=True), flush=True)
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,136 @@
#!/usr/bin/env python3
"""Render the six L129 b96dd73 mouth views against the L130 meshes."""
from __future__ import annotations
import hashlib
import html
import json
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
SOURCE = ROOT / "Saved/Linux/l129/captures/mouth-closeups/before"
SAVED = ROOT / "Saved/Linux/l130/captures/mouth-closeups"
CAPTURES = ROOT / "Standalone/Research/WorldAssemblerM3/Linux130/captures/mouth-closeups"
DATA = ROOT / "Standalone/data/owner_world.json"
HEADLESS = ROOT / "Saved/Linux/l130/bin/VoxelForgeCoreHeadless-l130-clang"
RENDERER = ROOT / "Saved/Linux/l130/bin/VoxelForgeOffscreenCapture-l130-clang"
HEADLESS_CONFIG = re.compile(
r"^\[HeadlessConfig\].*?camera_m=\(([^)]*)\) camera_rotation_deg=\(([^)]*)\)", re.M)
SCENE = re.compile(r"^\[OffscreenScene\].*?height_center_m=(-?[\d.]+)", re.M)
DRAWN = re.compile(r"drawn_triangles=(\d+)")
def wrapped(command: list[str]) -> list[str]:
return ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1740s", *map(str, command)]
def run(label: str, command: list[str], log: Path) -> str:
started = time.monotonic()
log.parent.mkdir(parents=True, exist_ok=True)
with log.open("w", encoding="utf-8") as output:
result = subprocess.run(wrapped(command), cwd=ROOT, stdout=output,
stderr=subprocess.STDOUT, check=False, timeout=1740)
content = log.read_text(encoding="utf-8", errors="replace")
if result.returncode:
raise SystemExit(f"{label} failed exit={result.returncode}: {log}")
print(f"[L130MouthProcess] {label} elapsed_s={time.monotonic()-started:.2f}",
flush=True)
return content
def parse_vector(text: str) -> tuple[float, ...]:
return tuple(float(value) for value in text.split(","))
def sha256(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
def main() -> int:
if any(not path.is_file() for path in (DATA, HEADLESS, RENDERER)):
raise SystemExit("missing L130 mouth capture input")
(CAPTURES / "b96dd73").mkdir(parents=True, exist_ok=True)
(CAPTURES / "L130").mkdir(parents=True, exist_ok=True)
rows = []
for source_log in sorted(SOURCE.glob("*-mouth-mesh.log")):
name = source_log.name.removesuffix("-mesh.log")
source_render = SOURCE / f"{name}-render.log"
baseline_mesh = SOURCE / f"{name}.vfmesh"
if not all(path.is_file() for path in (source_render, baseline_mesh)):
raise SystemExit(f"missing b96dd73 source artifact for {name}")
config = HEADLESS_CONFIG.search(source_log.read_text())
scene = SCENE.search(source_render.read_text())
if config is None or scene is None:
raise SystemExit(f"missing camera metadata for {name}")
camera = parse_vector(config.group(1))
yaw, pitch = parse_vector(config.group(2))
height_center = float(scene.group(1))
camera_arg = ",".join(f"{value:.6f}" for value in (*camera, yaw, pitch))
candidate_mesh = SAVED / "L130" / f"{name}.vfmesh"
candidate_mesh.parent.mkdir(parents=True, exist_ok=True)
run(f"{name}-mesh", [HEADLESS, DATA, "--world-assembler-prototype",
"--stream-benchmark", "--stream-pipeline", "--workers=12",
"--clip-radius=3", "--max-clip-level=0", "--max-level=0",
"--force-generate", "--recipe=surface-world", f"--camera={camera_arg}",
"--render-distance-chunks=1", f"--mesh-export={candidate_mesh}"],
SAVED / "L130" / f"{name}-mesh.log")
if not candidate_mesh.is_file() or candidate_mesh.stat().st_size == 0:
raise SystemExit(f"empty L130 mesh for {name}")
render_args = ["--size=1920x1080",
f"--camera-m={','.join(f'{value:.6f}' for value in camera)}",
f"--yaw-deg={yaw:.6f}", f"--pitch-deg={pitch:.6f}",
"--shade=readable", "--headlamp=on", "--height-tint=on",
f"--height-center-m={height_center:.6f}", "--height-range-m=32"]
images = {}
draw_counts = {}
for variant, mesh in (("b96dd73", baseline_mesh), ("L130", candidate_mesh)):
image = CAPTURES / variant / f"{name}.png"
render_log = SAVED / variant / f"{name}-render.log"
content = run(f"{name}-{variant}-render",
[RENDERER, mesh, image, *render_args], render_log)
drawn = DRAWN.search(content)
if drawn is None or int(drawn.group(1)) <= 0 or not image.is_file():
raise SystemExit(f"render has no visible triangles for {name}/{variant}")
images[variant] = image
draw_counts[variant] = int(drawn.group(1))
equal = images["b96dd73"].read_bytes() == images["L130"].read_bytes()
row = {"name": name, "camera_m": camera, "yaw_deg": yaw,
"pitch_deg": pitch, "height_center_m": height_center,
"source_mesh": str(baseline_mesh.relative_to(ROOT)),
"candidate_mesh": str(candidate_mesh.relative_to(ROOT)),
"baseline_png": str(images["b96dd73"].relative_to(ROOT)),
"candidate_png": str(images["L130"].relative_to(ROOT)),
"baseline_sha256": sha256(images["b96dd73"]),
"candidate_sha256": sha256(images["L130"]),
"byte_identical": equal, "drawn_triangles": draw_counts}
rows.append(row)
print(f"[L130MouthCompare] {name} byte_identical={equal} "
f"sha256={row['candidate_sha256']}", flush=True)
if len(rows) != 6:
raise SystemExit(f"expected six L129 mouth views, found {len(rows)}")
(CAPTURES / "summary.json").write_text(json.dumps(rows, indent=2) + "\n")
sections = []
for row in rows:
name = html.escape(row["name"])
sections.append(f"<section><h2>{name}</h2><p>byte identical: "
f"{str(row['byte_identical']).lower()}</p><div>"
f"<figure><figcaption>b96dd73 source mesh</figcaption><img src='b96dd73/{name}.png'></figure>"
f"<figure><figcaption>L130 reach units</figcaption><img src='L130/{name}.png'></figure>"
"</div></section>")
page = ("<!doctype html><meta charset='utf-8'><title>L130 mouth closeups</title>"
"<style>body{font:14px system-ui;margin:24px;background:#17191d;color:#eee}"
"section{margin:28px 0}div{display:grid;grid-template-columns:1fr 1fr;gap:12px}"
"img{width:100%;height:auto}p{color:#bbb}</style>"
"<h1>L129 mouth closeups: b96dd73 vs L130</h1>" + "\n".join(sections))
(CAPTURES / "COMPARE.html").write_text(page)
return 0 if all(row["byte_identical"] for row in rows) else 1
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,98 @@
#!/usr/bin/env python3
"""Render the 23 L125 camera poses with the L130 evaluator and compare them."""
from __future__ import annotations
import html
import importlib.util
import json
import shutil
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
RESEARCH = Path(__file__).resolve().parent
CAPTURES = RESEARCH / "captures"
SAVED = ROOT / "Saved/Linux/l130/captures"
BASELINE_INDEX = (ROOT / "Standalone/Research/WorldAssemblerM3/Linux119"
/ "captures/L119/capture-index.json")
REFERENCE = ROOT / "Standalone/Research/WorldAssemblerM3/Linux125/captures/L125"
LAYOUT = ROOT / "Saved/Linux/l130/hashes/owner4-surface-world-w12-clang.layout.json"
HEADLESS = ROOT / "Saved/Linux/l130/bin/VoxelForgeCoreHeadless-l130-clang"
RENDERER = ROOT / "Saved/Linux/l130/bin/VoxelForgeOffscreenCapture-l130-clang"
HELPER_PATH = ROOT / "Standalone/Research/WorldAssemblerM3/Linux113/RunL113Captures.py"
def main() -> int:
if any(not path.is_file() for path in
(BASELINE_INDEX, REFERENCE / "passage-0-1-0-rock-floor-station-1.png",
LAYOUT, HEADLESS, RENDERER)):
raise SystemExit("missing pose, layout, or L130 capture input")
spec = importlib.util.spec_from_file_location("l125_capture_runner",
ROOT / "Standalone/Research/WorldAssemblerM3/Linux125/RunL125Captures.py")
if spec is None or spec.loader is None:
raise SystemExit("cannot load the verified L125 pose set")
runner = importlib.util.module_from_spec(spec)
spec.loader.exec_module(runner)
runner.RESEARCH = RESEARCH
runner.CAPTURES = CAPTURES
runner.SAVED = SAVED
runner.LAYOUT = LAYOUT
runner.CLANG = HEADLESS
runner.RENDERER = RENDERER
runner.HELPERS.RESEARCH = RESEARCH
runner.HELPERS.CAPTURES = CAPTURES
runner.HELPERS.SAVED = SAVED
runner.HELPERS.BINARIES = {"L125": HEADLESS}
runner.HELPERS.RENDERERS = {"L125": RENDERER}
if runner.main() != 0:
raise SystemExit("L130 pose generation failed")
new_captures = CAPTURES / "L125"
new_saved = SAVED / "L125"
index_path = new_captures / "capture-index.json"
data = json.loads(index_path.read_text())
if len(data["captures"]) != 23:
raise SystemExit(f"expected 23 poses, found {len(data['captures'])}")
identical = 0
sections = []
for row in data["captures"]:
name = row["name"]
old_image = REFERENCE / f"{name}.png"
new_image = new_captures / f"{name}.png"
if not old_image.is_file() or not new_image.is_file():
raise SystemExit(f"missing comparison image for {name}")
same = old_image.read_bytes() == new_image.read_bytes()
identical += int(same)
row["variant"] = "L130-v227"
row["capture"] = row["capture"].replace("captures/L125/", "captures/L130/")
row["scene"] = row["scene"].replace("captures/L125/", "captures/L130/")
row["logs"] = [path.replace("captures/L125/", "captures/L130/")
for path in row.get("logs", [])]
sections.append(f"<section><h2>{html.escape(row['passage'])} — "
f"{html.escape(row.get('station', ''))}</h2><p>{html.escape(row['kind'])} · "
f"byte identical: {str(same).lower()}</p><div>"
f"<figure><figcaption>L125 v225</figcaption><img src='../../Linux125/captures/L125/{html.escape(name)}.png'></figure>"
f"<figure><figcaption>L130 v227</figcaption><img src='L130/{html.escape(name)}.png'></figure>"
"</div></section>")
if identical != 23:
raise SystemExit(f"only {identical}/23 L125 pose captures are byte-identical")
index_path.write_text(json.dumps(data, indent=2) + "\n")
old_captures, old_saved = CAPTURES / "L130", SAVED / "L130"
if old_captures.exists():
shutil.rmtree(old_captures)
if old_saved.exists():
shutil.rmtree(old_saved)
new_captures.rename(old_captures)
new_saved.rename(old_saved)
page = ("<!doctype html><meta charset='utf-8'><title>L130 passage captures</title>"
"<style>body{font:14px system-ui;margin:24px;background:#17191d;color:#eee}"
"section{margin:28px 0}div{display:grid;grid-template-columns:1fr 1fr;gap:12px}"
"img{width:100%;height:auto}p{color:#bbb}</style>"
"<h1>L125 v225 vs L130 v227: passage captures</h1>" + "\n".join(sections))
(CAPTURES / "COMPARE.html").write_text(page)
print(f"[L130PoseCapture] poses=23 byte_identical={identical}/23", flush=True)
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,137 @@
#!/usr/bin/env python3
"""Five interleaved L80 near-ready runs per stratum/worker vs b96dd73."""
from __future__ import annotations
import json
import os
import re
import statistics
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l130/timing"
PASS_ID = int(os.environ.get("VOXELFORGE_L130_TIMING_PASS", "1"))
REPLICATE_FIRST = (PASS_ID - 1) * 5 + 1
DATA = ROOT / "Standalone/data/owner_world.json"
BINARIES = {
"b96dd73": ROOT / "Saved/Linux/l125/bin/VoxelForgeCoreHeadless-l125-clang",
"L130": ROOT / "Saved/Linux/l130/bin/VoxelForgeCoreHeadless-l130-clang",
}
CAMERAS = {
0: (8.0, 16.0, -372.2626953125),
1: (25.8369140625, 3.19921875, -737.0),
2: (19.166015625, -14.2001953125, -753.3740234375),
3: (-20.0625, -5.068359375, -900.353515625),
}
PIPELINE_RE = re.compile(r"^\[StreamPipeline\] (.*)$", re.M)
FIELDS_RE = re.compile(r"([a-zA-Z0-9_]+)=([^ ]+)")
NEAR_RE = re.compile(
r"^\[WorldAssemblerNearLODReady\] lods=0-2 tiles=(\d+)/(\d+) cold_ready_s=([0-9.]+)$",
re.M)
def run(strate: int, workers: int, version: str, phase: str, replicate: int) -> dict:
OUT.mkdir(parents=True, exist_ok=True)
pass_suffix = "" if PASS_ID == 1 else f"-p{PASS_ID}"
label = f"s{strate}-w{workers}-{version}-{phase}{replicate}-l130-v227{pass_suffix}"
log = OUT / f"{label}.log"
camera = ",".join(f"{value:.12f}" for value in CAMERAS[strate]) + ",0,0"
command = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1740s", str(BINARIES[version]), str(DATA),
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
f"--workers={workers}", "--clip-radius=3", "--max-clip-level=8",
"--max-level=8", f"--camera={camera}", "--profile-lod-work",
"--stream-scheduler-policy=3"]
started = time.monotonic()
with log.open("w", encoding="utf-8") as output:
result = subprocess.run(command, cwd=ROOT, stdout=output,
stderr=subprocess.STDOUT, check=False, timeout=1740)
content = log.read_text(encoding="utf-8", errors="replace")
pipeline, near = PIPELINE_RE.search(content), NEAR_RE.search(content)
if result.returncode or pipeline is None or near is None:
raise SystemExit(f"timing run failed: {log}; exit={result.returncode}")
fields = dict(FIELDS_RE.findall(pipeline.group(1)))
row = {"strate": strate, "workers": workers, "version": version,
"phase": phase, "replicate": replicate, "exit_code": result.returncode,
"wall_s": round(time.monotonic() - started, 3),
"lod02_ready_s": float(near.group(3)),
"whole_view_ready_s": float(fields["whole_view_ready"].removesuffix("s")),
"log": str(log.relative_to(ROOT))}
print("[L130TimingRun] " + json.dumps(row, sort_keys=True), flush=True)
return row
def main() -> int:
if any(not path.is_file() for path in (DATA, *BINARIES.values())):
raise SystemExit("missing L130 timing input")
OUT.mkdir(parents=True, exist_ok=True)
load_1m = __import__("os").getloadavg()[0]
def cpu_idle(interval: float = 2.0) -> float:
def totals() -> tuple[int, int]:
fields = Path("/proc/stat").read_text().splitlines()[0].split()
values = [int(value) for value in fields[1:]]
return sum(values), values[3] + (values[4] if len(values) > 4 else 0)
before = totals()
time.sleep(interval)
after = totals()
return 100.0 * (after[1] - before[1]) / max(1, after[0] - before[0])
idle = cpu_idle()
pass_suffix = "" if PASS_ID == 1 else f"-p{PASS_ID}"
(OUT / f"machine-load-v227{pass_suffix}-before.txt").write_text(
f"1-minute load: {load_1m:.2f}\n2-second CPU idle: {idle:.1f}%\n")
print(f"[L130TimingGate] load_1m={load_1m:.2f} cpu_idle_2s={idle:.1f}%",
flush=True)
if load_1m >= 0.9 or idle < 97.0:
raise SystemExit("timing refused: machine is above the quiet-load gate")
rows = []
for strate in range(4):
for workers in (8, 12):
for version in BINARIES:
run(strate, workers, version, "warmup", 0)
for replicate in range(REPLICATE_FIRST, REPLICATE_FIRST + 5):
order = tuple(BINARIES) if replicate % 2 else tuple(reversed(BINARIES))
for version in order:
rows.append(run(strate, workers, version, "measured", replicate))
(OUT / f"runs-v227{pass_suffix}-partial.json").write_text(
json.dumps(rows, indent=2) + "\n", encoding="utf-8")
prior_runs = []
if PASS_ID > 1:
previous = OUT / ("v227-summary.json" if PASS_ID == 2
else "v227-combined-summary.json")
if previous.is_file():
prior_runs = json.loads(previous.read_text(encoding="utf-8")).get("runs", [])
all_rows = prior_runs + rows
measured_rows = [row for row in all_rows if row["phase"] == "measured"]
summary = []
for strate in range(4):
for workers in (8, 12):
group_size = sum(row["strate"] == strate and row["workers"] == workers
for row in measured_rows) // 2
item = {"strate": strate, "workers": workers,
"replicates_per_version": group_size}
medians = {}
for version in BINARIES:
group = [row for row in measured_rows if row["strate"] == strate
and row["workers"] == workers and row["version"] == version]
medians[version] = {}
for key in ("lod02_ready_s", "whole_view_ready_s"):
values = [row[key] for row in group]
medians[version][key] = statistics.median(values)
item[f"{version}_{key}_values"] = values
item[f"{version}_{key}_median"] = medians[version][key]
for key in ("lod02_ready_s", "whole_view_ready_s"):
item[f"L130_vs_b96dd73_{key}_delta_pct"] = round(
100.0*(medians["L130"][key]/medians["b96dd73"][key]-1.0), 2)
summary.append(item)
summary_path = OUT / ("v227-summary.json" if PASS_ID == 1
else f"v227-combined-summary.json")
summary_path.write_text(json.dumps(
{"runs": all_rows, "summary": summary}, indent=2) + "\n", encoding="utf-8")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,23 @@
<!doctype html><meta charset='utf-8'><title>L130 passage captures</title><style>body{font:14px system-ui;margin:24px;background:#17191d;color:#eee}section{margin:28px 0}div{display:grid;grid-template-columns:1fr 1fr;gap:12px}img{width:100%;height:auto}p{color:#bbb}</style><h1>L125 v225 vs L130 v227: passage captures</h1><section><h2>passage/0-1/0 — rock-floor/401.7m</h2><p>rock-floor-close · byte identical: true</p><div><figure><figcaption>L125 v225</figcaption><img src='../../Linux125/captures/L125/passage-0-1-0-rock-floor-station-1.png'></figure><figure><figcaption>L130 v227</figcaption><img src='L130/passage-0-1-0-rock-floor-station-1.png'></figure></div></section>
<section><h2>passage/0-1/0 — rock-floor/792.8m</h2><p>rock-floor-close · byte identical: true</p><div><figure><figcaption>L125 v225</figcaption><img src='../../Linux125/captures/L125/passage-0-1-0-rock-floor-station-2.png'></figure><figure><figcaption>L130 v227</figcaption><img src='L130/passage-0-1-0-rock-floor-station-2.png'></figure></div></section>
<section><h2>passage/0-1/0 — rock-floor/1634.5m</h2><p>rock-floor-close · byte identical: true</p><div><figure><figcaption>L125 v225</figcaption><img src='../../Linux125/captures/L125/passage-0-1-0-rock-floor-station-3.png'></figure><figure><figcaption>L130 v227</figcaption><img src='L130/passage-0-1-0-rock-floor-station-3.png'></figure></div></section>
<section><h2>passage/1-2/0 — rock-floor/27.0m</h2><p>rock-floor-close · byte identical: true</p><div><figure><figcaption>L125 v225</figcaption><img src='../../Linux125/captures/L125/passage-1-2-0-rock-floor-station-1.png'></figure><figure><figcaption>L130 v227</figcaption><img src='L130/passage-1-2-0-rock-floor-station-1.png'></figure></div></section>
<section><h2>passage/1-2/0 — rock-floor/52.9m</h2><p>rock-floor-close · byte identical: true</p><div><figure><figcaption>L125 v225</figcaption><img src='../../Linux125/captures/L125/passage-1-2-0-rock-floor-station-2.png'></figure><figure><figcaption>L130 v227</figcaption><img src='L130/passage-1-2-0-rock-floor-station-2.png'></figure></div></section>
<section><h2>passage/1-2/0 — rock-floor/78.9m</h2><p>rock-floor-close · byte identical: true</p><div><figure><figcaption>L125 v225</figcaption><img src='../../Linux125/captures/L125/passage-1-2-0-rock-floor-station-3.png'></figure><figure><figcaption>L130 v227</figcaption><img src='L130/passage-1-2-0-rock-floor-station-3.png'></figure></div></section>
<section><h2>passage/2-3/0 — rock-floor/323.4m</h2><p>rock-floor-close · byte identical: true</p><div><figure><figcaption>L125 v225</figcaption><img src='../../Linux125/captures/L125/passage-2-3-0-rock-floor-station-1.png'></figure><figure><figcaption>L130 v227</figcaption><img src='L130/passage-2-3-0-rock-floor-station-1.png'></figure></div></section>
<section><h2>passage/2-3/0 — rock-floor/333.4m</h2><p>rock-floor-close · byte identical: true</p><div><figure><figcaption>L125 v225</figcaption><img src='../../Linux125/captures/L125/passage-2-3-0-rock-floor-station-2.png'></figure><figure><figcaption>L130 v227</figcaption><img src='L130/passage-2-3-0-rock-floor-station-2.png'></figure></div></section>
<section><h2>passage/2-3/0 — rock-floor/343.4m</h2><p>rock-floor-close · byte identical: true</p><div><figure><figcaption>L125 v225</figcaption><img src='../../Linux125/captures/L125/passage-2-3-0-rock-floor-station-3.png'></figure><figure><figcaption>L130 v227</figcaption><img src='L130/passage-2-3-0-rock-floor-station-3.png'></figure></div></section>
<section><h2>passage/0-1/0 — section-1/4m-outside</h2><p>open-air-close · byte identical: true</p><div><figure><figcaption>L125 v225</figcaption><img src='../../Linux125/captures/L125/passage-0-1-0-openair-1-4m-outside.png'></figure><figure><figcaption>L130 v227</figcaption><img src='L130/passage-0-1-0-openair-1-4m-outside.png'></figure></div></section>
<section><h2>passage/0-1/0 — section-1/4m-interior-wall</h2><p>open-air-close · byte identical: true</p><div><figure><figcaption>L125 v225</figcaption><img src='../../Linux125/captures/L125/passage-0-1-0-openair-1-4m-interior-wall.png'></figure><figure><figcaption>L130 v227</figcaption><img src='L130/passage-0-1-0-openair-1-4m-interior-wall.png'></figure></div></section>
<section><h2>passage/0-1/0 — section-2/4m-outside</h2><p>open-air-close · byte identical: true</p><div><figure><figcaption>L125 v225</figcaption><img src='../../Linux125/captures/L125/passage-0-1-0-openair-2-4m-outside.png'></figure><figure><figcaption>L130 v227</figcaption><img src='L130/passage-0-1-0-openair-2-4m-outside.png'></figure></div></section>
<section><h2>passage/0-1/0 — section-2/4m-interior-wall</h2><p>open-air-close · byte identical: true</p><div><figure><figcaption>L125 v225</figcaption><img src='../../Linux125/captures/L125/passage-0-1-0-openair-2-4m-interior-wall.png'></figure><figure><figcaption>L130 v227</figcaption><img src='L130/passage-0-1-0-openair-2-4m-interior-wall.png'></figure></div></section>
<section><h2>passage/0-1/0 — section-3/4m-outside</h2><p>open-air-close · byte identical: true</p><div><figure><figcaption>L125 v225</figcaption><img src='../../Linux125/captures/L125/passage-0-1-0-openair-3-4m-outside.png'></figure><figure><figcaption>L130 v227</figcaption><img src='L130/passage-0-1-0-openair-3-4m-outside.png'></figure></div></section>
<section><h2>passage/0-1/0 — section-3/4m-interior-wall</h2><p>open-air-close · byte identical: true</p><div><figure><figcaption>L125 v225</figcaption><img src='../../Linux125/captures/L125/passage-0-1-0-openair-3-4m-interior-wall.png'></figure><figure><figcaption>L130 v227</figcaption><img src='L130/passage-0-1-0-openair-3-4m-interior-wall.png'></figure></div></section>
<section><h2>passage/1-2/0 — section-1/4m-outside</h2><p>open-air-close · byte identical: true</p><div><figure><figcaption>L125 v225</figcaption><img src='../../Linux125/captures/L125/passage-1-2-0-openair-1-4m-outside.png'></figure><figure><figcaption>L130 v227</figcaption><img src='L130/passage-1-2-0-openair-1-4m-outside.png'></figure></div></section>
<section><h2>passage/1-2/0 — section-1/4m-interior-wall</h2><p>open-air-close · byte identical: true</p><div><figure><figcaption>L125 v225</figcaption><img src='../../Linux125/captures/L125/passage-1-2-0-openair-1-4m-interior-wall.png'></figure><figure><figcaption>L130 v227</figcaption><img src='L130/passage-1-2-0-openair-1-4m-interior-wall.png'></figure></div></section>
<section><h2>passage/1-2/0 — section-2/4m-outside</h2><p>open-air-close · byte identical: true</p><div><figure><figcaption>L125 v225</figcaption><img src='../../Linux125/captures/L125/passage-1-2-0-openair-2-4m-outside.png'></figure><figure><figcaption>L130 v227</figcaption><img src='L130/passage-1-2-0-openair-2-4m-outside.png'></figure></div></section>
<section><h2>passage/1-2/0 — section-2/4m-interior-wall</h2><p>open-air-close · byte identical: true</p><div><figure><figcaption>L125 v225</figcaption><img src='../../Linux125/captures/L125/passage-1-2-0-openair-2-4m-interior-wall.png'></figure><figure><figcaption>L130 v227</figcaption><img src='L130/passage-1-2-0-openair-2-4m-interior-wall.png'></figure></div></section>
<section><h2>passage/2-3/0 — section-1/4m-outside</h2><p>open-air-close · byte identical: true</p><div><figure><figcaption>L125 v225</figcaption><img src='../../Linux125/captures/L125/passage-2-3-0-openair-1-4m-outside.png'></figure><figure><figcaption>L130 v227</figcaption><img src='L130/passage-2-3-0-openair-1-4m-outside.png'></figure></div></section>
<section><h2>passage/2-3/0 — section-1/4m-interior-wall</h2><p>open-air-close · byte identical: true</p><div><figure><figcaption>L125 v225</figcaption><img src='../../Linux125/captures/L125/passage-2-3-0-openair-1-4m-interior-wall.png'></figure><figure><figcaption>L130 v227</figcaption><img src='L130/passage-2-3-0-openair-1-4m-interior-wall.png'></figure></div></section>
<section><h2>passage/2-3/0 — section-2/4m-outside</h2><p>open-air-close · byte identical: true</p><div><figure><figcaption>L125 v225</figcaption><img src='../../Linux125/captures/L125/passage-2-3-0-openair-2-4m-outside.png'></figure><figure><figcaption>L130 v227</figcaption><img src='L130/passage-2-3-0-openair-2-4m-outside.png'></figure></div></section>
<section><h2>passage/2-3/0 — section-2/4m-interior-wall</h2><p>open-air-close · byte identical: true</p><div><figure><figcaption>L125 v225</figcaption><img src='../../Linux125/captures/L125/passage-2-3-0-openair-2-4m-interior-wall.png'></figure><figure><figcaption>L130 v227</figcaption><img src='L130/passage-2-3-0-openair-2-4m-interior-wall.png'></figure></div></section>
File diff suppressed because it is too large. Load diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

@@ -0,0 +1,6 @@
<!doctype html><meta charset='utf-8'><title>L130 mouth closeups</title><style>body{font:14px system-ui;margin:24px;background:#17191d;color:#eee}section{margin:28px 0}div{display:grid;grid-template-columns:1fr 1fr;gap:12px}img{width:100%;height:auto}p{color:#bbb}</style><h1>L129 mouth closeups: b96dd73 vs L130</h1><section><h2>passage-0-1-0-lower-mouth</h2><p>byte identical: true</p><div><figure><figcaption>b96dd73 source mesh</figcaption><img src='b96dd73/passage-0-1-0-lower-mouth.png'></figure><figure><figcaption>L130 reach units</figcaption><img src='L130/passage-0-1-0-lower-mouth.png'></figure></div></section>
<section><h2>passage-0-1-0-upper-mouth</h2><p>byte identical: true</p><div><figure><figcaption>b96dd73 source mesh</figcaption><img src='b96dd73/passage-0-1-0-upper-mouth.png'></figure><figure><figcaption>L130 reach units</figcaption><img src='L130/passage-0-1-0-upper-mouth.png'></figure></div></section>
<section><h2>passage-1-2-0-lower-mouth</h2><p>byte identical: true</p><div><figure><figcaption>b96dd73 source mesh</figcaption><img src='b96dd73/passage-1-2-0-lower-mouth.png'></figure><figure><figcaption>L130 reach units</figcaption><img src='L130/passage-1-2-0-lower-mouth.png'></figure></div></section>
<section><h2>passage-1-2-0-upper-mouth</h2><p>byte identical: true</p><div><figure><figcaption>b96dd73 source mesh</figcaption><img src='b96dd73/passage-1-2-0-upper-mouth.png'></figure><figure><figcaption>L130 reach units</figcaption><img src='L130/passage-1-2-0-upper-mouth.png'></figure></div></section>
<section><h2>passage-2-3-0-lower-mouth</h2><p>byte identical: true</p><div><figure><figcaption>b96dd73 source mesh</figcaption><img src='b96dd73/passage-2-3-0-lower-mouth.png'></figure><figure><figcaption>L130 reach units</figcaption><img src='L130/passage-2-3-0-lower-mouth.png'></figure></div></section>
<section><h2>passage-2-3-0-upper-mouth</h2><p>byte identical: true</p><div><figure><figcaption>b96dd73 source mesh</figcaption><img src='b96dd73/passage-2-3-0-upper-mouth.png'></figure><figure><figcaption>L130 reach units</figcaption><img src='L130/passage-2-3-0-upper-mouth.png'></figure></div></section>
Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

@@ -0,0 +1,134 @@
[
{
"name": "passage-0-1-0-lower-mouth",
"camera_m": [
32.688347,
9.553723,
-735.48053
],
"yaw_deg": -132.845,
"pitch_deg": -6.052,
"height_center_m": -736.267,
"source_mesh": "Saved/Linux/l129/captures/mouth-closeups/before/passage-0-1-0-lower-mouth.vfmesh",
"candidate_mesh": "Saved/Linux/l130/captures/mouth-closeups/L130/passage-0-1-0-lower-mouth.vfmesh",
"baseline_png": "Standalone/Research/WorldAssemblerM3/Linux130/captures/mouth-closeups/b96dd73/passage-0-1-0-lower-mouth.png",
"candidate_png": "Standalone/Research/WorldAssemblerM3/Linux130/captures/mouth-closeups/L130/passage-0-1-0-lower-mouth.png",
"baseline_sha256": "aff7310b94e1b57e9cd1e42d3294e10964aed4a56e74bec8c1cdc0a5b2bffa98",
"candidate_sha256": "aff7310b94e1b57e9cd1e42d3294e10964aed4a56e74bec8c1cdc0a5b2bffa98",
"byte_identical": true,
"drawn_triangles": {
"b96dd73": 549170,
"L130": 549170
}
},
{
"name": "passage-0-1-0-upper-mouth",
"camera_m": [
8.324777,
20.450708,
-371.84259
],
"yaw_deg": -175.826,
"pitch_deg": -0.889,
"height_center_m": -372.122,
"source_mesh": "Saved/Linux/l129/captures/mouth-closeups/before/passage-0-1-0-upper-mouth.vfmesh",
"candidate_mesh": "Saved/Linux/l130/captures/mouth-closeups/L130/passage-0-1-0-upper-mouth.vfmesh",
"baseline_png": "Standalone/Research/WorldAssemblerM3/Linux130/captures/mouth-closeups/b96dd73/passage-0-1-0-upper-mouth.png",
"candidate_png": "Standalone/Research/WorldAssemblerM3/Linux130/captures/mouth-closeups/L130/passage-0-1-0-upper-mouth.png",
"baseline_sha256": "76dbb3263eb4f2018d13496f42b6eff7eb875e2029358776e22337aea8893a6d",
"candidate_sha256": "76dbb3263eb4f2018d13496f42b6eff7eb875e2029358776e22337aea8893a6d",
"byte_identical": true,
"drawn_triangles": {
"b96dd73": 243380,
"L130": 243380
}
},
{
"name": "passage-1-2-0-lower-mouth",
"camera_m": [
27.129938,
-19.07345,
-751.877197
],
"yaw_deg": -58.537,
"pitch_deg": -5.944,
"height_center_m": -751.734,
"source_mesh": "Saved/Linux/l129/captures/mouth-closeups/before/passage-1-2-0-lower-mouth.vfmesh",
"candidate_mesh": "Saved/Linux/l130/captures/mouth-closeups/L130/passage-1-2-0-lower-mouth.vfmesh",
"baseline_png": "Standalone/Research/WorldAssemblerM3/Linux130/captures/mouth-closeups/b96dd73/passage-1-2-0-lower-mouth.png",
"candidate_png": "Standalone/Research/WorldAssemblerM3/Linux130/captures/mouth-closeups/L130/passage-1-2-0-lower-mouth.png",
"baseline_sha256": "1c193b6fcb65b98b1c721704990392dc816486639e047b53bb72b4a17d89ae4c",
"candidate_sha256": "1c193b6fcb65b98b1c721704990392dc816486639e047b53bb72b4a17d89ae4c",
"byte_identical": true,
"drawn_triangles": {
"b96dd73": 544808,
"L130": 544808
}
},
{
"name": "passage-1-2-0-upper-mouth",
"camera_m": [
23.391808,
-15.509525,
-731.781067
],
"yaw_deg": -72.785,
"pitch_deg": -0.897,
"height_center_m": -730.561,
"source_mesh": "Saved/Linux/l129/captures/mouth-closeups/before/passage-1-2-0-upper-mouth.vfmesh",
"candidate_mesh": "Saved/Linux/l130/captures/mouth-closeups/L130/passage-1-2-0-upper-mouth.vfmesh",
"baseline_png": "Standalone/Research/WorldAssemblerM3/Linux130/captures/mouth-closeups/b96dd73/passage-1-2-0-upper-mouth.png",
"candidate_png": "Standalone/Research/WorldAssemblerM3/Linux130/captures/mouth-closeups/L130/passage-1-2-0-upper-mouth.png",
"baseline_sha256": "28576cefb1b59601d2e68981a7b77fc28a1d667c58ab27292deac0acd9deb338",
"candidate_sha256": "28576cefb1b59601d2e68981a7b77fc28a1d667c58ab27292deac0acd9deb338",
"byte_identical": true,
"drawn_triangles": {
"b96dd73": 593026,
"L130": 593026
}
},
{
"name": "passage-2-3-0-lower-mouth",
"camera_m": [
-28.589163,
-1.226313,
-898.811707
],
"yaw_deg": 114.256,
"pitch_deg": -6.159,
"height_center_m": -898.713,
"source_mesh": "Saved/Linux/l129/captures/mouth-closeups/before/passage-2-3-0-lower-mouth.vfmesh",
"candidate_mesh": "Saved/Linux/l130/captures/mouth-closeups/L130/passage-2-3-0-lower-mouth.vfmesh",
"baseline_png": "Standalone/Research/WorldAssemblerM3/Linux130/captures/mouth-closeups/b96dd73/passage-2-3-0-lower-mouth.png",
"candidate_png": "Standalone/Research/WorldAssemblerM3/Linux130/captures/mouth-closeups/L130/passage-2-3-0-lower-mouth.png",
"baseline_sha256": "58cd3ea88c754afab20d1cdcaf5f0edf676f0303d9f392d3632a6f2257205766",
"candidate_sha256": "58cd3ea88c754afab20d1cdcaf5f0edf676f0303d9f392d3632a6f2257205766",
"byte_identical": true,
"drawn_triangles": {
"b96dd73": 293832,
"L130": 293832
}
},
{
"name": "passage-2-3-0-upper-mouth",
"camera_m": [
-24.455027,
-5.631774,
-811.975403
],
"yaw_deg": 82.691,
"pitch_deg": -0.896,
"height_center_m": -810.755,
"source_mesh": "Saved/Linux/l129/captures/mouth-closeups/before/passage-2-3-0-upper-mouth.vfmesh",
"candidate_mesh": "Saved/Linux/l130/captures/mouth-closeups/L130/passage-2-3-0-upper-mouth.vfmesh",
"baseline_png": "Standalone/Research/WorldAssemblerM3/Linux130/captures/mouth-closeups/b96dd73/passage-2-3-0-upper-mouth.png",
"candidate_png": "Standalone/Research/WorldAssemblerM3/Linux130/captures/mouth-closeups/L130/passage-2-3-0-upper-mouth.png",
"baseline_sha256": "6b903c68fac1a20783185afbac8cdad717c6e9374e665e3618e118c5a6aadf51",
"candidate_sha256": "6b903c68fac1a20783185afbac8cdad717c6e9374e665e3618e118c5a6aadf51",
"byte_identical": true,
"drawn_triangles": {
"b96dd73": 59554,
"L130": 59554
}
}
]
@@ -0,0 +1,50 @@
# Linux131 — current state and dead experiment cleanup
Branch `wip/l131-current-state`, based on `cbbfd46`; Core evaluator remains v227. The root [`CURRENT-STATE.md`](../../../../CURRENT-STATE.md) consolidates the shipping behavior, current references, verification commands, open gaps, and closed proposals.
## Removed
- Removed `VOXELFORGE_L86_DISABLE_PASSAGE_COMPOSITION`, `VOXELFORGE_L88_DISABLE_PASSAGES`, `VOXELFORGE_L88_DISABLE_BOUNDARY_BANDS`, and `VOXELFORGE_L88_ALLOW_PASSAGE_ABLATION_WALKABILITY_FAILURE`. These only supported archived passage/boundary ablations in `Linux86/RunL86Ceiling.py`, `Linux87/RunL87Timeline.py`, and `Linux88/RunL88CeilingSplit.py`; no current `verify.py` gate or live comparison uses them. Passage feature selection, passage composition, and boundary bands now run on the normal path, and passage-route validation cannot be waived by the old ablation flag.
- Removed the `VOXELFORGE_AUDIT_LEGACY_ROUTE_GRADE` block from `Standalone/Source/WorldAssemblyPrototype.h`. The source contained its only reference; no test, current tool, or report invoked it.
- Kept route-candidate grading and all user-visible behavior. This cleanup adds no evaluator version or baseline change.
## Switch and hook inventory
### Environment switches in compiled Core/Source
| Switch | Effect and consumer | Keep decision |
|---|---|---|
| `VOXELFORGE_L74_TIGHT_PROOF_CONTROL` | Proof negative control. `Linux74/FalsifierL74.py` and `Linux75/FalsifierL75.py`. | Keep for falsifier reproducibility; not a current fast-tier input. |
| `VOXELFORGE_L75_TN_BLOCK_REGRESSION` | Fixed TunnelNetwork regression path in Core and mesher. `RunTnBlockRegressionL75.py` and `FalsifierL75.py`. | Keep; the regression tools still consume it. |
| `VOXELFORGE_L98_PASSAGE_PREP_TRACE` | Emits preparation-stage timing/count data. `RunL98Hashes.py` and `RunL98ReuseCounts.py`. | Keep as a focused profiler for future preparation comparisons. |
| `VOXELFORGE_L98_PASSAGE_REUSE_PROBE` | Compares the prepared projection with direct scalar queries. `RunL98ReuseCounts.py`. | Keep for exactness probes. |
| `VOXELFORGE_L104_FAR_HIT_CACHE` | Selects indexed versus linear far-hit candidate lookup. `RunL104ReadinessPairs.py` and `RunL104ProbeStats.py`. | Keep for reproducible A/B comparisons; cache defaults on. |
| `VOXELFORGE_L104_FAR_HIT_STATS` | Emits far-hit candidate/query counters. `RunL104ProbeStats.py`. | Keep for the measurement tool. |
| `VOXELFORGE_L115_DENSITY_PROFILE` | Enables density and floor-query profiling. `RunL115DensityProbe.py`, `RunL119DensityProbe.py`, and `RunL125DensityProbe.py`. | Keep; these remain the focused floor-probe path. |
| `VOXELFORGE_L130_PASSAGE_REACH_PROOF_AUDIT` | Checks passage proof/feature influence bounds. L130's reach audit and report. | Keep for reach changes and audit reproduction. |
Archived runners also set `VOXELFORGE_L81_PASSAGE_BOUND_PADDING_VOXELS`, `VOXELFORGE_L86_PASSAGE_RESULT_CACHE`, `VOXELFORGE_L90_PASSAGE_BRICK_METRES`, `VOXELFORGE_L90_PASSAGE_CANDIDATE_CENSUS`, `VOXELFORGE_L90_PASSAGE_REACH_METRES`, `VOXELFORGE_L91_PASSAGE_SOURCE_FLOOR_VOXELS`, `VOXELFORGE_L93_PASSAGE_CARVE_REACH_METRES`, `VOXELFORGE_L98_PASSAGE_REUSE_COUNTS`, `VOXELFORGE_L114_DENSITY_PROFILE`, and `VOXELFORGE_L118_TAG_TIMING`. None has a compiled-source reader at `cbbfd46`; they are inert historical runner strings.
### Other experiments, diagnostics, and legacy paths retained
- **World-changing tunnel ablations:** all 14 `voxel.TunnelAblate*` controls remain (`CaveWarp`, `DetailOps`, `RoomSDF`, `TunnelSDF`, `TunnelCore`, `PassageCarving`, `PassageStructuralPosts`, `NativeFloor`, `Disturbances`, `OriginSpine`, `BoundarySeal`, `LandingPosts`, `XYEdgeSeal`, `PitChimneySDF`). The editor commandlet writes their resolved mask into export and result manifests; the archived `PERF-SAMPLED.md` and `WORK-NEXT.md` record their measurements. No current runner invokes an ablation. Keep for manifest attribution and historical reproduction; removing it needs a metadata migration. Source: [`VoxelDensityAblation.cpp`](../../../../Source/VoxelForge/Private/VoxelDensityAblation.cpp), [`VoxelForgeExploreCommandlet.cpp`](../../../../Source/VoxelForgeEditor/Private/VoxelForgeExploreCommandlet.cpp).
- **Research fields:** `voxel.ResearchRedefinedDensityField`, `voxel.ResearchRedefinedDensityDetailScale`, `voxel.ResearchParameterizedCave`, and `voxel.ResearchParameterizedCaveSet` are default-off and used by `Tools/VoxelForgeTest.ps1` steps 2d/2e/2f. The region-eval commandlet consumes `SetResearchBrickBinningEnabled`, `SetResearchBrickRoomReachScale`, and `SetResearchRegionCacheWindowEnabled`; keep these editor-only experiment paths. Source: [`VoxelForgeRegionEvalExperiment.cpp`](../../../../Source/VoxelForgeEditor/Private/VoxelForgeRegionEvalExperiment.cpp), [`VoxelForgeTest.ps1`](../../../../Tools/VoxelForgeTest.ps1).
- **Runtime profiling and trace:** `voxel.SampleStacks` still starts `FVoxelStackSampler` (the guarded `Tools/VoxelForgeTest.ps1 -SampleDensityStacks` path consumes it); `voxel.ProfileDensity`, `voxel.ProfileDensityFull`, `voxel.ProfileTileGeneration`, `voxel.ProfileCollisionCookStages`, and `voxel.PerfAttribution` remain opt-in. `voxel.StartupTraceFile`, `voxel.StartupTraceMaxTiles`, and `voxel.StartupTraceUntilEndPlay` feed the Unreal startup trace used by the arrival/movement runners. Keep. Build-time `VOXELFORGE_PROFILE_LEGACY_TERMS` (L76) and `VOXELFORGE_PROFILE_L77`/`--linux77-profile` (stack sampler and route census) remain in `Standalone/BuildLinux.sh` and Core.
- **Standalone profiles and timelines:** retained `--profile-column-overlap`, `--profile-lod-work`, `--profile-passage-composer`, `--instrument-tile-drain`, `--cold-worker-timeline`, `--linux77-profile`, `--tls-probe=N`, `--tls-probe-touch=...`, and `--diagnostic-slow-shape-compile-ms=N`. The viewer's `--profile-attribution`, `--profile-detail-band`, and `--profile-far-column-overlap` remain. The L77 profiler, tile drain instrumentation, timelines, and TLS probe are measurement hooks; archived reports and runners still provide reproducible consumers.
- **Standalone audits:** all source-advertised hooks remain: `--audit-ore-vein-yield`, `--audit-resolved-layout-boundaries`, `--audit-strate-transitions`, `--audit-resolved-layout-passages`, `--audit-heightfield-profile`, `--audit-motif-intervals`, `--audit-landmark-reachability`, `--audit-native-caves`, `--audit-native-lattice`, `--audit-surface-clearance`, `--audit-uniform-block-proofs`, `--audit-base-page-equivalence`, `--audit-route-parity`, `--audit-passage-tile-seams`, `--audit-passage-source-clamp`, `--audit-passage-carve-reach`, `--audit-tile-shape-readiness`, and the mesher's `--audit-raw-skirts`. `verify.py fast` consumes the current route, passage, block-proof, transition, native-field and negative-control subset; dedicated L37/L75/L125/L130 runners exercise the specialized audits. Keep the whole audit set.
- **Evaluator A/B controls:** `voxel.FastIsFinite`, `voxel.UseFusedEvaluator`, `voxel.WormBlockSkip`, `voxel.TileCacheWindow` / its two minimum-LOD controls, `voxel.SpatialIndex`, `voxel.ChunkRoutingCache`, `voxel.UseOperatorBlock`, `voxel.CoreColumnCacheDirectMapped`, `voxel.CoreMCGradientMemoization`, `voxel.FloorRound1PassageProjectionCache`, `voxel.PlayerFitMemo`, `voxel.UseBlockEarlyOut`, `voxel.OuterClassifierMode`, and `voxel.SealedSolidProof` remain. They select optimized or comparison paths; the density block equivalence test explicitly forces `UseFusedEvaluator=0`, while most others are production defaults or historical measurement controls. `voxel.WormsForceOff` is a world-changing development measurement override. Kept; no current L131 gate changes them.
- **Standalone A/B and geometry controls:** `--exact-midpoint-density-memo`, `--no-exact-midpoint-density-memo`, `--active-lane-batching`, `--proof-block-cells=N`, `--parallel-sign-edge-repair`, `--serial-sign-edge-repair`, and `--shape-helper-limit=8|12` remain. The L94 report documents serial edge repair; no current `verify.py` consumer was found for the other CLI A/B controls, so they remain pending a separately reviewed removal. `voxel.TilePostReachDebug`, `voxel.TilePostReachScale`, and `voxel.TileReachCostDiagnostics` remain for the tile-post-reach test; `voxel.TunnelMouthTrim` remains a development-only room-mouth geometry control. No L131 gate uses these.
- `FLegacyGeneratorFieldAdapter` remains the Core fallback for unsupported generator/recipe combinations, and Unreal's Core switch remains default-off. `voxel.StreamingLegacyPendingResultCap=1` remains the L120–L123 admission negative control; `voxel.StreamingCoarseFirst` remains opt-in pending a rendered owner-plan check. `bStandaloneResearchRoundTunnels` and `bStandaloneResearchEnhancedWalls` remain consumed by the standalone viewer/Core adapter.
## Checks
- Clang 19 and GCC standalone builds passed with the L131 source stamp `5119cee164406856cbca496d160c0e0a8124ae2529fa7c0ebd51634a7c65a6cb`. `Standalone/BuildLinux.sh` applied the required CPU and address-space limits.
- L80 timing: one warm-up per binary and five interleaved pairs in each of four strata × W8/W12 cells, comparing the pre-cleanup L130 binary with L131. Load was 0.81; CPU idle was 99.2%. Across 40 pairs per metric, mean relative change was −0.22% near-ready (95% CI −1.02% to +0.57%) and −0.29% whole-view (−1.04% to +0.46%); the intervals include zero. [Raw timing summary](../../../../Saved/Linux/l131/timing/summary.json).
- Exact hashes: **24/24** owner4 SW/FI/TN/VS × Clang/GCC × W1/W8/W12 tile and whole-view identities; **18/18** canonical cases across owner4 SurfaceWorld and owner2 SurfaceWorld/FloatingIslands. All match the pre-cleanup L130 binary and agree across compiler/worker combinations. For transparency, forcing the owner4 dataset through `--recipe=floating-islands` exited during setup with “could not find a finite SurfaceWorld strate”, before hashing; it is not a valid canonical case. [Tile matrix](../../../../Saved/Linux/l131/hash-matrix/summary.json), [canonical matrix](../../../../Saved/Linux/l131/canonical-matrix/summary.json).
- Walkability: **21,425/21,425** passage centerline samples open. Seam audit: **4 pairs / 4,900** shared samples, zero mismatches. [Audit log](../../../../Saved/Linux/l131/seam-audit.log).
- Fast tier: **PASS**, 259 gated checks plus 5 diagnostics, 80 runs, 371.0 s. The five diagnostic-only comparisons are historical baselines; all required checks pass. [Summary](../../../../Saved/Linux/l131/fast-summary.json).
- Linux `VFHostEditor`: **PASS**, 27/27 actions, 78.81 s with `-NoUBA -MaxParallelActions=4`, no `prlimit`. UBT's default log rotation first failed before compilation in the read-only user config directory; the successful rerun set `-Log=.../Saved/Linux/l131/ubt.log`. Three existing RealtimeMesh deprecation warnings remain. [Build log](../../../../Saved/Linux/l131/ue-build.log).
- Unreal `Source/` files changed: **none**. No Windows compile is requested by this diff; Windows determinism remains unverified.
- `git diff --check` passes. No commit, push, or stash was made.
Builds, per-run logs, timing records, hash records, and audit output are under `Saved/Linux/l131/`.
@@ -0,0 +1,75 @@
#!/usr/bin/env python3
"""Compare 18 canonical owner4/owner2 hashes with the pre-cleanup L130 binary."""
from __future__ import annotations
import json
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l131/canonical-matrix"
DATASETS = {
"owner4": ROOT / "Standalone/data/owner_world.json",
"owner2": ROOT / "Standalone/data/regression/owner_world_2strate_l61.json",
}
RECIPES = {"owner4": ("surface-world",), "owner2": ("surface-world", "floating-islands")}
BINARIES = {
"base": ROOT / "Saved/Linux/l130/bin/VoxelForgeCoreHeadless-l130-{compiler}",
"l131": ROOT / "Saved/Linux/l131/bin/VoxelForgeCoreHeadless-l131-{compiler}",
}
HASH_RE = re.compile(r"^\[Pipeline(?:FullDetailHash|WholeViewHash|LODHash|ExactQueryHash)\].*$", re.M)
def run(dataset: str, recipe: str, compiler: str, workers: int, version: str) -> tuple[list[str], float]:
binary = Path(str(BINARIES[version]).format(compiler=compiler))
label = f"{dataset}-{recipe}-{compiler}-w{workers}-{version}"
log = OUT / f"{label}.log"
command = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1740s", str(binary), str(DATASETS[dataset]),
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
"--force-generate", f"--workers={workers}", "--clip-radius=3",
"--max-clip-level=8", "--max-level=8", f"--recipe={recipe}", "--stream-hash"]
started = time.monotonic()
with log.open("w", encoding="utf-8") as stream:
process = subprocess.run(command, cwd=ROOT, stdout=stream,
stderr=subprocess.STDOUT, check=False, timeout=1740)
hashes = HASH_RE.findall(log.read_text(encoding="utf-8", errors="replace"))
if process.returncode or len(hashes) != 12:
raise RuntimeError(f"hash run failed: {log}; exit={process.returncode}; rows={len(hashes)}")
return hashes, time.monotonic() - started
def main() -> int:
OUT.mkdir(parents=True, exist_ok=True)
rows = []
groups = {}
for dataset, recipes in RECIPES.items():
for recipe in recipes:
signatures = []
for workers in (1, 8, 12):
for compiler in ("clang", "gcc"):
before, before_s = run(dataset, recipe, compiler, workers, "base")
after, after_s = run(dataset, recipe, compiler, workers, "l131")
row = {"dataset": dataset, "recipe": recipe, "compiler": compiler,
"workers": workers, "hash_mismatches": sum(a != b for a, b in zip(before, after)),
"rows": len(after), "base_s": round(before_s, 3), "l131_s": round(after_s, 3),
"base_log": f"Saved/Linux/l131/canonical-matrix/{dataset}-{recipe}-{compiler}-w{workers}-base.log",
"l131_log": f"Saved/Linux/l131/canonical-matrix/{dataset}-{recipe}-{compiler}-w{workers}-l131.log"}
rows.append(row)
signatures.append((compiler, workers, after))
print("[L131CanonicalHash] " + json.dumps(row, sort_keys=True), flush=True)
(OUT / "runs-partial.json").write_text(json.dumps(rows, indent=2) + "\n")
first = signatures[0][2]
stable = all(sig == first for _, _, sig in signatures)
groups[f"{dataset}/{recipe}"] = {"base_equals_l131": all(row["hash_mismatches"] == 0 for row in rows if row["dataset"] == dataset and row["recipe"] == recipe),
"all_clang_gcc_workers_agree": stable}
failures = [row for row in rows if row["hash_mismatches"]]
(OUT / "summary.json").write_text(json.dumps({"runs": rows, "groups": groups,
"passed": len(rows)-len(failures), "failed": len(failures)}, indent=2) + "\n")
return 1 if failures or not all(group["all_clang_gcc_workers_agree"] for group in groups.values()) else 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,84 @@
#!/usr/bin/env python3
"""Compare 24 owner4 tile-hash cases with the pre-cleanup L130 binary."""
from __future__ import annotations
import json
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l131/hash-matrix"
DATA = ROOT / "Standalone/data/owner_world.json"
BINARIES = {
"base": ROOT / "Saved/Linux/l130/bin/VoxelForgeCoreHeadless-l130-{compiler}",
"l131": ROOT / "Saved/Linux/l131/bin/VoxelForgeCoreHeadless-l131-{compiler}",
}
CAMERAS = {
"sw": ((8.0, 16.0, -372.2626953125), 2850),
"fi": ((25.8369140625, 3.19921875, -737.0), 2249),
"tn": ((19.166015625, -14.2001953125, -753.3740234375), 2233),
"vs": ((-20.0625, -5.068359375, -900.353515625), 2329),
}
TILE_RE = re.compile(
r"^\[PipelineTileHash\] lod=(-?\d+) x=(-?\d+) y=(-?\d+) z=(-?\d+) "
r"density=(0x[0-9a-f]+) mesh=(0x[0-9a-f]+) payload=(0x[0-9a-f]+) "
r"topology=(0x[0-9a-f]+)$", re.M)
WHOLE_RE = re.compile(
r"^\[PipelineWholeViewHash\] tiles=(\d+) density=(0x[0-9a-f]+) "
r"mesh=(0x[0-9a-f]+) payload=(0x[0-9a-f]+) topology=(0x[0-9a-f]+)$", re.M)
def capture(camera_name: str, compiler: str, workers: int, version: str) -> tuple[dict, tuple[str, ...], float]:
camera, expected_tiles = CAMERAS[camera_name]
camera_arg = ",".join(f"{value:.12f}" for value in camera) + ",0,0"
binary = Path(str(BINARIES[version]).format(compiler=compiler))
log = OUT / f"owner4-{camera_name}-{compiler}-w{workers}-{version}.log"
command = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1740s", str(binary), str(DATA), "--world-assembler-prototype",
"--stream-benchmark", "--stream-pipeline", f"--workers={workers}",
"--clip-radius=3", "--max-clip-level=8", "--max-level=8",
f"--camera={camera_arg}", "--stream-hash-tiles"]
started = time.monotonic()
with log.open("w", encoding="utf-8") as stream:
result = subprocess.run(command, cwd=ROOT, stdout=stream,
stderr=subprocess.STDOUT, check=False, timeout=1740)
elapsed = time.monotonic() - started
output = log.read_text(encoding="utf-8", errors="replace")
tiles = {(int(match.group(1)), int(match.group(2)), int(match.group(3)), int(match.group(4))):
match.group(5, 6, 7, 8) for match in TILE_RE.finditer(output)}
whole = WHOLE_RE.search(output)
if result.returncode or whole is None or len(tiles) != expected_tiles or len(tiles) != int(whole.group(1)):
raise RuntimeError(f"incomplete {version} {camera_name}/{compiler}/W{workers}: {log}")
return tiles, whole.groups(), elapsed
def main() -> int:
OUT.mkdir(parents=True, exist_ok=True)
rows = []
for camera_name in CAMERAS:
for compiler in ("clang", "gcc"):
for workers in (1, 8, 12):
old_tiles, old_whole, old_s = capture(camera_name, compiler, workers, "base")
new_tiles, new_whole, new_s = capture(camera_name, compiler, workers, "l131")
keys = set(old_tiles) | set(new_tiles)
tile_mismatches = sum(old_tiles.get(key) != new_tiles.get(key) for key in keys)
row = {"owner": "owner4", "camera": camera_name, "compiler": compiler,
"workers": workers, "tiles": len(new_tiles),
"tile_mismatches": tile_mismatches,
"whole_view_match": old_whole == new_whole,
"base_s": round(old_s, 3), "l131_s": round(new_s, 3),
"base_log": f"Saved/Linux/l131/hash-matrix/owner4-{camera_name}-{compiler}-w{workers}-base.log",
"l131_log": f"Saved/Linux/l131/hash-matrix/owner4-{camera_name}-{compiler}-w{workers}-l131.log"}
rows.append(row)
print("[L131HashIdentity] " + json.dumps(row, sort_keys=True), flush=True)
(OUT / "runs-partial.json").write_text(json.dumps(rows, indent=2) + "\n")
failures = [row for row in rows if row["tile_mismatches"] or not row["whole_view_match"]]
(OUT / "summary.json").write_text(json.dumps({"runs": rows, "passed": len(rows)-len(failures),
"failed": len(failures)}, indent=2) + "\n")
return 1 if failures else 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,115 @@
#!/usr/bin/env python3
"""Five interleaved L80 readiness measurements per stratum and worker count."""
from __future__ import annotations
import json
import os
import re
import statistics
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l131/timing"
DATA = ROOT / "Standalone/data/owner_world.json"
BINARIES = {
"base": ROOT / "Saved/Linux/l130/bin/VoxelForgeCoreHeadless-l130-clang",
"l131": ROOT / "Saved/Linux/l131/bin/VoxelForgeCoreHeadless-l131-clang",
}
CAMERAS = {
0: (8.0, 16.0, -372.2626953125),
1: (25.8369140625, 3.19921875, -737.0),
2: (19.166015625, -14.2001953125, -753.3740234375),
3: (-20.0625, -5.068359375, -900.353515625),
}
PIPELINE_RE = re.compile(r"^\[StreamPipeline\] (.*)$", re.M)
FIELDS_RE = re.compile(r"([a-zA-Z0-9_]+)=([^ ]+)")
NEAR_RE = re.compile(r"^\[WorldAssemblerNearLODReady\] lods=0-2 tiles=(\d+)/(\d+) cold_ready_s=([0-9.]+)$", re.M)
def run(strate: int, workers: int, version: str, phase: str, replicate: int) -> dict:
camera = ",".join(f"{value:.12f}" for value in CAMERAS[strate]) + ",0,0"
label = f"s{strate}-w{workers}-{version}-{phase}{replicate}-l131-v227"
log = OUT / f"{label}.log"
command = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1740s", str(BINARIES[version]), str(DATA),
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
f"--workers={workers}", "--clip-radius=3", "--max-clip-level=8",
"--max-level=8", f"--camera={camera}", "--profile-lod-work",
"--stream-scheduler-policy=3"]
started = time.monotonic()
with log.open("w", encoding="utf-8") as output:
result = subprocess.run(command, cwd=ROOT, stdout=output,
stderr=subprocess.STDOUT, check=False, timeout=1740)
content = log.read_text(encoding="utf-8", errors="replace")
pipeline, near = PIPELINE_RE.search(content), NEAR_RE.search(content)
if result.returncode or pipeline is None or near is None:
raise RuntimeError(f"timing run failed: {log}; exit={result.returncode}")
fields = dict(FIELDS_RE.findall(pipeline.group(1)))
row = {"strate": strate, "workers": workers, "version": version,
"phase": phase, "replicate": replicate,
"lod02_ready_s": float(near.group(3)),
"whole_view_ready_s": float(fields["whole_view_ready"].removesuffix("s")),
"wall_s": round(time.monotonic() - started, 3),
"log": str(log.relative_to(ROOT))}
print("[L131TimingRun] " + json.dumps(row, sort_keys=True), flush=True)
return row
def quiet_machine() -> tuple[float, float]:
load_1m = os.getloadavg()[0]
def totals() -> tuple[int, int]:
fields = Path("/proc/stat").read_text().splitlines()[0].split()
values = [int(value) for value in fields[1:]]
return sum(values), values[3] + (values[4] if len(values) > 4 else 0)
before = totals()
time.sleep(2.0)
after = totals()
idle = 100.0 * (after[1] - before[1]) / max(1, after[0] - before[0])
return load_1m, idle
def main() -> int:
if any(not path.is_file() for path in (DATA, *BINARIES.values())):
raise SystemExit("missing timing input")
OUT.mkdir(parents=True, exist_ok=True)
load_1m, idle = quiet_machine()
(OUT / "machine-load-before.txt").write_text(
f"1-minute load: {load_1m:.2f}\n2-second CPU idle: {idle:.1f}%\n")
print(f"[L131TimingGate] load_1m={load_1m:.2f} cpu_idle_2s={idle:.1f}%", flush=True)
if load_1m >= 0.9 or idle < 97.0:
raise SystemExit("timing refused: machine is above the L80 quiet-load gate")
rows = []
for strate in range(4):
for workers in (8, 12):
for version in BINARIES:
rows.append(run(strate, workers, version, "warmup", 0))
for replicate in range(1, 6):
order = tuple(BINARIES) if replicate % 2 else tuple(reversed(BINARIES))
for version in order:
rows.append(run(strate, workers, version, "measured", replicate))
(OUT / "runs-partial.json").write_text(json.dumps(rows, indent=2) + "\n")
measured = [row for row in rows if row["phase"] == "measured"]
summary = []
for strate in range(4):
for workers in (8, 12):
old = [row for row in measured if row["strate"] == strate and row["workers"] == workers and row["version"] == "base"]
new = [row for row in measured if row["strate"] == strate and row["workers"] == workers and row["version"] == "l131"]
item = {"strate": strate, "workers": workers, "replicates_per_version": 5}
for key in ("lod02_ready_s", "whole_view_ready_s"):
old_med, new_med = statistics.median(row[key] for row in old), statistics.median(row[key] for row in new)
item[f"base_{key}_median"] = old_med
item[f"l131_{key}_median"] = new_med
item[f"delta_pct_{key}"] = round(100.0 * (new_med / old_med - 1.0), 2)
item[f"base_{key}_values"] = [row[key] for row in old]
item[f"l131_{key}_values"] = [row[key] for row in new]
summary.append(item)
(OUT / "summary.json").write_text(json.dumps({"runs": rows, "summary": summary}, indent=2) + "\n")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,44 @@
#!/usr/bin/env python3
"""Create the isolated L132 owner snapshot with a native plan for existing FI strate 1."""
from __future__ import annotations
import json
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
SOURCE = ROOT / "Standalone/data/owner_world.json"
MANIFEST = ROOT / "Saved/Linux/l132/fixtures/owner-native-fi.world-assembly.json"
LAYOUT = ROOT / "Saved/Linux/l132/fixtures/owner-native-fi.resolved-layout.json"
OUTPUT = ROOT / "Saved/Linux/l132/fixtures/owner_world_native_fi.json"
def main() -> int:
owner = json.loads(SOURCE.read_text(encoding="utf-8"))
manifest_text = MANIFEST.read_text(encoding="utf-8").strip()
layout_text = LAYOUT.read_text(encoding="utf-8").strip()
manifest = json.loads(manifest_text)
layout = json.loads(layout_text)
islands = manifest.get("floating_islands", [])
native = [row for row in islands if row.get("strate_index") == 1]
resolved = [row for row in layout.get("strates", [])
if row.get("strate_index") == 1]
if len(native) != 1 or len(resolved) != 1:
raise SystemExit("native FI snapshot must contain exactly one strate-1 island plan/layout")
if (resolved[0].get("generator_kind") != "FloatingIslands"
or resolved[0].get("recipe_id") != "FloatingIslands.ClusterField"
or resolved[0].get("recipe_version") != 8):
raise SystemExit("resolved strate 1 is not FloatingIslands.ClusterField v8")
owner["world_assembly"] = manifest
owner["core_world_assembly_json"] = manifest_text
owner["resolved_layout_json"] = layout_text
OUTPUT.parent.mkdir(parents=True, exist_ok=True)
OUTPUT.write_text(json.dumps(owner, separators=(",", ":")) + "\n", encoding="utf-8")
print(f"[L132NativeFISnapshot] path={OUTPUT.relative_to(ROOT)} "
f"islands={len(native[0].get('manifest', {}).get('plan', {}).get('islands', []))} "
"strate=1 recipe=FloatingIslands.ClusterField/v8 status=PASS")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,41 @@
# L132 — owner density dispatch
## Result
With Core enabled in the owner headless pipeline, the four strates bind to four paths. Unreal’s `bUseVoxelForgeCore` remains opt-in and defaults off. The fast-tier diagnostic reports the Core bindings at the four L80 owner poses and attributes hashed tiles by tile-center height within the resolved stratum bounds.
| Strate | Owner recipe | Bound path | Tile centers / share |
|---|---|---|---:|
| SW (0) | `SurfaceWorld.Heightfield2D` v2 | `SurfaceWorld-native` | 2,640 / 100% |
| FI (1) | `FloatingIslands.ClusterField` v0 | `compiled-analytic/FloatingIslands` | 2,128 / 100% |
| TN (2) | `legacy.TunnelNetwork` v0 | `compiled-analytic/TunnelNetwork` | 2,208 / 100% |
| VS (3) | `legacy.VerticalShafts` v0 | `compiled-analytic/VerticalShafts` | 1,392 / 100% |
SW has a matching strate/bounds `SurfaceWorld` plan, and its `Heightfield2D` evaluator is valid. FI has a legacy v0 layout record but no matching `FloatingIslandPlan`, so `FloatingIslandFieldV1` cannot bind. The Core owner manifest has zero island plans. TN and VS have legacy recipe IDs, not `Core.Caves.PrimitiveField` v1 or `Core.Lattice.PrimitiveField` v1. Their resolved layouts therefore lack the matching graph recipe and native-field capability. All three fall through to `FLegacyGeneratorFieldAdapter`.
The dispatch check is in the fast tier and can also be requested from the headless binary with `--density-dispatch-report`. It prints the per-strate bound adapter/path and hashed tile-center path shares. Its tile counts aggregate the four standard owner poses; tiles whose centers fall outside an interior interval are excluded, and a tile spanning a boundary is assigned by its center. Per-view logs are `Saved/Linux/l132/owner-density-dispatch-s*-clang.log`; the printed shares are in `Saved/Linux/l132/logs/verify-fast.log` and `Saved/Linux/l132/fast-summary.json`.
Across the 8,368 classified tile centers, path shares are 31.5% SurfaceWorld-native, 25.4% compiled-analytic/FloatingIslands, 26.4% compiled-analytic/TunnelNetwork, and 16.6% compiled-analytic/VerticalShafts.
## Native FI decision
The native evaluator is present. `BuildFloatingIslandStratumPlan` can convert the owner’s legacy island parameters to a v8 plan, but the owner’s imported manifest does not contain that plan. The existing automatic-plan branch is guarded by `bImportedOverride` inside the generated-assembly branch, so it does not fill this owner’s missing imported plan.
I built a candidate v8 plan for the existing owner FI strate without changing the default owner data. Its raw island-route audit passed: 29 route points, 78,336 capsule samples, zero blocked samples, zero unsupported probes, and zero grade failures. The composed owner audit did not pass:
- Existing compiled-analytic owner: all 3 passage interfaces pass; shell floor violations 0; ripple checks 3/3.
- Native FI candidate: only 2/3 interfaces pass; the added FI route is not walkable (1/2 strate routes walkable); shell floor violations 13; ripple checks 2/3.
- Passage tile seams passed for both bindings: 4 pairs, 4,900 shared samples, zero mismatches.
Native FI is a quality regression on the owner’s current passages, despite its standalone route plan passing. I did not switch the owner. The conditional captures, W8/W12 timing matrix, and Clang/GCC W1/W8/W12 matrix were not run because the candidate failed the composed walk/shell gate. No Core version or output baseline changed; the default owner output remains on its existing bindings. No Linux UE host build was run because there was no qualifying native change or timing matrix.
## TN/VS graph translation size
The active TN `GenerationParams` block has 47 fields; VS `VerticalShaftParams` has 11. Each also has 15 passage settings. Both have `CoreGeometry.GeneratorRecipe=0` and empty `Rooms`, `Tunnels`, and `Stamps` arrays. Native cave/lattice authoring requires at least one explicit room and one edge, with geometry, links, and route data. There is no adapter translating those legacy generator blocks into graph records. This is a substantial procedural migration, not a small recipe mapping, so I did not build it this round.
## Verification
- Fast tier: PASS, 304.0 s, 84 runs and 273 checks; includes four-pose dispatch output.
- Passage tile seams: compiled-analytic and native FI candidate both PASS at the FI owner pose.
- Owner walk/shell comparison: default PASS; native candidate FAIL as detailed above.
- Build stamp: `766b94ca7638475975b0345e5907eebb96158360c384f5d916c78eccbb1aa815`.
@@ -0,0 +1,26 @@
# Linux134 — Tunnel Network cave planner
`TunnelNetwork` dispatches native `Core.Caves.PrimitiveField` v1. Seed 0 is strict-valid: 30 compound rooms, 72 room primitives, 31 edges, 3 branches, and a destination chamber. Its 27-point backbone spans 400.620 m horizontally with a 59.021 m floor change and an 8.381° maximum grade.
Design 4 gives backbone rooms and edges one continuous floor profile `h(s)`. Edge curves follow that profile; each room primitive is placed at its own profiled floor. Side branches keep independent flat floors. Overlap backing is capped at the lower floor. This removes the descending-joint capsule blockage. The incoming passage remains fully open: 21,425/21,425 samples.
The final-source fast tier passes in 370.8 s, including owner passage, shell, floor, ripple and negative-control audits. The default composed route audit passes: 0 blocked, 0 unsupported, 0 grade failures across 51,975 cave-route capsule samples; blocked-route controls detect their controls. The tile seam audit also passes (4 pairs, 4,900 shared samples, 0 mismatches).
Seed 2's cave route passes independently (0 blocked, 0 unsupported, 0 grade failures), but its full composed audit still fails at `passage/1-2/0`: 22 unsupported samples, first at `(15.153, 5.830, -754.680) m`. Seed 1's cave route has 24 blocked samples at the arrival mouth, beginning near `(8.380, -12.618, -864.420) m` on `passage/2-3/0`. Explicit seeds 0 and 3 produce no sweepable route candidates. The default seed is the only fully green composed audit among these runs.
Clang and GCC W1/W8/W12 match all 13 pipeline hash lines at the cave camera; each route audit passes. Seven legacy/planner cutaway pairs cover the arrival room, two backbone rooms, two tunnels, destination chamber, and departure room. All planner views render; several legacy cutaways show sparse geometry. Review them in `Saved/Linux/l134/captures/COMPARE.html`.
L80 timing used five interleaved samples per binary at W8/W12. Values below are the planner median change versus `assembler/world`; each cell is whole-view ready / LOD0–2 ready.
| Strate | W8 | W12 |
|---|---:|---:|
| 0 | -0.70% / +1.52% | +0.97% / +3.50% |
| 1 | +3.81% / +8.38% | +2.41% / +6.19% |
| 2 | +3.38% / +12.90% | +3.98% / +10.26% |
| 3 | +7.97% / +13.54% | +1.93% / +7.67% |
The VFHost Linux Development build succeeds with `-NoUBA -MaxParallelActions=4`. Final Clang/GCC source stamp: `f3b69ea0ce186c855643702a88e0d0732c81c18ec93c17d1824c25210e6408a3`. Canonical owner hashes are pinned at v230.
Logs and timing data are under `Saved/Linux/l134`; the timing summary is `timing/v230-summary.json`, the final fast log is `verify-fast-final-source.log`, and the host build log is `ubt.log`.
The separate legacy `--audit-native-caves` signature check still fails its mouth offset (it expects 1.00 m; the route contract is 1.13 m) and ripple span (1.8878 m against its 1.25 m cap). Its center-air, floor-solid, and shell probes pass; the composed owner route audit above is green.
Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

@@ -0,0 +1 @@
{"schema_version":3,"world_identity":"owner-world/seed/0","owner_voxel_size_um":125000,"core_voxel_size_um":250000,"strate_layout_chunk_size_um":8000000,"strate_layout_chunk_width_subvoxels":8192,"strates":[{"stable_id":"owner-world/seed/0/strate/0/generator/SurfaceWorld","order":0,"strate_index":0,"generator_kind":"SurfaceWorld","recipe_id":"SurfaceWorld.Heightfield2D","recipe_version":2,"interior_bottom_um":-504000000,"interior_top_um":8000000,"voxel_size_um":250000,"lattice_origin_um":[0,0,0],"ceiling":{"kind":"ceiling","bottom_um":7500000,"top_um":8000000,"present":true,"diggable":true,"protected":false,"solid_density_q16":524288,"behaviour":"solid ceiling from strate BoundarySealThickness; breakable by default","density_side":"interior-facing lower surface","cap_path":"shared resolved-layout density band"},"content":{"kind":"content","bottom_um":-503500000,"top_um":7500000,"present":true,"diggable":true,"protected":false,"solid_density_q16":65536,"behaviour":"resolved generator interior","density_side":"none","cap_path":"none"},"bedrock":{"kind":"bedrock","bottom_um":-504000000,"top_um":-503500000,"present":true,"diggable":true,"protected":false,"solid_density_q16":524288,"behaviour":"solid bedrock floor from strate BoundarySealThickness; breakable by default","density_side":"interior-facing upper surface","cap_path":"shared resolved-layout density band"},"transition_mode":"Gradient","transition_blend_chunks":1,"transition_width_um":8000000,"transition_warp_amplitude_um":0,"transition_interpenetration_um":2000000,"transition_honoured_in_core":true,"transition_falls_back_to_hard":false,"transition_dispatch_path":"shared composition moves both band faces together with the smooth declared-width transition field","bound_dispatch_path":"BuildSurfaceStratumPlan uses resolved layout chunk width from ResolvedLayoutSnapshotV3","query_dispatch_path":"NativeSurfaceDensityAt selects floor(CoreZ / resolved layout chunk width) from ResolvedLayoutSnapshotV3","manager_lookup":"UVoxelStrateManager::GetGeneratorTypeForChunk; transition is controlled by upper strate","manager_chunk_width_um":8000000,"dispatch_chunk_width_um":8000000,"anchors":[{"kind":"cap_profile","stable_id":"biome/4097","units":"1/256 Core voxel","values":[-18022,0,64000,1536,0]},{"kind":"route_point","stable_id":"route/0","units":"1/256 Core voxel","values":[8192,24576,-381223,1024]},{"kind":"route_point","stable_id":"route/1","units":"1/256 Core voxel","values":[8192,24320,-381254,1024]},{"kind":"route_point","stable_id":"route/2","units":"1/256 Core voxel","values":[8192,24064,-381284,1024]},{"kind":"route_point","stable_id":"route/3","units":"1/256 Core voxel","values":[8192,23808,-381302,1024]},{"kind":"route_point","stable_id":"route/4","units":"1/256 Core voxel","values":[8192,23552,-381304,1024]},{"kind":"route_point","stable_id":"route/5","units":"1/256 Core voxel","values":[8192,23296,-381291,1024]},{"kind":"route_point","stable_id":"route/6","units":"1/256 Core voxel","values":[8192,23040,-381270,1024]},{"kind":"route_point","stable_id":"route/7","units":"1/256 Core voxel","values":[8192,22784,-381242,1024]},{"kind":"route_point","stable_id":"route/8","units":"1/256 Core voxel","values":[8192,22528,-381218,1024]},{"kind":"route_point","stable_id":"route/9","units":"1/256 Core voxel","values":[8192,22272,-381192,1024]},{"kind":"route_point","stable_id":"route/10","units":"1/256 Core voxel","values":[8192,22016,-381161,1024]},{"kind":"route_point","stable_id":"route/11","units":"1/256 Core voxel","values":[8192,21760,-381118,1024]},{"kind":"route_point","stable_id":"route/12","units":"1/256 Core voxel","values":[8192,21504,-381122,1024]},{"kind":"route_point","stable_id":"route/13","units":"1/256 Core voxel","values":[8192,21248,-381155,1024]},{"kind":"route_point","stable_id":"route/14","units":"1/256 Core voxel","values":[8192,20992,-381183,1024]},{"kind":"route_point","stable_id":"route/15","units":"1/256 Core voxel","values":[8192,20736,-381207,1024]},{"kind":"route_point","stable_id":"route/16","units":"1/256 Core voxel","values":[8192,20480,-381219,1024]},{"kind":"route_point","stable_id":"route/17","units":"1/256 Core voxel","values":[8192,20224,-381220,1024]},{"kind":"route_point","stable_id":"route/18","units":"1/256 Core voxel","values":[8192,19968,-381202,1024]},{"kind":"route_point","stable_id":"route/19","units":"1/256 Core voxel","values":[8192,19712,-381167,1024]},{"kind":"route_point","stable_id":"route/20","units":"1/256 Core voxel","values":[8192,19456,-381130,1024]},{"kind":"route_point","stable_id":"route/21","units":"1/256 Core voxel","values":[8192,19200,-381101,1024]},{"kind":"route_point","stable_id":"route/22","units":"1/256 Core voxel","values":[8192,18944,-381072,1024]},{"kind":"route_point","stable_id":"route/23","units":"1/256 Core voxel","values":[8192,18688,-381092,1024]},{"kind":"route_point","stable_id":"route/24","units":"1/256 Core voxel","values":[8192,18432,-381149,1024]},{"kind":"route_point","stable_id"Line truncated
@@ -0,0 +1 @@
{"schema_version":3,"world_identity":"owner-world/seed/0","owner_voxel_size_um":125000,"core_voxel_size_um":250000,"strate_layout_chunk_size_um":8000000,"strate_layout_chunk_width_subvoxels":8192,"strates":[{"stable_id":"owner-world/seed/0/strate/0/generator/SurfaceWorld","order":0,"strate_index":0,"generator_kind":"SurfaceWorld","recipe_id":"SurfaceWorld.Heightfield2D","recipe_version":2,"interior_bottom_um":-504000000,"interior_top_um":8000000,"voxel_size_um":250000,"lattice_origin_um":[0,0,0],"ceiling":{"kind":"ceiling","bottom_um":7500000,"top_um":8000000,"present":true,"diggable":true,"protected":false,"solid_density_q16":524288,"behaviour":"solid ceiling from strate BoundarySealThickness; breakable by default","density_side":"interior-facing lower surface","cap_path":"shared resolved-layout density band"},"content":{"kind":"content","bottom_um":-503500000,"top_um":7500000,"present":true,"diggable":true,"protected":false,"solid_density_q16":65536,"behaviour":"resolved generator interior","density_side":"none","cap_path":"none"},"bedrock":{"kind":"bedrock","bottom_um":-504000000,"top_um":-503500000,"present":true,"diggable":true,"protected":false,"solid_density_q16":524288,"behaviour":"solid bedrock floor from strate BoundarySealThickness; breakable by default","density_side":"interior-facing upper surface","cap_path":"shared resolved-layout density band"},"transition_mode":"Gradient","transition_blend_chunks":1,"transition_width_um":8000000,"transition_warp_amplitude_um":0,"transition_interpenetration_um":2000000,"transition_honoured_in_core":true,"transition_falls_back_to_hard":false,"transition_dispatch_path":"shared composition moves both band faces together with the smooth declared-width transition field","bound_dispatch_path":"BuildSurfaceStratumPlan uses resolved layout chunk width from ResolvedLayoutSnapshotV3","query_dispatch_path":"NativeSurfaceDensityAt selects floor(CoreZ / resolved layout chunk width) from ResolvedLayoutSnapshotV3","manager_lookup":"UVoxelStrateManager::GetGeneratorTypeForChunk; transition is controlled by upper strate","manager_chunk_width_um":8000000,"dispatch_chunk_width_um":8000000,"anchors":[{"kind":"cap_profile","stable_id":"biome/4097","units":"1/256 Core voxel","values":[-18022,0,64000,1536,0]},{"kind":"route_point","stable_id":"route/0","units":"1/256 Core voxel","values":[8192,24576,-381223,1024]},{"kind":"route_point","stable_id":"route/1","units":"1/256 Core voxel","values":[8192,24320,-381254,1024]},{"kind":"route_point","stable_id":"route/2","units":"1/256 Core voxel","values":[8192,24064,-381284,1024]},{"kind":"route_point","stable_id":"route/3","units":"1/256 Core voxel","values":[8192,23808,-381302,1024]},{"kind":"route_point","stable_id":"route/4","units":"1/256 Core voxel","values":[8192,23552,-381304,1024]},{"kind":"route_point","stable_id":"route/5","units":"1/256 Core voxel","values":[8192,23296,-381291,1024]},{"kind":"route_point","stable_id":"route/6","units":"1/256 Core voxel","values":[8192,23040,-381270,1024]},{"kind":"route_point","stable_id":"route/7","units":"1/256 Core voxel","values":[8192,22784,-381242,1024]},{"kind":"route_point","stable_id":"route/8","units":"1/256 Core voxel","values":[8192,22528,-381218,1024]},{"kind":"route_point","stable_id":"route/9","units":"1/256 Core voxel","values":[8192,22272,-381192,1024]},{"kind":"route_point","stable_id":"route/10","units":"1/256 Core voxel","values":[8192,22016,-381161,1024]},{"kind":"route_point","stable_id":"route/11","units":"1/256 Core voxel","values":[8192,21760,-381118,1024]},{"kind":"route_point","stable_id":"route/12","units":"1/256 Core voxel","values":[8192,21504,-381122,1024]},{"kind":"route_point","stable_id":"route/13","units":"1/256 Core voxel","values":[8192,21248,-381155,1024]},{"kind":"route_point","stable_id":"route/14","units":"1/256 Core voxel","values":[8192,20992,-381183,1024]},{"kind":"route_point","stable_id":"route/15","units":"1/256 Core voxel","values":[8192,20736,-381207,1024]},{"kind":"route_point","stable_id":"route/16","units":"1/256 Core voxel","values":[8192,20480,-381219,1024]},{"kind":"route_point","stable_id":"route/17","units":"1/256 Core voxel","values":[8192,20224,-381220,1024]},{"kind":"route_point","stable_id":"route/18","units":"1/256 Core voxel","values":[8192,19968,-381202,1024]},{"kind":"route_point","stable_id":"route/19","units":"1/256 Core voxel","values":[8192,19712,-381167,1024]},{"kind":"route_point","stable_id":"route/20","units":"1/256 Core voxel","values":[8192,19456,-381130,1024]},{"kind":"route_point","stable_id":"route/21","units":"1/256 Core voxel","values":[8192,19200,-381101,1024]},{"kind":"route_point","stable_id":"route/22","units":"1/256 Core voxel","values":[8192,18944,-381072,1024]},{"kind":"route_point","stable_id":"route/23","units":"1/256 Core voxel","values":[8192,18688,-381092,1024]},{"kind":"route_point","stable_id":"route/24","units":"1/256 Core voxel","values":[8192,18432,-381149,1024]},{"kind":"route_point","stable_id"Line truncated
@@ -0,0 +1 @@
{"schema_version":3,"world_identity":"owner-world/seed/0","owner_voxel_size_um":125000,"core_voxel_size_um":250000,"strate_layout_chunk_size_um":8000000,"strate_layout_chunk_width_subvoxels":8192,"strates":[{"stable_id":"owner-world/seed/0/strate/0/generator/SurfaceWorld","order":0,"strate_index":0,"generator_kind":"SurfaceWorld","recipe_id":"SurfaceWorld.Heightfield2D","recipe_version":2,"interior_bottom_um":-504000000,"interior_top_um":8000000,"voxel_size_um":250000,"lattice_origin_um":[0,0,0],"ceiling":{"kind":"ceiling","bottom_um":7500000,"top_um":8000000,"present":true,"diggable":true,"protected":false,"solid_density_q16":524288,"behaviour":"solid ceiling from strate BoundarySealThickness; breakable by default","density_side":"interior-facing lower surface","cap_path":"shared resolved-layout density band"},"content":{"kind":"content","bottom_um":-503500000,"top_um":7500000,"present":true,"diggable":true,"protected":false,"solid_density_q16":65536,"behaviour":"resolved generator interior","density_side":"none","cap_path":"none"},"bedrock":{"kind":"bedrock","bottom_um":-504000000,"top_um":-503500000,"present":true,"diggable":true,"protected":false,"solid_density_q16":524288,"behaviour":"solid bedrock floor from strate BoundarySealThickness; breakable by default","density_side":"interior-facing upper surface","cap_path":"shared resolved-layout density band"},"transition_mode":"Gradient","transition_blend_chunks":1,"transition_width_um":8000000,"transition_warp_amplitude_um":0,"transition_interpenetration_um":2000000,"transition_honoured_in_core":true,"transition_falls_back_to_hard":false,"transition_dispatch_path":"shared composition moves both band faces together with the smooth declared-width transition field","bound_dispatch_path":"BuildSurfaceStratumPlan uses resolved layout chunk width from ResolvedLayoutSnapshotV3","query_dispatch_path":"NativeSurfaceDensityAt selects floor(CoreZ / resolved layout chunk width) from ResolvedLayoutSnapshotV3","manager_lookup":"UVoxelStrateManager::GetGeneratorTypeForChunk; transition is controlled by upper strate","manager_chunk_width_um":8000000,"dispatch_chunk_width_um":8000000,"anchors":[{"kind":"cap_profile","stable_id":"biome/4097","units":"1/256 Core voxel","values":[-18022,0,64000,1536,0]},{"kind":"route_point","stable_id":"route/0","units":"1/256 Core voxel","values":[8192,24576,-381223,1024]},{"kind":"route_point","stable_id":"route/1","units":"1/256 Core voxel","values":[8192,24320,-381254,1024]},{"kind":"route_point","stable_id":"route/2","units":"1/256 Core voxel","values":[8192,24064,-381284,1024]},{"kind":"route_point","stable_id":"route/3","units":"1/256 Core voxel","values":[8192,23808,-381302,1024]},{"kind":"route_point","stable_id":"route/4","units":"1/256 Core voxel","values":[8192,23552,-381304,1024]},{"kind":"route_point","stable_id":"route/5","units":"1/256 Core voxel","values":[8192,23296,-381291,1024]},{"kind":"route_point","stable_id":"route/6","units":"1/256 Core voxel","values":[8192,23040,-381270,1024]},{"kind":"route_point","stable_id":"route/7","units":"1/256 Core voxel","values":[8192,22784,-381242,1024]},{"kind":"route_point","stable_id":"route/8","units":"1/256 Core voxel","values":[8192,22528,-381218,1024]},{"kind":"route_point","stable_id":"route/9","units":"1/256 Core voxel","values":[8192,22272,-381192,1024]},{"kind":"route_point","stable_id":"route/10","units":"1/256 Core voxel","values":[8192,22016,-381161,1024]},{"kind":"route_point","stable_id":"route/11","units":"1/256 Core voxel","values":[8192,21760,-381118,1024]},{"kind":"route_point","stable_id":"route/12","units":"1/256 Core voxel","values":[8192,21504,-381122,1024]},{"kind":"route_point","stable_id":"route/13","units":"1/256 Core voxel","values":[8192,21248,-381155,1024]},{"kind":"route_point","stable_id":"route/14","units":"1/256 Core voxel","values":[8192,20992,-381183,1024]},{"kind":"route_point","stable_id":"route/15","units":"1/256 Core voxel","values":[8192,20736,-381207,1024]},{"kind":"route_point","stable_id":"route/16","units":"1/256 Core voxel","values":[8192,20480,-381219,1024]},{"kind":"route_point","stable_id":"route/17","units":"1/256 Core voxel","values":[8192,20224,-381220,1024]},{"kind":"route_point","stable_id":"route/18","units":"1/256 Core voxel","values":[8192,19968,-381202,1024]},{"kind":"route_point","stable_id":"route/19","units":"1/256 Core voxel","values":[8192,19712,-381167,1024]},{"kind":"route_point","stable_id":"route/20","units":"1/256 Core voxel","values":[8192,19456,-381130,1024]},{"kind":"route_point","stable_id":"route/21","units":"1/256 Core voxel","values":[8192,19200,-381101,1024]},{"kind":"route_point","stable_id":"route/22","units":"1/256 Core voxel","values":[8192,18944,-381072,1024]},{"kind":"route_point","stable_id":"route/23","units":"1/256 Core voxel","values":[8192,18688,-381092,1024]},{"kind":"route_point","stable_id":"route/24","units":"1/256 Core voxel","values":[8192,18432,-381149,1024]},{"kind":"route_point","stable_id"Line truncated
Loaded 100 of 413 files, more files were not shown because too many files have changed in this diff. Show more