Compare commits

...
Author SHA1 Message Date
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
Fr0zkaandClaude Opus 5.5 b96dd739b8 research: Astra low review of per-block bit-flag masks (read-only)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-30 21:30:51 +01:00
Fr0zkaandClaude Opus 5.5 4041d4bb4b research: smooth-tunnel merge verification (L127)
Builds, fast tier, walk 21425/21425, seams bit-identical, floor 7.884 mm, 18/18 hashes = v225, captures 23/23.
Headless near-ready vs pre-merge: SW +11..14%, other six cells faster. UE start->LOD0-2 2.23-2.73 -> 2.22-2.51 s.
30 m/s UE: near coverage 43.4 -> 40.2%, far holes 3.14 -> 3.46%, p99 11.25 -> 10.80 ms, worst 13.97 -> 13.27 ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-30 16:10:56 +01:00
Fr0zkaandClaude Opus 5.5 aeb60bbda3 merge: smooth inter-strate tunnels (L109-L126 line, evaluator v225) into the main line
Owner-approved. Brings the swept-tube shell, half-pipe open air, continuous floor h(s), rounded crest and
L125 slab stamping onto assembler/world (L120-L124 streaming work). Pending L127 verification.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-30 15:16:09 +01:00
Fr0zkaandClaude Opus 5.5 01a50946cb research: passage preparation filters (L126, no-go, source restored)
Pair overlap comes from per-segment bounds expanded by radius + 4.335 m route reach; two exact filters cut
floor-profile calls 20-23% but near-ready stayed +3..+11% vs L109 (within ~2% of L125).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-30 15:14:42 +01:00
Fr0zkaandClaude Opus 5.5 4111b967bd core: stamp passage route slabs into the tile cache (L125, evaluator v225)
Conservative route slabs with an outside sentinel, tight expanded segment bounds; DensityGrid exact fallbacks
479k -> 0 per run. Near-ready 2-14% faster than L119, still +4.7..+10.2% vs L109; whole view within -0.5..+8%.
Same look (floor offset 7.884 mm, captures checked), seam audit bit-identical, fast tier + walk audits,
Clang/GCC W1/W8/W12 determinism green. BVH and 25 cm tolerance trials rejected. Not for merge yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-30 14:43:31 +01:00
Fr0zkaandClaude Opus 5.5 103fa75e1c research: Astra low review of stamping tunnels along the curve (read-only)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-30 13:25:28 +01:00
Fr0zkaandClaude Opus 5.5 c743f2abc7 research: early arrival tiles before shape publication (L124, measured not worth it)
Shape gate is real (no LOD0-2 tile before shape ready, ~180-226 ms) but the shape pool is already ~8/8 busy;
independent 27-tile pages net at most ~62 ms (VS negative). Near-ready today: SW 0.69-0.71 s, VS 0.84-0.99,
TN 1.24-1.37, FI 1.33-1.44 s. No runtime change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-30 10:33:13 +01:00
Fr0zkaandClaude Opus 5.5 5e7e0eb1c1 research: region completion gate (L123, no production change)
The 18 ms gate is one game-thread tick, not compile cost (0.25 ms). MPSC handoff keeps ownership but still
polls per tick; velocity look-ahead didn't help (near -0.8 pt, p99 11.77 > 11.00). Both kept as a patch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-30 09:55:45 +01:00
Fr0zkaandClaude Opus 5.5 bae1144ee4 research: idle workers at 30 m/s (L122, no production change)
98% of idle-with-holes windows wait on the expanded critical set; region compile runs 0.26 ms but its
gate stays pending 18 ms p50. Broad early admission: near +15.4 pt but p99 13.4 ms (over the VR 11 ms
frame); near-only: +9.9 pt but far holes +1.5 pt. Trace instrumentation kept as a patch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-30 09:04:04 +01:00
Fr0zkaandClaude Opus 5.5 5c519f5206 streaming: one supplemental far apply per frame when the budget is half unused (L121)
After the normal visible-apply count is reached and <50% of the 2 ms budget is spent, apply one horizon
result (off during startup boost; voxel.StreamingFarApplySupplement=0 disables). 30 m/s UE, 5 pairs:
far holes 4.38% -> 2.70%, far finish-to-apply p50 1.105 -> 0.114 s, worst tick 14.74 ms (<15.43),
near coverage +0.3 pt, arrival whole-view -313 ms. Geometry unchanged (24/24 hashes), fast tier green.
Adds trace-only RMC proxy publication timing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-30 08:03:18 +01:00
Fr0zkaandClaude Opus 5.5 161565936c research: arrival/far streaming study (L120, no production change)
Far holes coexist with idle workers: far tiles wait median 1.12 s / p95 3.21 s from worker completion
to apply while generating in p95 10 ms, so the game-thread result queue and admission are the limit.
Priority split failed; 10 slots helped but broke the worst-tick bar. LOD2 still waits for far parents.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-30 07:02:55 +01:00
Fr0zkaandClaude Opus 5.5 707bc61553 core: coarser smooth-route tolerance + shell support bound (L119, evaluator v224)
Chord error 2.5 -> 10 cm, shell work skipped outside the 0-0.75 m band. Sweep pairs -39%, prepare -60%,
near-ready 3-8% faster than L115 but still +13..+22% vs L109. Floor offset 7.88 mm; captures match L115
by eye; fast tier, walk audits, Clang/GCC W1/W8/W12 determinism green. Not for merge yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-30 05:35:59 +01:00
Fr0zkaandClaude Opus 5.5 6e52fb48b2 research: L118 passage-tile designs (hash-exact, no readiness gain; kept as patch)
Tagged the passage bucket: per-tile prepareSweptTubeBase dominates (0.47-0.92 s summed per view),
smooth-min second. Segment lower-bound pruning (-17% pairs) and deferred fallback floor work stayed
hash-exact but moved near-ready <3%. Still +17..+26% vs L109.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-30 04:39:40 +01:00
Fr0zkaandClaude Opus 5.5 c2b8d39573 research: smooth-tunnel regression census L109 vs L115 (L117, measurement only)
Near +18..+32% is ~95% in passage-feature tiles (12-17% of tiles): sweep pairs 2.3-2.6x, exact
fallbacks +71..+101%, segment visits 3-4x, plus an untagged cache/prepare/smooth-min bucket
(+0.47 s/view). Skips, triangle counts (-0.1..-1.4%), skirts, normals, far shells ruled out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-30 03:40:37 +01:00
Fr0zkaandClaude Opus 5.5 89504ecc97 research: chord-bound + z-column fine-segment pruning (L116, exact, not merged)
Fine pairs 48.9M -> 38.8M (-21%) with L115-identical hashes, but no readiness change (still
+15..+30% near-ready vs L109). Pair evaluation is not where the regression lives. Kept as patch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-30 03:04:07 +01:00
Fr0zkaandClaude Opus 5.5 f403af1fc1 passages: continuous floor h(s) along the dense route; shell-probe oracle fix (L115, v223)
Floor height from the planner profile h(s) by horizontal projection + arc-length interpolation;
adjacent fine segments share endpoint values. Floor zero-crossing offsets max 7.8 mm (L114: up
to 73 mm); rock-floor close-ups show no ribs. The 2 L114 shell misses were oracle false positives
(points inside an overlapping bore); the oracle now checks every overlapping span; no gate relaxed.
Fast tier PASS; Clang/GCC W1/W8/W12 deterministic. Speed regression grows: near-ready +17..+31% vs
L109 (fine-segment/sample pairs 49.0M vs 19.8M). Not merged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-30 01:48:29 +01:00
Fr0zkaandClaude Opus 5.5 5e875ae504 passages: floor profile by nearest centreline + metric distance outside voids (L114, v222) - WIP
Floor ribs are present in L109/L112/L113 rock tunnels too: the floor height comes from the
nearest segment, so it jumps (up to 5.9-7.3 cm) where the nearest segment switches. v222 reduces
them; faint ribs remain. Quiet-machine timing (load 0.64): near-ready 9-17% slower than L109
(FI W12 0.95 -> 1.10 s); candidate totals ~5x L109. Fast tier FAIL: 2 SW wall-shell probes of
31,381 read host air. Walk/grade audits pass; Clang/GCC deterministic; UE builds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-29 23:18:40 +01:00
Fr0zkaandClaude Opus 5.5 36d954cf2f passages: rounded half-pipe crest (L113, v220); floor bands diagnosed, speed not recovered
Open-air walls: 0.75 m rounded shell, relief fades over the top 0.25 m - the needle spikes are
gone. Every owner plan passes the 15 deg forward-grade check (max ~10.7 deg) and all walk audits.
Floor bands: median 1.25 m interval, ~0.21 m drop on a ~10.4 deg floor = one 0.25 m voxel row
projected along the grade (not control points, not slab thickness) - not yet fixed. Exact
segment-to-tile filter did not reduce candidates; removed. Timings this round are unreliable
(a gitea process used ~43% CPU throughout). Fast tier PASS; Clang/GCC deterministic; UE builds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-29 21:57:13 +01:00
Fr0zkaandClaude Opus 5.5 16554cd426 passages: XY-smooth spline with planned height profile + open-air half-pipe (L112, v219)
Dense centripetal spline in XY, height piecewise-linear on the planner's certified profile,
radius/floor depth blended; open air = floor slab + 1 m side walls, no roof; audits sample the
generated centreline. All walk audits pass (grades <= 15 deg, headroom, shell, mouths, ripple);
Clang/GCC W1/W8/W12 deterministic; fast tier PASS; Linux UE builds. Chevron rings gone.
Open issues: jagged spikes along the half-pipe wall tops, visible step lines on the trough
floor; near-ready +7..+10% vs L109 (candidate list p95 4 -> 22). Not merged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-29 20:39:20 +01:00
Fr0zkaandClaude Opus 5.5 5ec09d1ff1 passages: dense centripetal spline + open-air half-pipe attempt (L111, failed audits)
Route resampled to ~1 m (310 -> 2,790 segments); open air writes a 0.75 m floor slab plus 1.0 m
side walls, no roof. The 3D spline overshoots between planned route points, raising floor grades
above 15 deg; a grade-limited variant left 2 unsupported samples on passage 0-1, and the shell/
ripple audits still model the old chord route. Stopped after two designs. Not merged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-29 18:09:46 +01:00
531 changed files with 40392 additions and 796 deletions

No files matched your search

@@ -423,6 +423,7 @@ void RecordApplyFrame(const FApplyFrameSample& Sample)
"\"active_tasks\":%d,\"running_workers\":%d,\"task_cap\":%d," "\"active_tasks\":%d,\"running_workers\":%d,\"task_cap\":%d,"
"\"pending_tiles\":%d,\"pending_results_estimate\":%d," "\"pending_tiles\":%d,\"pending_results_estimate\":%d,"
"\"results_drained\":%d,\"apply_results\":%d,\"meshes_applied\":%d,\"max_applies\":%d," "\"results_drained\":%d,\"apply_results\":%d,\"meshes_applied\":%d,\"max_applies\":%d,"
"\"supplemental_far_applies\":%d,"
"\"apply_budget_ms\":%s,\"count_limit_reached\":%d,\"time_limit_reached\":%d," "\"apply_budget_ms\":%s,\"count_limit_reached\":%d,\"time_limit_reached\":%d,"
"\"pending_by_lod\":%s,\"apply_results_by_lod\":%s," "\"pending_by_lod\":%s,\"apply_results_by_lod\":%s,"
"\"meshes_by_lod\":%s,\"apply_ms_by_lod\":%s"), "\"meshes_by_lod\":%s,\"apply_ms_by_lod\":%s"),
@@ -432,7 +433,8 @@ void RecordApplyFrame(const FApplyFrameSample& Sample)
Sample.RunningWorkerTasks, Sample.TaskCap, Sample.PendingTiles, Sample.RunningWorkerTasks, Sample.TaskCap, Sample.PendingTiles,
Sample.PendingResultsEstimate, Sample.ResultsDrained, Sample.ApplyResultsByLevelTotal, Sample.PendingResultsEstimate, Sample.ResultsDrained, Sample.ApplyResultsByLevelTotal,
Sample.MeshesApplied, Sample.MeshesApplied,
Sample.MaxApplies, *Number(Sample.ApplyBudgetMilliseconds), Sample.MaxApplies, Sample.SupplementalFarApplies,
*Number(Sample.ApplyBudgetMilliseconds),
Sample.bCountLimitReached ? 1 : 0, Sample.bTimeLimitReached ? 1 : 0, Sample.bCountLimitReached ? 1 : 0, Sample.bTimeLimitReached ? 1 : 0,
*PendingByLevel, *ApplyResultsByLevel, *MeshesByLevel, *ApplyMillisecondsByLevel)); *PendingByLevel, *ApplyResultsByLevel, *MeshesByLevel, *ApplyMillisecondsByLevel));
} }
+91 -4
View File
@@ -268,6 +268,12 @@ namespace
GVoxelForgeStreamingResultBacklogCap, GVoxelForgeStreamingResultBacklogCap,
TEXT("Maximum queued results plus admitted tile tasks; 0 disables the safety cap. This is separate from the worker-slot cap.")); TEXT("Maximum queued results plus admitted tile tasks; 0 disables the safety cap. This is separate from the worker-slot cap."));
int32 GVoxelForgeStreamingFarApplySupplement = 1;
FAutoConsoleVariableRef CVarVoxelForgeStreamingFarApplySupplement(
TEXT("voxel.StreamingFarApplySupplement"),
GVoxelForgeStreamingFarApplySupplement,
TEXT("After the normal visible-apply count is reached, allow one far mesh apply when half of the frame time budget remains; 0 disables the supplemental far lane."));
int32 GVoxelForgeStreamingReadyQueueDepth = 64; int32 GVoxelForgeStreamingReadyQueueDepth = 64;
FAutoConsoleVariableRef CVarVoxelForgeStreamingReadyQueueDepth( FAutoConsoleVariableRef CVarVoxelForgeStreamingReadyQueueDepth(
TEXT("voxel.StreamingReadyQueueDepth"), TEXT("voxel.StreamingReadyQueueDepth"),
@@ -2223,6 +2229,12 @@ void AVoxelWorld::BeginPlay()
} }
FParse::Value(FCommandLine::Get(), TEXT("voxel.StreamingStartupApplyBoost="), FParse::Value(FCommandLine::Get(), TEXT("voxel.StreamingStartupApplyBoost="),
GVoxelForgeStreamingStartupApplyBoost); GVoxelForgeStreamingStartupApplyBoost);
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.StreamingFarApplySupplement="),
GVoxelForgeStreamingFarApplySupplement))
{
GVoxelForgeStreamingFarApplySupplement =
GVoxelForgeStreamingFarApplySupplement != 0 ? 1 : 0;
}
// Headless BuildHost does not reliably apply arbitrary console-variable command-line tokens // Headless BuildHost does not reliably apply arbitrary console-variable command-line tokens
// before BeginPlay; parse the test switches explicitly so A/B runs exercise the requested path. // before BeginPlay; parse the test switches explicitly so A/B runs exercise the requested path.
// BuildHost sans interface n'applique pas toujours les CVar arbitraires avant BeginPlay ; // BuildHost sans interface n'applique pas toujours les CVar arbitraires avant BeginPlay ;
@@ -2737,6 +2749,7 @@ void AVoxelWorld::BeginPlay()
"\"streaming_collision_lookahead\":%d,\"core_region_concurrent_admission\":%d," "\"streaming_collision_lookahead\":%d,\"core_region_concurrent_admission\":%d,"
"\"collision_gate\":%d," "\"collision_gate\":%d,"
"\"streaming_result_backlog_cap\":%d,\"streaming_startup_apply_boost\":%d," "\"streaming_result_backlog_cap\":%d,\"streaming_startup_apply_boost\":%d,"
"\"streaming_far_apply_supplement\":%d,"
"\"streaming_background_worker_reserve\":%d,\"core_parallel_for_background_priority\":%d," "\"streaming_background_worker_reserve\":%d,\"core_parallel_for_background_priority\":%d,"
"\"streaming_tile_background_low\":%d,\"streaming_tile_background_normal\":%d," "\"streaming_tile_background_low\":%d,\"streaming_tile_background_normal\":%d,"
"\"core_column_cache_direct_mapped\":%d," "\"core_column_cache_direct_mapped\":%d,"
@@ -2758,6 +2771,7 @@ void AVoxelWorld::BeginPlay()
GVoxelForgeStreamingCollisionLookahead, GVoxelForgeCoreRegionConcurrentAdmission, GVoxelForgeStreamingCollisionLookahead, GVoxelForgeCoreRegionConcurrentAdmission,
GVoxelForgeCollisionGate, GVoxelForgeCollisionGate,
GVoxelForgeStreamingResultBacklogCap, GVoxelForgeStreamingStartupApplyBoost, GVoxelForgeStreamingResultBacklogCap, GVoxelForgeStreamingStartupApplyBoost,
GVoxelForgeStreamingFarApplySupplement,
GVoxelForgeStreamingBackgroundWorkerReserve, GVoxelForgeStreamingBackgroundWorkerReserve,
GVoxelForgeCoreParallelForBackgroundPriority, GVoxelForgeCoreParallelForBackgroundPriority,
GVoxelForgeStreamingTileBackgroundLow, GVoxelForgeStreamingTileBackgroundLow,
@@ -9881,6 +9895,7 @@ void AVoxelWorld::ProcessPendingChunks()
const double ApplyBudgetSeconds = const double ApplyBudgetSeconds =
static_cast<double>(ApplyBudgetMilliseconds) * 0.001; static_cast<double>(ApplyBudgetMilliseconds) * 0.001;
int32 MeshesApplied = 0; int32 MeshesApplied = 0;
int32 SupplementalFarApplies = 0;
int32 ResultsDrained = 0; int32 ResultsDrained = 0;
int32 ApplyResultsByLevelTotal = 0; int32 ApplyResultsByLevelTotal = 0;
double ApplySeconds = 0.0; double ApplySeconds = 0.0;
@@ -9888,6 +9903,8 @@ void AVoxelWorld::ProcessPendingChunks()
TStaticArray<int32, 9> MeshesAppliedByLevel{}; TStaticArray<int32, 9> MeshesAppliedByLevel{};
TStaticArray<double, 9> ApplySecondsByLevel{}; TStaticArray<double, 9> ApplySecondsByLevel{};
bool bTimeBudgetHit = false; bool bTimeBudgetHit = false;
bool bForceSupplementalFarDequeue = false;
bool bDequeuedSupplementalFar = false;
const auto RecordApplyFrame = [&]() const auto RecordApplyFrame = [&]()
{ {
if (!VoxelForgeStartupTrace::IsActive()) return; if (!VoxelForgeStartupTrace::IsActive()) return;
@@ -9911,6 +9928,7 @@ void AVoxelWorld::ProcessPendingChunks()
Sample.ResultsDrained = ResultsDrained; Sample.ResultsDrained = ResultsDrained;
Sample.ApplyResultsByLevelTotal = ApplyResultsByLevelTotal; Sample.ApplyResultsByLevelTotal = ApplyResultsByLevelTotal;
Sample.MeshesApplied = MeshesApplied; Sample.MeshesApplied = MeshesApplied;
Sample.SupplementalFarApplies = SupplementalFarApplies;
Sample.MaxApplies = MaxApplies; Sample.MaxApplies = MaxApplies;
Sample.ApplyBudgetMilliseconds = ApplyBudgetMilliseconds; Sample.ApplyBudgetMilliseconds = ApplyBudgetMilliseconds;
Sample.bCountLimitReached = MeshesApplied >= MaxApplies; Sample.bCountLimitReached = MeshesApplied >= MaxApplies;
@@ -9939,6 +9957,7 @@ void AVoxelWorld::ProcessPendingChunks()
FChunkResult DequeuedChunk; FChunkResult DequeuedChunk;
const auto DequeueNextResult = [&]() -> bool const auto DequeueNextResult = [&]() -> bool
{ {
bDequeuedSupplementalFar = false;
if (bStep1UploadCorpusReleased if (bStep1UploadCorpusReleased
&& Step1HeldApplyCursor < Step1HeldApplyResults.Num()) && Step1HeldApplyCursor < Step1HeldApplyResults.Num())
{ {
@@ -9954,8 +9973,19 @@ void AVoxelWorld::ProcessPendingChunks()
// Mirror the admission order. A horizon result that completed earlier must not consume // Mirror the admission order. A horizon result that completed earlier must not consume
// the game-thread apply budget ahead of collision support, an edit, or ordinary LOD0. // the game-thread apply budget ahead of collision support, an edit, or ordinary LOD0.
if (CriticalProcessQueue.Dequeue(DequeuedChunk) if (CriticalProcessQueue.Dequeue(DequeuedChunk)
|| EditedProcessQueue.Dequeue(DequeuedChunk) || EditedProcessQueue.Dequeue(DequeuedChunk))
|| NearProcessQueue.Dequeue(DequeuedChunk) {
RecordStreamingResultDequeued(DequeuedChunk);
return true;
}
if (bForceSupplementalFarDequeue)
{
if (!ProcessQueue.Dequeue(DequeuedChunk)) return false;
bDequeuedSupplementalFar = true;
RecordStreamingResultDequeued(DequeuedChunk);
return true;
}
if (NearProcessQueue.Dequeue(DequeuedChunk)
|| ProcessQueue.Dequeue(DequeuedChunk)) || ProcessQueue.Dequeue(DequeuedChunk))
{ {
RecordStreamingResultDequeued(DequeuedChunk); RecordStreamingResultDequeued(DequeuedChunk);
@@ -10104,6 +10134,10 @@ void AVoxelWorld::ProcessPendingChunks()
if (bMeshApplied) if (bMeshApplied)
{ {
++MeshesApplied; ++MeshesApplied;
if (bDequeuedSupplementalFar && ResultLevel >= 3)
{
++SupplementalFarApplies;
}
if (ResultLevel >= 0 && ResultLevel < 9) if (ResultLevel >= 0 && ResultLevel < 9)
{ {
++MeshesAppliedByLevel[ResultLevel]; ++MeshesAppliedByLevel[ResultLevel];
@@ -10122,6 +10156,19 @@ void AVoxelWorld::ProcessPendingChunks()
} }
if (MeshesApplied >= MaxApplies) if (MeshesApplied >= MaxApplies)
{ {
const double DrainElapsedSeconds = FPlatformTime::ToSeconds64(
FPlatformTime::Cycles64() - DrainStartCycles);
const bool bCanSupplementFar = MeshesApplied == MaxApplies
&& SupplementalFarApplies == 0
&& !bUseStartupApplyBoost
&& GVoxelForgeStreamingFarApplySupplement != 0
&& ApplyBudgetSeconds > 0.0
&& DrainElapsedSeconds < ApplyBudgetSeconds * 0.5;
if (bCanSupplementFar)
{
bForceSupplementalFarDequeue = true;
continue;
}
break; break;
} }
} }
@@ -15360,6 +15407,8 @@ bool AVoxelWorld::ApplyMeshToTile(FChunkResult& Result)
const int32 NumIndices = Result.NumTriangles * 3; const int32 NumIndices = Result.NumTriangles * 3;
const int32 FirstCapIndex = FMath::Clamp( const int32 FirstCapIndex = FMath::Clamp(
NumIndices - Result.NumCeilingTriangles * 3, 0, NumIndices); NumIndices - Result.NumCeilingTriangles * 3, 0, NumIndices);
TFuture<ERealtimeMeshProxyUpdateStatus> ProxyPublicationFuture;
bool bHasProxyPublicationFuture = false;
{ {
TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_RMC_CreateSections); TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_RMC_CreateSections);
if (bHasGroundTris) if (bHasGroundTris)
@@ -15369,9 +15418,10 @@ bool AVoxelWorld::ApplyMeshToTile(FChunkResult& Result)
const FRealtimeMeshStreamRange GroundRange = const FRealtimeMeshStreamRange GroundRange =
URealtimeMeshBlueprintFunctionLibrary::MakeStreamRange( URealtimeMeshBlueprintFunctionLibrary::MakeStreamRange(
0, Result.NumVertices, 0, FirstCapIndex); 0, Result.NumVertices, 0, FirstCapIndex);
RTMesh->CreateSection( ProxyPublicationFuture = RTMesh->CreateSection(
FRealtimeMeshSectionKey::CreateForPolyGroup(GroupKey, 0), FRealtimeMeshSectionKey::CreateForPolyGroup(GroupKey, 0),
GroundConfig, GroundRange, /*bShouldCreateCollision*/ bWantsCollision); GroundConfig, GroundRange, /*bShouldCreateCollision*/ bWantsCollision);
bHasProxyPublicationFuture = true;
} }
if (bHasCeilingTris) if (bHasCeilingTris)
{ {
@@ -15380,11 +15430,48 @@ bool AVoxelWorld::ApplyMeshToTile(FChunkResult& Result)
const FRealtimeMeshStreamRange CapRange = const FRealtimeMeshStreamRange CapRange =
URealtimeMeshBlueprintFunctionLibrary::MakeStreamRange( URealtimeMeshBlueprintFunctionLibrary::MakeStreamRange(
0, Result.NumVertices, FirstCapIndex, NumIndices); 0, Result.NumVertices, FirstCapIndex, NumIndices);
RTMesh->CreateSection( ProxyPublicationFuture = RTMesh->CreateSection(
FRealtimeMeshSectionKey::CreateForPolyGroup(GroupKey, 1), FRealtimeMeshSectionKey::CreateForPolyGroup(GroupKey, 1),
CapConfig, CapRange, /*bShouldCreateCollision*/ bWantsCollision); CapConfig, CapRange, /*bShouldCreateCollision*/ bWantsCollision);
bHasProxyPublicationFuture = true;
} }
} }
const uint64 ProxyPublicationSubmitCycles = FPlatformTime::Cycles64();
if (bTraceRmcTiming && bHasProxyPublicationFuture)
{
const TWeakObjectPtr<AVoxelWorld> WeakWorld(this);
const TWeakObjectPtr<URealtimeMeshComponent> WeakComponent(MeshComp);
const FVoxelTileKey PublishedTile = Tile;
const uint64 RequestOrdinal = Result.RequestOrdinal;
const uint64 ApplyStartedCycles = ApplyStartCycles;
ProxyPublicationFuture.Next(
[WeakWorld, WeakComponent, PublishedTile, RequestOrdinal,
ApplyStartedCycles, ProxyPublicationSubmitCycles](
ERealtimeMeshProxyUpdateStatus Status)
{
if (!VoxelForgeStartupTrace::IsActive()) return;
const uint64 PublishedCycles = FPlatformTime::Cycles64();
const URealtimeMeshComponent* Component = WeakComponent.Get();
VoxelForgeStartupTrace::RecordEvent(TEXT("rmc_proxy_published"), FString::Printf(
TEXT("\"tile\":[%d,%d,%d],\"level\":%d,\"request_ordinal\":%llu,"
"\"proxy_update_status\":%d,\"apply_to_proxy_ms\":%.3f,"
"\"section_submit_to_proxy_ms\":%.3f,\"component_visible\":%d,"
"\"world_valid\":%d"),
PublishedTile.Coord.X, PublishedTile.Coord.Y, PublishedTile.Coord.Z,
PublishedTile.Level, static_cast<unsigned long long>(RequestOrdinal),
static_cast<int32>(Status),
ApplyStartedCycles != 0 && PublishedCycles >= ApplyStartedCycles
? FPlatformTime::ToSeconds64(PublishedCycles - ApplyStartedCycles) * 1000.0
: 0.0,
ProxyPublicationSubmitCycles != 0
&& PublishedCycles >= ProxyPublicationSubmitCycles
? FPlatformTime::ToSeconds64(
PublishedCycles - ProxyPublicationSubmitCycles) * 1000.0
: 0.0,
Component && Component->IsVisible() ? 1 : 0,
WeakWorld.IsValid() ? 1 : 0));
});
}
if (bTraceRmcTiming) if (bTraceRmcTiming)
{ {
TraceSectionConfigMilliseconds = FPlatformTime::ToSeconds64( TraceSectionConfigMilliseconds = FPlatformTime::ToSeconds64(
@@ -59,6 +59,7 @@ namespace VoxelForgeStartupTrace
int32 ResultsDrained = 0; int32 ResultsDrained = 0;
int32 ApplyResultsByLevelTotal = 0; int32 ApplyResultsByLevelTotal = 0;
int32 MeshesApplied = 0; int32 MeshesApplied = 0;
int32 SupplementalFarApplies = 0;
int32 MaxApplies = 0; int32 MaxApplies = 0;
double ApplyBudgetMilliseconds = 0.0; double ApplyBudgetMilliseconds = 0.0;
bool bCountLimitReached = false; bool bCountLimitReached = false;
@@ -0,0 +1,255 @@
#pragma once
#include <algorithm>
#include <array>
#include <cmath>
#include <cstddef>
#include <limits>
#include <span>
#include <vector>
namespace VoxelForgeCore::PassageSpline
{
using FPoint = std::array<double,4>;
inline constexpr std::size_t ArcTableSteps = 32;
inline constexpr double UpperMouthFloorBlendRadiusMetres = 3.5;
struct FSegment
{
FPoint a{},b{};
double alongMetres=0.0,lengthMetres=0.0;
std::size_t controlSpan=0;
double tA=0.0,tB=1.0;
double plannedFloorA=0.0,plannedFloorB=0.0;
};
inline double KnotSpan(const FPoint& a,const FPoint& b,double unitsPerMetre)
{
const double dx=(b[0]-a[0])/unitsPerMetre;
const double dy=(b[1]-a[1])/unitsPerMetre;
return std::max(1.0e-6,std::sqrt(std::sqrt(dx*dx+dy*dy)));
}
inline FPoint EvaluateRaw(std::span<const FPoint> controls,std::size_t span,
double t,double unitsPerMetre)
{
const auto& p1=controls[span];
const auto& p2=controls[span+1];
const auto& p0=controls[span==0?0:span-1];
const auto& p3=controls[std::min(span+2,controls.size()-1)];
const double dt0=KnotSpan(p0,p1,unitsPerMetre);
const double dt1=KnotSpan(p1,p2,unitsPerMetre);
const double dt2=KnotSpan(p2,p3,unitsPerMetre);
const double t2=t*t,t3=t2*t;
FPoint result{};
for(const std::size_t axis:{0u,1u,3u})
{
const double m1=dt1*((p1[axis]-p0[axis])/dt0
-(p2[axis]-p0[axis])/(dt0+dt1)
+(p2[axis]-p1[axis])/dt1);
const double m2=dt1*((p2[axis]-p1[axis])/dt1
-(p3[axis]-p1[axis])/(dt1+dt2)
+(p3[axis]-p2[axis])/dt2);
result[axis]=(2.0*t3-3.0*t2+1.0)*p1[axis]
+(-2.0*t3+3.0*t2)*p2[axis]
+(t3-2.0*t2+t)*m1+(t3-t2)*m2;
}
result[2]=std::lerp(p1[2],p2[2],t);
result[3]=std::clamp(result[3],std::min(p1[3],p2[3]),
std::max(p1[3],p2[3]));
return result;
}
inline bool ClosestRouteHeightAtXY(std::span<const std::array<double,3>> route,
double x,double y,double& height,double& distanceSquared)
{
if(route.empty())return false;
distanceSquared=std::numeric_limits<double>::infinity();
const auto consider=[&](const std::array<double,3>& point)
{
const double dx=x-point[0],dy=y-point[1];
const double candidate=dx*dx+dy*dy;
if(candidate<distanceSquared)
{distanceSquared=candidate;height=point[2];}
};
if(route.size()==1)
{
consider(route.front());
return true;
}
for(std::size_t i=1;i<route.size();++i)
{
const auto& a=route[i-1];
const auto& b=route[i];
const double dx=b[0]-a[0],dy=b[1]-a[1];
const double lengthSquared=dx*dx+dy*dy;
const double t=lengthSquared>1.0e-12
?std::clamp(((x-a[0])*dx+(y-a[1])*dy)/lengthSquared,0.0,1.0):0.0;
const std::array<double,3> point={a[0]+t*dx,a[1]+t*dy,
std::lerp(a[2],b[2],t)};
consider(point);
}
return std::isfinite(distanceSquared);
}
inline double BlendUpperMouthFloorAtXY(
std::span<const std::array<double,3>> receivingRoute,double x,double y,
double plannedFloor,double mouthX,double mouthY,double mouthFloor,
double tangentX,double tangentY,
double blendRadiusMetres,double playerCenterAboveFloorMetres,
double unitsPerMetre=1.0)
{
if(receivingRoute.empty()||!(blendRadiusMetres>0.0)
||!(unitsPerMetre>0.0))return plannedFloor;
const double dx=(x-mouthX)/unitsPerMetre;
const double dy=(y-mouthY)/unitsPerMetre;
if(dx*tangentX+dy*tangentY>=0.0)return plannedFloor;
const double dz=(plannedFloor-mouthFloor)/unitsPerMetre;
const double distance=std::sqrt(dx*dx+dy*dy+dz*dz);
double routeHeight=0.0,routeDistanceSquared=0.0;
if(distance>blendRadiusMetres
||!ClosestRouteHeightAtXY(receivingRoute,x,y,routeHeight,
routeDistanceSquared)
||routeDistanceSquared>std::pow(2.0*blendRadiusMetres*unitsPerMetre,2.0))
return plannedFloor;
const double t=std::clamp(distance/blendRadiusMetres,0.0,1.0);
const double blend=t*t*(3.0-2.0*t);
const double receivingFloor=routeHeight
-playerCenterAboveFloorMetres*unitsPerMetre;
return std::lerp(plannedFloor,receivingFloor,blend);
}
inline std::vector<FSegment> BuildSegments(std::span<const FPoint> controls,
std::size_t spanCount,double spacingMetres,double unitsPerMetre=1.0,
std::span<const double> plannedFloorProfile={},
double maximumChordErrorMetres=0.0)
{
std::vector<FSegment> result;
if(controls.size()<2||!(spacingMetres>0.0)||!(unitsPerMetre>0.0))return result;
spanCount=std::min(spanCount,controls.size()-1);
double alongMetres=0.0;
for(std::size_t span=0;span<spanCount;++span)
{
std::array<double,ArcTableSteps+1> horizontalArc{};
auto previous=EvaluateRaw(controls,span,0.0,unitsPerMetre);
for(std::size_t step=1;step<=ArcTableSteps;++step)
{
const auto current=EvaluateRaw(controls,span,
static_cast<double>(step)/ArcTableSteps,unitsPerMetre);
const double dx=(current[0]-previous[0])/unitsPerMetre;
const double dy=(current[1]-previous[1])/unitsPerMetre;
horizontalArc[step]=horizontalArc[step-1]+std::sqrt(dx*dx+dy*dy);
previous=current;
}
const double spanLength=horizontalArc.back();
const std::size_t baseSegmentCount=std::max<std::size_t>(1,
static_cast<std::size_t>(std::ceil(spanLength/spacingMetres)));
std::vector<FPoint> points;
std::vector<double> parameters;
points.reserve(baseSegmentCount+1);
parameters.reserve(baseSegmentCount+1);
const auto parameterAtArc=[&](double targetArc)
{
const auto upper=std::lower_bound(horizontalArc.begin()+1,
horizontalArc.end(),targetArc);
const std::size_t upperIndex=static_cast<std::size_t>(
upper-horizontalArc.begin());
const std::size_t lowerIndex=upperIndex-1;
const double arcSpan=horizontalArc[upperIndex]-horizontalArc[lowerIndex];
const double arcT=arcSpan>1.0e-12
?std::clamp((targetArc-horizontalArc[lowerIndex])/arcSpan,0.0,1.0):0.0;
return (static_cast<double>(lowerIndex)+arcT)/ArcTableSteps;
};
parameters.push_back(0.0);
const double chordError=std::max(0.0,maximumChordErrorMetres);
const auto appendAdaptive=[&](auto&& self,double tA,double tB,
const FPoint& a,const FPoint& b,unsigned depth)->void
{
constexpr std::array<double,3> CheckFractions={0.25,0.5,0.75};
double maximumError=0.0,polylineLength=0.0;
FPoint previous=a;
for(const double fraction:CheckFractions)
{
const FPoint current=EvaluateRaw(controls,span,
std::lerp(tA,tB,fraction),unitsPerMetre);
const double chordX=std::lerp(a[0],b[0],fraction);
const double chordY=std::lerp(a[1],b[1],fraction);
const double dx=(current[0]-chordX)/unitsPerMetre;
const double dy=(current[1]-chordY)/unitsPerMetre;
const double dr=(current[3]-std::lerp(a[3],b[3],fraction))
/unitsPerMetre;
maximumError=std::max(maximumError,
std::sqrt(dx*dx+dy*dy+dr*dr));
const double segmentX=(current[0]-previous[0])/unitsPerMetre;
const double segmentY=(current[1]-previous[1])/unitsPerMetre;
polylineLength+=std::sqrt(segmentX*segmentX+segmentY*segmentY);
previous=current;
}
const double finalX=(b[0]-previous[0])/unitsPerMetre;
const double finalY=(b[1]-previous[1])/unitsPerMetre;
polylineLength+=std::sqrt(finalX*finalX+finalY*finalY);
const bool split=(polylineLength>spacingMetres*(1.0+1.0e-9)
||(chordError>0.0&&maximumError>chordError))&&depth<12;
if(split)
{
const double tMid=(tA+tB)*0.5;
const FPoint middle=EvaluateRaw(controls,span,tMid,unitsPerMetre);
self(self,tA,tMid,a,middle,depth+1);
self(self,tMid,tB,middle,b,depth+1);
return;
}
parameters.push_back(tB);
};
for(std::size_t segment=1;segment<=baseSegmentCount;++segment)
{
const double targetA=spanLength*static_cast<double>(segment-1)
/baseSegmentCount;
const double targetB=spanLength*static_cast<double>(segment)
/baseSegmentCount;
const double tA=parameterAtArc(targetA);
const double tB=parameterAtArc(targetB);
const FPoint a=EvaluateRaw(controls,span,tA,unitsPerMetre);
const FPoint b=EvaluateRaw(controls,span,tB,unitsPerMetre);
appendAdaptive(appendAdaptive,tA,tB,a,b,0);
}
points.reserve(parameters.size());
for(const double t:parameters)
points.push_back(EvaluateRaw(controls,span,t,unitsPerMetre));
std::vector<double> sampledHorizontalArc(points.size(),0.0);
for(std::size_t i=1;i<points.size();++i)
{
const double dx=(points[i][0]-points[i-1][0])/unitsPerMetre;
const double dy=(points[i][1]-points[i-1][1])/unitsPerMetre;
sampledHorizontalArc[i]=sampledHorizontalArc[i-1]
+std::sqrt(dx*dx+dy*dy);
}
const double sampledSpanLength=sampledHorizontalArc.back();
for(std::size_t i=0;i<points.size();++i)
{
const double progress=sampledSpanLength>1.0e-12
?sampledHorizontalArc[i]/sampledSpanLength:parameters[i];
points[i][2]=std::lerp(controls[span][2],controls[span+1][2],progress);
}
for(std::size_t i=1;i<points.size();++i)
{
const double dx=(points[i][0]-points[i-1][0])/unitsPerMetre;
const double dy=(points[i][1]-points[i-1][1])/unitsPerMetre;
const double dz=(points[i][2]-points[i-1][2])/unitsPerMetre;
const double length=std::sqrt(dx*dx+dy*dy+dz*dz);
const auto floorAt=[&](std::size_t index)
{
if(plannedFloorProfile.size()!=controls.size())
return points[index][2];
const double progress=sampledSpanLength>1.0e-12
?sampledHorizontalArc[index]/sampledSpanLength:parameters[index];
return std::lerp(plannedFloorProfile[span],
plannedFloorProfile[span+1],progress);
};
result.push_back({points[i-1],points[i],alongMetres,length,span,
parameters[i-1],parameters[i],floorAt(i-1),floorAt(i)});
alongMetres+=length;
}
}
return result;
}
}
@@ -12,6 +12,7 @@
#include <vector> #include <vector>
#include "VoxelForgeCore/Linux77Profiler.h" #include "VoxelForgeCore/Linux77Profiler.h"
#include "VoxelForgeCore/PassageSpline.h"
// UBT supplies this export/import macro for the runtime module. The standalone target has no // UBT supplies this export/import macro for the runtime module. The standalone target has no
// module boundary, so leave it empty there. UBT fournit la macro; le viewer autonome reste neutre. // module boundary, so leave it empty there. UBT fournit la macro; le viewer autonome reste neutre.
@@ -146,17 +147,35 @@ namespace VoxelForgeCore
// by the opt-in passage composer profile, except the mesh composition itself. // by the opt-in passage composer profile, except the mesh composition itself.
std::uint64_t passageSweepRouteSegmentCandidates = 0; std::uint64_t passageSweepRouteSegmentCandidates = 0;
std::uint64_t passageSweepSegmentSamplePairs = 0; std::uint64_t passageSweepSegmentSamplePairs = 0;
std::uint64_t passageSweepPrepareNanoseconds = 0;
std::uint64_t passageSweepOutsideInfluencePairs = 0; std::uint64_t passageSweepOutsideInfluencePairs = 0;
std::uint64_t passageSweepShellSupportSkips = 0;
std::uint64_t passageSweepMinimumUpdates = 0; std::uint64_t passageSweepMinimumUpdates = 0;
std::uint64_t passageSweepBaseInfluenceSamples = 0; std::uint64_t passageSweepBaseInfluenceSamples = 0;
std::uint64_t passageSweepDetailShellSamples = 0; std::uint64_t passageSweepDetailShellSamples = 0;
std::uint64_t passageSweepGridSamples = 0; std::uint64_t passageSweepGridSamples = 0;
std::uint64_t passageSweepNoInfluenceGridSamples = 0; std::uint64_t passageSweepNoInfluenceGridSamples = 0;
std::uint64_t passageSweepTouchedSamples = 0;
std::uint64_t passageSweepStampedOutsideSamples = 0;
std::uint64_t passageSweepCacheValidSamples = 0;
std::uint64_t passageSweepCacheValidUniqueSamples = 0;
std::uint64_t passageSweepUniqueTouchedSamples = 0;
std::uint64_t passageSweepBodyBandSamples = 0;
std::uint64_t passageSweepShellBandSamples = 0;
std::uint64_t passageSweepUniqueBandSamples = 0;
std::uint64_t passageSweepOverlapPairs = 0;
std::uint64_t passageSweepContributingPairs = 0;
std::uint64_t passageSweepCrossPassageOverlaps = 0;
std::uint64_t passageSweepFloorProfileCalls = 0;
std::uint64_t passageSweepAllocatedBytes = 0;
std::uint64_t passageSweepPrepareScratchBytes = 0;
std::uint64_t passageSweepLookups = 0; std::uint64_t passageSweepLookups = 0;
std::uint64_t passageSweepCacheHits = 0; std::uint64_t passageSweepCacheHits = 0;
std::uint64_t passageSweepBaseInfluenceHits = 0; std::uint64_t passageSweepBaseInfluenceHits = 0;
std::uint64_t passageSweepOutOfInfluenceHits = 0; std::uint64_t passageSweepOutOfInfluenceHits = 0;
std::uint64_t passageSweepMissingSamples = 0; std::uint64_t passageSweepMissingSamples = 0;
std::uint64_t passageSweepOffGridMisses = 0;
std::uint64_t passageSweepUnpreparedMisses = 0;
std::array<std::uint64_t,12> passageSweepMissingLookupsByCaller{}; std::array<std::uint64_t,12> passageSweepMissingLookupsByCaller{};
std::array<std::uint64_t,12> passageSweepExactRouteFallbacksByCaller{}; std::array<std::uint64_t,12> passageSweepExactRouteFallbacksByCaller{};
std::array<std::uint64_t,12> passageSweepFeatureBoundSkipsByCaller{}; std::array<std::uint64_t,12> passageSweepFeatureBoundSkipsByCaller{};
@@ -468,8 +487,17 @@ namespace VoxelForgeCore
// Version 210 keeps bulk native-cave sampling on the scalar field path at strata edges. // Version 210 keeps bulk native-cave sampling on the scalar field path at strata edges.
// Version 211 makes coordinate biasing in the shared cell hash unsigned, removing signed-overflow UB. // Version 211 makes coordinate biasing in the shared cell hash unsigned, removing signed-overflow UB.
// Version 212 balances generated entry routes against existing walk lanes and preserves their shared treads. // Version 212 balances generated entry routes against existing walk lanes and preserves their shared treads.
// Version 217 replaces passage supports with flat-floor hollow-shell geometry. // Version 218 sweeps passages around a dense smooth spline and limits exposed shells to a trough.
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 217; // Version 219 smooths passage XY while mapping each planned height chord over its sampled horizontal arc.
// Version 220 adaptively samples passage curves and rounds open-air wall crests.
// Version 221 ranks passage segments before floor shaping and preserves their signed exterior distance.
// 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
// continuous floor while relaxing the spline chord tolerance and bounding shell work.
// 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.
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 227;
enum class FieldChannel : std::uint32_t enum class FieldChannel : std::uint32_t
{ {
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,60 @@
# VoxelForge — read-only design review: stamp tunnels along the curve instead of searching the curve per sample
You are reviewing a design idea against real code. **Do not edit, create or delete any file, and do not build or run
anything.** Read code and reason. Be thorough; mark each claim as [CODE] (with file:line), [MEASURED] (from a report),
or [INFERENCE].
## Context
VoxelForge Core (`Standalone/Core`) generates a density field: negative = solid, positive = air, 25 cm voxels, 32³ tiles,
LOD0–8 clipmap, marching cubes. Same seed must give the same world on every machine and worker count; byte-identity with
older versions is NOT required.
Inter-strate tunnels ("passages") on branch `wip/l111-halfpipe-spline` (HEAD `707bc61`) are a smooth spline swept tube:
- a D-shaped walkable floor with a continuous floor height h(s), max grade 15°;
- a rounded crest;
- an open-air half-pipe where there is no rock;
- a thin solid shell ("plain shell rule": interior always air, a thin shell always solid);
- smooth-min where tunnels meet;
- mouths that ramp into the ground.
Key code: `Standalone/Core/src/ShapeWorldInternal.h` (`FScopedPassageTileQueries`, `prepareSweptTubeBase`, around
lines 374 and 11436), `Standalone/Core/src/VoxelForgeCore.cpp:~2747`. Reports: `Standalone/Research/WorldAssemblerM3/Linux109`
… `Linux119` (REPORT.md each).
Cost today [MEASURED, L117–L119]: the smooth tunnel is 13–22% slower than the coarse L109 tunnel at LOD0–2 readiness.
About 95% of the extra cost is in passage-feature tiles. The causes: sweep pairs (sample × curve segment), exact
fallbacks that re-scan segments for off-grid queries, segment visits, per-tile prepare, and smooth-min.
## The idea to review (owner + orchestrator)
Today each sample "pulls": it searches for the nearest curve segment and computes a distance. Proposed: "push"/stamp.
For each tile, walk the curve in rings (frames: position, forward, up, as with an Unreal spline mesh) and write the
tube's signed distance into only the lattice samples near each ring. Only a narrow band of about one voxel around the
wall needs an exact value; deep rock only needs "rock" and the tunnel interior only needs "air". Off-grid queries
(normals/gradients at seams, gameplay point queries) would read the stamped lattice, interpolating, instead of
re-searching the curve. Also: accept curve accuracy at one voxel (25 cm) rather than 2.5–10 cm, and build the curve
frames once per route rather than per tile.
## Questions
1. Is the cost model right? From the code, what does a passage-feature tile do per sample today, and what would stamping
replace? Estimate the work ratio (samples touched × ops) for a typical LOD0 tile and for LOD1–2.
2. What breaks or gets harder, concretely in this code?
- box verdicts/tile skipping (`ClassifyTile`-style proofs that a tile is all solid/air);
- seams between tiles: both neighbours must stamp identical values at shared samples, deterministically;
- LOD levels (coarser lattices);
- off-grid `DensityAtUnclamped` queries and gradients;
- min/smooth-min with the rest of the field and with other tunnels;
- the floor h(s), half-pipe, shell, mouths;
- walk audits;
- thread safety.
3. Is "write into the lattice" compatible with how density is evaluated (lattice fill vs point evaluation)? If the
pipeline is pull-only, what is the smallest change that gets the push benefit? For example, a per-tile sparse
band list built by walking rings, then lookups during fill.
4. Is there a simpler or cheaper alternative we're missing? For example, an analytic local frame per sample from the
spline parameter, or a coarse per-tile segment window that is already almost free.
5. Recommendation: the design you would build, in steps, with the evidence bar (what measurement would prove or kill
it). Keep it short.
Write your answer as plain Markdown, at most ~1,200 words.
@@ -0,0 +1,53 @@
[INFERENCE] **Build a tighter passage cache first.** Ring stamping is promising, but the code already pushes segment contributions into a tile lattice. The main opportunity is reducing coverage, overlap, and off-grid fallback work—not reversing a purely pull-based architecture.
References below: `H` = `Standalone/Core/src/ShapeWorldInternal.h`; `C` = `Standalone/Core/src/VoxelForgeCore.cpp`; `M` = `Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp`. Report references mean `Standalone/Research/WorldAssemblerM3/LinuxNNN/REPORT.md`.
**1. Cost model**
[CODE] `prepareSweptTubeBase` allocates per-passage `(cells+3)³` sample records, including a one-step halo. It walks candidate bounding boxes, lattice samples, then fine segments; individual segment bounds reject some pairs. Accepted pairs compute projection, deterministic square root, radius, floor profile, and potentially shell distance. A final pass flattens the selected tube and caches relief (`H:11436`, `H:11489`, `H:11521`, `H:11659`).
[CODE] During density evaluation, aligned samples retrieve that cache, then still perform relief weighting, floor shaping, mouth fades, shell composition, and host-field composition. Misses invoke two hierarchy searches: nearest body/floor and nearby shells—not necessarily a linear whole-route scan (`H:1247`, `H:1346`, `H:1427`, `H:1435`).
[MEASURED] L119 reports 29.40M preparation pairs and 24.46M segment visits across its four LOD0–2 profiles. Preparation fell 59.8% versus L118, while fallbacks increased 55–58%. L117 attributes about 95% of the tile-worker regression to passage-feature tiles. Thus the diagnosis is sound, but those counters do not establish equivalent costs per operation.
[INFERENCE] For an illustrative straight crossing, assume exposed surface perimeter \(P=16\) m, tile crossing length \(L=32\Delta\), and exact band ±one lattice step. Unique band samples are approximately \(2PL/\Delta^2\):
| [INFERENCE] Normal tile | LOD0 | LOD1 | LOD2 |
|---|---:|---:|---:|
| Spacing Δ | 0.25 m | 0.5 m | 1 m |
| Band samples | 4,096 | 2,048 | 1,024 |
| Fraction of 35³ prepared grid | 9.6% | 4.8% | 2.4% |
[INFERENCE] These are geometric estimates, not measured “typical tiles”; clipping, floor/outer-shell surfaces, bends, and crossings change them. If today fraction \(f\) of the grid receives \(k\) segment evaluations, and stamping averages \(m\) contributions per band sample, the expensive-work ratio is approximately `(table fraction × m)/(f × k)`, multiplied by relative operation cost. For `f=0.5, k=2, m=2`, that is 19%, 9.6%, 4.8%. Allocation, classification, host evaluation, and composition remain. Dense rings can make \(m\) large and erase this advantage.
**2. Concrete difficulties**
- [CODE] **Skipping:** passage-bound overlap currently vetoes a uniform proof and returns `Mixed` (`H:5684`). [INFERENCE] Retain conservative bounds, expanded for approximation and reconstruction support. “No band samples written” proves neither solid nor air: a thin shell can lie between samples.
- [CODE] **Seams:** cache coordinates derive from integer world coordinates; nearest ties use segment index (`H:11448`, `H:11564`, `H:11640`). [INFERENCE] Generate rings from route-global parameters, never tile-local stepping. Include every contributor affecting shared samples and halos; use stable ordering and arithmetic. Independently clipping ring generation at tile boundaries risks disagreement.
- [INFERENCE] **LOD:** distinguish 25 cm curve error from lattice spacing. A fixed 25 cm band is insufficient around 1 m cells; cover interpolation corners and gradient support. A 0.75 m shell can disappear at coarse resolution. Independently reconstructed LOD fields also disagree off-grid; define one canonical point-query field.
- [CODE] **Off-grid:** `sweptSample` rejects non-lattice coordinates; public point queries call `DensityAtUnclamped` without requiring a tile cache (`H:11721`; `C:1875`). Gradients normally use density-grid values, with unclamped fallback (`M:1972`). [INFERENCE] Interpolation needs all eight valid corners, including outside loaded tiles. It changes gradients and zero crossings; interpolating only passage channels also differs from interpolating the fully composed field.
- [CODE] **Composition:** passage bodies and shells use separate hard minima; final composition includes host/native-floor exceptions (`H:1496–1519`). The floor corner uses smooth-max, implemented through smooth-min (`H:3493`, `H:6940`). [INFERENCE] Do not assume the measured smooth-min cost is tunnel-to-tunnel joining. Preserve separate void/shell channels and composition order. Arbitrary “rock/air” magnitudes can move interpolated crossings and smooth blends.
- [CODE] **Shape:** floor selection is independent of minimum round distance; the code explicitly warns that minimizing flattened segment distances makes downhill floors jump (`H:1328`). Floor evaluation uses horizontal projection, authored `h(s)`, and XY mouth blending (`H:3448`). Shells have rounded crests and five-metre mouth fades (`H:1461`, `H:3503`). [INFERENCE] A conventional spline-normal D-section changes this geometry. Stamped isolated rings can scallop; rasterize bounded slabs between frames while preserving gravity-relative floors, shell unions, and interior priority.
- [MEASURED] **Walkability:** L119’s maximum sampled floor-root offset was 7.884 mm; L113 documented voxel-aligned floor ribs. [INFERENCE] A 25 cm curve tolerance does not authorize 25 cm floor error or guarantee ≤15° grade. Audit reconstructed density and meshes, including mouths, overlaps, support, and headroom—not only planner `h(s)`.
- [CODE] **Threads:** copied worker scopes share prepared data through a shared pointer; active scope is thread-local (`H:353`, `H:11411`; `C:2854`). [INFERENCE] Finish stamping before publication. Parallel ring writes require deterministic reductions; mutable shared lazy caches introduce synchronization and lifetime problems.
**3. Smallest useful change**
[CODE] Preparation already precedes `GenerateMesh`, whose fill calls lattice evaluators (`C:2747`, `C:2892`; `M:1488`). Route segments are already prepared globally with 10 cm chord tolerance and an 8 m spacing cap (`H:4601`).
[INFERENCE] Replace preparation’s broad pair enumeration with conservative slab-to-grid spans or small spatial bins. Keep indexed lookups during pull-based fill. Store body/floor ownership and shell contributions separately; use explicit outside/interior/band states. A dense index plus compact records may beat a sparse hash map. Additional frames can be route-global, but this does not eliminate existing per-tile rasterization.
**4. Alternatives and recommendation**
[MEASURED] L115 already reduced coarse candidates 74.1% without reducing fine pairs. L118’s restriction of fallbacks to prepared spans changed output; L116’s 20.8% pair reduction did not recover readiness.
[INFERENCE] An analytic local frame still needs a parameter lookup, especially near folds. Prefer globally indexed conservative segment bins, shared by preparation and exact point queries.
[INFERENCE] Build in three steps: instrument unique samples, overlap, misses by caller, memory, and per-LOD time; implement tighter bins/slabs while preserving geometry; then separately trial 25 cm curve tolerance and canonical interpolation. Accept only reproducible near-readiness gains against L119/L109 plus unchanged walk/seam requirements and cross-compiler/worker determinism. Kill the approach if saved distance work becomes rasterization, memory, or reconstruction cost.
@@ -0,0 +1,53 @@
# VoxelForge — read-only design review: per-block bit flags (masks) to skip work
You are reviewing an idea against real code. **Do not edit, create or delete any file, and do not build or run
anything.** Read code and reason. Be thorough; mark each claim as [CODE] (with file:line), [MEASURED] (from a report),
or [INFERENCE].
## Context
VoxelForge Core (`Standalone/Core`) generates a density field: negative = solid, positive = air, 25 cm voxels, 32³ tiles,
LOD0–8 clipmap, marching cubes. The production mesher for the standalone path is in `Standalone/Source/...`, and Core is
also compiled into Unreal. Same seed must give the same world on every machine and worker count; byte-identity with
older versions is NOT required.
Branch `assembler/world` (HEAD `7022d02`'s parent line, currently `4041d4b`) has the smooth inter-strate tunnels.
Readiness target: LOD0–2 near terrain in ~1 s on arrival. Measured today: SW ~0.7–0.8 s, VS ~0.9 s, FI/TN ~1.0 s
headless. In Linux UE, start→LOD0–2 is ~2.2–2.5 s. At 30 m/s in UE, near coverage is ~40%.
Reports to consult: `Standalone/Research/WorldAssemblerM3/Linux1*/REPORT.md`, especially:
- L77/L87/L117, cost census;
- L124, shape gate and per-strate timings;
- L125/L126/L128, tunnel prepare: per-segment work, 67–73% pair overlap, support up to 7.9 m;
- L128, where an exact one-owner slab scheme halved pairs but got slower because of per-sample ownership tests.
Tile skipping already exists at tile level: box verdicts / classify, where all-solid or all-air tiles are skipped.
## The idea (from the owner)
Keep bit flags per block (1 bit per voxel, or per small brick of e.g. 4³ or 8³ voxels): for example "certainly deep
solid", "certainly deep air", "near a surface", "touched by a tunnel", "already evaluated". Use them to skip work in
generation and meshing, and possibly elsewhere (collision, streaming, edits, AI/nav, lighting).
## Questions
1. Where does time actually go per tile today, and which of that work a mask could skip? Cover density fill,
classification, mesher (marching cubes, normals/gradients, skirts, seam refinement), passage prepare, and composition.
Use measured census numbers where they exist.
2. Which flags are sound? A flag must be a proof, never a guess. A mask "certainly solid" must be derived from a
conservative bound (the bound must be the supremum of what the evaluator produces), or the mesher will delete
surfaces. Which bounds already exist in the code (box verdicts, interval bounds) that a brick-level mask could reuse?
Name the existing hazards. For example, `Identity` means `Sdf >= T`, and `ClassifyTile` proving below-TerrainZ solid
would delete 3D caves.
3. Granularity: per-voxel bits vs per-brick (4³/8³) vs per-column. What does each cost to build, versus what it
saves? Consider SIMD and cache behaviour. Marching cubes needs only cells whose corner signs differ, and gradients
need a halo.
4. "Already evaluated" or "checked" flags: where would they be correct (memoisation of identical queries), and where
would they be wrong (nearest-of-many queries like the tunnel segments)? Is there repeated identical evaluation today,
for example halo samples shared between neighbouring tiles, or skirt/seam re-queries?
5. Other uses: player edits (diff layer), collision-only server tiles, streaming priority, nav. Which would a mask
genuinely help, given the multiplayer rule (replicate seed + layout + diff, never geometry)?
6. Recommendation: the one or two mask designs most likely to pay off, with the expected gain (a range, marked
inference), the measurement that would prove or kill each, and the risks. Keep it short.
Write plain Markdown, at most ~1,200 words.
@@ -0,0 +1,83 @@
[INFERENCE] **Improve the existing brick-proof system; don’t add a general “checked voxel” mask.** The promising savings are avoided field/composition work and passage preparation, rather than marching-cubes classification.
References below abbreviate these files: [M = production mesher](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp), [S = ShapeWorldInternal.h](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h), [C = VoxelForgeCore.cpp](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp), [O = VoxelDensityOpStack.cpp](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Source/VoxelForge/Private/VoxelDensityOpStack.cpp). L-numbers identify `Standalone/Research/WorldAssemblerM3/LinuxN/REPORT.md`.
[INFERENCE] Evidence limitation: Linux128 is absent from this checkout. Its supplied 7.9 m support and slower one-owner result remain unverified here. The reports below span different revisions; their percentages must not be presented as today’s census.
**1. Where time goes**
[MEASURED] L77’s exclusive FI/TN LOD0–2 worker account:
| Work | FI | TN |
|---|---:|---:|
| Total worker CPU/view | 23.02 s | 21.50 s |
| Passage composition | 65.9% | 65.4% |
| Support | 14.2% | 13.5% |
| Base field | 3.2% | 3.3% |
| Bands / dispatch | 2.3% / 3.7% | 2.6% / 3.6% |
| MC / skirts / refinement, excluding field work | 1.6% / 0.1% / 7.5% | 1.2% / 0.2% / 8.6% |
[MEASURED] Grid fill occupied 19.058/17.377 worker-seconds, **overlapping** those phase categories. L117’s later census found support zero; its smooth-tunnel regression concentrated about 95% of added tile CPU in passage-feature tiles. Cell classification stayed flat; gradients became cheaper. Thus L77 identifies historical leverage, not current percentages.
[MEASURED] L125 preparation averaged 514/130/4.1 ms worker work at LOD0/1/2. LOD0 evaluated 3.11–8.49 million segment/sample pairs with 67–72% overlap, although the exact band occupied only 5–7% of grid samples. L126 reduced floor-profile calls 20–23% without useful readiness improvement.
[CODE] Brick skipping already exists: configurable block verdicts, required-sample masks, and active-block gradient halos (M:646,1053). MC already rejects cells without sign crossings before interpolation/gradients (M:2186). Gradient memoisation exists (M:1956). Skirts issue separate density queries (M:2491); refinement has its own stage (C:2975).
[INFERENCE] A pre-evaluation proof can save density, composition, and subsequent MC work. A sign mask constructed after density evaluation saves only downstream scanning. Classification itself costs work; no isolated current classification percentage is established by these reports.
[MEASURED] L124’s shape gate alone took 179–226 ms, with no near-tile admissions before completion. L87 additionally measured a 107–153 ms far-parent gate and no headless collision wait. Tile masks cannot directly remove these gates or explain the supplied UE/headless gap.
**2. Sound flags and hazards**
[INFERENCE] For final density \(D\), solid requires an upper bound covering the evaluator’s supremum, \(U<0\); air requires a valid lower bound \(L\ge0\). “Deep” requires an explicit margin. Unknown must stay unknown. A crossing proves an MC-active cell; uniform sampled signs do not prove an entire continuous volume surface-free.
[CODE] Reusable proofs include:
- S:5428: halo-expanded box classification.
- S:5974: exact surface-column extrema over the MC lattice.
- S:6019: floating-island density intervals.
- S:5197,6032: native cave intervals.
- S:5949: stamp interval composition with outward numerical padding.
- O:6998: operation-specific inactivity from SDF lower bounds.
[CODE] These proofs have guards: passage overlap returns unknown (S:5687); unsupported stamp composition returns unknown (S:5655); stratum crossings are rejected (S:5900). The mesher distinguishes uniform candidates from `bSkipSafe` certificates (M:715,756), and otherwise validates candidates against actual samples (M:1838).
[CODE] For gated modifiers, `Identity` can mean `Sdf >= T`, not `abs(Sdf) >= T` (O:1496,6998). Noise bounds cannot assume nominal `[-1,1]`: FBM and unclamped cellular bounds are explicitly discussed at O:848,301.
[INFERENCE] Additional hazards: confusing internal SDF with final density; certifying base terrain before caves, tunnels, shells, stamps or edits; using lattice proofs for off-grid queries; ignoring rounding or gradient halos. A below-`TerrainZ` solid rule applied to 3D caves deletes caves. “Touched by tunnel” should mean conservative candidate overlap; only **proven untouched** authorizes omission.
**3. Granularity**
[INFERENCE] Storage per flag for 32³ cells:
| Granularity | Storage | Construction/tradeoff |
|---|---:|---|
| Per cell | 4 KiB | 32,768 decisions; expensive unless derived during necessary work |
| 4³ bricks | 64 B | 512 proofs; tighter boundaries, more proof/halo overhead |
| 8³ bricks | 8 B | 64 proofs; cheaper, more mixed bricks |
| XY columns | 128 B | 1,024 decisions; useful mainly for heightfield-specific proofs |
[INFERENCE] These exclude corner/halo samples, metadata and additional flags. A column bit cannot represent multiple cave intervals. Brick runs preserve contiguous X sampling and SIMD better than scattered per-sample branches; bit packing alone does not establish a speedup.
[CODE] The existing implementation uses byte arrays and preserves exact samples around active bricks (M:1053–1083). Replacing densities with arbitrary same-sign constants near active cells would corrupt interpolated positions or normals.
**4. “Already evaluated”**
[CODE] Passage preparation precedes meshing (C:2747). Its valid sentinel is initialization, not completion: subsequent segments update floor selection, shell minima and round-distance winners, with deterministic tie-breaking (S:11520,11615,11688).
[INFERENCE] Therefore first-visitor wins is invalid. Memoisation is valid only for identical queries against identical seed/layout/evaluator/edit revision and query semantics; cache the value, not just a bit.
[MEASURED] L77 counted 8.6–8.9% halo points, without measuring duplicate coordinates. L125 verified 4,900 shared samples across four neighbour pairs and still recorded substantial off-grid skirt/midpoint fallbacks.
[CODE] Shared-grid reuse and gradient validity caches already exist (M:1031,1956). [INFERENCE] Skirt/seam queries are reuse candidates only when coordinates and semantics actually match; “same voxel” is insufficient.
**5. Other uses**
[INFERENCE] Edit-dirty bricks can localize regeneration, with invalidation expanded through normals, seams and collision dependencies. Collision-only servers can skip certified empty regions, but still need boundary geometry. Streaming can prioritize potentially occupied bricks; nav and lighting need clearance/connectivity or visibility data beyond occupancy. Keep masks derived locally from replicated seed + layout + diff, versioned with that state.
**6. Recommendation**
- [INFERENCE] **Tune existing 4³/8³ certificates**, caching proofs and emitting contiguous required-sample runs. Plausible incremental near-ready saving: **0–5%**, with regressions possible. Measure proof time, samples avoided, halo expansion, total worker CPU and UE readiness.
- [INFERENCE] **Pass required-region information into passage preparation**, rejecting whole segment/brick regions conservatively. Plausible near-ready saving: **0–10%**, highly uncertain because passage overlap currently vetoes proofs. Kill it if pair reduction fails to reduce preparation and end-to-end time.
[INFERENCE] Both require seam/off-grid validation, adversarial bound checks, edit invalidation tests, and cross-machine/worker determinism. Preserve canonical tie-breaking; old-version byte identity need not constrain a sound redesign.
@@ -0,0 +1,42 @@
# Linux111 report
## Design
Version 218 resamples passage controls with a centripetal Catmull–Rom curve at about 1 m spacing (310 control spans became 2,790 prepared segments). Open-air shells use a 0.75 m floor slab and side walls up to 1.0 m above the floor; there is no roof. Passage interior remains higher priority than shell. The second design constrained the floor route to a 14.5° grade ceiling.
The per-tile candidate lists were not measured. The 310→2,790 figures are prepared route segment counts, not tile candidate list sizes.
## Speed
L109 medians below are the candidate column from the saved five-pair timing matrix. L111 timing was not run after the audits failed.
| Stratum | L109 W8 near-ready (s) | L109 W12 near-ready (s) | L111 W8/W12 |
|---|---:|---:|---|
| SurfaceWorld | 0.737 | 0.761 | Not run |
| FloatingIslands | 0.911 | 0.863 | Not run |
| TunnelNetwork | 0.956 | 0.895 | Not run |
| VerticalShafts | 0.789 | 0.738 | Not run |
## Audits
- Clang standalone build completed. The second design removed all grade failures. Passage 0–1 still had 2 unsupported walk samples; passages 1–2 and 2–3 passed. The combined resolved-passage audit failed.
- Mouth-profile audits passed 3/3. Naturalness and ripple audits passed 2/3.
- The half-pipe shell audit passed 2/3; passage 0–1 reported 11,544 floor and 6,945 wall violations. A targeted trace showed at least one reported floor sample was 0.101 m inside the passage after relief, where interior correctly overrides the shell. The audit's smooth-route overlap model still disagreed at that point, so the remaining shell counts are not conclusive.
- Fast tier, Clang/GCC W1/W8/W12 hash matrix, readiness timing, and Unreal Linux build were not run. Canonical v218 hashes were not generated.
## Captures
No Linux111 capture images were generated. Mouths, 25/50/75% stations, open-air sections, and programmatically verified 4 m outside/interior close-ups remain outstanding.
The candidate audit found these open-air sections:
- passage 0–1: 6.408–16.782 m; 19.500–24.439 m; 1,044.046–1,498.708 m
- passage 1–2: 81.654–85.646 m; 104.092–104.829 m
- passage 2–3: 7.701–26.008 m; 372.697–377.363 m
## Failed attempts
1. Dense spline plus half-pipe: walk grades exceeded 15° and passage 0–1 failed shell, naturalness, and ripple checks.
2. Dense spline plus a grade-limited floor: grade failures cleared, but passage 0–1 retained 2 unsupported walk samples and its shell/naturalness/ripple audits still failed. Work stopped after this second design as requested.
Logs: `Saved/Linux/l111/build-clang.log` and `Saved/Linux/l111/audit-owner.log`.
@@ -0,0 +1,44 @@
# Linux 112 — horizontal spline, planned floor profile
## Shape and audits
The passage now follows a dense centripetal spline in XY. Height stays piecewise linear on the planner's horizontal arc-length profile; radius and floor depth blend smoothly. Open-air sections have a floor slab and 1 m side walls, with no roof, and passage interiors take priority over shells. Version is v219; source stamp is `c31c8baca8f8308dca62edc6d286c8e84a8214ede7ca444fdaf337ae743ccee1`.
The audit samples the same generated centreline. No walkability thresholds were loosened: the grade limit remains 15°, headroom remains 0.30 m for Caves and 2.20 m for Lattice, and the shell, mouth, naturalness, and ripple gates remain in place. The owner route has zero blocked, unsupported, or grade-failure samples. The native Caves route passes at 15.000° maximum grade and 0.300 m minimum headroom; native Lattice passes with zero support failures at 10.028° maximum grade and 2.200 m headroom. All three passage interfaces pass shell, mouth, naturalness, and ripple audits. The FI blocked control at passage/1-2 remains detected (16,380 blocked capsule samples); all three blocked controls were detected.
The native Caves field owns its corridor so the transition shell cannot clip it; the passage interior still opens within its own stratum. Lattice's authored support below the floor remains solid. These preserve the same walkability and support requirements.
## Captures
[Capture index](captures/INDEX.md): 110 images verified (60 L109 before, 50 L112 after), covering mouths, 25/50/75% stations, and every open-air section. This includes 32 verified 4 m close-ups (18 before, 14 after) of the outside and interior walls. For the close-ups, the nominal route target snaps to a nearby mesh triangle; the camera remains 4.00 m from the mesh within 0.02 m, with wall-normal alignment at least 0.25.
## Readiness
Five interleaved L80 pairs per cell, all four strata, W8 and W12. Values are median seconds, L109 binary → L112 binary; near is LOD0–2 ready and whole is whole-view ready.
| Stratum | W8 near | W8 whole | W12 near | W12 whole |
|---|---:|---:|---:|---:|
| SW | 0.779 → 0.822 | 1.837 → 1.826 | 0.802 → 0.845 | 1.768 → 1.835 |
| FI | 0.972 → 1.063 | 1.935 → 2.022 | 0.924 → 0.992 | 1.792 → 1.884 |
| TN | 1.021 → 1.092 | 1.869 → 1.970 | 0.931 → 1.023 | 1.758 → 1.866 |
| VS | 0.832 → 0.921 | 1.764 → 1.827 | 0.783 → 0.861 | 1.666 → 1.741 |
L109's prior report recorded W12 near-ready at 0.863 s for FI and 0.895 s for TN. In this matrix the L109 binary remeasured at 0.924 s and 0.931 s; L112 measured 0.992 s and 1.023 s. That is +7.4% and +9.9% against the matched rerun, or +15.0% and +14.3% against the prior report values.
## Candidate lists
Feature and route-segment lists have the same counts. Each cell shows L109 → L112; totals are candidates across profiled tiles.
| Stratum | Profiled tiles | Total candidates | p95 per tile | Maximum per tile |
|---|---:|---:|---:|---:|
| SW | 1,763 → 1,742 | 583 → 4,216 | 3 → 18 | 21 → 185 |
| FI | 1,590 → 1,573 | 1,036 → 7,068 | 4 → 22 | 47 → 420 |
| TN | 1,492 → 1,480 | 869 → 4,784 | 4 → 22 | 23 → 116 |
| VS | 1,660 → 1,660 | 644 → 3,133 | 4 → 18 | 16 → 82 |
## Verification
- Fast tier: PASS, 264 checks.
- Clang/GCC W1/W8/W12 determinism: all 18 runs passed; hashes match within each configuration.
- Linux VFHostEditor build: succeeded with `-NoUBA -MaxParallelActions=4` after timing.
- No commit was created.
@@ -0,0 +1,83 @@
#!/usr/bin/env python3
"""Compare per-tile passage candidate-list sizes for L109 and L112."""
from __future__ import annotations
import json
import math
import re
import statistics
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l112/candidate-lists"
DATA = ROOT / "Standalone/data/owner_world.json"
BINARIES = {
"L109": ROOT / "Saved/Linux/l109/bin-final/VoxelForgeCoreHeadless-l109-clang",
"L112": ROOT / "Saved/Linux/l112/bin-clang/VoxelForgeCoreHeadless-l112-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),
}
LIST = re.compile(r"^\[PassageComposerTile\].*?features=(\d+) route_candidates=(\d+)", re.M)
def percentile(values: list[int], fraction: float) -> int:
ordered = sorted(values)
if not ordered:
return 0
return ordered[min(len(ordered) - 1,
max(0, math.ceil(fraction * len(ordered)) - 1))]
def run(strate: int, version: str) -> dict:
OUT.mkdir(parents=True, exist_ok=True)
log = OUT / f"{version}-s{strate}.log"
camera = ",".join(f"{v:.12f}" for v 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",
"--workers=12", "--clip-radius=3", "--max-clip-level=8", "--max-level=8",
f"--camera={camera}", "--profile-passage-composer", "--stream-scheduler-policy=3",
]
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")
features = [int(row[0]) for row in LIST.findall(output)]
routes = [int(row[1]) for row in LIST.findall(output)]
if result.returncode or not features:
raise SystemExit(f"candidate-list profile missing: {log}; exit={result.returncode}")
row = {
"strate": strate, "version": version, "profiled_tiles": len(features),
"feature_candidates": {"sum": sum(features), "min": min(features),
"median": statistics.median(features), "p95": percentile(features, .95),
"max": max(features)},
"route_segment_candidates": {"sum": sum(routes), "min": min(routes),
"median": statistics.median(routes), "p95": percentile(routes, .95),
"max": max(routes)},
"elapsed_s": round(time.monotonic() - started, 3),
"log": str(log.relative_to(ROOT)),
}
print("[L112CandidateLists] " + json.dumps(row, sort_keys=True), flush=True)
return row
def main() -> int:
if any(not path.is_file() for path in (*BINARIES.values(), DATA)):
raise SystemExit("missing L109/L112 candidate-list input")
rows = [run(strate, version) for strate in range(4)
for version in ("L109", "L112")]
(OUT / "summary.json").write_text(json.dumps(rows, indent=2) + "\n",
encoding="utf-8")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,839 @@
#!/usr/bin/env python3
"""Capture L109 and L112 routes, every open-air section, and verified 4 m wall views."""
from __future__ import annotations
import json
import math
import re
from array import array
import struct
import subprocess
import sys
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
RESEARCH = Path(__file__).resolve().parent
CAPTURES = RESEARCH / "captures"
SAVED = ROOT / "Saved/Linux/l112/captures"
DATA = ROOT / "Standalone/data/owner_world.json"
BINARIES = {
"before": ROOT / "Saved/Linux/l109/bin-final/VoxelForgeCoreHeadless-l109-clang",
"after": ROOT / "Saved/Linux/l112/bin-clang/VoxelForgeCoreHeadless-l112-clang",
}
LAYOUTS = {
"before": ROOT / "Saved/Linux/l109/audit-layout-final-stamp.json",
"after": ROOT / "Saved/Linux/l112/audit-layout-candidate.json",
}
AUDIT_LOGS = {
"before": ROOT / "Saved/Linux/l109/audit-owner4-final-stamp.log",
"after": ROOT / "Saved/Linux/l112/audit-owner-clang.log",
}
RENDERERS = {
"before": ROOT / "Saved/Linux/l109/bin-final/VoxelForgeOffscreenCapture-l109-clang",
"after": ROOT / "Saved/Linux/l112/bin-clang/VoxelForgeOffscreenCapture-l112-clang",
}
SECTION = re.compile(
r"^\[PassageOpenAirSection\] id=(\S+) section=(\d+) start_m=([\d.]+) "
r"end_m=([\d.]+) samples=(\d+) midpoint_xyz_m=\(([^)]*)\) "
r"midpoint_radius_m=([\d.]+)", re.M)
DRAWN = re.compile(r"drawn_triangles=(\d+)")
QUALITY_METRICS = re.compile(r"stddev_luma=([\d.]+).*coverage_luma_gt12=([\d.]+)")
def vector(text: str) -> tuple[float, float, float]:
return tuple(float(value) for value in text.split(","))
def add(a, b):
return tuple(x + y for x, y in zip(a, b))
def sub(a, b):
return tuple(x - y for x, y in zip(a, b))
def mul(a, scale):
return tuple(x * scale for x in a)
def norm(a):
length = math.sqrt(sum(x * x for x in a))
if length <= 1.0e-9:
return (0.0, 1.0, 0.0)
return tuple(value / length for value in a)
def angles(camera, target):
delta = sub(target, camera)
horizontal = math.hypot(delta[0], delta[1])
return (math.degrees(math.atan2(delta[0], delta[1])),
math.degrees(math.atan2(delta[2], horizontal)))
def fmt(values) -> str:
return ",".join(f"{value:.6f}" for value in values)
def mesh_triangle_count(mesh: Path) -> int:
"""Read only the VFMSH headers and count indexed triangles."""
with mesh.open("rb") as stream:
magic = stream.read(8)
materials = magic == b"VFMSH002"
if magic not in (b"VFMSH001", b"VFMSH002"):
raise RuntimeError(f"unsupported mesh scene header: {mesh}")
version, _voxel_um, tile_count = struct.unpack("<3I", stream.read(12))
if version != (2 if materials else 1):
raise RuntimeError(f"mesh scene version mismatch: {mesh}")
stream.seek(40, 1)
triangles = 0
for _ in range(tile_count):
stream.seek(16, 1)
surface_count, _skirts, vertex_count, index_count, mask_count = struct.unpack(
"<5I", stream.read(20))
triangles += index_count // 3
stream.seek(vertex_count * 24 + (10 * vertex_count if materials else 0)
+ index_count * 4 + mask_count, 1)
if stream.read(1):
raise RuntimeError(f"trailing data in mesh scene: {mesh}")
return triangles
def dot(a, b):
return sum(x * y for x, y in zip(a, b))
def closest_point_triangle(point, a, b, c):
def closest_segment(start, end, first_weight, second_weight):
edge = sub(end, start)
length_squared = dot(edge, edge)
t = (min(1.0, max(0.0, dot(sub(point, start), edge) / length_squared))
if length_squared > 1.0e-16 else 0.0)
position = add(start, mul(edge, t))
weights = [0.0, 0.0, 0.0]
weights[first_weight], weights[second_weight] = 1.0 - t, t
return position, tuple(weights)
ab, ac = sub(b, a), sub(c, a)
cross = ((ab[1] * ac[2] - ab[2] * ac[1]),
(ab[2] * ac[0] - ab[0] * ac[2]),
(ab[0] * ac[1] - ab[1] * ac[0]))
if dot(cross, cross) <= 1.0e-16:
candidates = [closest_segment(a, b, 0, 1),
closest_segment(b, c, 1, 2),
closest_segment(c, a, 2, 0)]
return min(candidates, key=lambda candidate:
dot(sub(candidate[0], point), sub(candidate[0], point)))
ap = sub(point, a)
d1, d2 = dot(ab, ap), dot(ac, ap)
if d1 <= 0.0 and d2 <= 0.0:
return a, (1.0, 0.0, 0.0)
bp = sub(point, b)
d3, d4 = dot(ab, bp), dot(ac, bp)
if d3 >= 0.0 and d4 <= d3:
return b, (0.0, 1.0, 0.0)
vc = d1 * d4 - d3 * d2
if vc <= 0.0 and d1 >= 0.0 and d3 <= 0.0:
denominator = d1 - d3
if denominator <= 1.0e-16:
return a, (1.0, 0.0, 0.0)
v = d1 / denominator
return add(a, mul(ab, v)), (1.0 - v, v, 0.0)
cp = sub(point, c)
d5, d6 = dot(ab, cp), dot(ac, cp)
if d6 >= 0.0 and d5 <= d6:
return c, (0.0, 0.0, 1.0)
vb = d5 * d2 - d1 * d6
if vb <= 0.0 and d2 >= 0.0 and d6 <= 0.0:
denominator = d2 - d6
if denominator <= 1.0e-16:
return a, (1.0, 0.0, 0.0)
w = d2 / denominator
return add(a, mul(ac, w)), (1.0 - w, 0.0, w)
va = d3 * d6 - d5 * d4
if va <= 0.0 and d4 - d3 >= 0.0 and d5 - d6 >= 0.0:
denominator = (d4 - d3) + (d5 - d6)
if denominator <= 1.0e-16:
return b, (0.0, 1.0, 0.0)
w = (d4 - d3) / denominator
edge = sub(c, b)
return add(b, mul(edge, w)), (0.0, 1.0 - w, w)
face_denominator = va + vb + vc
if face_denominator <= 1.0e-16:
candidates = [closest_segment(a, b, 0, 1),
closest_segment(b, c, 1, 2),
closest_segment(c, a, 2, 0)]
return min(candidates, key=lambda candidate:
dot(sub(candidate[0], point), sub(candidate[0], point)))
denominator = 1.0 / face_denominator
v, w = vb * denominator, vc * denominator
weights = (1.0 - v - w, v, w)
closest = tuple(weights[0] * a[axis] + weights[1] * b[axis]
+ weights[2] * c[axis] for axis in range(3))
return closest, weights
def nearest_surface_anchor(mesh: Path, point, margin_m=200.0, *,
preferred_normal=None, minimum_alignment=-1.0) -> dict:
"""Return the nearest point on a VFMSH surface triangle and its normal."""
best_distance_sq = float("inf")
best_position = best_normal = None
with mesh.open("rb") as stream:
magic = stream.read(8)
materials = magic == b"VFMSH002"
if magic not in (b"VFMSH001", b"VFMSH002"):
raise RuntimeError(f"unsupported mesh scene header: {mesh}")
version, voxel_um, tile_count = struct.unpack("<3I", stream.read(12))
if version != (2 if materials else 1):
raise RuntimeError(f"mesh scene version mismatch: {mesh}")
stream.seek(40, 1)
for _ in range(tile_count):
key = struct.unpack("<4i", stream.read(16))
surface_count, _skirts, vertex_count, index_count, mask_count = struct.unpack(
"<5I", stream.read(20))
extent = (32 << key[3]) * voxel_um / 1_000_000.0
origin = (key[0] * extent, key[1] * extent, key[2] * extent)
if any(point[axis] < origin[axis] - margin_m or
point[axis] > origin[axis] + extent + margin_m for axis in range(3)):
stream.seek(vertex_count * 24 + (10 * vertex_count if materials else 0)
+ index_count * 4 + mask_count, 1)
continue
positions = array("f")
positions.frombytes(stream.read(vertex_count * 12))
normals = array("f")
normals.frombytes(stream.read(vertex_count * 12))
if materials:
stream.seek(vertex_count * 10, 1)
indices = array("I")
indices.frombytes(stream.read(index_count * 4))
stream.seek(mask_count, 1)
if struct.pack("=I", 1) != struct.pack("<I", 1):
positions.byteswap()
normals.byteswap()
indices.byteswap()
def vertex(index):
offset = index * 3
return tuple(float(positions[offset + axis]) for axis in range(3))
def vertex_normal(index):
offset = index * 3
return tuple(float(normals[offset + axis]) for axis in range(3))
for index in range(0, index_count - 2, 3):
triangle = (indices[index], indices[index + 1], indices[index + 2])
if any(vertex_index >= surface_count or vertex_index >= vertex_count
for vertex_index in triangle):
continue
a, b, c = (vertex(vertex_index) for vertex_index in triangle)
position, weights = closest_point_triangle(point, a, b, c)
delta = sub(position, point)
distance_sq = dot(delta, delta)
if distance_sq >= best_distance_sq:
continue
raw_normals = [vertex_normal(vertex_index) for vertex_index in triangle]
raw_normal = tuple(sum(weights[corner] * raw_normals[corner][axis]
for corner in range(3)) for axis in range(3))
if dot(raw_normal, raw_normal) < 1.0e-10:
raw_normal = (
(b[1] - a[1]) * (c[2] - a[2]) - (b[2] - a[2]) * (c[1] - a[1]),
(b[2] - a[2]) * (c[0] - a[0]) - (b[0] - a[0]) * (c[2] - a[2]),
(b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]))
if dot(raw_normal, raw_normal) < 1.0e-10:
continue
candidate_normal = norm(raw_normal)
if preferred_normal is not None:
alignment = dot(candidate_normal, preferred_normal)
if abs(alignment) < minimum_alignment:
continue
if alignment < 0.0:
candidate_normal = mul(candidate_normal, -1.0)
best_distance_sq = distance_sq
best_position = position
best_normal = candidate_normal
if best_position is None:
raise RuntimeError(f"no nearby surface vertices in mesh scene: {mesh}")
return {"position_m": best_position, "normal": best_normal,
"distance_m": math.sqrt(best_distance_sq)}
def sanitize(value: str) -> str:
return re.sub(r"[^A-Za-z0-9_-]+", "-", value).strip("-").replace("/", "-")
def route_height_at_xy(route, x, y):
if not route:
return None
if len(route) == 1:
point = route[0]
return point[2], (x - point[0]) ** 2 + (y - point[1]) ** 2
best = (float("inf"), None)
for a, b in zip(route, route[1:]):
dx, dy = b[0] - a[0], b[1] - a[1]
length_sq = dx * dx + dy * dy
t = min(1.0, max(0.0, ((x - a[0]) * dx + (y - a[1]) * dy) / length_sq)) \
if length_sq > 1.0e-12 else 0.0
px, py = a[0] + t * dx, a[1] + t * dy
distance_sq = (x - px) ** 2 + (y - py) ** 2
if distance_sq < best[0]:
best = (distance_sq, a[2] + (b[2] - a[2]) * t)
return best[1], best[0]
def upper_mouth_floor_depth(layout: dict, passage: dict, controls) -> float:
scale = layout["core_voxel_size_um"] / 256.0 / 1_000_000.0
authored = max(0.0, controls[0][0][2] - passage["upper_mouth_q"][2] * scale + 1.13)
stratum = next((row for row in layout["strates"]
if row["strate_index"] == passage["upper_strate_index"]), None)
if stratum is None or stratum["generator_kind"] != "FloatingIslands":
return authored
route = [tuple(value * scale for value in anchor["values"][:3])
for anchor in stratum.get("anchors", [])
if anchor.get("kind") == "route_point" and len(anchor.get("values", [])) >= 3]
projected = route_height_at_xy(route, controls[0][0][0], controls[0][0][1])
if projected is None or projected[1] > (controls[0][1] + 2.0) ** 2:
return authored
route_depth = max(0.0, controls[0][0][2] - projected[0] + 1.13)
return max(authored, route_depth)
def route_controls(layout: dict, passage: dict, *, adjust_upper_mouth=False):
scale = layout["core_voxel_size_um"] / 256.0 / 1_000_000.0
controls = [(tuple(value * scale for value in row["position_q"]),
row["radius_q"] * scale) for row in passage["control_points"]]
lower_q = passage["lower_mouth_q"]
player_center_above_floor_q = round(
1.13 * 256.0 * 1_000_000.0 / layout["core_voxel_size_um"])
lower_index = next((index for index, row in enumerate(passage["control_points"])
if row["position_q"][0] == lower_q[0]
and row["position_q"][1] == lower_q[1]
and row["position_q"][2] - row["radius_q"]
+ player_center_above_floor_q == lower_q[2]), None)
if lower_index is None:
raise RuntimeError(f"{passage['stable_id']}: lower mouth control is unbound")
if lower_index < 1:
raise RuntimeError(f"{passage['stable_id']}: incomplete route controls")
if adjust_upper_mouth:
controls[0] = (controls[0][0], max(controls[0][1],
upper_mouth_floor_depth(layout, passage, controls)))
return controls, lower_index
def route_for(layout: dict, passage: dict) -> list[tuple[tuple[float, float, float], float]]:
controls, lower_index = route_controls(layout, passage)
return controls[:lower_index + 1]
def spline_knot_span(a, b):
return max(1.0e-6, math.sqrt(math.sqrt((b[0] - a[0]) ** 2
+ (b[1] - a[1]) ** 2)))
def spline_point(controls, span, t):
as_point = lambda index: (*controls[index][0], controls[index][1])
p0, p1, p2, p3 = (as_point(max(0, span - 1)), as_point(span),
as_point(span + 1), as_point(min(span + 2, len(controls) - 1)))
dt0, dt1, dt2 = (spline_knot_span(p0, p1), spline_knot_span(p1, p2),
spline_knot_span(p2, p3))
t2, t3 = t * t, t * t * t
result = [0.0] * 4
for axis in (0, 1, 3):
m1 = dt1 * ((p1[axis] - p0[axis]) / dt0
- (p2[axis] - p0[axis]) / (dt0 + dt1)
+ (p2[axis] - p1[axis]) / dt1)
m2 = dt1 * ((p2[axis] - p1[axis]) / dt1
- (p3[axis] - p1[axis]) / (dt1 + dt2)
+ (p3[axis] - p2[axis]) / dt2)
result[axis] = ((2 * t3 - 3 * t2 + 1) * p1[axis]
+ (-2 * t3 + 3 * t2) * p2[axis]
+ (t3 - 2 * t2 + t) * m1 + (t3 - t2) * m2)
result[2] = p1[2] + (p2[2] - p1[2]) * t
result[3] = min(max(result[3], min(p1[3], p2[3])), max(p1[3], p2[3]))
return result
def smooth_route(layout: dict, passage: dict):
"""Mirror PassageSpline::BuildSegments from the runtime shared sampler."""
scale = layout["core_voxel_size_um"] / 256.0 / 1_000_000.0
planned_floor_profile = [
(row["position_q"][2] - row["radius_q"]) * scale
for row in passage["control_points"]]
controls, route_end = route_controls(layout, passage, adjust_upper_mouth=True)
result = []
along = 0.0
arc_steps = 32
for span in range(route_end):
arc = [0.0]
previous = spline_point(controls, span, 0.0)
for step in range(1, arc_steps + 1):
current = spline_point(controls, span, step / arc_steps)
arc.append(arc[-1] + math.hypot(current[0] - previous[0],
current[1] - previous[1]))
previous = current
segment_count = max(1, math.ceil(arc[-1]))
points = [spline_point(controls, span, 0.0)]
parameters = [0.0]
for segment in range(1, segment_count + 1):
target = min(arc[-1], arc[-1] * segment / segment_count)
upper = next(index for index in range(1, len(arc))
if arc[index] >= target)
lower = upper - 1
width = arc[upper] - arc[lower]
fraction = min(1.0, max(0.0, (target - arc[lower]) / width)) if width > 1e-12 else 0.0
t = (lower + fraction) / arc_steps
points.append(spline_point(controls, span, t))
parameters.append(t)
sampled_arc = [0.0]
for a, b in zip(points, points[1:]):
sampled_arc.append(sampled_arc[-1] + math.hypot(b[0] - a[0], b[1] - a[1]))
horizontal_length = sampled_arc[-1]
for index, point in enumerate(points):
progress = sampled_arc[index] / horizontal_length if horizontal_length > 1e-12 else parameters[index]
point[2] = controls[span][0][2] + (controls[span + 1][0][2]
- controls[span][0][2]) * progress
point.append(planned_floor_profile[span]
+ (planned_floor_profile[span + 1]
- planned_floor_profile[span]) * progress)
if not result:
result.append((tuple(points[0][:3]), points[0][3], 0.0, points[0][4]))
for a, b in zip(points, points[1:]):
length = math.dist(a[:3], b[:3])
along += length
result.append((tuple(b[:3]), b[3], along, b[4]))
return result
def derived_smooth_route(route, passage, layout):
total = route[-1][2]
interior_radii = [radius for _, radius, distance, _ in route
if distance >= 10.0 and total - distance >= 10.0]
body_depth = max(0.5, min(interior_radii or [radius for _, radius, _, _ in route]) - 0.23)
result = []
for point, radius, distance, planned_floor in route:
local_radius_depth = max(0.0, radius - 0.23)
tube_depth = min(body_depth, local_radius_depth)
center = (point[0], point[1], planned_floor + tube_depth)
result.append((center, radius, distance, planned_floor))
return result, body_depth
def route_lengths(route):
lengths = [((b[2] - a[2]) if len(a) > 2 and len(b) > 2
else math.dist(a[0], b[0])) for a, b in zip(route, route[1:])]
prefix = [0.0]
for length in lengths:
prefix.append(prefix[-1] + length)
return lengths, prefix
def at_distance(route, lengths, prefix, distance):
distance = max(0.0, min(distance, prefix[-1]))
for index, length in enumerate(lengths):
if distance <= prefix[index + 1] or index + 1 == len(lengths):
t = 0.0 if length <= 1.0e-9 else (distance - prefix[index]) / length
a, b = route[index], route[index + 1]
return (tuple(a[0][axis] + (b[0][axis] - a[0][axis]) * t
for axis in range(3)),
a[1] + (b[1] - a[1]) * t)
return route[-1]
def tangent_at(route, lengths, prefix, distance):
before = at_distance(route, lengths, prefix, max(0.0, distance - 0.25))[0]
after = at_distance(route, lengths, prefix, min(prefix[-1], distance + 0.25))[0]
return norm(sub(after, before))
def derived_route(route, passage, layout):
"""Apply L109's fixed-depth floor offset to raw layout controls."""
lengths, prefix = route_lengths(route)
total = prefix[-1]
body_radii = [radius for index, (_, radius) in enumerate(route)
if prefix[index] >= 10.0 and total - prefix[index] >= 10.0]
body_depth = max(0.5, min(body_radii or [radius for _, radius in route]) - 0.23)
scale = layout["core_voxel_size_um"] / 256.0 / 1_000_000.0
upper_z = passage["upper_mouth_q"][2] * scale
lower_z = passage["lower_mouth_q"][2] * scale
upper_target = max(0.0, route[0][0][2] - upper_z + 1.13)
lower_target = max(0.0, route[-1][0][2] - lower_z + 1.13)
transformed = []
for index, (point, radius) in enumerate(route):
local_radius_depth = max(0.0, radius - 0.23)
mouth_distance = max(0.0, min(prefix[index], total - prefix[index]))
u = max(0.0, min(1.0, mouth_distance / 5.0))
ramp = u * u * (3.0 - 2.0 * u)
target = upper_target if prefix[index] <= total * 0.5 else lower_target
mouth_depth = max(0.0, min(radius, target))
route_depth = mouth_depth + (local_radius_depth - mouth_depth) * ramp
tube_depth = min(body_depth, local_radius_depth)
transformed.append(((point[0], point[1], point[2] - route_depth + tube_depth),
radius))
return transformed, body_depth
def make_views(layout: dict, audit_sections: list[dict], *, use_derived_shape: bool,
use_spline: bool,
include_open_air: bool = True) -> list[dict]:
views = []
passages = sorted(layout["passages"], key=lambda item: item["stable_id"])
for passage in passages:
raw_route = smooth_route(layout, passage) if use_spline else route_for(layout, passage)
if use_derived_shape:
route, body_depth = (derived_smooth_route(raw_route, passage, layout)
if use_spline else derived_route(raw_route, passage, layout))
else:
route = raw_route
body_depth = max(point[1] for point in route)
def floor_depth(radius: float) -> float:
if not use_derived_shape:
return radius
return min(body_depth, max(0.0, radius - 0.23))
lengths, prefix = route_lengths(route)
total = prefix[-1]
passage_id = passage["stable_id"]
safe_id = sanitize(passage_id)
for end_name, distance, outward_sign in (
("upper-mouth", 0.0, -1.0), ("lower-mouth", total, 1.0)):
center, radius = at_distance(route, lengths, prefix, distance)
tangent = tangent_at(route, lengths, prefix, distance)
outward = mul(tangent, outward_sign)
floor_z = center[2] - floor_depth(radius)
eye = (center[0] + outward[0] * 4.5,
center[1] + outward[1] * 4.5,
floor_z + 1.55)
if end_name == "lower-mouth":
eye = add(eye, add(mul(outward, 5.0), (0.0, 0.0, 2.0)))
target = (center[0] - outward[0] * 2.0,
center[1] - outward[1] * 2.0,
floor_z + 1.45)
yaw, pitch = angles(eye, target)
views.append({"name": f"{safe_id}-{end_name}", "passage": passage_id,
"kind": "mouth", "station": end_name,
"camera": eye, "target": target, "yaw": yaw, "pitch": pitch,
"focus": center, "center_z": center[2]})
for fraction in (0.25, 0.50, 0.75):
distance = total * fraction
center, radius = at_distance(route, lengths, prefix, distance)
tangent = tangent_at(route, lengths, prefix, distance)
depth = floor_depth(radius)
floor_z = center[2] - depth
horizontal = norm((-tangent[1], tangent[0], 0.0))
eye = (center[0] + horizontal[0] * 0.75,
center[1] + horizontal[1] * 0.75, floor_z + 1.60)
target_center, target_radius = at_distance(route, lengths, prefix,
min(total, distance + 14.0))
target_depth = floor_depth(target_radius)
target = (target_center[0] + horizontal[0] * 2.35,
target_center[1] + horizontal[1] * 2.35,
target_center[2] - target_depth + 1.80)
yaw, pitch = angles(eye, target)
views.append({"name": f"{safe_id}-station-{int(fraction * 100)}",
"passage": passage_id, "kind": "station",
"station": f"{int(fraction * 100)}%", "camera": eye,
"target": target, "yaw": yaw, "pitch": pitch,
"focus": center, "center_z": center[2],
"route_distance_m": distance,
"route_length_m": total,
"requested_route_distance_m": distance})
if not include_open_air:
continue
for section in (item for item in audit_sections if item["passage"] == passage_id):
distance = 0.5 * (section["start_m"] + section["end_m"])
center, radius = (section["midpoint"], section["radius_m"])
tangent = tangent_at(route, lengths, prefix, distance)
side = norm((-tangent[1], tangent[0], 0.0))
depth = floor_depth(radius)
section_id = section["section"]
interior_eye = (center[0], center[1], center[2] - depth + 1.25)
interior_target = add(interior_eye, mul(tangent, 7.0))
side_eye = add(center, add(mul(side, radius + 5.0), (0.0, 0.0, 1.2)))
oblique_eye = add(center, add(mul(side, -(radius + 3.5)), (0.0, 0.0, 4.0)))
targets = (add(center, (0.0, 0.0, 0.1)),
add(center, mul(tangent, 2.0)), interior_target)
for view_index, (eye, target) in enumerate(zip(
(side_eye, oblique_eye, interior_eye), targets), start=1):
yaw, pitch = angles(eye, target)
views.append({"name": f"{safe_id}-openair-{section_id}-view-{view_index}",
"passage": passage_id, "kind": "open-air",
"station": f"section-{section_id}/view-{view_index}",
"camera": eye, "target": target, "yaw": yaw,
"pitch": pitch, "focus": center,
"center_z": center[2], "open_air_section": section_id,
"route_distance_m": distance,
"requested_route_distance_m": distance,
"route_length_m": total})
floor_z = center[2] - depth
outside_target = add(center, add(mul(side, radius), (0.0, 0.0, -depth + 0.5)))
outside_eye = add(outside_target, mul(side, 4.0))
interior_radius = math.sqrt(max(0.0, radius * radius
- max(0.0, depth - 0.5) ** 2))
inside_target = add(center, add(mul(side, interior_radius),
(0.0, 0.0, -depth + 0.5)))
inside_eye = add(inside_target,
add(mul(side, -1.0), mul(tangent, math.sqrt(15.0))))
for closeup, eye, target in (("outside", outside_eye, outside_target),
("interior-wall", inside_eye, inside_target)):
yaw, pitch = angles(eye, target)
views.append({"name": f"{safe_id}-openair-{section_id}-4m-{closeup}",
"passage": passage_id, "kind": "open-air-close",
"station": f"section-{section_id}/4m-{closeup}",
"camera": eye, "target": target, "yaw": yaw,
"pitch": pitch, "focus": center, "center_z": center[2],
"open_air_section": section_id, "closeup_side": closeup,
"closeup_preferred_normal": side if closeup == "outside"
else mul(side, -1.0),
"camera_target_distance_m": math.dist(eye, target),
"route_distance_m": distance,
"requested_route_distance_m": distance,
"route_length_m": total})
return views
def run_process(name: str, command: list[str], log: Path, *, allow_failure=False) -> str:
wrapped = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1740s", *map(str, command)]
started = time.monotonic()
with log.open("w", encoding="utf-8") as stream:
result = subprocess.run(wrapped, cwd=ROOT, stdout=stream,
stderr=subprocess.STDOUT, check=False, timeout=1740)
if result.returncode and not allow_failure:
raise RuntimeError(f"{name}: exit={result.returncode} log={log}")
print(f"[L112CaptureProcess] name={name} elapsed_s={time.monotonic()-started:.2f} "
f"exit={result.returncode} log={log.relative_to(ROOT)}", flush=True)
return log.read_text(encoding="utf-8", errors="replace")
def capture(variant: str, view: dict, layout: dict) -> dict:
variant_saved = SAVED / variant
variant_saved.mkdir(parents=True, exist_ok=True)
variant_capture = CAPTURES / variant
variant_capture.mkdir(parents=True, exist_ok=True)
scene = variant_saved / f"{view['name']}.vfmesh"
image = variant_capture / f"{view['name']}.png"
candidates = [(view["camera"], view["target"], view["focus"],
view.get("route_distance_m"), 0.0, "requested")]
if variant == "before" and view["kind"] not in ("mouth", "open-air-close"):
passage = next(item for item in layout["passages"]
if item["stable_id"] == view["passage"])
route = route_for(layout, passage)
lengths, prefix = route_lengths(route)
total = prefix[-1]
eye_offset = sub(view["camera"], view["focus"])
target_offset = sub(view["target"], view["focus"])
requested = view["route_distance_m"]
for delta in (5, -5, 10, -10, 20, -20, 35, -35, 50, -50, 75, -75,
100, -100, 150, -150, 200, -200):
distance = max(0.0, min(total, requested + delta))
if abs(distance - requested) < 0.01:
continue
center, _radius = at_distance(route, lengths, prefix, distance)
candidates.append((add(center, eye_offset), add(center, target_offset), center,
distance, distance - requested, "route-offset"))
attempt_errors = []
for attempt_index, (probe_camera, probe_target, probe_focus, route_distance,
route_adjustment, attempt_kind) in enumerate(candidates):
suffix = "" if attempt_index == 0 else f"-attempt-{attempt_index:02d}"
headless_log = variant_saved / f"{view['name']}{suffix}-mesh.log"
render_log = variant_saved / f"{view['name']}{suffix}-render.log"
quality_log = variant_saved / f"{view['name']}{suffix}-quality.log"
yaw, pitch = angles(probe_camera, probe_target)
camera = (*probe_camera, yaw, pitch)
export_command = [
str(BINARIES[variant]), str(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={fmt(camera)}",
"--render-distance-chunks=1", f"--mesh-export={scene}",
]
export_output = run_process(f"{variant}-{view['name']}{suffix}-mesh", export_command,
headless_log)
if "status=PASS" not in export_output or not scene.is_file():
raise RuntimeError(f"{variant}-{view['name']}: missing successful scene export")
source_triangles = mesh_triangle_count(scene)
if source_triangles <= 0:
attempt_errors.append(f"{attempt_kind}@{route_distance}: empty mesh")
if variant == "after" or view["kind"] == "mouth":
break
continue
actual_camera, actual_target = probe_camera, probe_target
actual_focus = probe_focus
surface_distance = None
closeup_surface_distance = None
closeup_anchor_offset = None
closeup_normal_alignment = None
closeup_distance = None
if view["kind"] == "open-air-close":
try:
wall_anchor = nearest_surface_anchor(scene, probe_target, margin_m=5.0,
preferred_normal=view["closeup_preferred_normal"],
minimum_alignment=0.25)
closeup_surface_distance = wall_anchor["distance_m"]
closeup_anchor_offset = closeup_surface_distance
closeup_normal_alignment = dot(wall_anchor["normal"],
view["closeup_preferred_normal"])
if closeup_surface_distance > 5.0:
raise RuntimeError(f"{variant}-{view['name']}: nearest wall surface is "
f"{closeup_surface_distance:.3f} m from the route-local target")
actual_target = wall_anchor["position_m"]
actual_camera = add(actual_target, mul(wall_anchor["normal"], 4.0))
closeup_distance = math.dist(actual_camera, actual_target)
if abs(closeup_distance - 4.0) > 0.02:
raise RuntimeError(f"{variant}-{view['name']}: camera-to-wall distance is "
f"{closeup_distance:.3f} m, expected 4 m")
except RuntimeError as error:
raise RuntimeError(f"{variant}-{view['name']}: could not verify the 4 m wall "
f"view: {error}") from error
if attempt_kind == "route-offset":
anchor = nearest_surface_anchor(scene, probe_focus)
surface_distance = anchor["distance_m"]
actual_camera = add(anchor["position_m"], mul(anchor["normal"], 12.0))
actual_target = anchor["position_m"]
actual_focus = anchor["position_m"]
actual_yaw, actual_pitch = angles(actual_camera, actual_target)
render_command = [
str(RENDERERS[variant]), str(scene), str(image), "--size=1920x1080",
f"--camera-m={fmt(actual_camera)}", f"--yaw-deg={actual_yaw:.6f}",
f"--pitch-deg={actual_pitch:.6f}", "--shade=readable", "--headlamp=on",
"--height-tint=on", f"--height-center-m={actual_focus[2]:.6f}",
"--height-range-m=32",
]
render_output = run_process(f"{variant}-{view['name']}{suffix}-render", render_command,
render_log)
drawn = DRAWN.search(render_output)
if drawn is None or int(drawn.group(1)) <= 0 or not image.is_file():
attempt_errors.append(f"{attempt_kind}@{route_distance}: no drawn geometry")
if variant == "after" or view["kind"] == "mouth":
break
continue
quality = run_process(f"{variant}-{view['name']}{suffix}-quality", [
"python3", "-B", str(ROOT / "Standalone/Tools/verify/capture_quality.py"),
str(image)], quality_log, allow_failure=True)
quality_status = "pass"
if "status=PASS" not in quality:
metrics = QUALITY_METRICS.search(quality)
coverage = float(metrics.group(2)) if metrics else 0.0
if coverage >= 0.001:
quality_status = "low_contrast_geometry_visible"
else:
attempt_errors.append(f"{attempt_kind}@{route_distance}: no visible pixel coverage")
if variant == "after" or view["kind"] == "mouth":
break
continue
row = {**view, "variant": variant, "capture": str(image.relative_to(RESEARCH)),
"scene": str(scene.relative_to(ROOT)), "drawn_triangles": int(drawn.group(1)),
"source_triangles": source_triangles, "verified": True,
"pixel_quality_status": quality_status,
"route_distance_actual_m": route_distance,
"route_station_adjustment_m": route_adjustment,
"closeup_camera_target_distance_m": closeup_distance,
"closeup_target_surface_distance_m": closeup_surface_distance,
"closeup_predicted_target_offset_m": closeup_anchor_offset,
"closeup_wall_normal_alignment": closeup_normal_alignment,
"fallback_kind": attempt_kind if attempt_kind != "requested" else None,
"fallback_surface_distance_m": surface_distance,
"camera_actual_m": actual_camera, "target_actual_m": actual_target,
"logs": [str(headless_log.relative_to(ROOT)),
str(render_log.relative_to(ROOT)),
str(quality_log.relative_to(ROOT))]}
print("[L112Capture] " + json.dumps(row, sort_keys=True), flush=True)
return row
raise RuntimeError(f"{variant}-{view['name']}: no verified image; "
+ "; ".join(attempt_errors))
def main() -> int:
if any(not path.is_file() for path in (*BINARIES.values(), *RENDERERS.values(),
*LAYOUTS.values(), *AUDIT_LOGS.values())):
raise SystemExit("missing L109 capture binary, layout, or audit log")
all_rows = []
variants = ("before", "after")
if "--after-only" in sys.argv[1:]:
prior_log = SAVED.parent / "captures-before-first.log"
if not prior_log.is_file():
raise SystemExit(f"missing reusable L109 capture rows: {prior_log}")
for line in prior_log.read_text(encoding="utf-8", errors="replace").splitlines():
if line.startswith("[L112Capture] ") or line.startswith("[L109Capture] "):
all_rows.append(json.loads(line.split(" ", 1)[1]))
if not all_rows or any(row["variant"] != "before" for row in all_rows):
raise SystemExit("saved L109 capture rows are incomplete")
variants = ("after",)
sections_by_variant = {}
for variant in ("before", "after"):
sections = []
for passage, section, start, end, samples, midpoint, radius in SECTION.findall(
AUDIT_LOGS[variant].read_text(encoding="utf-8", errors="replace")):
sections.append({"passage": passage, "section": int(section),
"start_m": float(start), "end_m": float(end),
"samples": int(samples), "midpoint": vector(midpoint),
"radius_m": float(radius)})
if not sections:
raise SystemExit(f"no open-air sections found in the {variant} passage audit")
sections_by_variant[variant] = sections
if variant not in variants:
continue
if variant == "after" and variants == ("after",):
expected_before = 15 + 5 * len(sections_by_variant["before"])
if len(all_rows) != expected_before or any(
row.get("variant") != "before" or not row.get("verified")
for row in all_rows):
raise RuntimeError("saved L109 capture rows do not cover every required view")
layout = json.loads(LAYOUTS[variant].read_text(encoding="utf-8"))
include_open_air = True
views = make_views(layout, sections, use_derived_shape=True,
use_spline=variant == "after",
include_open_air=include_open_air)
expected = 5 * len(layout["passages"])
if include_open_air:
expected += 5 * len(sections)
if len(views) != expected:
raise RuntimeError(f"unexpected camera count: {len(views)} != {expected}")
print(f"[L112CapturePlan] variant={variant} passage_views={5*len(layout['passages'])} "
f"open_air_sections={len(sections) if include_open_air else 0} "
f"open_air_views={5*len(sections) if include_open_air else 0} "
f"total={len(views)}", flush=True)
for view in views:
all_rows.append(capture(variant, view, layout))
CAPTURES.mkdir(parents=True, exist_ok=True)
(CAPTURES / "capture-index.json").write_text(
json.dumps({"open_air_sections_by_variant": sections_by_variant,
"captures": all_rows}, indent=2) + "\n",
encoding="utf-8")
lines = ["# L112 passage captures", "",
"Each PNG was rendered from its own exported mesh. The renderer reported visible "
"triangles, and each decoded image met the pixel coverage floor. "
"`low_contrast_geometry_visible` marks images that met this floor below the "
"preferred contrast target.", ""]
for variant in ("before", "after"):
lines.extend([f"## {variant.title()}", ""])
for row in all_rows:
if row["variant"] != variant:
continue
flag = " **OPEN AIR**" if row["kind"] == "open-air" else ""
quality_note = (f"; {row['pixel_quality_status']}"
if row["pixel_quality_status"] != "pass" else "")
lines.append(f"- [{row['station']}]({variant}/{Path(row['capture']).name}) — "
f"{row['passage']}{flag}; {row['drawn_triangles']} visible triangles"
f"{quality_note}")
lines.append("")
(CAPTURES / "INDEX.md").write_text("\n".join(lines), encoding="utf-8")
failures = [row for row in all_rows if not row["verified"]]
closeups = [row for row in all_rows if row["kind"] == "open-air-close"]
closeups_ok = (len(closeups) == 2 * sum(1 for row in all_rows
if row["kind"] == "open-air" and row["station"].endswith("view-1"))
and all(row["verified"] and abs(row["closeup_camera_target_distance_m"] - 4.0) <= 0.02
and row["closeup_target_surface_distance_m"] <= 5.0
and row["closeup_wall_normal_alignment"] >= 0.25
and row["drawn_triangles"] > 0
and row["pixel_quality_status"] in
("pass", "low_contrast_geometry_visible") for row in closeups))
print(f"[L112CaptureSummary] captures={len(all_rows)} verified={len(all_rows)-len(failures)} "
f"failed={len(failures)} 4m_closeups={len(closeups)} closeups_verified="
f"{str(closeups_ok).lower()}", flush=True)
return 1 if failures or not closeups_ok else 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,103 @@
#!/usr/bin/env python3
"""Check Clang/GCC hash identity at W1/W8/W12 and pin v219 owner hashes."""
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/l112/hashes"
DATASETS = {
"owner4": ROOT / "Standalone/data/owner_world.json",
"owner2": ROOT / "Standalone/data/regression/owner_world_2strate_l61.json",
}
BINARIES = {
"clang": ROOT / "Saved/Linux/l112/bin-clang/VoxelForgeCoreHeadless-l112-clang",
"gcc": ROOT / "Saved/Linux/l112/bin-gcc/VoxelForgeCoreHeadless-l112-gcc",
}
HASH = re.compile(r"^\[Pipeline(?:FullDetailHash|WholeViewHash|LODHash|ExactQueryHash)\].*$", re.M)
STAMP = 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.findall(output)
if result.returncode or len(hashes) != 12:
raise SystemExit(f"hash run failed or incomplete: {label}; exit={result.returncode} "
f"hashes={len(hashes)} log={log}")
row = {"dataset": dataset, "recipe": recipe, "workers": workers,
"compiler": compiler, "exit_code": result.returncode,
"hashes": hashes, "source_stamp": (STAMP.search(output).group(1)
if STAMP.search(output) else None),
"elapsed_s": round(time.monotonic() - started, 3),
"log": str(log.relative_to(ROOT))}
print("[L112HashRun] " + 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 L112 hash input")
rows = []
recipes = {"owner4": ("surface-world",),
"owner2": ("surface-world", "floating-islands")}
for dataset, dataset_recipes in recipes.items():
for recipe in dataset_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, dataset_recipes in recipes.items():
for recipe in dataset_recipes:
key = f"{dataset}/{recipe}"
group = [row for row in rows if row["dataset"] == dataset
and row["recipe"] == recipe]
reference = group[0]["hashes"]
groups[key] = {"all_worker_compiler_hashes_match": all(
row["hashes"] == reference for row in group),
"source_stamps": sorted({row["source_stamp"] for row in group}),
"runs": [{"workers": row["workers"], "compiler": row["compiler"],
"log": row["log"]} for row in group]}
if not groups[key]["all_worker_compiler_hashes_match"]:
raise SystemExit(f"determinism mismatch: {key}")
if dataset == "owner2":
canonical = next(row for row in group if row["workers"] == 12
and row["compiler"] == "clang")
recipe_name = "floating-islands" if recipe == "floating-islands" else "surface-world"
style = 1 if recipe_name == "surface-world" else 2
baseline = (ROOT / "Standalone/Tools/verify/baselines/owner-hash/canonical"
/ f"owner-hash-{recipe_name}-style{style}-v219.baseline")
baseline.write_text("\n".join(canonical["hashes"]) + "\n",
encoding="utf-8")
summary = {"runs": rows, "groups": groups,
"owner4_all_four_strata": "owner4/surface-world",
"owner2_canonical_pins": ["owner2/surface-world",
"owner2/floating-islands"]}
(OUT / "summary.json").write_text(json.dumps(summary, indent=2) + "\n",
encoding="utf-8")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,105 @@
#!/usr/bin/env python3
"""Five interleaved L80 readiness pairs comparing L109 and L112 at W8 and W12."""
from __future__ import annotations
import json
import re
import statistics
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l112/timing"
DATA = ROOT / "Standalone/data/owner_world.json"
BINARIES = {
"base": ROOT / "Saved/Linux/l109/bin-final/VoxelForgeCoreHeadless-l109-clang",
"candidate": ROOT / "Saved/Linux/l112/bin-clang/VoxelForgeCoreHeadless-l112-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.compile(r"^\[StreamPipeline\] (.*)$", re.M)
FIELDS = re.compile(r"([a-zA-Z0-9_]+)=([^ ]+)")
NEAR = re.compile(
r"^\[WorldAssemblerNearLODReady\] lods=0-2 tiles=(\d+)/(\d+) cold_ready_s=([0-9.]+)$",
re.M,
)
def run(strate: int, workers: int, variant: str, phase: str, replicate: int) -> dict:
OUT.mkdir(parents=True, exist_ok=True)
label = f"s{strate}-w{workers}-{variant}-{phase}{replicate}"
log = OUT / f"{label}.log"
camera = ",".join(f"{v:.12f}" for v in CAMERAS[strate]) + ",0,0"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
"timeout", "1740s", str(BINARIES[variant]), 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",
]
start = 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_match = PIPELINE.search(content)
near_match = NEAR.search(content)
if result.returncode or pipeline_match is None or near_match is None:
raise SystemExit(f"readiness run failed or trace missing: {log}; exit={result.returncode}")
pipeline = dict(FIELDS.findall(pipeline_match.group(1)))
row = {
"strate": strate, "workers": workers, "variant": variant,
"phase": phase, "replicate": replicate, "exit_code": result.returncode,
"wall_s": round(time.monotonic() - start, 3),
"lod02_ready_s": float(near_match.group(3)),
"whole_view_ready_s": float(pipeline["whole_view_ready"].removesuffix("s")),
"whole_tiles": int(pipeline["whole_tiles"].split("/")[0]),
"log": str(log.relative_to(ROOT)),
}
print("[L112TimingRun] " + json.dumps(row, sort_keys=True), flush=True)
return row
def main() -> int:
if not DATA.is_file() or any(not binary.is_file() for binary in BINARIES.values()):
raise SystemExit("missing L109/L112 data or timing binary")
rows = []
for strate in range(4):
for workers in (8, 12):
run(strate, workers, "base", "warmup", 0)
run(strate, workers, "candidate", "warmup", 0)
for replicate in range(1, 6):
order = ("base", "candidate") if replicate % 2 else ("candidate", "base")
for variant in order:
rows.append(run(strate, workers, variant, "measured", replicate))
(OUT / "runs-partial.json").write_text(
json.dumps(rows, indent=2) + "\n", encoding="utf-8")
summary = []
for strate in range(4):
for workers in (8, 12):
item = {"strate": strate, "workers": workers, "pairs": 5}
for variant in ("base", "candidate"):
group = [row for row in rows if row["strate"] == strate
and row["workers"] == workers and row["variant"] == variant]
for key in ("lod02_ready_s", "whole_view_ready_s"):
values = [row[key] for row in group]
item[f"{variant}_{key}"] = values
item[f"{variant}_{key}_median"] = statistics.median(values)
for key in ("lod02_ready_s", "whole_view_ready_s"):
base = item[f"base_{key}_median"]
candidate = item[f"candidate_{key}_median"]
item[f"{key}_delta_pct"] = round(100 * (candidate / base - 1), 2)
summary.append(item)
(OUT / "summary.json").write_text(
json.dumps({"runs": rows, "summary": summary}, indent=2) + "\n",
encoding="utf-8")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,119 @@
# L112 passage captures
Each PNG was rendered from its own exported mesh. The renderer reported visible triangles, and each decoded image met the pixel coverage floor. `low_contrast_geometry_visible` marks images that met this floor below the preferred contrast target.
## Before
- [upper-mouth](before/passage-0-1-0-upper-mouth.png) — passage/0-1/0; 253282 visible triangles
- [lower-mouth](before/passage-0-1-0-lower-mouth.png) — passage/0-1/0; 559326 visible triangles
- [25%](before/passage-0-1-0-station-25.png) — passage/0-1/0; 145630 visible triangles
- [50%](before/passage-0-1-0-station-50.png) — passage/0-1/0; 430386 visible triangles
- [75%](before/passage-0-1-0-station-75.png) — passage/0-1/0; 294374 visible triangles
- [section-1/view-1](before/passage-0-1-0-openair-1-view-1.png) — passage/0-1/0 **OPEN AIR**; 289074 visible triangles
- [section-1/view-2](before/passage-0-1-0-openair-1-view-2.png) — passage/0-1/0 **OPEN AIR**; 298057 visible triangles
- [section-1/view-3](before/passage-0-1-0-openair-1-view-3.png) — passage/0-1/0 **OPEN AIR**; 322309 visible triangles
- [section-1/4m-outside](before/passage-0-1-0-openair-1-4m-outside.png) — passage/0-1/0; 289074 visible triangles
- [section-1/4m-interior-wall](before/passage-0-1-0-openair-1-4m-interior-wall.png) — passage/0-1/0; 322309 visible triangles
- [section-2/view-1](before/passage-0-1-0-openair-2-view-1.png) — passage/0-1/0 **OPEN AIR**; 283642 visible triangles
- [section-2/view-2](before/passage-0-1-0-openair-2-view-2.png) — passage/0-1/0 **OPEN AIR**; 316771 visible triangles
- [section-2/view-3](before/passage-0-1-0-openair-2-view-3.png) — passage/0-1/0 **OPEN AIR**; 322309 visible triangles
- [section-2/4m-outside](before/passage-0-1-0-openair-2-4m-outside.png) — passage/0-1/0; 283642 visible triangles
- [section-2/4m-interior-wall](before/passage-0-1-0-openair-2-4m-interior-wall.png) — passage/0-1/0; 363071 visible triangles
- [section-3/view-1](before/passage-0-1-0-openair-3-view-1.png) — passage/0-1/0 **OPEN AIR**; 262299 visible triangles
- [section-3/view-2](before/passage-0-1-0-openair-3-view-2.png) — passage/0-1/0 **OPEN AIR**; 310919 visible triangles; low_contrast_geometry_visible
- [section-3/view-3](before/passage-0-1-0-openair-3-view-3.png) — passage/0-1/0 **OPEN AIR**; 363071 visible triangles
- [section-3/4m-outside](before/passage-0-1-0-openair-3-4m-outside.png) — passage/0-1/0; 298863 visible triangles
- [section-3/4m-interior-wall](before/passage-0-1-0-openair-3-4m-interior-wall.png) — passage/0-1/0; 340799 visible triangles
- [section-4/view-1](before/passage-0-1-0-openair-4-view-1.png) — passage/0-1/0 **OPEN AIR**; 378007 visible triangles
- [section-4/view-2](before/passage-0-1-0-openair-4-view-2.png) — passage/0-1/0 **OPEN AIR**; 471132 visible triangles
- [section-4/view-3](before/passage-0-1-0-openair-4-view-3.png) — passage/0-1/0 **OPEN AIR**; 382187 visible triangles
- [section-4/4m-outside](before/passage-0-1-0-openair-4-4m-outside.png) — passage/0-1/0; 309174 visible triangles
- [section-4/4m-interior-wall](before/passage-0-1-0-openair-4-4m-interior-wall.png) — passage/0-1/0; 382187 visible triangles
- [upper-mouth](before/passage-1-2-0-upper-mouth.png) — passage/1-2/0; 570417 visible triangles; low_contrast_geometry_visible
- [lower-mouth](before/passage-1-2-0-lower-mouth.png) — passage/1-2/0; 511318 visible triangles
- [25%](before/passage-1-2-0-station-25.png) — passage/1-2/0; 661032 visible triangles
- [50%](before/passage-1-2-0-station-50.png) — passage/1-2/0; 602996 visible triangles
- [75%](before/passage-1-2-0-station-75.png) — passage/1-2/0; 615520 visible triangles
- [section-1/view-1](before/passage-1-2-0-openair-1-view-1.png) — passage/1-2/0 **OPEN AIR**; 629052 visible triangles
- [section-1/view-2](before/passage-1-2-0-openair-1-view-2.png) — passage/1-2/0 **OPEN AIR**; 588414 visible triangles
- [section-1/view-3](before/passage-1-2-0-openair-1-view-3.png) — passage/1-2/0 **OPEN AIR**; 616234 visible triangles
- [section-1/4m-outside](before/passage-1-2-0-openair-1-4m-outside.png) — passage/1-2/0; 629052 visible triangles
- [section-1/4m-interior-wall](before/passage-1-2-0-openair-1-4m-interior-wall.png) — passage/1-2/0; 573086 visible triangles
- [upper-mouth](before/passage-2-3-0-upper-mouth.png) — passage/2-3/0; 59562 visible triangles
- [lower-mouth](before/passage-2-3-0-lower-mouth.png) — passage/2-3/0; 294522 visible triangles; low_contrast_geometry_visible
- [25%](before/passage-2-3-0-station-25.png) — passage/2-3/0; 55484 visible triangles; low_contrast_geometry_visible
- [50%](before/passage-2-3-0-station-50.png) — passage/2-3/0; 457754 visible triangles
- [75%](before/passage-2-3-0-station-75.png) — passage/2-3/0; 475706 visible triangles
- [section-1/view-1](before/passage-2-3-0-openair-1-view-1.png) — passage/2-3/0 **OPEN AIR**; 104612 visible triangles
- [section-1/view-2](before/passage-2-3-0-openair-1-view-2.png) — passage/2-3/0 **OPEN AIR**; 81078 visible triangles
- [section-1/view-3](before/passage-2-3-0-openair-1-view-3.png) — passage/2-3/0 **OPEN AIR**; 94962 visible triangles; low_contrast_geometry_visible
- [section-1/4m-outside](before/passage-2-3-0-openair-1-4m-outside.png) — passage/2-3/0; 104612 visible triangles; low_contrast_geometry_visible
- [section-1/4m-interior-wall](before/passage-2-3-0-openair-1-4m-interior-wall.png) — passage/2-3/0; 99334 visible triangles
- [section-2/view-1](before/passage-2-3-0-openair-2-view-1.png) — passage/2-3/0 **OPEN AIR**; 475688 visible triangles
- [section-2/view-2](before/passage-2-3-0-openair-2-view-2.png) — passage/2-3/0 **OPEN AIR**; 479134 visible triangles
- [section-2/view-3](before/passage-2-3-0-openair-2-view-3.png) — passage/2-3/0 **OPEN AIR**; 479134 visible triangles
- [section-2/4m-outside](before/passage-2-3-0-openair-2-4m-outside.png) — passage/2-3/0; 475688 visible triangles
- [section-2/4m-interior-wall](before/passage-2-3-0-openair-2-4m-interior-wall.png) — passage/2-3/0; 486364 visible triangles
- [section-3/view-1](before/passage-2-3-0-openair-3-view-1.png) — passage/2-3/0 **OPEN AIR**; 485216 visible triangles
- [section-3/view-2](before/passage-2-3-0-openair-3-view-2.png) — passage/2-3/0 **OPEN AIR**; 479198 visible triangles
- [section-3/view-3](before/passage-2-3-0-openair-3-view-3.png) — passage/2-3/0 **OPEN AIR**; 490436 visible triangles; low_contrast_geometry_visible
- [section-3/4m-outside](before/passage-2-3-0-openair-3-4m-outside.png) — passage/2-3/0; 485216 visible triangles
- [section-3/4m-interior-wall](before/passage-2-3-0-openair-3-4m-interior-wall.png) — passage/2-3/0; 490436 visible triangles
- [section-4/view-1](before/passage-2-3-0-openair-4-view-1.png) — passage/2-3/0 **OPEN AIR**; 475298 visible triangles
- [section-4/view-2](before/passage-2-3-0-openair-4-view-2.png) — passage/2-3/0 **OPEN AIR**; 495856 visible triangles
- [section-4/view-3](before/passage-2-3-0-openair-4-view-3.png) — passage/2-3/0 **OPEN AIR**; 496236 visible triangles; low_contrast_geometry_visible
- [section-4/4m-outside](before/passage-2-3-0-openair-4-4m-outside.png) — passage/2-3/0; 475298 visible triangles
- [section-4/4m-interior-wall](before/passage-2-3-0-openair-4-4m-interior-wall.png) — passage/2-3/0; 475316 visible triangles
## After
- [upper-mouth](after/passage-0-1-0-upper-mouth.png) — passage/0-1/0; 244874 visible triangles
- [lower-mouth](after/passage-0-1-0-lower-mouth.png) — passage/0-1/0; 551596 visible triangles
- [25%](after/passage-0-1-0-station-25.png) — passage/0-1/0; 135136 visible triangles
- [50%](after/passage-0-1-0-station-50.png) — passage/0-1/0; 316508 visible triangles
- [75%](after/passage-0-1-0-station-75.png) — passage/0-1/0; 257040 visible triangles
- [section-1/view-1](after/passage-0-1-0-openair-1-view-1.png) — passage/0-1/0 **OPEN AIR**; 278629 visible triangles
- [section-1/view-2](after/passage-0-1-0-openair-1-view-2.png) — passage/0-1/0 **OPEN AIR**; 287396 visible triangles
- [section-1/view-3](after/passage-0-1-0-openair-1-view-3.png) — passage/0-1/0 **OPEN AIR**; 311780 visible triangles
- [section-1/4m-outside](after/passage-0-1-0-openair-1-4m-outside.png) — passage/0-1/0; 278629 visible triangles
- [section-1/4m-interior-wall](after/passage-0-1-0-openair-1-4m-interior-wall.png) — passage/0-1/0; 311780 visible triangles
- [section-2/view-1](after/passage-0-1-0-openair-2-view-1.png) — passage/0-1/0 **OPEN AIR**; 278629 visible triangles
- [section-2/view-2](after/passage-0-1-0-openair-2-view-2.png) — passage/0-1/0 **OPEN AIR**; 311780 visible triangles
- [section-2/view-3](after/passage-0-1-0-openair-2-view-3.png) — passage/0-1/0 **OPEN AIR**; 311780 visible triangles
- [section-2/4m-outside](after/passage-0-1-0-openair-2-4m-outside.png) — passage/0-1/0; 278629 visible triangles
- [section-2/4m-interior-wall](after/passage-0-1-0-openair-2-4m-interior-wall.png) — passage/0-1/0; 350192 visible triangles
- [section-3/view-1](after/passage-0-1-0-openair-3-view-1.png) — passage/0-1/0 **OPEN AIR**; 210501 visible triangles
- [section-3/view-2](after/passage-0-1-0-openair-3-view-2.png) — passage/0-1/0 **OPEN AIR**; 189354 visible triangles
- [section-3/view-3](after/passage-0-1-0-openair-3-view-3.png) — passage/0-1/0 **OPEN AIR**; 189354 visible triangles
- [section-3/4m-outside](after/passage-0-1-0-openair-3-4m-outside.png) — passage/0-1/0; 156696 visible triangles
- [section-3/4m-interior-wall](after/passage-0-1-0-openair-3-4m-interior-wall.png) — passage/0-1/0; 156658 visible triangles
- [upper-mouth](after/passage-1-2-0-upper-mouth.png) — passage/1-2/0; 595932 visible triangles
- [lower-mouth](after/passage-1-2-0-lower-mouth.png) — passage/1-2/0; 546268 visible triangles
- [25%](after/passage-1-2-0-station-25.png) — passage/1-2/0; 655074 visible triangles
- [50%](after/passage-1-2-0-station-50.png) — passage/1-2/0; 604662 visible triangles
- [75%](after/passage-1-2-0-station-75.png) — passage/1-2/0; 612135 visible triangles
- [section-1/view-1](after/passage-1-2-0-openair-1-view-1.png) — passage/1-2/0 **OPEN AIR**; 623352 visible triangles
- [section-1/view-2](after/passage-1-2-0-openair-1-view-2.png) — passage/1-2/0 **OPEN AIR**; 576164 visible triangles; low_contrast_geometry_visible
- [section-1/view-3](after/passage-1-2-0-openair-1-view-3.png) — passage/1-2/0 **OPEN AIR**; 570605 visible triangles
- [section-1/4m-outside](after/passage-1-2-0-openair-1-4m-outside.png) — passage/1-2/0; 623352 visible triangles
- [section-1/4m-interior-wall](after/passage-1-2-0-openair-1-4m-interior-wall.png) — passage/1-2/0; 612135 visible triangles
- [section-2/view-1](after/passage-1-2-0-openair-2-view-1.png) — passage/1-2/0 **OPEN AIR**; 628316 visible triangles
- [section-2/view-2](after/passage-1-2-0-openair-2-view-2.png) — passage/1-2/0 **OPEN AIR**; 585440 visible triangles
- [section-2/view-3](after/passage-1-2-0-openair-2-view-3.png) — passage/1-2/0 **OPEN AIR**; 613722 visible triangles
- [section-2/4m-outside](after/passage-1-2-0-openair-2-4m-outside.png) — passage/1-2/0; 585802 visible triangles
- [section-2/4m-interior-wall](after/passage-1-2-0-openair-2-4m-interior-wall.png) — passage/1-2/0; 574472 visible triangles
- [upper-mouth](after/passage-2-3-0-upper-mouth.png) — passage/2-3/0; 59568 visible triangles
- [lower-mouth](after/passage-2-3-0-lower-mouth.png) — passage/2-3/0; 294004 visible triangles; low_contrast_geometry_visible
- [25%](after/passage-2-3-0-station-25.png) — passage/2-3/0; 55514 visible triangles; low_contrast_geometry_visible
- [50%](after/passage-2-3-0-station-50.png) — passage/2-3/0; 458230 visible triangles
- [75%](after/passage-2-3-0-station-75.png) — passage/2-3/0; 475838 visible triangles
- [section-1/view-1](after/passage-2-3-0-openair-1-view-1.png) — passage/2-3/0 **OPEN AIR**; 104612 visible triangles
- [section-1/view-2](after/passage-2-3-0-openair-1-view-2.png) — passage/2-3/0 **OPEN AIR**; 76426 visible triangles
- [section-1/view-3](after/passage-2-3-0-openair-1-view-3.png) — passage/2-3/0 **OPEN AIR**; 94962 visible triangles
- [section-1/4m-outside](after/passage-2-3-0-openair-1-4m-outside.png) — passage/2-3/0; 104612 visible triangles; low_contrast_geometry_visible
- [section-1/4m-interior-wall](after/passage-2-3-0-openair-1-4m-interior-wall.png) — passage/2-3/0; 94962 visible triangles; low_contrast_geometry_visible
- [section-2/view-1](after/passage-2-3-0-openair-2-view-1.png) — passage/2-3/0 **OPEN AIR**; 476214 visible triangles
- [section-2/view-2](after/passage-2-3-0-openair-2-view-2.png) — passage/2-3/0 **OPEN AIR**; 479366 visible triangles
- [section-2/view-3](after/passage-2-3-0-openair-2-view-3.png) — passage/2-3/0 **OPEN AIR**; 479366 visible triangles
- [section-2/4m-outside](after/passage-2-3-0-openair-2-4m-outside.png) — passage/2-3/0; 476214 visible triangles
- [section-2/4m-interior-wall](after/passage-2-3-0-openair-2-4m-interior-wall.png) — passage/2-3/0; 486990 visible triangles
Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 347 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 426 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 477 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 461 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 475 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 547 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 507 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 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: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 538 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 448 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 493 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 287 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 523 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 505 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

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