Author SHA1 Message Date
Fr0zkaandClaude Opus 5.5 6ae5c4460d wip(l62): shared boundary-sample cache + hidden skirt skip -- PARKED: SW +8.5%, moving p50 +119..265%, full tier incomplete
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-26 09:47:58 +01:00
Fr0zkaandClaude Opus 5.5 135957831b fix: sanitizer sweep across all generators; bound signed shifts/division/overflow at coordinate limits (L61); R31
New verify.py sanitize tier (Clang address,undefined,float-cast-overflow; GCC address,undefined;
-fno-sanitize-recover) runs 19 cases per compiler at LOD0-8 across every generator, passages and the
crossing: 0 runtime reports; a planted signed-overflow control is caught by both. A static scan fixed
INT_MIN negation in floor division, unbounded signed LOD shifts and coordinate-times-extent overflow in
VoxelClipmapDesiredTiles.h and VoxelTypes.h (plugin and standalone), and VoxelWorld.cpp, VoxelContentManager.cpp
and VoxelDensityVolume.cpp (plugin only, uncompiled here). Supported-input output unchanged (version 211).
fast/medium/full PASS. Research R31 (self-checked): far-LOD 4-8 cut plan.

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

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

No files matched your search

+21 -25
View File
@@ -2265,36 +2265,32 @@ mouth and off the walk route. It stays disabled by default. Active records carry
L43's report records the off/on hashes, walk and interface audits, outside-stamp timing, tier L43's report records the off/on hashes, walk and interface audits, outside-stamp timing, tier
results, captures and any failed runs. results, captures and any failed runs.
### 8.54 Opt-in native lattice field — World Assembler M3 R26 slices 1–2 (Linux L58–L59) ### 8.54 Opt-in native lattice field — World Assembler M3 R26 slice 1 (Linux L58)
`Core.Lattice.PrimitiveField` is an opt-in recipe selected only for one Maze or VerticalShafts `Core.Lattice.PrimitiveField` is a version-1 recipe selected only for one Maze or VerticalShafts
strate by `--recipe=lattice --lattice-strate=N`. The legacy generator assets and their default strate by `--recipe=lattice --lattice-strate=N`. The legacy generator assets and their default
dispatch stay unchanged. Version 2 binds the actual incoming lower mouth and outgoing upper mouth, dispatch stay unchanged. Planning first binds the actual incoming lower mouth and outgoing upper
then creates arrival/exit overlap corridors, an open vertical shaft and the maze spine; two mouth from the resolved passage records, then creates arrival/exit overlap corridors, a mandatory
deterministic dead ends are added after that route. Resolved room and edge seeds use four-turn helix and the maze spine; two deterministic dead ends are added after that route. The
`(world seed, strate, recipe version, stable ID)`. The orphan control translates the shaft 20 m resolved room and edge seeds use `(world seed, strate, recipe version, stable ID)`. An audit control
away from both route junctions and must fail reachability. translates the helix 20 m away from both route junctions to make the shaft network orphaned.
The version-2 shaft core is a vertical cylinder of 4.5 m radius. Its air continues 8 m above the The prototype helix uses a 6 m centreline radius, 3 m clear width, 5 m pitch, four turns and 2.6 m
top tread and 2 m below the bottom tread. The ramp winds around the core at a 6 m centreline radius, finished floor-to-soffit height. Its centreline descends 20 m over roughly 151 m; the inner-edge
with 3 m clear width, 5 m pitch and four turns. The open annular shaft air joins the core air, while grade is about 10.0 degrees. The 2.4 m floor-wall smooth-max gives an isolated ideal fillet radius
one-metre support slabs follow the helix under each tread. This leaves a 4 m vertical gap between of 0.8485 m. A separate 0.4 m feature-union blend prevents adjoining ramps from erasing the tread.
one tread and the underside of the next. The ledge's inner edge is open to the well; the route The swept-clear diameter is 15 m; the conservative padded feature envelope is about 20.6 m (the
centreline stays 1.5 m from the drop and leaves 1.16 m beyond the 0.34 m capsule radius. Its inner approved approximately 21 m site envelope).
edge grade is about 10.0 degrees. The maze retains 2.6 m room headroom. The shaft's padded outer
design envelope is 21 m. The 1.9 m floor-wall smooth-max gives an isolated ideal fillet radius of
0.6718 m; the separate 0.05 m feature-union blend keeps route junctions connected.
The layout reuses the resolved cavern graph vocabulary. A per-strate opt-in selects the native The layout reuses the resolved cavern graph vocabulary. A per-strate opt-in selects the native
negative-solid/positive-air field; canonical feature order is stable-ID order. The ring-shaped shaft negative-solid/positive-air field; canonical feature order is stable-ID order. Room/corridor bounds
air and its support layers use an analytic helix lookup instead of testing every polyline segment are indexed by the graph BVH and polyline segments have a nested deterministic BVH for density and
per density sample. The core cylinder and annular wall have conservative interval bounds; false interval queries. Box classification stays conservative, and `isTileShapeFree` refuses any tile
all-air and all-solid controls remain rejected, and `isTileShapeFree` refuses occupied tiles. The that intersects the graph. The L58 pipeline audits the composed field from actual mouth to mouth
L59 pipeline checks the composed field from actual mouth to mouth with a 0.34 m by 1.76 m capsule, with a 0.34 m by 1.76 m capsule, per-station support, 2.2 m clearance and a 15 degree grade limit;
support at every station, 2.2 m clearance and a 15 degree grade limit. It also checks the orphan it also checks the orphan control, brute-force interval samples and the composed wall-foot fillet.
control, whole-surface ramp lip/wall-foot/underside, brute-force interval samples and the composed The Linux58 report contains the measured reachability, surface, peer-determinism, timing and capture
wall-foot fillet. See the Linux58 and Linux59 reports for evidence. This code path is not promoted results; the code path is not promoted to legacy Maze/VerticalShafts generation.
to legacy Maze/VerticalShafts generation.
## 9. Multiplayer model (listen-server first, dedicated-friendly) ## 9. Multiplayer model (listen-server first, dedicated-friendly)
+10 -11
View File
@@ -170,8 +170,8 @@ remains `0`, and lateral regions remain gated off.
| `../../VoxelForgeCore/Private/ModuleEntry/` | Unreal module entry and an isolated `CoreMinimal.h` shim for the actual engine header. | | `../../VoxelForgeCore/Private/ModuleEntry/` | Unreal module entry and an isolated `CoreMinimal.h` shim for the actual engine header. |
| `../../Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` | Standard-library-only public Core API. `VOXELFORGECORE_API` exports the API from the Unreal module and is empty for standalone builds. | | `../../Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` | Standard-library-only public Core API. `VOXELFORGECORE_API` exports the API from the Unreal module and is empty for standalone builds. |
| `../../Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` / `../../Standalone/Core/src/ResolvedLayoutInternal.h` | `ResolvedLayoutSnapshotV3`: physical layout-chunk width, versioned micrometre intervals, owned ceiling/content/bedrock bands, honored transition settings, deterministic per-column transition bands, recipe anchors, director-planned passages, canonical JSON/FNV identity, compatibility/strict validators, and negative-control audit. Built once from the director-resolved plan and consumed by bounds planning, Core dispatch, composition, and validation. §8.40–8.42, §8.46. | | `../../Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` / `../../Standalone/Core/src/ResolvedLayoutInternal.h` | `ResolvedLayoutSnapshotV3`: physical layout-chunk width, versioned micrometre intervals, owned ceiling/content/bedrock bands, honored transition settings, deterministic per-column transition bands, recipe anchors, director-planned passages, canonical JSON/FNV identity, compatibility/strict validators, and negative-control audit. Built once from the director-resolved plan and consumed by bounds planning, Core dispatch, composition, and validation. §8.40–8.42, §8.46. |
| `NativeLatticeFieldRecipeVersion`, `IsNativeLatticeFieldStratum`, `LayoutLatticeFeatureSeed` | `Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` / `Standalone/Core/src/ResolvedLayoutInternal.h` | Explicit Maze/VerticalShafts opt-in at lattice recipe version 2; stable feature seeds include world seed, strate, recipe version and feature ID. §8.54. | | `ResolvedLayoutStratePassageCapabilitiesV1::{hasNativeCaveField,hasNativeLatticeField}`, `IsNativeLatticeFieldStratum`, `HasNativeGraphField` | `Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` / `Standalone/Core/src/ResolvedLayoutInternal.h` | Explicit per-strate native graph capability; `Core.Lattice.PrimitiveField` is restricted to Maze/VerticalShafts recipe version 1. Lattice seeds include world seed, strate, recipe version and stable feature ID; explicit polyline records keep their endpoints and receive conservative bounds. §8.54. |
| `FCavernRuntimeFeature::openShaftRamp`, `ApplyResolvedNativeCavesField`, `ClassifyNativeCaveFieldBox` | `Standalone/Core/src/ShapeWorldInternal.h` | Analytic open shaft ring, helical support lookup, exact cylinder core bounds and conservative interval proofs; the outer graph BVH supplies tile candidates and protects `isTileShapeFree`. §8.54. | | `FCavernRuntimeFeature::FSegmentNode`, `ApplyResolvedNativeCavesField`, `ClassifyNativeCaveFieldBox` | `Standalone/Core/src/ShapeWorldInternal.h` | A deterministic nested segment BVH accelerates native graph distance and conservative interval evaluation; the outer graph BVH still supplies tile candidates and protects `isTileShapeFree`. The 0.4 m feature-union width is separate from the floor fillet width. §8.54. |
| `../../Standalone/Core/src/FloatingIslandsInternal.h` | Canonical standalone/Core floating-island planner, geometry-versioned field, manifest serializer, influence bounds, capsule-route checks, and thickness audit. | | `../../Standalone/Core/src/FloatingIslandsInternal.h` | Canonical standalone/Core floating-island planner, geometry-versioned field, manifest serializer, influence bounds, capsule-route checks, and thickness audit. |
| `Tools/VoxelForgeTest.ps1` | The single guarded test entry point. `-Scenario canonical|owner|probe|perf|tests|surface-fall|crossing|gate-stress|visual-edit|visual-lod|visual-gap-walk|visual-gap-inject|dig-motion|parity|horizon|horizon-render|determinism|region-eval|step2d|step2e|step2f|step2f-capture|step3-scale|step3-game`, optional `-Build`, `-Assets owner|default`, `-Cvars`, absolute `-Out`, `-Label`, `-HorizonLevel`, `-HorizonRenderDistanceChunks`, `-Step2fProfile=0..7`, `-Step2fCaptureView=inside|wall-closeup`, `-ParityExportSize=32|64|128`, `-ParityExportTile=x,y,z`, `-SharedDDC`, `-SampleDensityStacks`, `-StackSampleIntervalUs`, and `-RegionHashProbe -RegionWorkers N -RegionReferenceHash HASH`; stages the host/assets, isolates user/DDC/config state, serializes one Unreal launch at a time, enforces the 8 GiB/30-minute guards, waits for 6 GiB free RAM before launches, and emits compact `result.json`/`summary.txt` plus full logs. Parity can render its game leg with `-EnableRender` and compares the selected export region against the exact streamed tile hashes. `visual-gap-walk` records camera-frustum surface-point coverage at 6000 cm/s; `visual-gap-inject` proves an early component hide is detected; `dig-motion` logs per-frame vertical velocity, gate, support readiness, and movement mode. `-Cvars @{ 'voxel.TestGapAuditLegacyHandoff'=1 }` replays the old visibility handoff for A/B audit baselines. Horizon traces report desired/loaded tiles, resident stream geometry, worker seconds, steady wall time, and peak private memory; `horizon-render` captures the owner view; `step2f-capture` selects one of eight parameter sets, finds a positive-density camera point and aims at a sampled rock wall for an inside or wall-closeup view, then saves it under `Saved/Research/step2f/captures` (pass the persistent render DDC with `-SharedDDC`); `determinism` compares per-tile hashes across the default CRT path and `-voxel.CrtFma3=0`; `region-eval` compares the live path with the default-off editor chunk program on the 259³ lattice and five-seed probe set; `step2d` retains the earlier independent field; `step2e` retains its earlier benchmark; `step2f` compares frozen step 2e and the coherent field for all eight profiles on the same 259³ ring, applies geometry gates, and checks sound bounds, seams, and 1-vs-6-worker hashes. `-RegionHashProbe -RegionWorkers N -RegionReferenceHash HASH` checks a full-ring hash at another partition count. `-SampleDensityStacks` captures profiles, and `-StackSampleIntervalUs` sets the sampling interval. | `Tools/VoxelForgeTest.ps1` | The single guarded test entry point. `-Scenario canonical|owner|probe|perf|tests|surface-fall|crossing|gate-stress|visual-edit|visual-lod|visual-gap-walk|visual-gap-inject|dig-motion|parity|horizon|horizon-render|determinism|region-eval|step2d|step2e|step2f|step2f-capture|step3-scale|step3-game`, optional `-Build`, `-Assets owner|default`, `-Cvars`, absolute `-Out`, `-Label`, `-HorizonLevel`, `-HorizonRenderDistanceChunks`, `-Step2fProfile=0..7`, `-Step2fCaptureView=inside|wall-closeup`, `-ParityExportSize=32|64|128`, `-ParityExportTile=x,y,z`, `-SharedDDC`, `-SampleDensityStacks`, `-StackSampleIntervalUs`, and `-RegionHashProbe -RegionWorkers N -RegionReferenceHash HASH`; stages the host/assets, isolates user/DDC/config state, serializes one Unreal launch at a time, enforces the 8 GiB/30-minute guards, waits for 6 GiB free RAM before launches, and emits compact `result.json`/`summary.txt` plus full logs. Parity can render its game leg with `-EnableRender` and compares the selected export region against the exact streamed tile hashes. `visual-gap-walk` records camera-frustum surface-point coverage at 6000 cm/s; `visual-gap-inject` proves an early component hide is detected; `dig-motion` logs per-frame vertical velocity, gate, support readiness, and movement mode. `-Cvars @{ 'voxel.TestGapAuditLegacyHandoff'=1 }` replays the old visibility handoff for A/B audit baselines. Horizon traces report desired/loaded tiles, resident stream geometry, worker seconds, steady wall time, and peak private memory; `horizon-render` captures the owner view; `step2f-capture` selects one of eight parameter sets, finds a positive-density camera point and aims at a sampled rock wall for an inside or wall-closeup view, then saves it under `Saved/Research/step2f/captures` (pass the persistent render DDC with `-SharedDDC`); `determinism` compares per-tile hashes across the default CRT path and `-voxel.CrtFma3=0`; `region-eval` compares the live path with the default-off editor chunk program on the 259³ lattice and five-seed probe set; `step2d` retains the earlier independent field; `step2e` retains its earlier benchmark; `step2f` compares frozen step 2e and the coherent field for all eight profiles on the same 259³ ring, applies geometry gates, and checks sound bounds, seams, and 1-vs-6-worker hashes. `-RegionHashProbe -RegionWorkers N -RegionReferenceHash HASH` checks a full-ring hash at another partition count. `-SampleDensityStacks` captures profiles, and `-StackSampleIntervalUs` sets the sampling interval.
| Step-3 scale and game validation | `step3-scale` measures the same 519³ and 259³ six-worker lattices at 12.5 cm. The cached medians are 7.347496 s and 1.169567 s respectively; determinism and brick sound-proof audits pass. `step3-game` routes in-game generation through the brick evaluator and records tile timings, spawn fill, moving frames, and profile-switch rebuilds. The visible D3D12 run measured tile generation p50/p95/max 3.326/31.045/169.867 ms, full-detail L0 fill 12.369 s for 2,765 tiles, and moving-frame p50/p95/p99/max 4.856/8.922/13.090/80.575 ms. Switching profiles rebuilt 5,815 loaded tiles. Both high-resolution screenshot requests were logged, but the harness found only one fresh PNG and it is a blank pale viewport; no lit comparison capture is accepted. Raw output: `Saved/TestHarness/step3_game_visible_final`; scale output: `Saved/Research/step3/scale/step3_scale_cacheperf_v1`. | | Step-3 scale and game validation | `step3-scale` measures the same 519³ and 259³ six-worker lattices at 12.5 cm. The cached medians are 7.347496 s and 1.169567 s respectively; determinism and brick sound-proof audits pass. `step3-game` routes in-game generation through the brick evaluator and records tile timings, spawn fill, moving frames, and profile-switch rebuilds. The visible D3D12 run measured tile generation p50/p95/max 3.326/31.045/169.867 ms, full-detail L0 fill 12.369 s for 2,765 tiles, and moving-frame p50/p95/p99/max 4.856/8.922/13.090/80.575 ms. Switching profiles rebuilt 5,815 loaded tiles. Both high-resolution screenshot requests were logged, but the harness found only one fresh PNG and it is a blank pale viewport; no lit comparison capture is accepted. Raw output: `Saved/TestHarness/step3_game_visible_final`; scale output: `Saved/Research/step3/scale/step3_scale_cacheperf_v1`. |
@@ -1310,19 +1310,18 @@ See ARCHITECTURE §8.53 and
[`Standalone/Research/WorldAssemblerM3/Linux43/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux43/REPORT.md) [`Standalone/Research/WorldAssemblerM3/Linux43/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux43/REPORT.md)
for layout identities, owner hashes, outside-box timing, audits and captures. for layout identities, owner hashes, outside-box timing, audits and captures.
### 3.36 Native lattice field — World Assembler M3 R26 slices 1–2 (Linux L58–L59) ### 3.36 Native lattice field — World Assembler M3 R26 slice 1 (Linux L58)
| Symbol | Canonical file | Role | | Symbol | Canonical file | Role |
|---|---|---| |---|---|---|
| `NativeLatticeFieldRecipeVersion`, `IsNativeLatticeFieldStratum`, `LayoutLatticeFeatureSeed` | `Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h`, `Standalone/Core/src/ResolvedLayoutInternal.h` | Version-2 Maze/VerticalShafts opt-in and deterministic seed from `(seed, strate, recipe version, stable ID)`. | | `IsNativeLatticeFieldStratum`, `LayoutLatticeFeatureSeed`, `ResolvedCavernRoomV1::unionWidthMicrometres`, `ResolvedCavernEdgeV1::unionWidthMicrometres` | `Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h`, `Standalone/Core/src/ResolvedLayoutInternal.h` | Versioned Maze/VerticalShafts opt-in and deterministic seed from `(seed, strate, recipe version, stable ID)`; optional union width preserves prior cave JSON when unset. |
| `MakeNativeLatticeSpine`, `--recipe=lattice`, `--lattice-strate=N` | `Standalone/Source/StreamBenchmarkHeadless.cpp` | Resolves actual adjacent mouths, builds the radius-4.5 m open shaft core, supported four-turn wall ledge and maze spine, then adds two seeded dead ends; the orphan control translates the shaft away from both junctions. | | `MakeNativeLatticeSpine`, `--recipe=lattice`, `--lattice-strate=N` | `Standalone/Source/StreamBenchmarkHeadless.cpp` | Resolves actual adjacent mouths, builds a mandatory four-turn helix and maze spine, then adds two seeded dead ends; `--lattice-orphan-control` moves the helix away from both junctions. |
| `AuditNativeLatticeField` | `Standalone/Source/StreamBenchmarkPipeline.h` | Samples the final field along the mouth-to-mouth capsule route, checking every-station support, headroom, grade, orphan rejection, composed fillet, shaft air/wall intervals and tile exclusion. | | `AuditNativeLatticeField` | `Standalone/Source/StreamBenchmarkPipeline.h` | Samples the final field along the mouth-to-mouth capsule route, checking support, headroom, grade, orphan rejection, composed fillet, interval signs and feature-tile exclusion. |
| `BuildLatticeFixtureL59.py`, `CaptureNativeLatticeL59.py`, `AuditLatticeSurfaceL59.py` | `Standalone/Research/WorldAssemblerM3/Linux59/` | Isolated Maze fixture, four JPEG views of the open shaft and maze, and L54 whole-surface audit extended to include the ramp underside. | | `BuildLatticeFixtureL58.py`, `CaptureNativeLatticeL58.py`, `AuditLatticeSurfaceL58.py` | `Standalone/Research/WorldAssemblerM3/Linux58/` | Isolated Maze strate fixture, four reviewed captures and the L54 whole-surface triangle audit adapted to explicit native polylines. |
See ARCHITECTURE §8.54 and the See ARCHITECTURE §8.54 and
[`Linux59 report`](Standalone/Research/WorldAssemblerM3/Linux59/REPORT.md) for the open shaft's [`Standalone/Research/WorldAssemblerM3/Linux58/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux58/REPORT.md)
measured geometry, final-field reachability, controls, timing, hashes and captures. The L58 report for resolved records, final-field reachability, controls, timing, hashes and captures.
retains the first-slice evidence.
## 4. The density pipeline (most-edited hot path) ## 4. The density pipeline (most-edited hot path)
@@ -4,6 +4,27 @@
namespace VoxelClipmapDesiredTiles namespace VoxelClipmapDesiredTiles
{ {
FORCEINLINE int64 FloorDivWide(int64 Value, int64 Divisor)
{
if (Divisor <= 0) { return 0; }
const int64 Quotient = Value / Divisor;
const int64 Remainder = Value % Divisor;
return Remainder < 0 ? Quotient - 1 : Quotient;
}
FORCEINLINE int64 CeilDivWide(int64 Value, int64 Divisor)
{
if (Divisor <= 0) { return 0; }
const int64 Quotient = Value / Divisor;
const int64 Remainder = Value % Divisor;
return Remainder > 0 ? Quotient + 1 : Quotient;
}
FORCEINLINE int32 ClampInt32(int64 Value)
{
return static_cast<int32>(FMath::Clamp<int64>(Value, MIN_int32, MAX_int32));
}
struct FParameters struct FParameters
{ {
int32 ClipRadius = 3; int32 ClipRadius = 3;
@@ -13,7 +34,10 @@ namespace VoxelClipmapDesiredTiles
FORCEINLINE int32 FloorDiv(int32 Value, int32 Divisor) FORCEINLINE int32 FloorDiv(int32 Value, int32 Divisor)
{ {
return Value >= 0 ? (Value / Divisor) : -(((-Value) + Divisor - 1) / Divisor); if (Divisor <= 0) { return 0; }
const int32 Quotient = Value / Divisor;
const int32 Remainder = Value % Divisor;
return Remainder < 0 ? Quotient - 1 : Quotient;
} }
FORCEINLINE FIntVector FloorDiv(const FIntVector& Value, int32 Divisor) FORCEINLINE FIntVector FloorDiv(const FIntVector& Value, int32 Divisor)
@@ -24,7 +48,10 @@ namespace VoxelClipmapDesiredTiles
FORCEINLINE int32 CeilDiv(int32 Value, int32 Divisor) FORCEINLINE int32 CeilDiv(int32 Value, int32 Divisor)
{ {
return -FloorDiv(-Value, Divisor); if (Divisor <= 0) { return 0; }
const int32 Quotient = Value / Divisor;
const int32 Remainder = Value % Divisor;
return Remainder > 0 ? Quotient + 1 : Quotient;
} }
FORCEINLINE void AlignedRingBounds(const FIntVector& Center, int32 Radius, int32 Alignment, FORCEINLINE void AlignedRingBounds(const FIntVector& Center, int32 Radius, int32 Alignment,
@@ -33,10 +60,12 @@ namespace VoxelClipmapDesiredTiles
const auto AlignAxis = [Radius, Alignment](int32 CenterAxis, const auto AlignAxis = [Radius, Alignment](int32 CenterAxis,
int32& OutAxisMin, int32& OutAxisMax) int32& OutAxisMin, int32& OutAxisMax)
{ {
const int32 BaseMin = CenterAxis - Radius; const int64 SafeRadius = FMath::Max<int64>(0, Radius);
const int32 BaseEnd = CenterAxis + Radius + 1; const int64 SafeAlignment = FMath::Max<int64>(1, Alignment);
OutAxisMin = FloorDiv(BaseMin, Alignment) * Alignment; const int64 BaseMin = static_cast<int64>(CenterAxis) - SafeRadius;
OutAxisMax = CeilDiv(BaseEnd, Alignment) * Alignment - 1; const int64 BaseEnd = static_cast<int64>(CenterAxis) + SafeRadius + 1;
OutAxisMin = ClampInt32(FloorDivWide(BaseMin, SafeAlignment) * SafeAlignment);
OutAxisMax = ClampInt32(CeilDivWide(BaseEnd, SafeAlignment) * SafeAlignment - 1);
}; };
AlignAxis(Center.X, OutMin.X, OutMax.X); AlignAxis(Center.X, OutMin.X, OutMax.X);
AlignAxis(Center.Y, OutMin.Y, OutMax.Y); AlignAxis(Center.Y, OutMin.Y, OutMax.Y);
@@ -49,8 +78,13 @@ namespace VoxelClipmapDesiredTiles
{ {
return Radius; return Radius;
} }
return FMath::Max(Radius, const int32 SafeLevel = FMath::Clamp(MaxLevel, 0, 8);
(Parameters.RenderDistanceChunks + (1 << MaxLevel) - 1) >> MaxLevel); const int64 Scale = static_cast<int64>(uint32{1} << static_cast<uint32>(SafeLevel));
const int64 Numerator = static_cast<int64>(Parameters.RenderDistanceChunks)
+ Scale - 1;
const int64 DesiredRadius = Numerator / Scale;
return static_cast<int32>(FMath::Clamp<int64>(
FMath::Max<int64>(Radius, DesiredRadius), 0, MAX_int32));
} }
FORCEINLINE void OuterShell(const FParameters& Parameters, int32 Radius, int32 MaxLevel, FORCEINLINE void OuterShell(const FParameters& Parameters, int32 Radius, int32 MaxLevel,
@@ -73,12 +107,12 @@ namespace VoxelClipmapDesiredTiles
return; return;
} }
OutMinChunkZ = CenterChunkZ - ViewDistanceDown; OutMinChunkZ = ClampInt32(static_cast<int64>(CenterChunkZ) - ViewDistanceDown);
OutMaxChunkZ = CenterChunkZ + ViewDistanceUp; OutMaxChunkZ = ClampInt32(static_cast<int64>(CenterChunkZ) + ViewDistanceUp);
if (bHasStrate) if (bHasStrate)
{ {
OutMinChunkZ = StrateBottomChunkZ - StrateViewMarginChunks; OutMinChunkZ = ClampInt32(static_cast<int64>(StrateBottomChunkZ) - StrateViewMarginChunks);
OutMaxChunkZ = StrateTopChunkZ + StrateViewMarginChunks; OutMaxChunkZ = ClampInt32(static_cast<int64>(StrateTopChunkZ) + StrateViewMarginChunks);
} }
} }
@@ -94,7 +128,7 @@ namespace VoxelClipmapDesiredTiles
for (int32 Level = 0; Level <= MaxLevel; ++Level) for (int32 Level = 0; Level <= MaxLevel; ++Level)
{ {
const int32 Pow = 1 << Level; const int32 Pow = static_cast<int32>(uint32{1} << static_cast<uint32>(Level));
const FIntVector CenterLevel = FloorDiv(Center, Pow); const FIntVector CenterLevel = FloorDiv(Center, Pow);
const int32 LevelRadius = (Level == MaxLevel) ? OuterRadius : Radius; const int32 LevelRadius = (Level == MaxLevel) ? OuterRadius : Radius;
@@ -105,24 +139,23 @@ namespace VoxelClipmapDesiredTiles
FIntVector RingMin, RingMax; FIntVector RingMin, RingMax;
AlignedRingBounds(CenterLevel, LevelRadius, RingAlignment, RingMin, RingMax); AlignedRingBounds(CenterLevel, LevelRadius, RingAlignment, RingMin, RingMax);
int32 DeltaZMin = RingMin.Z - CenterLevel.Z; int64 TileMinZ = RingMin.Z;
int32 DeltaZMax = RingMax.Z - CenterLevel.Z; int64 TileMaxZ = RingMax.Z;
if (LevelRadius > Radius && MinChunkZ != MIN_int32) if (LevelRadius > Radius && MinChunkZ != MIN_int32)
{ {
DeltaZMin = FMath::Max(DeltaZMin, FloorDiv(MinChunkZ, Pow) - CenterLevel.Z); TileMinZ = FMath::Max<int64>(TileMinZ, FloorDiv(MinChunkZ, Pow));
DeltaZMax = FMath::Min(DeltaZMax, FloorDiv(MaxChunkZ, Pow) - CenterLevel.Z); TileMaxZ = FMath::Min<int64>(TileMaxZ, FloorDiv(MaxChunkZ, Pow));
} }
for (int32 DeltaZ = DeltaZMin; DeltaZ <= DeltaZMax; ++DeltaZ) for (int64 Z = TileMinZ; Z <= TileMaxZ; ++Z)
for (int32 DeltaY = RingMin.Y - CenterLevel.Y; for (int64 Y = RingMin.Y; Y <= RingMax.Y; ++Y)
DeltaY <= RingMax.Y - CenterLevel.Y; ++DeltaY) for (int64 X = RingMin.X; X <= RingMax.X; ++X)
for (int32 DeltaX = RingMin.X - CenterLevel.X;
DeltaX <= RingMax.X - CenterLevel.X; ++DeltaX)
{ {
const FIntVector Tile = CenterLevel + FIntVector(DeltaX, DeltaY, DeltaZ); const FIntVector Tile(static_cast<int32>(X), static_cast<int32>(Y),
const int32 TileMinZ = Tile.Z << Level; static_cast<int32>(Z));
const int32 TileMaxZ = ((Tile.Z + 1) << Level) - 1; const int64 TileBottomChunkZ = static_cast<int64>(Tile.Z) * Pow;
if (TileMaxZ < MinChunkZ || TileMinZ > MaxChunkZ) const int64 TileTopChunkZ = (static_cast<int64>(Tile.Z) + 1) * Pow - 1;
if (TileTopChunkZ < MinChunkZ || TileBottomChunkZ > MaxChunkZ)
{ {
continue; continue;
} }
@@ -130,12 +163,12 @@ namespace VoxelClipmapDesiredTiles
if (Level > 0) if (Level > 0)
{ {
const bool bCovered = const bool bCovered =
(2 * Tile.X >= PreviousRingMin.X) (2 * static_cast<int64>(Tile.X) >= PreviousRingMin.X)
&& (2 * Tile.X + 1 <= PreviousRingMax.X) && (2 * static_cast<int64>(Tile.X) + 1 <= PreviousRingMax.X)
&& (2 * Tile.Y >= PreviousRingMin.Y) && (2 * static_cast<int64>(Tile.Y) >= PreviousRingMin.Y)
&& (2 * Tile.Y + 1 <= PreviousRingMax.Y) && (2 * static_cast<int64>(Tile.Y) + 1 <= PreviousRingMax.Y)
&& (2 * Tile.Z >= PreviousRingMin.Z) && (2 * static_cast<int64>(Tile.Z) >= PreviousRingMin.Z)
&& (2 * Tile.Z + 1 <= PreviousRingMax.Z); && (2 * static_cast<int64>(Tile.Z) + 1 <= PreviousRingMax.Z);
if (bCovered) if (bCovered)
{ {
continue; continue;
@@ -559,9 +559,13 @@ int32 UVoxelContentManager::RegionSize() const
// truncation, a negative-coord cell is queued under one region but routed back to another → its march // truncation, a negative-coord cell is queued under one region but routed back to another → its march
// result is dropped → blank chunk. Truncation == floor for A>=0 and for R==1, which is exactly why only // result is dropped → blank chunk. Truncation == floor for A>=0 and for R==1, which is exactly why only
// NEGATIVE coordinates at R>=2 were affected. R (region size) is always > 0 here. // NEGATIVE coordinates at R>=2 were affected. R (region size) is always > 0 here.
static FORCEINLINE int32 FloorDivPos(int32 A, int32 R) static FORCEINLINE int32 FloorDivPos(int64 A, int32 R)
{ {
return (A >= 0) ? (A / R) : -(((-A) + R - 1) / R); if (R <= 0) { return 0; }
const int64 Quotient = A / R;
const int64 Remainder = A % R;
return static_cast<int32>(FMath::Clamp<int64>(
Remainder < 0 ? Quotient - 1 : Quotient, MIN_int32, MAX_int32));
} }
static FORCEINLINE FIntPoint CellToRegion(const FIntPoint& Cell, int32 R) static FORCEINLINE FIntPoint CellToRegion(const FIntPoint& Cell, int32 R)
@@ -573,10 +577,10 @@ static FORCEINLINE FIntPoint CellToRegion(const FIntPoint& Cell, int32 R)
static FORCEINLINE bool IsRegionDesired(const FIntPoint& Region, const FIntPoint& PlayerCell, static FORCEINLINE bool IsRegionDesired(const FIntPoint& Region, const FIntPoint& PlayerCell,
int32 FarR, int32 R) int32 FarR, int32 R)
{ {
const int32 MinX = FloorDivPos(PlayerCell.X - FarR, R); const int32 MinX = FloorDivPos(static_cast<int64>(PlayerCell.X) - FarR, R);
const int32 MaxX = FloorDivPos(PlayerCell.X + FarR, R); const int32 MaxX = FloorDivPos(static_cast<int64>(PlayerCell.X) + FarR, R);
const int32 MinY = FloorDivPos(PlayerCell.Y - FarR, R); const int32 MinY = FloorDivPos(static_cast<int64>(PlayerCell.Y) - FarR, R);
const int32 MaxY = FloorDivPos(PlayerCell.Y + FarR, R); const int32 MaxY = FloorDivPos(static_cast<int64>(PlayerCell.Y) + FarR, R);
return Region.X >= MinX && Region.X <= MaxX && Region.Y >= MinY && Region.Y <= MaxY; return Region.X >= MinX && Region.X <= MaxX && Region.Y >= MinY && Region.Y <= MaxY;
} }
@@ -1102,17 +1106,22 @@ void UVoxelContentManager::RebuildDesiredCells(FDecoGrid& G, const FIntPoint& Pl
const int32 R = RegionSize(); const int32 R = RegionSize();
// Desired regions = every region whose footprint touches the radius-FarR cell box around the player. // Desired regions = every region whose footprint touches the radius-FarR cell box around the player.
const FIntPoint RMin(FloorDivPos(PlayerCell.X - FarR, R), const FIntPoint RMin(FloorDivPos(static_cast<int64>(PlayerCell.X) - FarR, R),
FloorDivPos(PlayerCell.Y - FarR, R)); FloorDivPos(static_cast<int64>(PlayerCell.Y) - FarR, R));
const FIntPoint RMax(FloorDivPos(PlayerCell.X + FarR, R), const FIntPoint RMax(FloorDivPos(static_cast<int64>(PlayerCell.X) + FarR, R),
FloorDivPos(PlayerCell.Y + FarR, R)); FloorDivPos(static_cast<int64>(PlayerCell.Y) + FarR, R));
TSet<FIntPoint> DesiredRegions; TSet<FIntPoint> DesiredRegions;
DesiredRegions.Reserve((RMax.X - RMin.X + 1) * (RMax.Y - RMin.Y + 1)); constexpr int64 MaxRegionReserve = 4096;
for (int32 ry = RMin.Y; ry <= RMax.Y; ++ry) const int64 WidthX = static_cast<int64>(RMax.X) - RMin.X + 1;
for (int32 rx = RMin.X; rx <= RMax.X; ++rx) const int64 WidthY = static_cast<int64>(RMax.Y) - RMin.Y + 1;
const int64 ReserveCount = WidthY > 0 && WidthX > MaxRegionReserve / WidthY
? MaxRegionReserve : FMath::Max<int64>(0, WidthX * WidthY);
DesiredRegions.Reserve(static_cast<int32>(ReserveCount));
for (int64 ry = RMin.Y; ry <= RMax.Y; ++ry)
for (int64 rx = RMin.X; rx <= RMax.X; ++rx)
{ {
DesiredRegions.Add(FIntPoint(rx, ry)); DesiredRegions.Add(FIntPoint(static_cast<int32>(rx), static_cast<int32>(ry)));
} }
// Unload loaded regions no longer desired (plain DestroyComponent — no per-instance removal). // Unload loaded regions no longer desired (plain DestroyComponent — no per-instance removal).
@@ -1464,8 +1473,15 @@ void UVoxelContentManager::BuildCellSpawns(const UVoxelGenerator* Gen, const FTr
const uint32 CH = VoxelHash::Mix(H ^ (0x00C0FFEEu + (uint32)ci * 0x9E3779B1u)); const uint32 CH = VoxelHash::Mix(H ^ (0x00C0FFEEu + (uint32)ci * 0x9E3779B1u));
if (VoxelHash::ToFloat01(CH) > Comp.Probability) continue; if (VoxelHash::ToFloat01(CH) > Comp.Probability) continue;
const int32 Span = FMath::Max(0, Comp.CountMax - Comp.CountMin); const int64 Span = FMath::Max<int64>(0,
const int32 Count = Comp.CountMin + (int32)(VoxelHash::ToFloat01(VoxelHash::Mix(CH ^ 0x1234u)) * (float)(Span + 1)); static_cast<int64>(Comp.CountMax) - Comp.CountMin);
const uint64 CountRange = static_cast<uint64>(Span) + 1u;
const float CountOffset = VoxelHash::ToFloat01(
VoxelHash::Mix(CH ^ 0x1234u)) * static_cast<float>(CountRange);
const int64 CountWide = static_cast<int64>(Comp.CountMin)
+ static_cast<int64>(CountOffset);
const int32 Count = static_cast<int32>(FMath::Clamp<int64>(
CountWide, MIN_int32, MAX_int32));
for (int32 ii = 0; ii < Count; ++ii) for (int32 ii = 0; ii < Count; ++ii)
{ {
if (CompBudget <= 0) break; if (CompBudget <= 0) break;
@@ -1514,8 +1530,15 @@ void UVoxelContentManager::BuildCellSpawns(const UVoxelGenerator* Gen, const FTr
const uint32 SH = VoxelHash::Mix(IH ^ (0x0000544Bu + (uint32)si * 0x27D4EB2Fu)); const uint32 SH = VoxelHash::Mix(IH ^ (0x0000544Bu + (uint32)si * 0x27D4EB2Fu));
if (VoxelHash::ToFloat01(SH) > Sub.Probability) continue; if (VoxelHash::ToFloat01(SH) > Sub.Probability) continue;
const int32 SubSpan = FMath::Max(0, Sub.CountMax - Sub.CountMin); const int64 SubSpan = FMath::Max<int64>(0,
const int32 SubCount = Sub.CountMin + (int32)(VoxelHash::ToFloat01(VoxelHash::Mix(SH ^ 0x1234u)) * (float)(SubSpan + 1)); static_cast<int64>(Sub.CountMax) - Sub.CountMin);
const uint64 SubCountRange = static_cast<uint64>(SubSpan) + 1u;
const float SubCountOffset = VoxelHash::ToFloat01(
VoxelHash::Mix(SH ^ 0x1234u)) * static_cast<float>(SubCountRange);
const int64 SubCountWide = static_cast<int64>(Sub.CountMin)
+ static_cast<int64>(SubCountOffset);
const int32 SubCount = static_cast<int32>(FMath::Clamp<int64>(
SubCountWide, MIN_int32, MAX_int32));
for (int32 sj = 0; sj < SubCount; ++sj) for (int32 sj = 0; sj < SubCount; ++sj)
{ {
if (CompBudget <= 0) break; if (CompBudget <= 0) break;
@@ -155,9 +155,11 @@ int32 UVoxelDensityVolume::NumLevels() const
FORCEINLINE int32 UVoxelDensityVolume::FloorDiv(int32 A, int32 B) FORCEINLINE int32 UVoxelDensityVolume::FloorDiv(int32 A, int32 B)
{ {
// True floor division (B > 0). FMath::DivideAndRoundDown truncates toward zero for negatives — // True floor division (B > 0). Remainder adjustment handles INT_MIN without negating it.
// a footgun the content manager hit too (see FloorDivPos there). Cells span the origin, so floor. if (B <= 0) { return 0; }
return (A >= 0) ? (A / B) : -(((-A) + B - 1) / B); const int32 Quotient = A / B;
const int32 Remainder = A % B;
return Remainder < 0 ? Quotient - 1 : Quotient;
} }
FORCEINLINE uint8 UVoxelDensityVolume::Quantize(float MCDensity) FORCEINLINE uint8 UVoxelDensityVolume::Quantize(float MCDensity)
@@ -180,7 +182,8 @@ void UVoxelDensityVolume::EnsureAllocated()
for (int32 L = 0; L < N; ++L) for (int32 L = 0; L < N; ++L)
{ {
FClipLevel& Lv = Levels[L]; FClipLevel& Lv = Levels[L];
Lv.Step = 1 << L; Lv.Step = static_cast<int32>(uint32{1}
<< static_cast<uint32>(FMath::Clamp(L, 0, 8)));
Lv.OriginCells = FIntVector(INT32_MAX, INT32_MAX, INT32_MAX); Lv.OriginCells = FIntVector(INT32_MAX, INT32_MAX, INT32_MAX);
Lv.bHasData = false; Lv.bHasData = false;
Lv.Density.SetNumZeroed(Count); // start all-air (0) Lv.Density.SetNumZeroed(Count); // start all-air (0)
@@ -632,7 +635,8 @@ void UVoxelDensityVolume::ProcessOneFill(const FPendingFill& F)
if (!Gen) return; if (!Gen) return;
if (bShuttingDown.load(std::memory_order_relaxed) || bFillThreadStop.load(std::memory_order_relaxed)) return; if (bShuttingDown.load(std::memory_order_relaxed) || bFillThreadStop.load(std::memory_order_relaxed)) return;
const int32 Step = 1 << F.Level; const int32 Step = static_cast<int32>(uint32{1}
<< static_cast<uint32>(FMath::Clamp(F.Level, 0, 8)));
FFillResult R; FFillResult R;
R.Level = F.Level; R.Level = F.Level;
R.Epoch = F.Epoch; R.Epoch = F.Epoch;
+60 -27
View File
@@ -82,6 +82,17 @@
#include <atomic> #include <atomic>
#include <unordered_set> #include <unordered_set>
static FORCEINLINE int32 VF_ClipLevelScale(int32 Level)
{
const uint32 SafeLevel = static_cast<uint32>(FMath::Clamp(Level, 0, 8));
return static_cast<int32>(uint32{1} << SafeLevel);
}
static FORCEINLINE int32 VF_ClipLevelExtentVoxels(int32 Level)
{
return static_cast<int32>(static_cast<uint32>(CHUNK_SIZE)
* static_cast<uint32>(VF_ClipLevelScale(Level)));
}
static bool VF_TileOverlapsVoxelBounds(const FVoxelTileKey& Tile, static bool VF_TileOverlapsVoxelBounds(const FVoxelTileKey& Tile,
const FVector& BoundsMin, const FVector& BoundsMax); const FVector& BoundsMin, const FVector& BoundsMax);
static bool VF_TileFootprintsOverlap(const FVoxelTileKey& A, const FVoxelTileKey& B); static bool VF_TileFootprintsOverlap(const FVoxelTileKey& A, const FVoxelTileKey& B);
@@ -6240,17 +6251,19 @@ static void VF_AppendTouchingAncestors(
const FVoxelTileKey& Tile, int32 AncestorLevel, CandidateArray& OutCandidates) const FVoxelTileKey& Tile, int32 AncestorLevel, CandidateArray& OutCandidates)
{ {
if (AncestorLevel <= Tile.Level) { return; } if (AncestorLevel <= Tile.Level) { return; }
const int32 Divisor = 1 << (AncestorLevel - Tile.Level); const int32 Divisor = VF_ClipLevelScale(AncestorLevel - Tile.Level);
const auto FloorDiv = [Divisor](int32 Value) const auto FloorDiv = [Divisor](int64 Value)
{ {
return Value >= 0 ? Value / Divisor : -(((-Value) + Divisor - 1) / Divisor); const int64 Quotient = Value / Divisor;
const int64 Remainder = Value % Divisor;
return static_cast<int32>(Remainder < 0 ? Quotient - 1 : Quotient);
}; };
const auto AxisCandidates = [&FloorDiv, Divisor](int32 Value) const auto AxisCandidates = [&FloorDiv, Divisor](int32 Value)
{ {
TArray<int32, TInlineAllocator<2>> Candidates; TArray<int32, TInlineAllocator<2>> Candidates;
int32 First = FloorDiv(Value); int32 First = FloorDiv(Value);
if ((Value % Divisor) == 0) { --First; } if ((Value % Divisor) == 0) { --First; }
const int32 Last = FloorDiv(Value + 1); const int32 Last = FloorDiv(static_cast<int64>(Value) + 1);
for (int32 Coord = First; Coord <= Last; ++Coord) { Candidates.Add(Coord); } for (int32 Coord = First; Coord <= Last; ++Coord) { Candidates.Add(Coord); }
return Candidates; return Candidates;
}; };
@@ -6338,7 +6351,7 @@ void AVoxelWorld::UpdateVisualStalenessAudit()
const FVector& WorldPosition, int32 Level, auto& OutCandidates) const FVector& WorldPosition, int32 Level, auto& OutCandidates)
{ {
const FVector LocalVoxel = WorldToLocalVoxel(WorldPosition); const FVector LocalVoxel = WorldToLocalVoxel(WorldPosition);
const float TileExtentVoxels = static_cast<float>(CHUNK_SIZE * (1 << Level)); const float TileExtentVoxels = static_cast<float>(VF_ClipLevelExtentVoxels(Level));
TArray<int32, TInlineAllocator<2>> XCandidates; TArray<int32, TInlineAllocator<2>> XCandidates;
TArray<int32, TInlineAllocator<2>> YCandidates; TArray<int32, TInlineAllocator<2>> YCandidates;
TArray<int32, TInlineAllocator<2>> ZCandidates; TArray<int32, TInlineAllocator<2>> ZCandidates;
@@ -7182,7 +7195,7 @@ void AVoxelWorld::Tick(float DeltaTime)
{ {
const FIntVector Min = SteadyLevelMins.FindChecked(Level); const FIntVector Min = SteadyLevelMins.FindChecked(Level);
const FIntVector Max = SteadyLevelMaxs.FindChecked(Level); const FIntVector Max = SteadyLevelMaxs.FindChecked(Level);
const int32 ExtentVoxels = CHUNK_SIZE << Level; const int32 ExtentVoxels = VF_ClipLevelExtentVoxels(Level);
const double TileExtentMetres = static_cast<double>(ExtentVoxels) * VOXEL_SIZE / 100.0; const double TileExtentMetres = static_cast<double>(ExtentVoxels) * VOXEL_SIZE / 100.0;
const double SpanX = static_cast<double>(Max.X - Min.X + 1) * TileExtentMetres; const double SpanX = static_cast<double>(Max.X - Min.X + 1) * TileExtentMetres;
const double SpanY = static_cast<double>(Max.Y - Min.Y + 1) * TileExtentMetres; const double SpanY = static_cast<double>(Max.Y - Min.Y + 1) * TileExtentMetres;
@@ -7663,10 +7676,20 @@ bool AVoxelWorld::InitializeVoxelForgeCoreWorld(FString& OutFailureReason)
}; };
for (const VoxelForgeCore::TileKey& Key : InitialDesired) for (const VoxelForgeCore::TileKey& Key : InitialDesired)
{ {
const int32 Extent = 32 << Key.lod; const int32 Extent = VF_ClipLevelExtentVoxels(Key.lod);
const int32 OriginX = Key.coordinate.x * Extent; const int64 OriginXWide = static_cast<int64>(Key.coordinate.x) * Extent;
const int32 OriginY = Key.coordinate.y * Extent; const int64 OriginYWide = static_cast<int64>(Key.coordinate.y) * Extent;
const int32 OriginZ = Key.coordinate.z * Extent; const int64 OriginZWide = static_cast<int64>(Key.coordinate.z) * Extent;
if (OriginXWide < MIN_int32 || OriginYWide < MIN_int32 || OriginZWide < MIN_int32
|| OriginXWide + Extent > MAX_int32 || OriginYWide + Extent > MAX_int32
|| OriginZWide + Extent > MAX_int32)
{
OutFailureReason = TEXT("Core initial clipmap exceeds the supported voxel-coordinate range.");
return false;
}
const int32 OriginX = static_cast<int32>(OriginXWide);
const int32 OriginY = static_cast<int32>(OriginYWide);
const int32 OriginZ = static_cast<int32>(OriginZWide);
IncludeExtent(ClipMin.x, ClipMax.x, OriginX, Extent); IncludeExtent(ClipMin.x, ClipMax.x, OriginX, Extent);
IncludeExtent(ClipMin.y, ClipMax.y, OriginY, Extent); IncludeExtent(ClipMin.y, ClipMax.y, OriginY, Extent);
IncludeExtent(ClipMin.z, ClipMax.z, OriginZ, Extent); IncludeExtent(ClipMin.z, ClipMax.z, OriginZ, Extent);
@@ -10074,8 +10097,11 @@ void AVoxelWorld::SyncRemeshTile(const FVoxelTileKey& Tile)
{ {
BandChunkLo = MeshBandChunkLo; BandChunkLo = MeshBandChunkLo;
BandChunkHi = MeshBandChunkHi; BandChunkHi = MeshBandChunkHi;
BandVoxLo = MeshBandChunkLo * CHUNK_SIZE; BandVoxLo = static_cast<int32>(FMath::Clamp<int64>(
BandVoxHi = (MeshBandChunkHi + 1) * CHUNK_SIZE - 1; static_cast<int64>(MeshBandChunkLo) * CHUNK_SIZE, MIN_int32, MAX_int32));
BandVoxHi = static_cast<int32>(FMath::Clamp<int64>(
(static_cast<int64>(MeshBandChunkHi) + 1) * CHUNK_SIZE - 1,
MIN_int32, MAX_int32));
} }
FChunkResult Result; FChunkResult Result;
@@ -10238,7 +10264,7 @@ void AVoxelWorld::ReconcileReadyTransitionVisibility()
ReadyToHide.Reserve(TileComponents.Num()); ReadyToHide.Reserve(TileComponents.Num());
auto GetAncestorAtLevel = [](const FVoxelTileKey& Tile, int32 AncestorLevel) auto GetAncestorAtLevel = [](const FVoxelTileKey& Tile, int32 AncestorLevel)
{ {
const int32 Divisor = 1 << (AncestorLevel - Tile.Level); const int32 Divisor = VF_ClipLevelScale(AncestorLevel - Tile.Level);
return FVoxelTileKey(VoxelClipmapDesiredTiles::FloorDiv(Tile.Coord, Divisor), AncestorLevel); return FVoxelTileKey(VoxelClipmapDesiredTiles::FloorDiv(Tile.Coord, Divisor), AncestorLevel);
}; };
for (const TPair<FVoxelTileKey, URealtimeMeshComponent*>& Pair : TileComponents) for (const TPair<FVoxelTileKey, URealtimeMeshComponent*>& Pair : TileComponents)
@@ -10361,7 +10387,10 @@ void AVoxelWorld::ReconcileReadyTransitionVisibility()
// Integer floor-division (correct for negatives), scalar + vector. // Integer floor-division (correct for negatives), scalar + vector.
static FORCEINLINE int32 VF_FloorDiv(int32 V, int32 D) static FORCEINLINE int32 VF_FloorDiv(int32 V, int32 D)
{ {
return V >= 0 ? (V / D) : -(((-V) + D - 1) / D); if (D <= 0) { return 0; }
const int32 Quotient = V / D;
const int32 Remainder = V % D;
return Remainder < 0 ? Quotient - 1 : Quotient;
} }
static FORCEINLINE FIntVector VF_FloorDiv(const FIntVector& V, int32 D) static FORCEINLINE FIntVector VF_FloorDiv(const FIntVector& V, int32 D)
{ {
@@ -11441,13 +11470,13 @@ bool AVoxelWorld::IsTileInClipRange(const FVoxelTileKey& Tile, const FIntVector&
{ {
VF_OuterShell(Settings, R, MaxLevelBase, MaxLevel, ROuter); VF_OuterShell(Settings, R, MaxLevelBase, MaxLevel, ROuter);
} }
const int32 PowMax = 1 << MaxLevel; const int32 PowMax = VF_ClipLevelScale(MaxLevel);
const FIntVector CMax = VF_FloorDiv(Center, PowMax); const FIntVector CMax = VF_FloorDiv(Center, PowMax);
FVector CV; FVector CV;
if (bUseVoxelForgeCore) if (bUseVoxelForgeCore)
{ {
const double TileExtentVoxels = static_cast<double>(CHUNK_SIZE << Tile.Level); const double TileExtentVoxels = static_cast<double>(VF_ClipLevelExtentVoxels(Tile.Level));
CV = FVector( CV = FVector(
(static_cast<double>(Tile.Coord.X) + 0.5) * TileExtentVoxels, (static_cast<double>(Tile.Coord.X) + 0.5) * TileExtentVoxels,
(static_cast<double>(Tile.Coord.Y) + 0.5) * TileExtentVoxels, (static_cast<double>(Tile.Coord.Y) + 0.5) * TileExtentVoxels,
@@ -11463,9 +11492,9 @@ bool AVoxelWorld::IsTileInClipRange(const FVoxelTileKey& Tile, const FIntVector&
VF_FloorDiv(FMath::FloorToInt(CV.Y), SizeMax), VF_FloorDiv(FMath::FloorToInt(CV.Y), SizeMax),
VF_FloorDiv(FMath::FloorToInt(CV.Z), SizeMax)); VF_FloorDiv(FMath::FloorToInt(CV.Z), SizeMax));
return FMath::Abs(TMax.X - CMax.X) <= ROuter return FMath::Abs(static_cast<int64>(TMax.X) - CMax.X) <= ROuter
&& FMath::Abs(TMax.Y - CMax.Y) <= ROuter && FMath::Abs(static_cast<int64>(TMax.Y) - CMax.Y) <= ROuter
&& FMath::Abs(TMax.Z - CMax.Z) <= ROuter; && FMath::Abs(static_cast<int64>(TMax.Z) - CMax.Z) <= ROuter;
} }
int32 AVoxelWorld::GetMaxConcurrentTasks() const int32 AVoxelWorld::GetMaxConcurrentTasks() const
@@ -12058,7 +12087,7 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
LevelSummaries.Num(), Leavers.Num(), CollisionOnlyTiles.Num())); LevelSummaries.Num(), Leavers.Num(), CollisionOnlyTiles.Num()));
for (const FDesiredLevelSummary& Summary : LevelSummaries) for (const FDesiredLevelSummary& Summary : LevelSummaries)
{ {
const int32 ExtentVoxels = CHUNK_SIZE << Summary.Level; const int32 ExtentVoxels = VF_ClipLevelExtentVoxels(Summary.Level);
const double TileExtentMetres = static_cast<double>(ExtentVoxels) * VOXEL_SIZE / 100.0; const double TileExtentMetres = static_cast<double>(ExtentVoxels) * VOXEL_SIZE / 100.0;
const double SpanX = static_cast<double>(Summary.Max.X - Summary.Min.X + 1) * TileExtentMetres; const double SpanX = static_cast<double>(Summary.Max.X - Summary.Min.X + 1) * TileExtentMetres;
const double SpanY = static_cast<double>(Summary.Max.Y - Summary.Min.Y + 1) * TileExtentMetres; const double SpanY = static_cast<double>(Summary.Max.Y - Summary.Min.Y + 1) * TileExtentMetres;
@@ -12109,8 +12138,9 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
for (const FVoxelTileKey& T : LoadedTiles) for (const FVoxelTileKey& T : LoadedTiles)
{ {
if (T.Level < CutMin) continue; if (T.Level < CutMin) continue;
const int32 CLo = T.Coord.Z << T.Level; const int64 LevelScale = VF_ClipLevelScale(T.Level);
const int32 CHi = ((T.Coord.Z + 1) << T.Level) - 1; const int64 CLo = static_cast<int64>(T.Coord.Z) * LevelScale;
const int64 CHi = (static_cast<int64>(T.Coord.Z) + 1) * LevelScale - 1;
const bool bInOld = CLo >= MeshBandChunkLo && CHi <= MeshBandChunkHi; const bool bInOld = CLo >= MeshBandChunkLo && CHi <= MeshBandChunkHi;
const bool bInNew = CLo >= NewLo && CHi <= NewHi; const bool bInNew = CLo >= NewLo && CHi <= NewHi;
const bool bOutOld = CHi < MeshBandChunkLo || CLo > MeshBandChunkHi; const bool bOutOld = CHi < MeshBandChunkLo || CLo > MeshBandChunkHi;
@@ -12330,7 +12360,7 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
} }
Heading = Heading.GetSafeNormal(); Heading = Heading.GetSafeNormal();
const FVector PlayerLocalCm = WorldToLocalCm(CenterPosition); const FVector PlayerLocalCm = WorldToLocalCm(CenterPosition);
const double TileSizeCm = static_cast<double>(CHUNK_SIZE << Level) const double TileSizeCm = static_cast<double>(VF_ClipLevelExtentVoxels(Level))
* GetActiveVoxelSizeCm(); * GetActiveVoxelSizeCm();
LevelTiles.Sort([&](const FVoxelTileKey& A, const FVoxelTileKey& B) LevelTiles.Sort([&](const FVoxelTileKey& A, const FVoxelTileKey& B)
{ {
@@ -12788,7 +12818,7 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
const int32 FullRes = GVoxelForgeTestFullResClipLevels >= 0 const int32 FullRes = GVoxelForgeTestFullResClipLevels >= 0
? FMath::Clamp(GVoxelForgeTestFullResClipLevels, 1, 9) ? FMath::Clamp(GVoxelForgeTestFullResClipLevels, 1, 9)
: (Settings ? FMath::Max(1, Settings->FullResClipLevels) : 2); : (Settings ? FMath::Max(1, Settings->FullResClipLevels) : 2);
const int32 Extent = CHUNK_SIZE << Tile.Level; const int32 Extent = VF_ClipLevelExtentVoxels(Tile.Level);
const int32 Cells = (Tile.Level < FullRes) const int32 Cells = (Tile.Level < FullRes)
? CHUNK_SIZE ? CHUNK_SIZE
@@ -12853,7 +12883,9 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
// et mélanges de matériaux qu'avant la coupe) — la tuile ne peut rendre que des artefacts // et mélanges de matériaux qu'avant la coupe) — la tuile ne peut rendre que des artefacts
// ⇒ on n'émet RIEN. Résultat vide via ProcessQueue (bookkeeping normal : PendingTiles, // ⇒ on n'émet RIEN. Résultat vide via ProcessQueue (bookkeeping normal : PendingTiles,
// LoadedTiles, epoch) ; jamais figé — le changement de bande re-queue via BandRemeshQueue. // LoadedTiles, epoch) ; jamais figé — le changement de bande re-queue via BandRemeshQueue.
if (Step > (BandChunkHi - BandChunkLo + 1) * CHUNK_SIZE) const int64 BandHeightVoxels =
(static_cast<int64>(BandChunkHi) - BandChunkLo + 1) * CHUNK_SIZE;
if (static_cast<int64>(Step) > BandHeightVoxels)
{ {
FChunkResult Empty; FChunkResult Empty;
Empty.Tile = Tile; Empty.Tile = Tile;
@@ -13988,7 +14020,8 @@ bool AVoxelWorld::ApplyMeshToTile(FChunkResult& Result)
// Convertir les bornes physiques de tuile vers les strates owner, y compris les rapports fractionnaires. // Convertir les bornes physiques de tuile vers les strates owner, y compris les rapports fractionnaires.
const double OwnerChunksPerCoreTile = static_cast<double>(GetActiveVoxelSizeCm()) const double OwnerChunksPerCoreTile = static_cast<double>(GetActiveVoxelSizeCm())
/ VOXEL_SIZE; / VOXEL_SIZE;
const double TileExtentCoreVoxels = static_cast<double>(CHUNK_SIZE << Tile.Level); const double TileExtentCoreVoxels =
static_cast<double>(VF_ClipLevelExtentVoxels(Tile.Level));
const int32 MinOwnerChunkX = FMath::FloorToInt( const int32 MinOwnerChunkX = FMath::FloorToInt(
Tile.Coord.X * TileExtentCoreVoxels * OwnerChunksPerCoreTile / CHUNK_SIZE); Tile.Coord.X * TileExtentCoreVoxels * OwnerChunksPerCoreTile / CHUNK_SIZE);
const int32 MinOwnerChunkY = FMath::FloorToInt( const int32 MinOwnerChunkY = FMath::FloorToInt(
@@ -15180,7 +15213,7 @@ void AVoxelWorld::RemeshDirtyChunks(const TArray<FIntVector>& DirtyCoords, const
// BackgroundHigh; the old component remains visible until its post-edit replacement is applied. // BackgroundHigh; the old component remains visible until its post-edit replacement is applied.
auto TileCoversDirtyCoord = [&DirtyCoords](const FVoxelTileKey& Tile) -> bool auto TileCoversDirtyCoord = [&DirtyCoords](const FVoxelTileKey& Tile) -> bool
{ {
const int32 Span = 1 << Tile.Level; const int32 Span = VF_ClipLevelScale(Tile.Level);
const FIntVector MinChunk = Tile.Coord * Span; const FIntVector MinChunk = Tile.Coord * Span;
const FIntVector MaxChunk = MinChunk + FIntVector(Span, Span, Span); const FIntVector MaxChunk = MinChunk + FIntVector(Span, Span, Span);
for (const FIntVector& Coord : DirtyCoords) for (const FIntVector& Coord : DirtyCoords)
+20 -9
View File
@@ -261,8 +261,12 @@ namespace VoxelHash
FORCEINLINE uint32 Cell(int32 CellX, int32 CellY, uint32 Seed) FORCEINLINE uint32 Cell(int32 CellX, int32 CellY, uint32 Seed)
{ {
uint32 H = Seed; uint32 H = Seed;
H ^= Mix((uint32)(CellX + 0x7FFFFFFF)); // Bias in unsigned arithmetic. Adding the signed cell coordinate first overflows for
H ^= Mix((uint32)(CellY + 0x7FFFFFFF) * 2654435761u); // positive cells and makes the world hash compiler-dependent.
const uint32 BiasedX = static_cast<uint32>(CellX) + 0x7FFFFFFFu;
const uint32 BiasedY = static_cast<uint32>(CellY) + 0x7FFFFFFFu;
H ^= Mix(BiasedX);
H ^= Mix(BiasedY * 2654435761u);
return Mix(H); return Mix(H);
} }
@@ -274,10 +278,14 @@ namespace VoxelHash
int32 MinX = FMath::Min(AX, BX), MinY = FMath::Min(AY, BY); int32 MinX = FMath::Min(AX, BX), MinY = FMath::Min(AY, BY);
int32 MaxX = FMath::Max(AX, BX), MaxY = FMath::Max(AY, BY); int32 MaxX = FMath::Max(AX, BX), MaxY = FMath::Max(AY, BY);
uint32 H = Seed ^ 0xDEADBEEF; uint32 H = Seed ^ 0xDEADBEEF;
H ^= Mix((uint32)(MinX + 0x7FFFFFFF)); const uint32 BiasedMinX = static_cast<uint32>(MinX) + 0x7FFFFFFFu;
H ^= Mix((uint32)(MinY + 0x7FFFFFFF) * 2654435761u); const uint32 BiasedMinY = static_cast<uint32>(MinY) + 0x7FFFFFFFu;
H ^= Mix((uint32)(MaxX + 0x7FFFFFFF) * 374761393u); const uint32 BiasedMaxX = static_cast<uint32>(MaxX) + 0x7FFFFFFFu;
H ^= Mix((uint32)(MaxY + 0x7FFFFFFF) * 668265263u); const uint32 BiasedMaxY = static_cast<uint32>(MaxY) + 0x7FFFFFFFu;
H ^= Mix(BiasedMinX);
H ^= Mix(BiasedMinY * 2654435761u);
H ^= Mix(BiasedMaxX * 374761393u);
H ^= Mix(BiasedMaxY * 668265263u);
return Mix(H); return Mix(H);
} }
@@ -477,10 +485,13 @@ namespace VoxelNoise
// Hash the neighbor cell to get a feature point position [0,1) // Hash the neighbor cell to get a feature point position [0,1)
// Using three different hash mixes for X, Y, Z offsets // Using three different hash mixes for X, Y, Z offsets
const uint32 BiasedX = static_cast<uint32>(NX) + 0x7FFFFFFFu;
const uint32 BiasedY = static_cast<uint32>(NY) + 0x7FFFFFFFu;
const uint32 BiasedZ = static_cast<uint32>(NZ) + 0x7FFFFFFFu;
uint32 H = VoxelHash::Mix( uint32 H = VoxelHash::Mix(
(uint32)(NX + 0x7FFFFFFF) BiasedX
^ VoxelHash::Mix((uint32)(NY + 0x7FFFFFFF) * 2654435761u) ^ VoxelHash::Mix(BiasedY * 2654435761u)
^ VoxelHash::Mix((uint32)(NZ + 0x7FFFFFFF) * 374761393u) ^ VoxelHash::Mix(BiasedZ * 374761393u)
); );
float FPX = (float)DX + VoxelHash::ToFloat01(H) - FracX; float FPX = (float)DX + VoxelHash::ToFloat01(H) - FracX;
+21 -4
View File
@@ -332,16 +332,33 @@ struct FVoxelTileKey
FVoxelTileKey(const FIntVector& InCoord, int32 InLevel) : Coord(InCoord), Level(InLevel) {} FVoxelTileKey(const FIntVector& InCoord, int32 InLevel) : Coord(InCoord), Level(InLevel) {}
// Cell size (sampling step) in voxels, and tile extent in voxels per axis. // Cell size (sampling step) in voxels, and tile extent in voxels per axis.
int32 StepVoxels() const { return 1 << Level; } int32 StepVoxels() const
int32 ExtentVoxels() const { return CHUNK_SIZE << Level; } {
const uint32 SafeLevel = static_cast<uint32>(FMath::Clamp(Level, 0, 8));
return static_cast<int32>(uint32{1} << SafeLevel);
}
int32 ExtentVoxels() const
{
const uint32 SafeLevel = static_cast<uint32>(FMath::Clamp(Level, 0, 8));
return static_cast<int32>(static_cast<uint32>(CHUNK_SIZE) * (uint32{1} << SafeLevel));
}
// Min-corner of the tile in VOXEL coords (what the mesher takes). // Min-corner of the tile in VOXEL coords (what the mesher takes).
FIntVector OriginVoxels() const { return Coord * (CHUNK_SIZE << Level); } FIntVector OriginVoxels() const
{
const int64 Extent = ExtentVoxels();
const auto ScaleAxis = [Extent](int32 Axis)
{
return static_cast<int32>(FMath::Clamp<int64>(
static_cast<int64>(Axis) * Extent, MIN_int32, MAX_int32));
};
return FIntVector(ScaleAxis(Coord.X), ScaleAxis(Coord.Y), ScaleAxis(Coord.Z));
}
// Tile centre in world cm (for distance sorting / LOD selection). // Tile centre in world cm (for distance sorting / LOD selection).
FVector CenterCm() const FVector CenterCm() const
{ {
const double Ext = (double)(CHUNK_SIZE << Level) * (double)VOXEL_SIZE; const double Ext = static_cast<double>(ExtentVoxels()) * static_cast<double>(VOXEL_SIZE);
return FVector((Coord.X + 0.5) * Ext, (Coord.Y + 0.5) * Ext, (Coord.Z + 0.5) * Ext); return FVector((Coord.X + 0.5) * Ext, (Coord.Y + 0.5) * Ext, (Coord.Z + 0.5) * Ext);
} }
+19 -3
View File
@@ -13,6 +13,8 @@ COMPILER="clang++-19"
VARIANT="clang19" VARIANT="clang19"
FP_CONTRACT="off" FP_CONTRACT="off"
JOBS=12 JOBS=12
SANITIZERS=""
EXCLUDE_UNSIGNED_OVERFLOW=0
while (($#)); do while (($#)); do
case "$1" in case "$1" in
@@ -20,6 +22,8 @@ while (($#)); do
--variant=*) VARIANT="${1#*=}" ;; --variant=*) VARIANT="${1#*=}" ;;
--fp-contract=*) FP_CONTRACT="${1#*=}" ;; --fp-contract=*) FP_CONTRACT="${1#*=}" ;;
--jobs=*) JOBS="${1#*=}" ;; --jobs=*) JOBS="${1#*=}" ;;
--sanitize=*) SANITIZERS="${1#*=}" ;;
--exclude-unsigned-integer-overflow) EXCLUDE_UNSIGNED_OVERFLOW=1 ;;
*) printf 'Unknown build option: %s\n' "$1" >&2; exit 2 ;; *) printf 'Unknown build option: %s\n' "$1" >&2; exit 2 ;;
esac esac
shift shift
@@ -60,11 +64,22 @@ INCLUDES=(
"-I$ROOT/Source/VoxelForge/Public" "-I$ROOT/Source/VoxelForge/Public"
"-I$ROOT/Source/VoxelForge/Private" "-I$ROOT/Source/VoxelForge/Private"
) )
OPT_LEVEL=-O2
LINK_FLAGS=()
if [[ -n "$SANITIZERS" ]]; then
OPT_LEVEL=-O1
LINK_FLAGS+=("-fsanitize=$SANITIZERS" -fno-sanitize-recover=undefined)
if ((EXCLUDE_UNSIGNED_OVERFLOW)); then
LINK_FLAGS+=(-fno-sanitize=unsigned-integer-overflow)
fi
fi
FLAGS=( FLAGS=(
-std=c++20 -O2 -mavx2 -mfma -fno-fast-math -std=c++20 "$OPT_LEVEL" -mavx2 -mfma -fno-fast-math
-fexceptions -pthread "${INCLUDES[@]}" -fexceptions -pthread "${INCLUDES[@]}"
"-DVOXELFORGE_SOURCE_STAMP=\"$SOURCE_STAMP\"" "-DVOXELFORGE_SOURCE_STAMP=\"$SOURCE_STAMP\""
) )
FLAGS+=("${LINK_FLAGS[@]}")
if [[ "$FP_CONTRACT" != "default" ]]; then if [[ "$FP_CONTRACT" != "default" ]]; then
FLAGS+=("-ffp-contract=$FP_CONTRACT") FLAGS+=("-ffp-contract=$FP_CONTRACT")
fi fi
@@ -131,7 +146,7 @@ PIDS=()
CORE_LIBRARY="$BUILD_DIR/libVoxelForgeCore.a" CORE_LIBRARY="$BUILD_DIR/libVoxelForgeCore.a"
"$(command -v ar)" rcs "$CORE_LIBRARY" "${CORE_OBJECTS[@]}" "$(command -v ar)" rcs "$CORE_LIBRARY" "${CORE_OBJECTS[@]}"
OUTPUT="$BIN_DIR/VoxelForgeCoreHeadless-$VARIANT" OUTPUT="$BIN_DIR/VoxelForgeCoreHeadless-$VARIANT"
"$COMPILER" -pthread "${LEGACY_OBJECTS[@]}" "$CORE_LIBRARY" -o "$OUTPUT" "$COMPILER" -pthread "${LINK_FLAGS[@]}" "${LEGACY_OBJECTS[@]}" "$CORE_LIBRARY" -o "$OUTPUT"
CAPTURE_OUTPUT="$BIN_DIR/VoxelForgeOffscreenCapture-$VARIANT" CAPTURE_OUTPUT="$BIN_DIR/VoxelForgeOffscreenCapture-$VARIANT"
"$COMPILER" -std=c++20 -O2 -fno-fast-math -pthread \ "$COMPILER" -std=c++20 -O2 -fno-fast-math -pthread \
"-DVOXELFORGE_SOURCE_STAMP=\"$SOURCE_STAMP\"" -I"$ROOT/Source" \ "-DVOXELFORGE_SOURCE_STAMP=\"$SOURCE_STAMP\"" -I"$ROOT/Source" \
@@ -145,4 +160,5 @@ if [[ "$POST_BUILD_STAMP" != "$SOURCE_STAMP" ]]; then
fi fi
printf 'Built Core library: %s\nBuilt headless benchmark: %s\n' "$CORE_LIBRARY" "$OUTPUT" printf 'Built Core library: %s\nBuilt headless benchmark: %s\n' "$CORE_LIBRARY" "$OUTPUT"
printf 'Built EGL offscreen capture: %s\n' "$CAPTURE_OUTPUT" printf 'Built EGL offscreen capture: %s\n' "$CAPTURE_OUTPUT"
printf 'Compiler: %s | variant: %s | fp-contract: %s | jobs: %s\n' "$COMPILER" "$VARIANT" "$FP_CONTRACT" "$JOBS" printf 'Compiler: %s | variant: %s | fp-contract: %s | sanitizers: %s | jobs: %s\n' \
"$COMPILER" "$VARIANT" "$FP_CONTRACT" "${SANITIZERS:-off}" "$JOBS"
@@ -54,12 +54,36 @@ namespace VoxelForgeCoreShared
} }
}; };
// Exact identity for one world-lattice density sample. Unlike a column key this includes
// Z, sampling settings, and the edit generation; all coordinates remain explicit words.
struct FDensitySampleCacheKey
{
std::uint64_t fieldIdentity = 0;
std::uint64_t fieldGeneration = 0;
std::uint64_t editSequence = 0;
std::uint32_t lod = 0;
std::uint32_t step = 1;
std::uint32_t octaveDrop = 0;
std::int64_t interpolationStride = 0;
std::int64_t x = 0, y = 0, z = 0;
std::uint64_t queryContextLow = 0, queryContextHigh = 0;
std::array<std::uint64_t, 12> words() const noexcept
{
return {fieldIdentity, fieldGeneration, editSequence, lod, step, octaveDrop,
static_cast<std::uint64_t>(interpolationStride), static_cast<std::uint64_t>(x),
static_cast<std::uint64_t>(y), static_cast<std::uint64_t>(z),
queryContextLow, queryContextHigh};
}
};
// A bounded, world-owned second-level cache. Existing thread-local caches stay first, so // A bounded, world-owned second-level cache. Existing thread-local caches stay first, so
// their ordinary hit path remains private and lock-free. Shared hits read atomic words; only // their ordinary hit path remains private and lock-free. Shared hits read atomic words; only
// a shared miss takes the set spin lock. Entries are four-way set associative, evict on // a shared miss takes the set spin lock. Entries are four-way set associative, evict on
// collision, and carry pointer identities plus content hashes to reject stale reuse. // collision, and carry pointer identities plus content hashes to reject stale reuse.
// Les caches TLS restent le premier niveau; seul un defaut partage prend le verrou du groupe. // Les caches TLS restent le premier niveau; seul un defaut partage prend le verrou du groupe.
template <typename TValue, std::size_t SetCount, std::size_t Ways = 4> template <typename TValue, std::size_t SetCount, std::size_t Ways = 4,
typename TKey = FColumnCacheKey>
class TSharedColumnValueCache class TSharedColumnValueCache
{ {
static_assert(SetCount > 0 && (SetCount & (SetCount - 1)) == 0, static_assert(SetCount > 0 && (SetCount & (SetCount - 1)) == 0,
@@ -69,14 +93,14 @@ namespace VoxelForgeCoreShared
"shared column values must be trivially copyable"); "shared column values must be trivially copyable");
static constexpr std::size_t ValueWordCount = (sizeof(TValue) + 7) / 8; static constexpr std::size_t ValueWordCount = (sizeof(TValue) + 7) / 8;
using FKeyWords = std::array<std::uint64_t, 7>; using FKeyWords = decltype(std::declval<TKey>().words());
using FValueWords = std::array<std::uint64_t, ValueWordCount>; using FValueWords = std::array<std::uint64_t, ValueWordCount>;
struct FEntry struct FEntry
{ {
std::atomic<std::uint64_t> Version{0}; std::atomic<std::uint64_t> Version{0};
std::atomic<std::uint64_t> Tag{0}; std::atomic<std::uint64_t> Tag{0};
std::array<std::atomic<std::uint64_t>, 7> Key{}; std::array<std::atomic<std::uint64_t>, FKeyWords{}.size()> Key{};
std::array<std::atomic<std::uint64_t>, ValueWordCount> Value{}; std::array<std::atomic<std::uint64_t>, ValueWordCount> Value{};
}; };
@@ -128,7 +152,7 @@ namespace VoxelForgeCoreShared
static TSharedColumnValueCache* threadActive() noexcept { return Active; } static TSharedColumnValueCache* threadActive() noexcept { return Active; }
template <typename TFactory> template <typename TFactory>
TValue getOrCompute(const FColumnCacheKey& key, TFactory&& factory, TValue getOrCompute(const TKey& key, TFactory&& factory,
bool& computed, FColumnCacheMetrics* metrics = nullptr) bool& computed, FColumnCacheMetrics* metrics = nullptr)
{ {
computed = false; computed = false;
@@ -139,6 +139,10 @@ namespace VoxelForgeCore
std::uint64_t columnCacheConflictMisses = 0; std::uint64_t columnCacheConflictMisses = 0;
std::uint64_t sharedColumnCacheHits = 0; std::uint64_t sharedColumnCacheHits = 0;
std::uint64_t columnEvaluations = 0; std::uint64_t columnEvaluations = 0;
std::uint64_t densitySampleCacheHits = 0;
std::uint64_t densitySampleCacheComputes = 0;
std::uint64_t marchingCubesSkirtTrianglesEmitted = 0;
std::uint64_t marchingCubesSkirtTriangleSeedsSkipped = 0;
std::uint64_t islandLocalCacheHits = 0; std::uint64_t islandLocalCacheHits = 0;
std::uint64_t islandLocalCacheMisses = 0; std::uint64_t islandLocalCacheMisses = 0;
std::uint64_t islandLocalCacheConflictReplacements = 0; std::uint64_t islandLocalCacheConflictReplacements = 0;
@@ -400,7 +404,9 @@ namespace VoxelForgeCore
// version 169 sank every connector 2 m; it left four FI route support probes low. // version 169 sank every connector 2 m; it left four FI route support probes low.
// version 170 sinks only connectors touching the anchored root by 1 m. // version 170 sinks only connectors touching the anchored root by 1 m.
// Version 208 keeps the crater tread scoped to style-zero FI mouths. // Version 208 keeps the crater tread scoped to style-zero FI mouths.
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 209; // Version 210 keeps bulk native-cave sampling on the scalar field path at strata edges.
// Version 211 makes coordinate biasing in the shared cell hash unsigned, removing signed-overflow UB.
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 211;
enum class FieldChannel : std::uint32_t enum class FieldChannel : std::uint32_t
{ {
@@ -717,8 +723,42 @@ namespace VoxelForgeCore
// Hors du domaine prepare, revenir au parcours complet canonique. // Hors du domaine prepare, revenir au parcours complet canonique.
std::array<std::int64_t, 3> minimumSubvoxels{}; std::array<std::int64_t, 3> minimumSubvoxels{};
std::array<std::int64_t, 3> maximumSubvoxels{}; std::array<std::int64_t, 3> maximumSubvoxels{};
// Stable semantic identity used by the exact shared density-sample cache.
std::uint64_t densityCacheContextLow = 0;
std::uint64_t densityCacheContextHigh = 0;
}; };
inline void FinalizeWorldAssemblyFeatureTileListDensityContextV1(
WorldAssemblyFeatureTileListV1& list) noexcept
{
std::uint64_t low = 0x243f6a8885a308d3ull;
std::uint64_t high = 0x13198a2e03707344ull;
const auto add = [&](std::uint64_t value)
{
low ^= value + 0x9e3779b97f4a7c15ull + (low << 6) + (low >> 2);
low *= 0xbf58476d1ce4e5b9ull;
high ^= value + 0x94d049bb133111ebull + (high << 7) + (high >> 3);
high *= 0x9e3779b185ebca87ull;
};
add(list.featureIndices.size());
for (const auto value : list.featureIndices) add(value);
add(list.groupBounds.size());
for (const auto& group : list.groupBounds)
{
for (auto value : group.minimumSubvoxels) add(static_cast<std::uint64_t>(value));
for (auto value : group.maximumSubvoxels) add(static_cast<std::uint64_t>(value));
}
add(list.groupSearchOrder.size());
for (const auto value : list.groupSearchOrder) add(value);
add(list.groupDistanceLowerBoundsSubvoxels.size());
for (const auto value : list.groupDistanceLowerBoundsSubvoxels)
add(static_cast<std::uint64_t>(value));
add(list.groupSearchComplete ? 1u : 0u);
add(list.candidateEvaluationUseful ? 1u : 0u);
list.densityCacheContextLow = low;
list.densityCacheContextHigh = high;
}
using FloatingIslandTileFeatureListV1 = WorldAssemblyFeatureTileListV1; using FloatingIslandTileFeatureListV1 = WorldAssemblyFeatureTileListV1;
using FloatingIslandFeatureGroupBoundsV1 = WorldAssemblyFeatureGroupBoundsV1; using FloatingIslandFeatureGroupBoundsV1 = WorldAssemblyFeatureGroupBoundsV1;
@@ -741,10 +781,13 @@ namespace VoxelForgeCore
ScopedWorldAssemblyFeatureQueriesV1& operator=(const ScopedWorldAssemblyFeatureQueriesV1&) = delete; ScopedWorldAssemblyFeatureQueriesV1& operator=(const ScopedWorldAssemblyFeatureQueriesV1&) = delete;
static const WorldAssemblyFeatureTileListV1* find(const void* recipe, const void* plan, static const WorldAssemblyFeatureTileListV1* find(const void* recipe, const void* plan,
std::int64_t x, std::int64_t y, std::int64_t z, bool columnOnly = false) noexcept; std::int64_t x, std::int64_t y, std::int64_t z, bool columnOnly = false) noexcept;
static std::array<std::uint64_t, 2> densityCacheContext(
std::int64_t x, std::int64_t y, std::int64_t z) noexcept;
private: private:
std::span<const WorldAssemblyFeatureQueryV1> queries_; std::span<const WorldAssemblyFeatureQueryV1> queries_;
bool forceCandidates_ = false; bool forceCandidates_ = false;
const ScopedWorldAssemblyFeatureQueriesV1* previous_ = nullptr; const ScopedWorldAssemblyFeatureQueriesV1* previous_ = nullptr;
std::uint64_t densityCacheContextLow_ = 0, densityCacheContextHigh_ = 0;
}; };
struct VFCORE_API FloatingIslandManifestV1 struct VFCORE_API FloatingIslandManifestV1
@@ -1004,13 +1047,11 @@ namespace VoxelForgeCore
&&stratum.recipeVersion==1; &&stratum.recipeVersion==1;
} }
inline constexpr std::uint32_t NativeLatticeFieldRecipeVersion=2;
inline bool IsNativeLatticeFieldStratum(const ResolvedLayoutStratumV2& stratum) noexcept inline bool IsNativeLatticeFieldStratum(const ResolvedLayoutStratumV2& stratum) noexcept
{ {
return (stratum.generatorKind=="Maze"||stratum.generatorKind=="VerticalShafts") return (stratum.generatorKind=="Maze"||stratum.generatorKind=="VerticalShafts")
&&stratum.recipeId=="Core.Lattice.PrimitiveField" &&stratum.recipeId=="Core.Lattice.PrimitiveField"
&&stratum.recipeVersion==NativeLatticeFieldRecipeVersion; &&stratum.recipeVersion==1;
} }
inline bool HasNativeGraphField(const ResolvedLayoutStratumV2& stratum) noexcept inline bool HasNativeGraphField(const ResolvedLayoutStratumV2& stratum) noexcept
@@ -1343,6 +1384,8 @@ namespace VoxelForgeCore
std::uint64_t columnConflictMisses = 0; std::uint64_t columnConflictMisses = 0;
std::uint64_t sharedColumnHits = 0; std::uint64_t sharedColumnHits = 0;
std::uint64_t columnEvaluations = 0; std::uint64_t columnEvaluations = 0;
std::uint64_t densitySampleCacheHits = 0;
std::uint64_t densitySampleCacheComputes = 0;
double columnComputeMilliseconds = 0.0; double columnComputeMilliseconds = 0.0;
bool captureCoordinates = false; bool captureCoordinates = false;
bool coordinateCaptureComplete = true; bool coordinateCaptureComplete = true;
@@ -1741,6 +1784,9 @@ namespace VoxelForgeCore
// Diagnostic seulement; enregistre les colonnes XY evaluees par cette tuile LOD8. // Diagnostic seulement; enregistre les colonnes XY evaluees par cette tuile LOD8.
bool captureColumnCoordinates = false; bool captureColumnCoordinates = false;
bool useSharedColumnCache = true; bool useSharedColumnCache = true;
// Fixed visibility mask for the four horizontal skirts and the two vertical cap skirts.
// The mask is part of the standalone mesh-cache identity and defaults to all faces.
std::uint8_t visibleSkirtFaceMask = 0x3f;
// Separate cold pass only; samples the MC inner stages without changing output. // Separate cold pass only; samples the MC inner stages without changing output.
// Passe froide separee seulement; mesure les etapes internes MC sans changer la sortie. // Passe froide separee seulement; mesure les etapes internes MC sans changer la sortie.
bool profileMesherStages = false; bool profileMesherStages = false;
@@ -2325,7 +2325,8 @@ FloatingIslandTileFeatureListV1 FloatingIslandFieldV1::buildFeatureTileList(
result.candidateEvaluationUseful=plan_->islands.size()>4; result.candidateEvaluationUseful=plan_->islands.size()>4;
result.minimumSubvoxels = tileMinimumSubvoxels; result.minimumSubvoxels = tileMinimumSubvoxels;
result.maximumSubvoxels = tileMaximumSubvoxels; result.maximumSubvoxels = tileMaximumSubvoxels;
if (!isValid()) return result; if (!isValid())
{ FinalizeWorldAssemblyFeatureTileListDensityContextV1(result); return result; }
const auto features = solidSupportBounds(); const auto features = solidSupportBounds();
result.featureIndices = BuildWorldAssemblyFeatureTileListV1(features, result.featureIndices = BuildWorldAssemblyFeatureTileListV1(features,
@@ -2347,7 +2348,8 @@ FloatingIslandTileFeatureListV1 FloatingIslandFieldV1::buildFeatureTileList(
{ {
const auto source = findSourceIsland(edge); const auto source = findSourceIsland(edge);
if (source == plan_->islands.end()) if (source == plan_->islands.end())
{ result.groupBounds.clear(); result.featureIndices.clear(); return result; } { result.groupBounds.clear(); result.featureIndices.clear();
FinalizeWorldAssemblyFeatureTileListDensityContextV1(result); return result; }
auto& group = result.groupBounds[static_cast<std::size_t>(source - plan_->islands.begin())]; auto& group = result.groupBounds[static_cast<std::size_t>(source - plan_->islands.begin())];
for (std::size_t axis = 0; axis < 3; ++axis) for (std::size_t axis = 0; axis < 3; ++axis)
{ {
@@ -2438,10 +2440,12 @@ FloatingIslandTileFeatureListV1 FloatingIslandFieldV1::buildFeatureTileList(
{ {
const std::size_t edgeIndex = static_cast<std::size_t>(featureIndex) - islandCount; const std::size_t edgeIndex = static_cast<std::size_t>(featureIndex) - islandCount;
if (edgeIndex >= plan_->clusterEdges.size()) if (edgeIndex >= plan_->clusterEdges.size())
{ result.groupBounds.clear(); result.featureIndices.clear(); return result; } { result.groupBounds.clear(); result.featureIndices.clear();
FinalizeWorldAssemblyFeatureTileListDensityContextV1(result); return result; }
const auto source = findSourceIsland(plan_->clusterEdges[edgeIndex]); const auto source = findSourceIsland(plan_->clusterEdges[edgeIndex]);
if (source == plan_->islands.end()) if (source == plan_->islands.end())
{ result.groupBounds.clear(); result.featureIndices.clear(); return result; } { result.groupBounds.clear(); result.featureIndices.clear();
FinalizeWorldAssemblyFeatureTileListDensityContextV1(result); return result; }
selectedGroups[static_cast<std::size_t>(source - plan_->islands.begin())] = 1; selectedGroups[static_cast<std::size_t>(source - plan_->islands.begin())] = 1;
} }
} }
@@ -2464,6 +2468,7 @@ FloatingIslandTileFeatureListV1 FloatingIslandFieldV1::buildFeatureTileList(
if (plan_->clusterEdges[edgeIndex].fromIslandId == plan_->islands[islandIndex].stableId) if (plan_->clusterEdges[edgeIndex].fromIslandId == plan_->islands[islandIndex].stableId)
insertSorted(static_cast<std::uint32_t>(islandCount + edgeIndex)); insertSorted(static_cast<std::uint32_t>(islandCount + edgeIndex));
} }
FinalizeWorldAssemblyFeatureTileListDensityContextV1(result);
return result; return result;
} }
+2 -4
View File
@@ -1901,8 +1901,7 @@ bool ResolveResolvedCavernGraphV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
// The shared JSON value type stores numbers as IEEE-754 doubles. Keep the // The shared JSON value type stores numbers as IEEE-754 doubles. Keep the
// serialized seed in the exact integer range, as the stamp vocabulary does. // serialized seed in the exact integer range, as the stamp vocabulary does.
room.irregularitySeed=(isNativeLatticeStrate(room.strateIndex) room.irregularitySeed=(isNativeLatticeStrate(room.strateIndex)
?LayoutLatticeFeatureSeed(seed,room.strateIndex, ?LayoutLatticeFeatureSeed(seed,room.strateIndex,1,room.stableId)
NativeLatticeFieldRecipeVersion,room.stableId)
:LayoutStampSeed(seed,room.strateIndex,room.stableId)) :LayoutStampSeed(seed,room.strateIndex,room.stableId))
&0x001fffffffffffffull; &0x001fffffffffffffull;
const std::int64_t pad=room.smoothWidthMicrometres const std::int64_t pad=room.smoothWidthMicrometres
@@ -1919,8 +1918,7 @@ bool ResolveResolvedCavernGraphV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
{ {
const bool nativeLattice=isNativeLatticeStrate(edge.strateIndex); const bool nativeLattice=isNativeLatticeStrate(edge.strateIndex);
edge.irregularitySeed=(nativeLattice edge.irregularitySeed=(nativeLattice
?LayoutLatticeFeatureSeed(seed,edge.strateIndex, ?LayoutLatticeFeatureSeed(seed,edge.strateIndex,1,edge.stableId)
NativeLatticeFieldRecipeVersion,edge.stableId)
:LayoutStampSeed(seed,edge.strateIndex,edge.stableId)) :LayoutStampSeed(seed,edge.strateIndex,edge.stableId))
&0x001fffffffffffffull; &0x001fffffffffffffull;
using FCurvePoint=std::array<double,3>; using FCurvePoint=std::array<double,3>;
+151 -419
View File
@@ -208,6 +208,7 @@ public:
routeFeatureOverlap_=true; routeFeatureOverlap_=true;
} }
} }
buildDensityCacheContext();
ActivePassageTileQueries=this; ActivePassageTileQueries=this;
} }
~FScopedPassageTileQueries(){ActivePassageTileQueries=previous_;} ~FScopedPassageTileQueries(){ActivePassageTileQueries=previous_;}
@@ -240,8 +241,46 @@ public:
bool hasCavernCandidates() const{return !cavernFeatureIndices_.empty();} bool hasCavernCandidates() const{return !cavernFeatureIndices_.empty();}
const std::vector<std::uint32_t>& cavernFeatureCandidates() const const std::vector<std::uint32_t>& cavernFeatureCandidates() const
{return cavernFeatureIndices_;} {return cavernFeatureIndices_;}
static std::array<std::uint64_t,2> densityCacheContext(
const FStandaloneShapeWorld& shape) noexcept
{
const auto* queries=ActivePassageTileQueries;
if(!queries||!queries->matches(shape))return {};
return {queries->densityCacheContextLow_,queries->densityCacheContextHigh_};
}
private: private:
void buildDensityCacheContext() noexcept
{
std::uint64_t low=0x3c6ef372fe94f82bull;
std::uint64_t high=0xa54ff53a5f1d36f1ull;
const auto add=[&](std::uint64_t value)
{
low^=value+0x9e3779b97f4a7c15ull+(low<<6)+(low>>2);
low*=0xbf58476d1ce4e5b9ull;
high^=value+0x94d049bb133111ebull+(high<<7)+(high>>3);
high*=0x9e3779b185ebca87ull;
};
add(static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(shape_)));
const auto addIndices=[&](const std::vector<std::uint32_t>& values)
{
add(values.size());
for(const auto value:values)add(value);
};
addIndices(featureIndices_);
addIndices(stampIndices_);
addIndices(cavernFeatureIndices_);
for(const auto value:passageRoutes_)add(value);
for(const auto value:passageColumns_)add(value);
for(const auto value:passageMouthSupports_)add(value);
add(routeFeatureOverlap_?1u:0u);
add(columnZOverlap_?1u:0u);
add(mayContainResolvedBoundaryBand_?1u:0u);
add(mayContainTransitionPassageFade_?1u:0u);
densityCacheContextLow_=low;
densityCacheContextHigh_=high;
}
const FStandaloneShapeWorld* shape_; const FStandaloneShapeWorld* shape_;
std::array<std::int64_t,3> minimumSubvoxels_,maximumSubvoxels_; std::array<std::int64_t,3> minimumSubvoxels_,maximumSubvoxels_;
std::vector<std::uint32_t> featureIndices_; std::vector<std::uint32_t> featureIndices_;
@@ -251,6 +290,7 @@ public:
bool routeFeatureOverlap_=false,columnZOverlap_=false; bool routeFeatureOverlap_=false,columnZOverlap_=false;
bool mayContainResolvedBoundaryBand_=true; bool mayContainResolvedBoundaryBand_=true;
bool mayContainTransitionPassageFade_=true; bool mayContainTransitionPassageFade_=true;
std::uint64_t densityCacheContextLow_=0,densityCacheContextHigh_=0;
const FScopedPassageTileQueries* previous_; const FScopedPassageTileQueries* previous_;
}; };
@@ -3187,9 +3227,11 @@ public:
const float SampleY=static_cast<float>(Y); const float SampleY=static_cast<float>(Y);
const float SampleZ=static_cast<float>(Z); const float SampleZ=static_cast<float>(Z);
bHasLegacySurfaceSample|=UsesLegacySurfaceField(SampleX,SampleY,SampleZ); bHasLegacySurfaceSample|=UsesLegacySurfaceField(SampleX,SampleY,SampleZ);
const auto* bound=FindBoundStratumForZ(SampleZ); const auto* managerBound=FindBoundStratumForManagerZ(SampleZ);
bHasNativeGraphSample|=bound const auto* resolvedBound=FindBoundStratumForZ(SampleZ);
&&bound->Kind==FBoundStratumField::EKind::Caves; bHasNativeGraphSample|=(managerBound
&&managerBound->Kind==FBoundStratumField::EKind::Caves)
||(resolvedBound&&resolvedBound->Kind==FBoundStratumField::EKind::Caves);
HasNativeIslandSample[static_cast<std::size_t>(Lane)]=NativeFloatingIslandDensityAt( HasNativeIslandSample[static_cast<std::size_t>(Lane)]=NativeFloatingIslandDensityAt(
SampleX,SampleY,SampleZ,NativeIslandDensity[static_cast<std::size_t>(Lane)]); SampleX,SampleY,SampleZ,NativeIslandDensity[static_cast<std::size_t>(Lane)]);
bHasNativeIslandSample|=HasNativeIslandSample[static_cast<std::size_t>(Lane)]; bHasNativeIslandSample|=HasNativeIslandSample[static_cast<std::size_t>(Lane)];
@@ -3332,14 +3374,6 @@ private:
const VoxelForgeCore::ResolvedCavernRoomV1* room=nullptr; const VoxelForgeCore::ResolvedCavernRoomV1* room=nullptr;
const VoxelForgeCore::ResolvedCavernEdgeV1* edge=nullptr; const VoxelForgeCore::ResolvedCavernEdgeV1* edge=nullptr;
bool nativeGraph=false; bool nativeGraph=false;
bool openShaftRamp=false;
std::array<double,2> openShaftCenterMetres{};
std::array<double,2> openShaftStartDirection{1.0,0.0};
double openShaftCenterlineRadiusMetres=0.0;
double openShaftStartFloorMetres=0.0;
double openShaftPitchPerTurnMetres=0.0;
double openShaftTurnCount=0.0;
double openShaftRotationSign=1.0;
std::array<std::int64_t,3> boundsMinimumMicrometres{},boundsMaximumMicrometres{}; std::array<std::int64_t,3> boundsMinimumMicrometres{},boundsMaximumMicrometres{};
std::array<double,3> centerMetres{},halfExtentsMetres{}; std::array<double,3> centerMetres{},halfExtentsMetres{};
struct FSegment struct FSegment
@@ -3800,15 +3834,6 @@ private:
:found->passageCapabilities.hasNativeLatticeField); :found->passageCapabilities.hasNativeLatticeField);
}; };
const float micronsPerVoxel=static_cast<float>(Owner->CoreVoxelSizeMicrons); const float micronsPerVoxel=static_cast<float>(Owner->CoreVoxelSizeMicrons);
const auto shaftCoreRoom=std::find_if(ResolvedLayout->cavernRooms.begin(),
ResolvedLayout->cavernRooms.end(),[](const auto& room)
{return room.stableId=="lattice/spine/shaft-core-v2";});
std::array<double,2> shaftCoreCenter{};
if(shaftCoreRoom!=ResolvedLayout->cavernRooms.end())
{
shaftCoreCenter={shaftCoreRoom->centerMicrometres[0]/1.0e6,
shaftCoreRoom->centerMicrometres[1]/1.0e6};
}
for(const auto& room:ResolvedLayout->cavernRooms) for(const auto& room:ResolvedLayout->cavernRooms)
{ {
FCavernRuntimeFeature runtime;runtime.room=&room; FCavernRuntimeFeature runtime;runtime.room=&room;
@@ -3839,11 +3864,6 @@ private:
{ {
FCavernRuntimeFeature runtime;runtime.edge=&edge; FCavernRuntimeFeature runtime;runtime.edge=&edge;
runtime.nativeGraph=isNativeGraphStrate(edge.strateIndex); runtime.nativeGraph=isNativeGraphStrate(edge.strateIndex);
runtime.openShaftRamp=runtime.nativeGraph
&&edge.stableId=="lattice/spine/01-helix-ramp-v1"
&&shaftCoreRoom!=ResolvedLayout->cavernRooms.end();
if(runtime.openShaftRamp)
runtime.openShaftCenterMetres=shaftCoreCenter;
runtime.boundsMinimumMicrometres=edge.boundsMinimumMicrometres; runtime.boundsMinimumMicrometres=edge.boundsMinimumMicrometres;
runtime.boundsMaximumMicrometres=edge.boundsMaximumMicrometres; runtime.boundsMaximumMicrometres=edge.boundsMaximumMicrometres;
for(std::size_t point=0;point<4;++point) for(std::size_t point=0;point<4;++point)
@@ -3887,37 +3907,6 @@ private:
std::abs((segment.end[2]-segment.start[2])/horizontal)); std::abs((segment.end[2]-segment.start[2])/horizontal));
runtime.segments.push_back(segment); runtime.segments.push_back(segment);
} }
if(runtime.openShaftRamp&&!runtime.segments.empty())
{
const auto& first=runtime.segments.front().start;
const auto& last=runtime.segments.back().end;
const double startX=first[0]-shaftCoreCenter[0];
const double startY=first[1]-shaftCoreCenter[1];
runtime.openShaftCenterlineRadiusMetres=std::hypot(startX,startY);
if(runtime.openShaftCenterlineRadiusMetres>1.0e-9)
runtime.openShaftStartDirection={
startX/runtime.openShaftCenterlineRadiusMetres,
startY/runtime.openShaftCenterlineRadiusMetres};
runtime.openShaftStartFloorMetres=first[2];
double totalAngle=0.0;
for(std::size_t segment=0;segment<runtime.segments.size();++segment)
{
const auto& previous=runtime.segments[segment].start;
const auto& current=runtime.segments[segment].end;
const double previousX=previous[0]-shaftCoreCenter[0];
const double previousY=previous[1]-shaftCoreCenter[1];
const double currentX=current[0]-shaftCoreCenter[0];
const double currentY=current[1]-shaftCoreCenter[1];
totalAngle+=std::atan2(previousX*currentY-previousY*currentX,
previousX*currentX+previousY*currentY);
}
constexpr double TwoPi=6.283185307179586476925286766559;
runtime.openShaftTurnCount=std::max(1.0,
std::round(std::abs(totalAngle)/TwoPi));
runtime.openShaftRotationSign=totalAngle<0.0?-1.0:1.0;
runtime.openShaftPitchPerTurnMetres=(first[2]-last[2])
/runtime.openShaftTurnCount;
}
runtime.segmentOrder.resize(runtime.segments.size()); runtime.segmentOrder.resize(runtime.segments.size());
for(std::size_t index=0;index<runtime.segmentOrder.size();++index) for(std::size_t index=0;index<runtime.segmentOrder.size();++index)
runtime.segmentOrder[index]=static_cast<std::uint32_t>(index); runtime.segmentOrder[index]=static_cast<std::uint32_t>(index);
@@ -3950,8 +3939,7 @@ private:
>node.maximum[splitAxis]-node.minimum[splitAxis]) >node.maximum[splitAxis]-node.minimum[splitAxis])
splitAxis=axis; splitAxis=axis;
const std::size_t middle=first+(end-first)/2; const std::size_t middle=first+(end-first)/2;
std::nth_element(runtime.segmentOrder.begin()+first, std::sort(runtime.segmentOrder.begin()+first,
runtime.segmentOrder.begin()+middle,
runtime.segmentOrder.begin()+end, runtime.segmentOrder.begin()+end,
[&](std::uint32_t a,std::uint32_t b) [&](std::uint32_t a,std::uint32_t b)
{ {
@@ -5298,50 +5286,9 @@ public:
const std::int32_t featureStrate=feature.room?feature.room->strateIndex const std::int32_t featureStrate=feature.room?feature.room->strateIndex
:feature.edge?feature.edge->strateIndex:-1; :feature.edge?feature.edge->strateIndex:-1;
if(featureStrate!=strateIndex)continue; if(featureStrate!=strateIndex)continue;
bool featureOverlapsBox=true,boxWithinFeatureBounds=true;
for(std::size_t axis=0;axis<3;++axis)
{
const double featureMinimum=feature.boundsMinimumMicrometres[axis]/1.0e6;
const double featureMaximum=feature.boundsMaximumMicrometres[axis]/1.0e6;
if(hi[axis]<featureMinimum||lo[axis]>featureMaximum)
{featureOverlapsBox=false;break;}
boxWithinFeatureBounds=boxWithinFeatureBounds
&&lo[axis]>=featureMinimum&&hi[axis]<=featureMaximum;
}
if(!featureOverlapsBox)continue;
// The point field omits a record outside its resolved bounds. A partial
// overlap cannot reuse the record's unbounded SDF for an all-air proof.
if(!boxWithinFeatureBounds)return {0.0,0.0,false};
FMetricInterval featureVoid; FMetricInterval featureVoid;
bool openHelixSupportCouldApply=true;
double openHelixSupportLowerBound=std::numeric_limits<double>::infinity();
if(feature.room) if(feature.room)
{ {
if(nativeLattice
&&feature.room->stableId=="lattice/spine/shaft-core-v2")
{
const double radius=feature.halfExtentsMetres[0];
if(!(radius>0.0))return {0.0,0.0,false};
double radialMinimumSquared=0.0,radialMaximumSquared=0.0;
for(std::size_t axis=0;axis<2;++axis)
{
const double center=feature.centerMetres[axis];
const double deltaMinimum=center<lo[axis]?lo[axis]-center
:center>hi[axis]?center-hi[axis]:0.0;
const double deltaMaximum=std::max(std::abs(lo[axis]-center),
std::abs(hi[axis]-center));
radialMinimumSquared+=deltaMinimum*deltaMinimum;
radialMaximumSquared+=deltaMaximum*deltaMaximum;
}
const double radialMinimum=std::sqrt(radialMinimumSquared)-radius;
const double radialMaximum=std::sqrt(radialMaximumSquared)-radius;
const double floorZ=feature.room->floorMicrometres/1000000.0;
const FMetricInterval bottom={floorZ-hi[2],floorZ-lo[2],true};
featureVoid={std::max(radialMinimum,bottom.minimum),
std::max(radialMaximum,bottom.maximum),true};
}
else
{
double normalizedMinimumSquared=0.0,normalizedMaximumSquared=0.0; double normalizedMinimumSquared=0.0,normalizedMaximumSquared=0.0;
double meanRadius=0.0; double meanRadius=0.0;
for(std::size_t axis=0;axis<3;++axis) for(std::size_t axis=0;axis<3;++axis)
@@ -5365,135 +5312,14 @@ public:
const double width=feature.room->smoothWidthMicrometres/1000000.0; const double width=feature.room->smoothWidthMicrometres/1000000.0;
featureVoid={SmoothMaxMetres(ellipsoid.minimum,floor.minimum,width), featureVoid={SmoothMaxMetres(ellipsoid.minimum,floor.minimum,width),
SmoothMaxMetres(ellipsoid.maximum,floor.maximum,width),true}; SmoothMaxMetres(ellipsoid.maximum,floor.maximum,width),true};
}
} }
else if(feature.edge&&!feature.segments.empty()) else if(feature.edge&&!feature.segments.empty())
{ {
const bool openHelixRamp=nativeLattice&&feature.openShaftRamp;
const double radius=feature.edge->boreRadiusMicrometres/1000000.0; const double radius=feature.edge->boreRadiusMicrometres/1000000.0;
const double height=feature.edge->boreHeightMicrometres/1000000.0; const double height=feature.edge->boreHeightMicrometres/1000000.0;
if(!(radius>0.0&&height>0.0))return {0.0,0.0,false}; if(!(radius>0.0&&height>0.0))return {0.0,0.0,false};
double tubeMinimum=std::numeric_limits<double>::infinity(); double tubeMinimum=std::numeric_limits<double>::infinity();
double tubeMaximum=std::numeric_limits<double>::infinity(); double tubeMaximum=std::numeric_limits<double>::infinity();
if(openHelixRamp)
{
double radialMinimumSquared=0.0,radialMaximumSquared=0.0;
for(std::size_t axis=0;axis<2;++axis)
{
const double center=feature.openShaftCenterMetres[axis];
const double deltaMinimum=center<lo[axis]?lo[axis]-center
:center>hi[axis]?center-hi[axis]:0.0;
const double deltaMaximum=std::max(std::abs(lo[axis]-center),
std::abs(hi[axis]-center));
radialMinimumSquared+=deltaMinimum*deltaMinimum;
radialMaximumSquared+=deltaMaximum*deltaMaximum;
}
const double radialMinimum=std::sqrt(radialMinimumSquared);
const double radialMaximum=std::sqrt(radialMaximumSquared);
const double centerline=feature.openShaftCenterlineRadiusMetres;
const double nearestRingDistance=centerline<radialMinimum
?radialMinimum-centerline:centerline>radialMaximum
?centerline-radialMaximum:0.0;
tubeMinimum=nearestRingDistance-radius;
tubeMaximum=std::max(std::abs(radialMinimum-centerline),
std::abs(radialMaximum-centerline))-radius;
openHelixSupportCouldApply=tubeMinimum<0.0;
const double startFloor=feature.openShaftStartFloorMetres;
const double pitch=feature.openShaftPitchPerTurnMetres;
const double turnCount=feature.openShaftTurnCount;
if(!std::isfinite(tubeMinimum)||!std::isfinite(tubeMaximum)
||!std::isfinite(startFloor)||!(pitch>0.0)
||!(turnCount>=1.0))return {0.0,0.0,false};
const FMetricInterval tube={tubeMinimum,tubeMaximum,true};
const double grade=pitch/(6.283185307179586476925286766559
*feature.openShaftCenterlineRadiusMetres);
const double verticalScale=1.0/std::sqrt(1.0+grade*grade);
double phaseMinimum=0.0,phaseMaximum=1.0;
const double boxCenterX=(lo[0]+hi[0])*0.5;
const double boxCenterY=(lo[1]+hi[1])*0.5;
const double centerDx=boxCenterX-feature.openShaftCenterMetres[0];
const double centerDy=boxCenterY-feature.openShaftCenterMetres[1];
const double centerDistance=std::hypot(centerDx,centerDy);
const double halfDiagonal=0.5*std::hypot(hi[0]-lo[0],hi[1]-lo[1]);
if(centerDistance>halfDiagonal)
{
const double angle=std::atan2(feature.openShaftRotationSign
*(feature.openShaftStartDirection[0]*centerDy
-feature.openShaftStartDirection[1]*centerDx),
feature.openShaftStartDirection[0]*centerDx
+feature.openShaftStartDirection[1]*centerDy);
constexpr double TwoPi=6.283185307179586476925286766559;
double centerPhase=angle/TwoPi;
const double angleRadius=std::asin(std::clamp(
halfDiagonal/centerDistance,0.0,1.0));
const double phaseRadius=angleRadius/TwoPi;
// Point evaluation normalizes phase into [0,1). If this box
// crosses the start ray, retain the full phase range; otherwise
// unwrap its center into the same range as the point field.
if(std::abs(angle)>angleRadius)
{
if(centerPhase<0.0)centerPhase+=1.0;
phaseMinimum=centerPhase-phaseRadius;
phaseMaximum=centerPhase+phaseRadius;
}
}
double verticalMinimum=std::numeric_limits<double>::infinity();
double verticalMaximum=-std::numeric_limits<double>::infinity();
const double coordinateMinimum=(startFloor-hi[2])/pitch-phaseMaximum;
const double coordinateMaximum=(startFloor-lo[2])/pitch-phaseMinimum;
if(!std::isfinite(coordinateMinimum)||!std::isfinite(coordinateMaximum))
return {0.0,0.0,false};
const int lastTurn=std::max(0,static_cast<int>(turnCount)-1);
const int firstPossibleTurn=std::clamp(
static_cast<int>(std::floor(coordinateMinimum)),-1,lastTurn+1);
const int lastPossibleTurn=std::clamp(
static_cast<int>(std::floor(coordinateMaximum)),-1,lastTurn+1);
const auto corridorInterval=[&](int turn)
{
const double floorMinimum=startFloor-pitch*(turn+phaseMaximum);
const double floorMaximum=startFloor-pitch*(turn+phaseMinimum);
const double floorToQueryMinimum=(floorMinimum-hi[2])*verticalScale;
const double floorToQueryMaximum=(floorMaximum-lo[2])*verticalScale;
const double queryToCeilingMinimum=(lo[2]-floorMaximum-height)
*verticalScale;
const double queryToCeilingMaximum=(hi[2]-floorMinimum-height)
*verticalScale;
const double turnMinimum=std::max(floorToQueryMinimum,
queryToCeilingMinimum);
const double turnMaximum=std::max(floorToQueryMaximum,
queryToCeilingMaximum);
return FMetricInterval{turnMinimum,turnMaximum,true};
};
for(int turn=firstPossibleTurn;turn<=lastPossibleTurn;++turn)
{
const int turnA=std::clamp(turn,0,lastTurn);
const int turnB=std::clamp(turn+1,0,lastTurn);
const FMetricInterval corridorA=corridorInterval(turnA);
const FMetricInterval corridorB=corridorInterval(turnB);
verticalMinimum=std::min(verticalMinimum,
std::min(corridorA.minimum,corridorB.minimum));
verticalMaximum=std::max(verticalMaximum,
std::min(corridorA.maximum,corridorB.maximum));
const double supportDepth=feature.edge->floorSupportDepthMicrometres
/1000000.0;
const double progressMinimum=turn+phaseMinimum;
const double progressMaximum=turn+phaseMaximum;
const double floorMinimum=startFloor-pitch*progressMaximum;
const double floorMaximum=startFloor-pitch*progressMinimum;
const double supportFloorMinimum=(floorMinimum-hi[2])*verticalScale;
const double supportUndersideMinimum=(lo[2]-floorMaximum+supportDepth)
*verticalScale;
openHelixSupportLowerBound=std::min(openHelixSupportLowerBound,
std::max({tubeMinimum,supportFloorMinimum,
supportUndersideMinimum}));
}
const FMetricInterval vertical={verticalMinimum,verticalMaximum,true};
const double width=feature.edge->smoothWidthMicrometres/1000000.0;
featureVoid={SmoothMaxMetres(tube.minimum,vertical.minimum,width),
SmoothMaxMetres(tube.maximum,vertical.maximum,width),true};
}
else
{
const std::array<double,3> inverseScale={1.0/radius,1.0/radius, const std::array<double,3> inverseScale={1.0/radius,1.0/radius,
1.0/height}; 1.0/height};
const std::array<double,3> boxMinimum={lo[0]*inverseScale[0], const std::array<double,3> boxMinimum={lo[0]*inverseScale[0],
@@ -5602,7 +5428,6 @@ public:
const double width=feature.edge->smoothWidthMicrometres/1000000.0; const double width=feature.edge->smoothWidthMicrometres/1000000.0;
featureVoid={SmoothMaxMetres(tube.minimum,floor.minimum,width), featureVoid={SmoothMaxMetres(tube.minimum,floor.minimum,width),
SmoothMaxMetres(tube.maximum,floor.maximum,width),true}; SmoothMaxMetres(tube.maximum,floor.maximum,width),true};
}
} }
else return {0.0,0.0,false}; else return {0.0,0.0,false};
if(nativeLattice) if(nativeLattice)
@@ -5617,17 +5442,10 @@ public:
&&lo[0]<=feature.boundsMaximumMicrometres[0]/1.0e6 &&lo[0]<=feature.boundsMaximumMicrometres[0]/1.0e6
&&hi[1]>=feature.boundsMinimumMicrometres[1]/1.0e6 &&hi[1]>=feature.boundsMinimumMicrometres[1]/1.0e6
&&lo[1]<=feature.boundsMaximumMicrometres[1]/1.0e6; &&lo[1]<=feature.boundsMaximumMicrometres[1]/1.0e6;
const bool supportCouldApply=overlapsXY if(supportDepth>0.0&&overlapsXY
&&(!feature.openShaftRamp||openHelixSupportCouldApply);
if(supportDepth>0.0&&supportCouldApply
&&hi[2]>=floorMinimum-supportDepth&&lo[2]<=floorMaximum) &&hi[2]>=floorMinimum-supportDepth&&lo[2]<=floorMaximum)
{
double supportLower=std::max(-supportDepth,lo[2]-floorMaximum);
if(feature.openShaftRamp)
supportLower=openHelixSupportLowerBound;
latticeFloorSupportLowerBound=std::min(latticeFloorSupportLowerBound, latticeFloorSupportLowerBound=std::min(latticeFloorSupportLowerBound,
supportLower); std::max(-supportDepth,lo[2]-floorMaximum));
}
} }
if(!std::isfinite(featureVoid.minimum)||!std::isfinite(featureVoid.maximum)) if(!std::isfinite(featureVoid.minimum)||!std::isfinite(featureVoid.maximum))
return {0.0,0.0,false}; return {0.0,0.0,false};
@@ -7185,10 +7003,6 @@ private:
return; return;
} }
if(!feature.edge)return; if(!feature.edge)return;
// The open helix is already carved with its exact cylindrical/turn
// field above. Its four serialized control points are a compact record
// envelope, not a walkable Bezier centerline for this legacy cap.
if(feature.openShaftRamp)return;
const auto bezier=[&](float t,std::size_t axis) const auto bezier=[&](float t,std::size_t axis)
{ {
const float one=1.0f-t; const float one=1.0f-t;
@@ -7325,209 +7139,118 @@ private:
const std::int32_t strateIndex=feature.room?feature.room->strateIndex const std::int32_t strateIndex=feature.room?feature.room->strateIndex
:feature.edge?feature.edge->strateIndex:-1; :feature.edge?feature.edge->strateIndex:-1;
if(strateIndex!=bound.StrateIndex)continue; if(strateIndex!=bound.StrateIndex)continue;
bool insideFeatureBounds=true;
for(std::size_t axis=0;axis<3;++axis)
insideFeatureBounds=insideFeatureBounds
&&point[axis]*1000000.0>=feature.boundsMinimumMicrometres[axis]
&&point[axis]*1000000.0<=feature.boundsMaximumMicrometres[axis];
if(!insideFeatureBounds)continue;
double featureVoid=std::numeric_limits<double>::infinity(); double featureVoid=std::numeric_limits<double>::infinity();
if(feature.room) if(feature.room)
{ {
if(nativeLattice double normalizedSquared=0.0,meanRadius=0.0;
&&feature.room->stableId=="lattice/spine/shaft-core-v2") for(std::size_t axis=0;axis<3;++axis)
{ {
const double radius=feature.halfExtentsMetres[0]; const double radius=feature.halfExtentsMetres[axis];
if(!(radius>0.0))continue; if(!(radius>0.0)){normalizedSquared=std::numeric_limits<double>::infinity();break;}
const double radial=std::hypot( const double delta=(point[axis]-feature.centerMetres[axis])/radius;
point[0]-feature.centerMetres[0], normalizedSquared+=delta*delta;meanRadius+=radius/3.0;
point[1]-feature.centerMetres[1])-radius;
const double bottom=feature.room->floorMicrometres/1000000.0-point[2];
featureVoid=std::max(radial,bottom);
}
else
{
double normalizedSquared=0.0,meanRadius=0.0;
for(std::size_t axis=0;axis<3;++axis)
{
const double radius=feature.halfExtentsMetres[axis];
if(!(radius>0.0))
{normalizedSquared=std::numeric_limits<double>::infinity();break;}
const double delta=(point[axis]-feature.centerMetres[axis])/radius;
normalizedSquared+=delta*delta;meanRadius+=radius/3.0;
}
const double ellipsoid=(std::sqrt(normalizedSquared)-1.0)*meanRadius;
const double floor=feature.room->floorMicrometres/1000000.0-point[2];
const double width=feature.room->smoothWidthMicrometres/1000000.0;
featureVoid=SmoothMaxMetres(ellipsoid,floor,width);
} }
const double ellipsoid=(std::sqrt(normalizedSquared)-1.0)*meanRadius;
const double floor=feature.room->floorMicrometres/1000000.0-point[2];
const double width=feature.room->smoothWidthMicrometres/1000000.0;
featureVoid=SmoothMaxMetres(ellipsoid,floor,width);
} }
else if(feature.edge&&!feature.segments.empty()) else if(feature.edge&&!feature.segments.empty())
{ {
const bool openHelixRamp=nativeLattice&&feature.openShaftRamp;
double tube=std::numeric_limits<double>::infinity(); double tube=std::numeric_limits<double>::infinity();
double nearestFloor=0.0,nearestGrade=0.0,nearestRadial=0.0; double nearestFloor=0.0,nearestGrade=0.0,nearestRadial=0.0;
double openHelixVerticalSdf=std::numeric_limits<double>::infinity();
double openHelixPhase=0.0;
int openHelixTurnA=0,openHelixTurnB=0;
double openHelixVerticalScale=1.0;
const double radius=feature.edge->boreRadiusMicrometres/1000000.0; const double radius=feature.edge->boreRadiusMicrometres/1000000.0;
const double height=feature.edge->boreHeightMicrometres/1000000.0; const double height=feature.edge->boreHeightMicrometres/1000000.0;
constexpr double SegmentDistanceTieToleranceMetres=1.0e-12;
if(!(radius>0.0&&height>0.0))continue; if(!(radius>0.0&&height>0.0))continue;
const double minScale=std::min(radius,height); const double minScale=std::min(radius,height);
if(openHelixRamp) std::uint32_t nearestSegment=std::numeric_limits<std::uint32_t>::max();
const auto evaluateSegment=[&](std::uint32_t segmentIndex)
{ {
const double dx=point[0]-feature.openShaftCenterMetres[0]; if(segmentIndex>=feature.segments.size())return;
const double dy=point[1]-feature.openShaftCenterMetres[1]; const auto& segment=feature.segments[segmentIndex];
const double shaftRadial=std::hypot(dx,dy); double lengthSquared=0.0,dot=0.0;
nearestRadial=std::abs(shaftRadial for(std::size_t axis=0;axis<3;++axis)
-feature.openShaftCenterlineRadiusMetres);
tube=nearestRadial-radius;
const double phase=std::atan2(feature.openShaftRotationSign
*(feature.openShaftStartDirection[0]*dy
-feature.openShaftStartDirection[1]*dx),
feature.openShaftStartDirection[0]*dx
+feature.openShaftStartDirection[1]*dy);
constexpr double TwoPi=6.283185307179586476925286766559;
openHelixPhase=(phase<0.0?phase+TwoPi:phase)/TwoPi;
const double pitch=feature.openShaftPitchPerTurnMetres;
if(!(pitch>0.0)||!(feature.openShaftTurnCount>=1.0))continue;
const double grade=pitch/(TwoPi
*feature.openShaftCenterlineRadiusMetres);
openHelixVerticalScale=1.0/std::sqrt(1.0+grade*grade);
const double turnCoordinate=(feature.openShaftStartFloorMetres-point[2])
/pitch-openHelixPhase;
const int turnFloor=static_cast<int>(std::floor(turnCoordinate));
const int lastTurn=std::max(0,
static_cast<int>(feature.openShaftTurnCount)-1);
openHelixTurnA=std::clamp(turnFloor,0,lastTurn);
openHelixTurnB=std::clamp(turnFloor+1,0,lastTurn);
const auto floorForTurn=[&](int turn)
{ {
return feature.openShaftStartFloorMetres const double delta=segment.end[axis]-segment.start[axis];
-pitch*(turn+openHelixPhase); lengthSquared+=delta*delta;
}; dot+=(point[axis]-segment.start[axis])*delta;
const auto corridorSdf=[&](int turn)
{
const double floorZ=floorForTurn(turn);
return std::max((floorZ-point[2])*openHelixVerticalScale,
(point[2]-(floorZ+height))*openHelixVerticalScale);
};
openHelixVerticalSdf=std::min(corridorSdf(openHelixTurnA),
corridorSdf(openHelixTurnB));
}
else
{
std::uint32_t nearestSegment=std::numeric_limits<std::uint32_t>::max();
const auto evaluateSegment=[&](std::uint32_t segmentIndex)
{
if(segmentIndex>=feature.segments.size())return;
const auto& segment=feature.segments[segmentIndex];
double lengthSquared=0.0,dot=0.0;
for(std::size_t axis=0;axis<3;++axis)
{
const double delta=segment.end[axis]-segment.start[axis];
lengthSquared+=delta*delta;
dot+=(point[axis]-segment.start[axis])*delta;
}
const double t=lengthSquared>0.0
?std::clamp(dot/lengthSquared,0.0,1.0):0.0;
double radialSquared=0.0,vertical=0.0;
for(std::size_t axis=0;axis<3;++axis)
{
const double delta=point[axis]-(segment.start[axis]
+t*(segment.end[axis]-segment.start[axis]));
if(axis<2)radialSquared+=delta*delta;
else vertical=delta;
}
const double normalized=std::sqrt(radialSquared/(radius*radius)
+vertical*vertical/(height*height));
const double distance=(normalized-1.0)*minScale;
if(distance<tube||(distance==tube&&segmentIndex<nearestSegment))
{
tube=distance;nearestSegment=segmentIndex;
nearestFloor=segment.start[2]+t*(segment.end[2]-segment.start[2]);
nearestRadial=std::sqrt(radialSquared);
const double horizontal=std::hypot(
segment.end[0]-segment.start[0],segment.end[1]-segment.start[1]);
nearestGrade=horizontal>1.0e-9
?(segment.end[2]-segment.start[2])/horizontal:0.0;
}
};
const auto nodeLowerBound=[&](const FCavernRuntimeFeature::FSegmentNode& node)
{
double gapSquared=0.0;
for(std::size_t axis=0;axis<3;++axis)
{
const double scale=axis==2?height:radius;
const double q=point[axis]/scale;
const double low=node.minimum[axis]/scale;
const double high=node.maximum[axis]/scale;
const double gap=q<low?low-q:q>high?q-high:0.0;
gapSquared+=gap*gap;
}
return (std::sqrt(gapSquared)-1.0)*minScale;
};
if(!feature.segmentNodes.empty())
{
const auto visitNode=[&](const auto& self,int nodeIndex)->void
{
if(nodeIndex<0||static_cast<std::size_t>(nodeIndex)>=feature.segmentNodes.size())return;
const auto& node=feature.segmentNodes[static_cast<std::size_t>(nodeIndex)];
if(nodeLowerBound(node)>tube)return;
if(node.left<0)
{
for(std::size_t offset=node.first;offset<node.end;++offset)
evaluateSegment(feature.segmentOrder[offset]);
return;
}
const double leftBound=nodeLowerBound(feature.segmentNodes[
static_cast<std::size_t>(node.left)]);
const double rightBound=nodeLowerBound(feature.segmentNodes[
static_cast<std::size_t>(node.right)]);
if(leftBound<=rightBound)
{self(self,node.left);self(self,node.right);}
else {self(self,node.right);self(self,node.left);}
};
visitNode(visitNode,0);
} }
else for(std::uint32_t segment=0;segment<feature.segments.size();++segment) const double t=lengthSquared>0.0
evaluateSegment(segment); ?std::clamp(dot/lengthSquared,0.0,1.0):0.0;
double radialSquared=0.0,vertical=0.0;
for(std::size_t axis=0;axis<3;++axis)
{
const double delta=point[axis]-(segment.start[axis]
+t*(segment.end[axis]-segment.start[axis]));
if(axis<2)radialSquared+=delta*delta;
else vertical=delta;
}
const double normalized=std::sqrt(radialSquared/(radius*radius)
+vertical*vertical/(height*height));
const double distance=(normalized-1.0)*std::min(radius,height);
const bool clearlyCloser=distance<tube-SegmentDistanceTieToleranceMetres;
const bool stableTie=std::abs(distance-tube)
<=SegmentDistanceTieToleranceMetres&&segmentIndex<nearestSegment;
if(clearlyCloser||stableTie)
{
tube=distance;nearestSegment=segmentIndex;
nearestFloor=segment.start[2]+t*(segment.end[2]-segment.start[2]);
nearestRadial=std::sqrt(radialSquared);
const double horizontal=std::hypot(
segment.end[0]-segment.start[0],segment.end[1]-segment.start[1]);
nearestGrade=horizontal>1.0e-9
?(segment.end[2]-segment.start[2])/horizontal:0.0;
}
};
const auto nodeLowerBound=[&](const FCavernRuntimeFeature::FSegmentNode& node)
{
double gapSquared=0.0;
for(std::size_t axis=0;axis<3;++axis)
{
const double scale=axis==2?height:radius;
const double q=point[axis]/scale;
const double low=node.minimum[axis]/scale;
const double high=node.maximum[axis]/scale;
const double gap=q<low?low-q:q>high?q-high:0.0;
gapSquared+=gap*gap;
}
return (std::sqrt(gapSquared)-1.0)*minScale;
};
if(!feature.segmentNodes.empty())
{
const auto visitNode=[&](const auto& self,int nodeIndex)->void
{
if(nodeIndex<0||static_cast<std::size_t>(nodeIndex)>=feature.segmentNodes.size())return;
const auto& node=feature.segmentNodes[static_cast<std::size_t>(nodeIndex)];
if(nodeLowerBound(node)>tube+SegmentDistanceTieToleranceMetres)return;
if(node.left<0)
{
for(std::size_t offset=node.first;offset<node.end;++offset)
evaluateSegment(feature.segmentOrder[offset]);
return;
}
const double leftBound=nodeLowerBound(feature.segmentNodes[
static_cast<std::size_t>(node.left)]);
const double rightBound=nodeLowerBound(feature.segmentNodes[
static_cast<std::size_t>(node.right)]);
if(leftBound<=rightBound)
{self(self,node.left);self(self,node.right);}
else {self(self,node.right);self(self,node.left);}
};
visitNode(visitNode,0);
} }
else for(std::uint32_t segment=0;segment<feature.segments.size();++segment)
evaluateSegment(segment);
const double supportDepth=feature.edge->floorSupportDepthMicrometres/1000000.0; const double supportDepth=feature.edge->floorSupportDepthMicrometres/1000000.0;
if(nativeLattice&&supportDepth>0.0) if(nativeLattice&&supportDepth>0.0&&nearestRadial<radius-0.12
{ &&point[2]<nearestFloor&&nearestFloor-point[2]<=supportDepth)
if(openHelixRamp) latticeFloorSupportMinimum=std::min(latticeFloorSupportMinimum,
{ (point[2]-nearestFloor)/std::sqrt(1.0+nearestGrade*nearestGrade));
const auto supportSdf=[&](int turn) const double floor=(nearestFloor-point[2])
{ /std::sqrt(1.0+nearestGrade*nearestGrade);
const double floorZ=feature.openShaftStartFloorMetres
-feature.openShaftPitchPerTurnMetres
*(turn+openHelixPhase);
const double vertical=std::max(
(floorZ-point[2])*openHelixVerticalScale,
(point[2]-(floorZ-supportDepth))
*openHelixVerticalScale);
return std::max(nearestRadial-radius,vertical);
};
latticeFloorSupportMinimum=std::min(latticeFloorSupportMinimum,
std::min(supportSdf(openHelixTurnA),
supportSdf(openHelixTurnB)));
}
else if(nearestRadial<radius-0.12&&point[2]<nearestFloor
&&nearestFloor-point[2]<=supportDepth)
latticeFloorSupportMinimum=std::min(latticeFloorSupportMinimum,
(point[2]-nearestFloor)/std::sqrt(1.0+nearestGrade*nearestGrade));
}
const double width=feature.edge->smoothWidthMicrometres/1000000.0; const double width=feature.edge->smoothWidthMicrometres/1000000.0;
if(openHelixRamp) featureVoid=SmoothMaxMetres(tube,floor,width);
featureVoid=SmoothMaxMetres(tube,openHelixVerticalSdf,width);
else
{
const double floor=(nearestFloor-point[2])
/std::sqrt(1.0+nearestGrade*nearestGrade);
featureVoid=SmoothMaxMetres(tube,floor,width);
}
} }
if(!std::isfinite(featureVoid))continue; if(!std::isfinite(featureVoid))continue;
if(!std::isfinite(caveVoid))caveVoid=featureVoid; if(!std::isfinite(caveVoid))caveVoid=featureVoid;
@@ -10517,10 +10240,19 @@ public:
return; return;
} }
bool bHasLegacySurfaceSample=false; bool bHasLegacySurfaceSample=false;
bool bHasNativeGraphSample=false;
for(int32 Lane=0;Lane<Count;++Lane) for(int32 Lane=0;Lane<Count;++Lane)
{
const float SampleZ=static_cast<float>(ZStart+Lane*ZStep);
bHasLegacySurfaceSample|=UsesLegacySurfaceField(static_cast<float>(X), bHasLegacySurfaceSample|=UsesLegacySurfaceField(static_cast<float>(X),
static_cast<float>(Y),static_cast<float>(ZStart+Lane*ZStep)); static_cast<float>(Y),SampleZ);
if(bHasLegacySurfaceSample) const auto* managerBound=FindBoundStratumForManagerZ(SampleZ);
const auto* resolvedBound=FindBoundStratumForZ(SampleZ);
bHasNativeGraphSample|=(managerBound
&&managerBound->Kind==FBoundStratumField::EKind::Caves)
||(resolvedBound&&resolvedBound->Kind==FBoundStratumField::EKind::Caves);
}
if(bHasLegacySurfaceSample||bHasNativeGraphSample)
{ {
for(int32 Lane=0;Lane<Count;++Lane) for(int32 Lane=0;Lane<Count;++Lane)
OutDensity[Lane]=DensityAtLattice(X,Y,ZStart+Lane*ZStep); OutDensity[Lane]=DensityAtLattice(X,Y,ZStart+Lane*ZStep);
+49 -1
View File
@@ -1041,6 +1041,8 @@ namespace VoxelForgeCore
std::atomic<bool> shapeReady{false}; std::atomic<bool> shapeReady{false};
std::unique_ptr<world_assembly_detail::FSharedHeightfieldColumnCache> std::unique_ptr<world_assembly_detail::FSharedHeightfieldColumnCache>
sharedHeightfieldColumns; sharedHeightfieldColumns;
std::unique_ptr<world_assembly_detail::FSharedDensitySampleCache>
sharedDensitySamples;
std::unique_ptr<floating_island_detail::FSharedIslandColumnCache> std::unique_ptr<floating_island_detail::FSharedIslandColumnCache>
sharedIslandColumns; sharedIslandColumns;
ShapeBuildRequest buildRequest; ShapeBuildRequest buildRequest;
@@ -1090,6 +1092,8 @@ namespace VoxelForgeCore
impl_->worldAssembly=snapshot.data_->worldAssembly; impl_->worldAssembly=snapshot.data_->worldAssembly;
impl_->resolvedLayout=snapshot.data_->resolvedLayout; impl_->resolvedLayout=snapshot.data_->resolvedLayout;
impl_->configuration=snapshot.data_->configuration; impl_->configuration=snapshot.data_->configuration;
impl_->sharedDensitySamples=
std::make_unique<world_assembly_detail::FSharedDensitySampleCache>();
if(impl_->worldAssembly) if(impl_->worldAssembly)
{ {
if(!impl_->worldAssembly->plan.strates.empty()) if(!impl_->worldAssembly->plan.strates.empty())
@@ -2051,6 +2055,7 @@ namespace VoxelForgeCore
mesher.bStandaloneProofBlockCells=static_cast<int32>(std::clamp<std::uint32_t>( mesher.bStandaloneProofBlockCells=static_cast<int32>(std::clamp<std::uint32_t>(
options.proofBlockCells,1u,static_cast<std::uint32_t>(std::max(1,CHUNK_SIZE)))); options.proofBlockCells,1u,static_cast<std::uint32_t>(std::max(1,CHUNK_SIZE))));
mesher.bGenerateSkirts=world.bGenerateSkirts; mesher.bGenerateSkirts=world.bGenerateSkirts;
mesher.SetVisibleSkirtFaceMask(options.visibleSkirtFaceMask);
mesher.SkirtCells=static_cast<float>(world.SkirtCells) mesher.SkirtCells=static_cast<float>(world.SkirtCells)
*static_cast<float>(world.OwnerVoxelSizeMicrons) *static_cast<float>(world.OwnerVoxelSizeMicrons)
/static_cast<float>(world.CoreVoxelSizeMicrons); /static_cast<float>(world.CoreVoxelSizeMicrons);
@@ -2157,6 +2162,42 @@ namespace VoxelForgeCore
MeshCells=FMath::Max(1,cells/Stride); MeshCells=FMath::Max(1,cells/Stride);
MeshStep=FMath::Max(1,extent/MeshCells); MeshStep=FMath::Max(1,extent/MeshCells);
} }
if(!bNativeOnly&&options.useSharedColumnCache&&impl_->sharedDensitySamples)
{
mesher.SetSharedDensitySampleCache(impl_->sharedDensitySamples.get(),
[](void* Cache,const FStandaloneShapeWorld* ShapeWorld,uint64 FieldGeneration,
uint64 EditSequence,int32 Lod,int32 Step,int32 X,int32 Y,int32 Z,
bool& OutComputed)->float
{
VoxelForgeCoreShared::FDensitySampleCacheKey Key;
Key.fieldIdentity=static_cast<uint64>(reinterpret_cast<std::uintptr_t>(ShapeWorld));
Key.fieldGeneration=FieldGeneration;Key.editSequence=EditSequence;
Key.lod=static_cast<uint32>(Lod);Key.step=static_cast<uint32>(Step);
Key.octaveDrop=Heightfield2D::threadColumnOctaveDrop();
Key.interpolationStride=Heightfield2D::threadColumnInterpolationStrideSubvoxels();
Key.x=X;Key.y=Y;Key.z=Z;
const auto queryContext = ScopedWorldAssemblyFeatureQueriesV1::densityCacheContext(X,Y,Z);
const auto passageContext = FStandaloneShapeWorld::FScopedPassageTileQueries
::densityCacheContext(*ShapeWorld);
const auto mixContext=[](uint64 Value)
{
Value^=Value>>30;Value*=0xbf58476d1ce4e5b9ull;
Value^=Value>>27;Value*=0x94d049bb133111ebull;
return Value^(Value>>31);
};
Key.queryContextLow=mixContext(queryContext[0]^passageContext[0]
^0x9e3779b97f4a7c15ull);
Key.queryContextHigh=mixContext(queryContext[1]^passageContext[1]
^0xd1b54a32d192ed03ull);
const float Value=static_cast<world_assembly_detail::FSharedDensitySampleCache*>(Cache)
->getOrCompute(Key,[&](){return ShapeWorld->DensityAtLattice(X,Y,Z);},OutComputed);
auto& Stats=world_assembly_detail::HeightfieldColumnStats;
if(OutComputed)++Stats.densitySampleCacheComputes;
else ++Stats.densitySampleCacheHits;
return Value;
},static_cast<uint64>(reinterpret_cast<std::uintptr_t>(impl_->worldAssembly.get())),
impl_->lastSequence,key.lod,MeshStep);
}
int64 sampleCount=0;std::vector<float> densityValues; int64 sampleCount=0;std::vector<float> densityValues;
FVoxelMarchingCubesStageTimings McStageTimings; FVoxelMarchingCubesStageTimings McStageTimings;
TileMesh out;out.key=key; TileMesh out;out.key=key;
@@ -2639,12 +2680,19 @@ namespace VoxelForgeCore
VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds; VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds;
const auto MarchingCubesStart=std::chrono::steady_clock::now(); const auto MarchingCubesStart=std::chrono::steady_clock::now();
FVoxelUniformBlockProofAuditStats BlockProofAudit; FVoxelUniformBlockProofAuditStats BlockProofAudit;
uint64 SkippedSkirtTriangleSeeds=0;
FVoxelMeshData mesh=mesher.GenerateMesh(origin,MeshStep,MeshCells,nullptr, FVoxelMeshData mesh=mesher.GenerateMesh(origin,MeshStep,MeshCells,nullptr,
MeshBandMinZVoxel,MeshBandMaxZVoxel,&sampleCount,nullptr,nullptr, MeshBandMinZVoxel,MeshBandMaxZVoxel,&sampleCount,nullptr,nullptr,
(options.hashDensity||options.captureDensitySamples)?&densityValues:nullptr, (options.hashDensity||options.captureDensitySamples)?&densityValues:nullptr,
options.profileMesherStages?&McStageTimings:nullptr, options.profileMesherStages?&McStageTimings:nullptr,
options.auditUniformBlockProofs?&BlockProofAudit:nullptr, options.auditUniformBlockProofs?&BlockProofAudit:nullptr,
options.forceFullDensityCapture); options.forceFullDensityCapture,&SkippedSkirtTriangleSeeds);
const auto& SampleCacheStats=Heightfield2D::threadColumnCacheStats();
out.diagnostics.densitySampleCacheHits=SampleCacheStats.densitySampleCacheHits;
out.diagnostics.densitySampleCacheComputes=SampleCacheStats.densitySampleCacheComputes;
out.diagnostics.marchingCubesSkirtTrianglesEmitted=
static_cast<uint64>(std::max(0,mesh.NumSkirtTriangles));
out.diagnostics.marchingCubesSkirtTriangleSeedsSkipped=SkippedSkirtTriangleSeeds;
if(options.auditUniformBlockProofs) if(options.auditUniformBlockProofs)
{ {
out.diagnostics.uniformBlockProofsChecked=BlockProofAudit.CheckedBlocks; out.diagnostics.uniformBlockProofsChecked=BlockProofAudit.CheckedBlocks;
+3 -3
View File
@@ -394,9 +394,9 @@ VF_FIELD_INLINE float VF_FieldOwnerCellular(float x,float y,float z)
{ {
const VF_FieldI32 nx=cellX+dx,ny=cellY+dy,nz=cellZ+dz; const VF_FieldI32 nx=cellX+dx,ny=cellY+dy,nz=cellZ+dz;
const VF_FieldU32 h=VF_FieldVoxelHashMix( const VF_FieldU32 h=VF_FieldVoxelHashMix(
VF_FIELD_CAST_U32(nx+0x7FFFFFFF) (VF_FIELD_CAST_U32(nx)+0x7FFFFFFFu)
^VF_FieldVoxelHashMix(VF_FIELD_CAST_U32(ny+0x7FFFFFFF)*2654435761u) ^VF_FieldVoxelHashMix((VF_FIELD_CAST_U32(ny)+0x7FFFFFFFu)*2654435761u)
^VF_FieldVoxelHashMix(VF_FIELD_CAST_U32(nz+0x7FFFFFFF)*374761393u)); ^VF_FieldVoxelHashMix((VF_FIELD_CAST_U32(nz)+0x7FFFFFFFu)*374761393u));
const float fpx=VF_FIELD_CAST_F32(dx) const float fpx=VF_FIELD_CAST_F32(dx)
+VF_FIELD_CAST_F32(h&0xFFFFFFu)*(1.0f/16777216.0f)-fracX; +VF_FIELD_CAST_F32(h&0xFFFFFFu)*(1.0f/16777216.0f)-fracX;
const VF_FieldU32 hy=VF_FieldVoxelHashMix(h^0x12345678u); const VF_FieldU32 hy=VF_FieldVoxelHashMix(h^0x12345678u);
@@ -32,6 +32,34 @@ ScopedWorldAssemblyFeatureQueriesV1::ScopedWorldAssemblyFeatureQueriesV1(
for (const auto& query : queries_) for (const auto& query : queries_)
if (forceCandidates_ || query.features.candidateEvaluationUseful) if (forceCandidates_ || query.features.candidateEvaluationUseful)
{ ActiveAssemblyQueries = this; break; } { ActiveAssemblyQueries = this; break; }
if (ActiveAssemblyQueries == this)
{
std::uint64_t low = 0x6a09e667f3bcc909ull;
std::uint64_t high = 0xbb67ae8584caa73bull;
const auto add = [&](std::uint64_t value)
{
low ^= value + 0x9e3779b97f4a7c15ull + (low << 6) + (low >> 2);
low *= 0xbf58476d1ce4e5b9ull;
high ^= value + 0x94d049bb133111ebull + (high << 7) + (high >> 3);
high *= 0x9e3779b185ebca87ull;
};
add(forceCandidates_ ? 1u : 0u);
std::uint64_t activeCount = 0;
for (const auto& query : queries_)
if (forceCandidates_ || query.features.candidateEvaluationUseful) ++activeCount;
add(activeCount);
for (const auto& query : queries_)
{
if (!forceCandidates_ && !query.features.candidateEvaluationUseful) continue;
add(static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(query.recipe)));
add(static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(query.plan)));
add(query.features.densityCacheContextLow);
add(query.features.densityCacheContextHigh);
}
if (low == 0 && high == 0) high = 1;
densityCacheContextLow_ = low;
densityCacheContextHigh_ = high;
}
} }
ScopedWorldAssemblyFeatureQueriesV1::~ScopedWorldAssemblyFeatureQueriesV1() ScopedWorldAssemblyFeatureQueriesV1::~ScopedWorldAssemblyFeatureQueriesV1()
@@ -56,6 +84,15 @@ const WorldAssemblyFeatureTileListV1* ScopedWorldAssemblyFeatureQueriesV1::find(
return nullptr; return nullptr;
} }
std::array<std::uint64_t, 2> ScopedWorldAssemblyFeatureQueriesV1::densityCacheContext(
std::int64_t x, std::int64_t y, std::int64_t z) noexcept
{
(void)x; (void)y; (void)z;
if (!ActiveAssemblyQueries) return {};
return {ActiveAssemblyQueries->densityCacheContextLow_,
ActiveAssemblyQueries->densityCacheContextHigh_};
}
std::vector<std::uint32_t> BuildWorldAssemblyFeatureTileListV1( std::vector<std::uint32_t> BuildWorldAssemblyFeatureTileListV1(
std::span<const WorldAssemblyFeatureBoundsV1> features, std::span<const WorldAssemblyFeatureBoundsV1> features,
const std::array<std::int64_t, 3>& tileMinimumSubvoxels, const std::array<std::int64_t, 3>& tileMinimumSubvoxels,
@@ -124,6 +161,8 @@ namespace world_assembly_detail
{ {
using FSharedHeightfieldColumnCache = using FSharedHeightfieldColumnCache =
VoxelForgeCoreShared::TSharedColumnValueCache<HeightfieldPointSample,131072,4>; VoxelForgeCoreShared::TSharedColumnValueCache<HeightfieldPointSample,131072,4>;
using FSharedDensitySampleCache = VoxelForgeCoreShared::TSharedColumnValueCache<
float,32768,4,VoxelForgeCoreShared::FDensitySampleCacheKey>;
// Preserve recipe order without allocating or looking up metadata in the point loop. // Preserve recipe order without allocating or looking up metadata in the point loop.
// Garder l'ordre de recette sans allocation ni recherche de metadonnees par point. // Garder l'ordre de recette sans allocation ni recherche de metadonnees par point.
@@ -2570,6 +2609,7 @@ WorldAssemblyFeatureTileListV1 Heightfield2D::buildFeatureTileList(
bounds.push_back(bound); bounds.push_back(bound);
} }
result.featureIndices=BuildWorldAssemblyFeatureTileListV1(bounds,minimum,maximum); result.featureIndices=BuildWorldAssemblyFeatureTileListV1(bounds,minimum,maximum);
FinalizeWorldAssemblyFeatureTileListDensityContextV1(result);
return result; return result;
} }
@@ -2634,6 +2674,8 @@ void Heightfield2D::resetThreadColumnCacheStats(bool captureCoordinates, bool cl
Stats.columnConflictMisses=0; Stats.columnConflictMisses=0;
Stats.sharedColumnHits=0; Stats.sharedColumnHits=0;
Stats.columnEvaluations=0; Stats.columnEvaluations=0;
Stats.densitySampleCacheHits=0;
Stats.densitySampleCacheComputes=0;
Stats.columnComputeMilliseconds=0.0; Stats.columnComputeMilliseconds=0.0;
Stats.captureCoordinates=captureCoordinates; Stats.captureCoordinates=captureCoordinates;
Stats.coordinateCaptureComplete=true; Stats.coordinateCaptureComplete=true;
@@ -1,259 +0,0 @@
#!/usr/bin/env python3
"""Run L54's triangle audit over L59's open-shaft lattice and controls."""
from __future__ import annotations
import argparse
import importlib.util
import json
import math
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
AUDIT54 = ROOT / "Standalone/Research/WorldAssemblerM3/Linux54/AuditWholeSurfaceL54.py"
spec = importlib.util.spec_from_file_location("whole_surface_l54", AUDIT54)
audit54 = importlib.util.module_from_spec(spec)
assert spec and spec.loader
spec.loader.exec_module(audit54)
original_make_route = audit54.make_route
def build_segment_bvh(segments: list[tuple]) -> dict:
def build(indices: list[int]) -> dict:
minimum = tuple(min(min(segments[index][0][axis], segments[index][1][axis])
for index in indices) for axis in range(3))
maximum = tuple(max(max(segments[index][0][axis], segments[index][1][axis])
for index in indices) for axis in range(3))
if len(indices) <= 8:
return {"minimum": minimum, "maximum": maximum,
"segments": tuple(sorted(indices)), "left": None, "right": None}
axis = max(range(3), key=lambda value: maximum[value] - minimum[value])
indices.sort(key=lambda index: (segments[index][0][axis]
+ segments[index][1][axis]) * 0.5)
middle = len(indices) // 2
return {"minimum": minimum, "maximum": maximum, "segments": None,
"left": build(indices[:middle]), "right": build(indices[middle:])}
if not segments:
raise ValueError("lattice audit route has no measurable segments")
return build(list(range(len(segments))))
def make_lattice_route(layout: dict, phase_m: float = 0.0) -> dict:
edges = sorted((edge for edge in layout.get("cavern_edges", [])
if edge["stable_id"] in ("lattice/spine/00-arrival-portal-v1",
"lattice/spine/01-helix-ramp-v1", "lattice/spine/99-departure-portal-v1")
or edge["stable_id"].startswith("lattice/spine/maze-edge-")),
key=lambda edge: (0 if edge["stable_id"].startswith("lattice/spine/00-") else
1 if edge["stable_id"].startswith("lattice/spine/01-") else
2 if edge["stable_id"].startswith("lattice/spine/maze-edge-") else 3,
edge["stable_id"]))
if not edges:
raise ValueError("lattice audit needs its resolved mandatory spine edges")
segments = []
routes = []
for edge_index, edge in enumerate(edges):
points = [[value / 1_000_000.0 for value in row]
for row in edge.get("polyline_um", [])]
if len(points) < 2:
raise ValueError(f"{edge['stable_id']}: explicit path is missing")
radius = edge["bore_radius_um"] / 1_000_000.0
height = edge["bore_height_um"] / 1_000_000.0
support_depth = edge.get("floor_support_depth_um", 0) / 1_000_000.0
horizontal_s = [0.0]
for a, b in zip(points, points[1:]):
horizontal_s.append(horizontal_s[-1] + ((b[0] - a[0]) ** 2
+ (b[1] - a[1]) ** 2) ** 0.5)
first = points[1]
first_dx, first_dy = first[0] - points[0][0], first[1] - points[0][1]
first_length = max(1.0e-9, (first_dx * first_dx + first_dy * first_dy) ** 0.5)
extension = (points[0][0] - first_dx / first_length * audit54.OPEN_GROUND_EXTENSION_M,
points[0][1] - first_dy / first_length * audit54.OPEN_GROUND_EXTENSION_M,
points[0][2] - (first[2] - points[0][2]) / first_length
* audit54.OPEN_GROUND_EXTENSION_M)
route_points = [extension, *points]
route_s = [-audit54.OPEN_GROUND_EXTENSION_M, *horizontal_s]
for index, (a, b) in enumerate(zip(route_points, route_points[1:])):
segments.append((a, b, route_s[index], route_s[index + 1],
radius, height, edge_index))
routes.append({"edge": edge, "points": points, "radius": radius,
"height": height, "support_depth": support_depth,
"length": horizontal_s[-1], "index": edge_index})
# The shaft core is an open cylindrical volume, not a walkable room. Its
# axis-aligned bounds include unrelated rock outside the cylinder and
# above the ramp headroom; the helix corridor already includes the inner
# wall, lip, foot, and underside where those surfaces meet the route.
rooms = [{"room": room,
"center": tuple(value / 1_000_000.0 for value in room["center_um"]),
"half": tuple(value / 1_000_000.0 for value in room["half_extents_um"]),
"floor": room["floor_um"] / 1_000_000.0}
for room in sorted(layout.get("cavern_rooms", []), key=lambda row: row["stable_id"])
if room.get("stable_id") != "lattice/spine/shaft-core-v2"]
return {"segments": segments, "edges": routes, "rooms": rooms, "phase_m": phase_m,
"segment_bvh": build_segment_bvh(segments)}
def make_route(layout: dict, phase_m: float = 0.0) -> dict:
if any(edge.get("stable_id", "").startswith("lattice/")
for edge in layout.get("cavern_edges", [])):
return make_lattice_route(layout, phase_m)
return original_make_route(layout, phase_m)
def lattice_route_nearest(point: tuple[float, float, float], route: dict):
"""Resolve stacked helix turns in 3D, then measure corridor radius in XY."""
best = None
def consider(segment_index: int) -> None:
nonlocal best
a, b, s0, s1, radius, height, edge_index = route["segments"][segment_index]
dx, dy, dz = (b[axis] - a[axis] for axis in range(3))
length_squared = dx * dx + dy * dy + dz * dz
t = 0.0 if length_squared <= 1.0e-12 else max(0.0, min(1.0,
((point[0] - a[0]) * dx + (point[1] - a[1]) * dy
+ (point[2] - a[2]) * dz) / length_squared))
x, y, z = (a[axis] + (b[axis] - a[axis]) * t for axis in range(3))
distance3d_squared = ((point[0] - x) ** 2 + (point[1] - y) ** 2
+ (point[2] - z) ** 2)
if (best is None or distance3d_squared < best[0]
or (distance3d_squared == best[0] and segment_index < best[1])):
horizontal_length = math.hypot(dx, dy)
lateral = ((dx * (point[1] - y) - dy * (point[0] - x))
/ horizontal_length if horizontal_length > 1.0e-9 else 0.0)
best = (distance3d_squared, segment_index,
math.hypot(point[0] - x, point[1] - y),
s0 + (s1 - s0) * t, z, lateral, radius, height, edge_index,
route["edges"][edge_index]["length"])
def lower_bound_squared(node: dict) -> float:
distance_squared = 0.0
for axis in range(3):
gap = (node["minimum"][axis] - point[axis]
if point[axis] < node["minimum"][axis]
else point[axis] - node["maximum"][axis]
if point[axis] > node["maximum"][axis] else 0.0)
distance_squared += gap * gap
return distance_squared
def visit(node: dict) -> None:
if best is not None and lower_bound_squared(node) > best[0]:
return
if node["segments"] is not None:
for segment_index in node["segments"]:
consider(segment_index)
return
left, right = node["left"], node["right"]
left_bound, right_bound = lower_bound_squared(left), lower_bound_squared(right)
if left_bound <= right_bound:
visit(left)
visit(right)
else:
visit(right)
visit(left)
visit(route["segment_bvh"])
if best is None:
raise ValueError("lattice audit route has no measurable segments")
return best[2:]
def mark_lattice_region(triangles: list, route: dict) -> None:
margin = audit54.CORRIDOR_MARGIN_M
extension = audit54.OPEN_GROUND_EXTENSION_M
bounds = []
for edge in route["edges"]:
points = edge["points"]
radius, height = edge["radius"], edge["height"]
support_depth = edge["support_depth"]
bounds.append((
min(point[0] for point in points) - radius - margin,
max(point[0] for point in points) + radius + margin,
min(point[1] for point in points) - radius - margin,
max(point[1] for point in points) + radius + margin,
min(point[2] for point in points) - support_depth - margin,
max(point[2] for point in points) + height + margin))
for room in route["rooms"]:
center, half, floor = room["center"], room["half"], room["floor"]
bounds.append((center[0] - half[0] - margin, center[0] + half[0] + margin,
center[1] - half[1] - margin, center[1] + half[1] + margin,
floor - margin, floor + 2.0 * half[2] + margin))
for triangle in triangles:
x, y, z = triangle.center
if not any(low_x <= x <= high_x and low_y <= y <= high_y
and low_z <= z <= high_z
for low_x, high_x, low_y, high_y, low_z, high_z in bounds):
triangle.in_region = False
triangle.reference = False
continue
nearest = lattice_route_nearest(triangle.center, route)
distance, distance_s, floor_z, lateral, radius, height, edge_index, edge_length = nearest
support_depth = route["edges"][edge_index]["support_depth"]
corridor = (-extension <= distance_s <= edge_length
and distance <= radius + margin
and floor_z - support_depth - margin <= z <= floor_z + height + margin)
room_box = any(abs(x - room["center"][0]) <= room["half"][0] + margin
and abs(y - room["center"][1]) <= room["half"][1] + margin
and room["floor"] - margin <= z
<= room["floor"] + 2.0 * room["half"][2] + margin
for room in route["rooms"])
triangle.in_region = corridor or room_box
inside_room = any(abs(x - room["center"][0]) <= room["half"][0] + 0.45
and abs(y - room["center"][1]) <= room["half"][1] + 0.45
for room in route["rooms"])
outside_bore = distance > radius + 0.45 and not inside_room
triangle.reference = (triangle.in_region and outside_bore
and triangle.smooth[2] >= 0.45)
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--case", nargs=5, action="append", required=True,
metavar=("LABEL", "MESH", "LAYOUT_OR_DASH", "EXPECT", "FOCUS"))
parser.add_argument("--json", type=Path, required=True)
parser.add_argument("--svg-dir", type=Path)
parser.add_argument("--phase", type=float, choices=(-0.5, 0.0, 0.5), default=0.0)
args = parser.parse_args()
audit54.make_route = make_route
results = []
for label, mesh, layout, expect, focus in args.case:
if expect not in ("pass", "fail", "measure") or focus not in ("whole", "floor"):
parser.error("EXPECT must be pass/fail/measure and FOCUS must be whole/floor")
native_lattice = layout != "-" and any(
edge.get("stable_id", "").startswith("lattice/")
for edge in json.loads(Path(layout).read_text(encoding="utf-8")).get(
"cavern_edges", []))
original_margin = audit54.CORRIDOR_MARGIN_M
original_extension = audit54.OPEN_GROUND_EXTENSION_M
original_nearest = audit54.route_nearest
original_mark_region = audit54.mark_region
if native_lattice:
# Keep the whole-surface test on native records: one core voxel of
# tolerance captures their meshed walls while excluding the separate
# legacy transition above the resolved mouth.
audit54.CORRIDOR_MARGIN_M = 0.25
audit54.OPEN_GROUND_EXTENSION_M = 0.0
audit54.route_nearest = lattice_route_nearest
audit54.mark_region = mark_lattice_region
try:
results.append(audit54.audit_case(label, Path(mesh), Path(layout), expect,
focus, args.svg_dir, args.phase))
finally:
audit54.CORRIDOR_MARGIN_M = original_margin
audit54.OPEN_GROUND_EXTENSION_M = original_extension
audit54.route_nearest = original_nearest
audit54.mark_region = original_mark_region
if native_lattice:
print("[L59LatticeAuditScope] margin_m=0.250 ramp_underside_depth_m=1.000 "
"outside_mouth_extension_m=0.000 nearest_centerline=3D "
"radius_measure=horizontal status=PASS")
args.json.parent.mkdir(parents=True, exist_ok=True)
args.json.write_text(json.dumps({"audit": "whole-surface-open-shaft-l59-v1",
"cases": results}, indent=2) + "\n", encoding="utf-8")
failures = [result for result in results if result["status"] != "PASS"]
print(f"[L59WholeSurfaceAuditSummary] cases={len(results)} failed={len(failures)} "
f"json={args.json} status={'FAIL' if failures else 'PASS'}")
return 1 if failures else 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -1,83 +0,0 @@
#!/usr/bin/env python3
"""Split the owner surface strate around the L59 native lattice fixture."""
from __future__ import annotations
import argparse
import hashlib
import json
from pathlib import Path
def sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as stream:
for block in iter(lambda: stream.read(1024 * 1024), b""):
digest.update(block)
return digest.hexdigest()
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--owner", type=Path, required=True)
parser.add_argument("--out", type=Path, required=True)
args = parser.parse_args()
root = json.loads(args.owner.read_text(encoding="utf-8"))
strates = root.get("strates")
if not isinstance(strates, list):
raise ValueError("owner input has no strates array")
by_index = {int(row.get("index", -1)): row for row in strates if isinstance(row, dict)}
if set(by_index) != {0, 1} or len(strates) != 2:
raise ValueError("fixture requires the reviewed two-strate owner input")
upper, surface = by_index[0], by_index[1]
if (int(upper.get("asset", {}).get("GeneratorType", -1)) != 0
or int(surface.get("asset", {}).get("GeneratorType", -1)) != 4):
raise ValueError("fixture requires TunnelNetwork over SurfaceWorld")
if int(upper.get("passage_config", {}).get("Connections", -1)) != 1:
raise ValueError("owner arrival passage count differs from the reviewed input")
# Keep both resolved mouths in the small synthetic Maze layer. The authored
# owner reach ranges are intentionally broad and can place a synthetic mouth
# hundreds of metres beyond a four-chunk prototype layer.
upper["passage_config"].update({"ReachMin": 32, "ReachMax": 32})
upper["asset"]["PassageConfig"].update({"ReachMin": 32, "ReachMax": 32})
maze = json.loads(json.dumps(surface))
maze.update({"index": 1, "top_chunk_z": -16, "bottom_chunk_z": -19,
"height_chunks": 4})
maze["asset"].update({"StrateName": "L59 Native Lattice Maze",
"GeneratorType": 3, "StrateHeightInChunks": 4})
maze["asset"].setdefault("MazeParams", {})["BoundarySealThickness"] = 8
maze["passage_config"]["Connections"] = 1
maze["passage_config"].update({"ReachMin": 32, "ReachMax": 32})
maze["asset"]["PassageConfig"].update({"Connections": 1, "ReachMin": 32, "ReachMax": 32})
lower = json.loads(json.dumps(surface))
lower.update({"index": 2, "top_chunk_z": -20, "bottom_chunk_z": -23,
"height_chunks": 4})
lower["asset"].update({"StrateName": "L59 Lower Surface", "GeneratorType": 4,
"StrateHeightInChunks": 4})
lower["passage_config"]["Connections"] = 1
lower["asset"]["PassageConfig"]["Connections"] = 1
root["total_strates"] = 3
root["strates"] = [upper, maze, lower]
# Keep the legacy passage portals proportional to this compact native
# prototype; the owner world remains at its authored scale.
root.update({"owner_voxel_size_cm": 6.25,
"owner_voxel_size_microns": 62500,
"core_voxel_size_cm": 25,
"core_voxel_size_microns": 250000,
"owner_to_core_length_scale": 0.25})
args.out.parent.mkdir(parents=True, exist_ok=True)
args.out.write_text(json.dumps(root, indent=2, sort_keys=True) + "\n",
encoding="utf-8")
print(f"[LatticeFixtureBuild] source={args.owner} source_sha256={sha256(args.owner)} "
f"fixture={args.out} fixture_sha256={sha256(args.out)} "
"strates=0:TunnelNetwork,1:Maze[4 chunks],2:SurfaceWorld[60 chunks] "
"owner_input_untouched=true status=PASS")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -1,179 +0,0 @@
#!/usr/bin/env python3
"""Export the L59 open-shaft lattice and render four reviewed viewpoints."""
from __future__ import annotations
import argparse
import hashlib
import importlib.util
import json
import math
import os
from pathlib import Path
import subprocess
import sys
ROOT = Path(__file__).resolve().parents[4]
CAPTURE54 = ROOT / "Standalone/Research/WorldAssemblerM3/Linux54/CaptureNativeCavesL54.py"
spec = importlib.util.spec_from_file_location("capture_native_caves_l54", CAPTURE54)
capture54 = importlib.util.module_from_spec(spec)
assert spec and spec.loader
spec.loader.exec_module(capture54)
run_step = capture54.run_step
camera_angles = capture54.camera_angles
filter_mesh_y_leq = capture54.filter_mesh_y_leq
fmt = capture54.fmt
def sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as stream:
for block in iter(lambda: stream.read(1024 * 1024), b""):
digest.update(block)
return digest.hexdigest()
def midpoint(points: list[list[float]]) -> tuple[float, float, float]:
return tuple(sum(point[axis] for point in points) / len(points) for axis in range(3))
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--out", type=Path, required=True)
parser.add_argument("--fixture", type=Path, required=True)
parser.add_argument("--layout", type=Path, required=True)
parser.add_argument("--headless", type=Path, required=True)
parser.add_argument("--renderer", type=Path, required=True)
parser.add_argument("--artifact-dir", type=Path, required=True)
args = parser.parse_args()
out = args.out.resolve()
artifact_dir = args.artifact_dir.resolve()
capture_dir = out / "lattice-capture-png"
mesh_dir = out / "lattice-meshes"
for directory in (capture_dir, mesh_dir, artifact_dir):
directory.mkdir(parents=True, exist_ok=True)
layout = json.loads(args.layout.read_text(encoding="utf-8"))
edges = sorted(layout.get("cavern_edges", []), key=lambda row: row["stable_id"])
helix = next((edge for edge in edges
if edge["stable_id"] == "lattice/spine/01-helix-ramp-v1"), None)
shaft_core = next((room for room in layout.get("cavern_rooms", [])
if room["stable_id"] == "lattice/spine/shaft-core-v2"), None)
maze_edges = [edge for edge in edges
if edge["stable_id"].startswith("lattice/spine/maze-edge-")]
if helix is None or shaft_core is None or len(maze_edges) < 4:
raise ValueError("layout is missing the resolved open shaft, helix, or maze spine")
helix_points = [[value / 1_000_000.0 for value in point]
for point in helix["polyline_um"]]
core_center = [value / 1_000_000.0 for value in shaft_core["center_um"]]
shaft_center = (core_center[0], core_center[1], core_center[2])
top = helix_points[0]
bottom = helix_points[-1]
whole_mesh = mesh_dir / "lattice-whole-view.vfmesh"
export_layout = out / "layouts" / "lattice-capture-layout.json"
env = os.environ.copy()
env["LP_NUM_THREADS"] = "12"
env["XDG_CACHE_HOME"] = str(out / "xdg-cache")
export_camera = (top[0], top[1], top[2] + 1.5, 0.0, -8.0)
run_step("lattice-whole-view-export", [args.headless, args.fixture,
"--workers=12", "--world-assembler-prototype", "--stream-benchmark",
"--stream-pipeline", "--force-generate", "--clip-radius=4",
"--max-clip-level=0", "--max-level=0", "--render-distance-chunks=0",
"--recipe=lattice", "--lattice-strate=1",
f"--camera={fmt(export_camera)}", f"--resolved-layout-output={export_layout}",
f"--mesh-export={whole_mesh}"], out / "capture-lattice-export.log",
env=env, stamped=True)
if not whole_mesh.is_file() or whole_mesh.stat().st_size == 0:
raise RuntimeError("whole-lattice export produced no mesh")
cutaway_mesh = mesh_dir / "lattice-cutaway-y-center.vfmesh"
cutaway_triangles = filter_mesh_y_leq(whole_mesh, cutaway_mesh, shaft_center[1])
print(f"[L59LatticeCutaway] plane_y_m={shaft_center[1]:.3f} "
f"retained_triangles={cutaway_triangles} mesh={cutaway_mesh}", flush=True)
# Start inside the open shaft, offset toward the first tread so the view
# follows the well down while keeping the helical ledges in frame.
start_dx = top[0] - shaft_center[0]
start_dy = top[1] - shaft_center[1]
start_radius = max(1.0e-9, math.hypot(start_dx, start_dy))
top_camera = (shaft_center[0] + start_dx / start_radius * 3.5,
shaft_center[1] + start_dy / start_radius * 3.5,
top[2] + 1.6)
top_target = (shaft_center[0], shaft_center[1], bottom[2] + 1.3)
eye_index = len(helix_points) // 2
eye_floor = helix_points[eye_index]
eye_dx = eye_floor[0] - shaft_center[0]
eye_dy = eye_floor[1] - shaft_center[1]
eye_radius = max(1.0e-9, math.hypot(eye_dx, eye_dy))
# Put the eye on the walk line, looking across the open core at the far
# wall and the ledges above and below.
eye_camera = (eye_floor[0], eye_floor[1], eye_floor[2] + 1.35)
eye_target = (shaft_center[0], shaft_center[1], eye_camera[2] + 0.35)
maze_edge = next(edge for edge in maze_edges
if edge["stable_id"] == "lattice/spine/maze-edge-02-v1")
maze_points = [[value / 1_000_000.0 for value in point]
for point in maze_edge["polyline_um"]]
maze_a, maze_b = maze_points[0], maze_points[-1]
maze_t = 0.5
maze_floor = tuple(maze_a[axis] + (maze_b[axis] - maze_a[axis]) * maze_t
for axis in range(3))
maze_horizontal = max(1.0e-9, math.hypot(maze_b[0] - maze_a[0],
maze_b[1] - maze_a[1]))
maze_camera = (maze_floor[0] - (maze_b[1] - maze_a[1]) / maze_horizontal * 0.2,
maze_floor[1] + (maze_b[0] - maze_a[0]) / maze_horizontal * 0.2,
maze_floor[2] + 1.4)
maze_target = (maze_camera[0] + (maze_b[0] - maze_a[0]) / maze_horizontal * 8.0,
maze_camera[1] + (maze_b[1] - maze_a[1]) / maze_horizontal * 8.0,
maze_camera[2] + (maze_b[2] - maze_a[2]) / maze_horizontal * 8.0)
overview_camera = (shaft_center[0], shaft_center[1] + 12.0, top[2] + 4.0)
overview_target = (shaft_center[0], shaft_center[1],
(top[2] + bottom[2]) * 0.5 - 2.0)
views = (
("shaft-top-down", whole_mesh, top_camera, top_target, 22.0, 78.0, None),
("ramp-eye-across", whole_mesh, eye_camera, eye_target, 15.0, 100.0, None),
("maze-corridor", whole_mesh, maze_camera, maze_target, 8.0, 45.0, None),
("shaft-overview", cutaway_mesh, overview_camera, overview_target, 40.0, 80.0, None),
)
quality = ROOT / "Standalone/Tools/verify/capture_quality.py"
for name, render_mesh, camera, target, focus_radius, fov, clip_plane in views:
yaw, pitch = camera_angles(camera, target)
if name == "ramp-eye-across":
yaw, pitch = camera_angles(camera, target)
png = capture_dir / f"lattice-{name}.png"
image = artifact_dir / f"lattice-{name}.jpg"
height_tint = "off" if name == "maze-corridor" else "on"
render_args = [args.renderer, render_mesh, png,
"--size=1280x720", f"--fov-deg={fov:.1f}",
f"--camera-m={fmt(camera)}", f"--yaw-deg={yaw:.6f}",
f"--pitch-deg={pitch:.6f}", "--tile-skirts=off", "--shade=readable",
"--headlamp=on", f"--height-tint={height_tint}",
f"--height-center-m={(top[2] + bottom[2]) * 0.5:.6f}",
"--height-range-m=24", "--tile-edges=off",
f"--focus-region={fmt((*target, focus_radius))}"]
if clip_plane is not None:
render_args.append(f"--clip-plane={fmt(clip_plane)}")
run_step(f"{name}-render", render_args,
out / f"capture-{name}-render.log", env=env, stamped=True)
run_step(f"{name}-jpeg", ["ffmpeg", "-nostdin", "-hide_banner", "-loglevel",
"error", "-y", "-i", png, "-frames:v", "1", "-q:v", "5", image],
out / f"capture-{name}-jpeg.log", env=env)
quality_output = run_step(f"{name}-quality", [sys.executable, quality, image],
out / f"capture-{name}-quality.log", env=env)
quality_lines = [line for line in quality_output.splitlines()
if line.startswith("[CaptureQuality]")]
if not quality_lines or not quality_lines[-1].endswith("status=PASS"):
raise RuntimeError(f"capture quality gate failed for {name}")
size = image.stat().st_size
if size > 300_000:
raise RuntimeError(f"capture exceeds 300 KB: {image} ({size} bytes)")
print(f"[L59LatticeCapture] view={name} file={image} bytes={size} "
f"sha256={sha256(image)} status=PASS", flush=True)
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except Exception as error:
print(f"[L59LatticeCapture] status=FAIL error={error}", file=sys.stderr, flush=True)
raise SystemExit(1)
@@ -1,49 +0,0 @@
# Linux59 report
## Geometry and route
Resolved shaft: 21.0 m outer envelope; 4.5 m core radius; four-turn ramp at 6.0 m centerline radius, 3.0 m clear width, and 5.0 m pitch. The ramp spans radii 4.5–7.5 m and has 1.0 m floor support. Its inner edge is open to the core. The route keeps a 1.5 m margin from that edge (1.16 m capsule drop clearance). No raised lip was added.
Maximum route grade is 10.028° at the inner edge; minimum measured headroom is 2.20 m. Arrival-to-departure reachability passes all 883 stations, 44,150 capsule samples, and 4,415 support/headroom samples. The orphan-shaft control is disconnected as expected.
## Audits and determinism
The final-field interval oracle checked 2,058 samples in six boxes with zero misses. It proves shaft-core air, returns a mixed result at the core wall, rejects false all-air and all-solid proofs, and blocks the occupied maze tile.
The whole-surface audit passes with zero flagged triangles and zero clusters. Its scope includes a 0.25 m ramp-edge margin and the 1.0 m underside. The L45 ramp, L46 entrance, and L47 wall-foot controls fail as expected; the plain-native control passes. GCC and Clang camera peers at the shaft center `(11.790, 4.090, -130.630)` match across W1/W8/W12.
## Speed
| Measurement | L58 instrumented | L59 final |
|---|---:|---:|
| First tile | 0.222 s | 0.210 s |
| Near ring | 0.364 s | 0.328 s |
| Whole view | 1.625 s (1.69 s cited) | 1.605 s |
| Moving p50 / p95 | 90.567 / 165.304 ms | 479.544 / 963.764 ms |
The roughly 1 s whole-view target was not reached; the measured whole view improved only slightly, and moving latency regressed. L59 tile-drain instrumentation recorded 972 stage rows and 28,507 ms summed worker time: 23,332 ms in marching cubes and 717 ms in column work. Marching cubes dominates the measured worker time.
## Hashes, tiers, and captures
Source stamp: `389ac77f5d35328a68e4639cac4f7d03ad5ab13b72d4832e89459437cf960ca4`
Resolved layout: `resolved-layout-v3-fnv64:ab2f330ada023393`
Headless SHA-256: Clang `9fc1ac6c4c5a18b6bbdc0d907d26fcaaa63a49c163a68365f3cc79726db1c474`; GCC `47b37a28a7f132af2a3f91cdd61693fb8ac519e2f23f4064b48bec0b7f8ad964`.
Fast passed in 174.7 s; medium passed in 1,140.1 s; full passed in 1,206.4 s with its own 7,200 s timeout. Trial count: 15/15. Earlier failed trials exposed ramp-surface spikes and an orphan-control interval false proof; the final surface and interval controls pass.
All captures are JPEG and under 300 KB:
- [Shaft top-down](captures/lattice-shaft-top-down.jpg) — 31,500 B — SHA-256 `83e38fce238ddbde84fe3ee13867f5722eac30fe177c44e746eb06b7ae341783`
- [Ramp eye across shaft](captures/lattice-ramp-eye-across.jpg) — 24,409 B — SHA-256 `985a16fbd45c986c5bcdd0974e1cc32aac82a06a84b2df98a7b6a358f930cf14`
- [Maze corridor](captures/lattice-maze-corridor.jpg) — 15,746 B — SHA-256 `fe72cdd5a3907f8b9071784b08889719593d8839f69a863aa45dc9ac547e771d`
- [Shaft overview](captures/lattice-shaft-overview.jpg) — 39,081 B — SHA-256 `6394f0656200f91e77a23aed9c135e2e913dd0053c301921dff24336713c52d2`
## Orchestrator review (Claude)
**Parked on `wip/l59-open-shaft`, not merged.** The top-down capture finally reads as an open shaft with a ledge
spiralling down the wall, so the concept is right. But the ramp's **inner (open) edge is severely jagged:** shard-like
torn scallops along the whole edge (`lattice-ramp-eye-across.jpg`, and the top-down rim). The whole-surface audit
passed only because its ROI stops 0.25 m in from the ramp edge: the blind-spot pattern again. Speed moved only
1.69 → 1.61 s, at 15 of 15 trials. `assembler/world` keeps L58. Likely cause, to verify: a 1 m-thick ledge with a free
edge is a thin feature meshed at 25 cm. The edge needs a bullnose/fillet radius of at least 2 voxels and a thickness
the sampler resolves, and the audit ROI must include the edge itself.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

@@ -0,0 +1,219 @@
#!/usr/bin/env python3
"""Render cave, motif, and lattice features from meshes streamed at LOD4-8."""
from __future__ import annotations
import argparse
import hashlib
import importlib.util
import json
import math
import os
from pathlib import Path
import re
import struct
import sys
ROOT = Path(__file__).resolve().parents[4]
CAPTURE54 = ROOT / "Standalone/Research/WorldAssemblerM3/Linux54/CaptureNativeCavesL54.py"
spec = importlib.util.spec_from_file_location("capture_native_caves_l54", CAPTURE54)
capture54 = importlib.util.module_from_spec(spec)
assert spec and spec.loader
spec.loader.exec_module(capture54)
run_step = capture54.run_step
camera_angles = capture54.camera_angles
fmt = capture54.fmt
QUALITY = ROOT / "Standalone/Tools/verify/capture_quality.py"
Q = 1_000_000.0
def sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as stream:
for block in iter(lambda: stream.read(1024 * 1024), b""):
digest.update(block)
return digest.hexdigest()
def midpoint(points: list[tuple[float, float, float]]) -> tuple[float, float, float]:
return tuple(sum(point[axis] for point in points) / len(points) for axis in range(3))
def read_feature(name: str, layout_path: Path):
layout = json.loads(layout_path.read_text(encoding="utf-8"))
if name == "caves":
edges = sorted(layout.get("cavern_edges", []), key=lambda row: row["stable_id"])
if len(edges) != 2:
raise ValueError("cave layout must have two resolved spine edges")
points = [[value / Q for value in point]
for point in edges[0]["control_points_um"]]
target = tuple(points[-1][axis] for axis in range(3))
return target, target[2] + 2.0, 28.0, "cave mouth"
if name == "motifs":
monolith = next(row for row in layout.get("stamps", []) if row.get("kind") == 5)
target = tuple(value / Q for value in monolith["position_um"])
floor = monolith["floor_um"] / Q
return (target[0], target[1], floor + 12.0), floor + 12.0, 32.0, "monolith"
edges = layout.get("cavern_edges", [])
helix = next((edge for edge in edges
if edge.get("stable_id") == "lattice/spine/01-helix-ramp-v1"), None)
if helix is None:
raise ValueError("lattice layout has no resolved helix")
points = [tuple(value / Q for value in point) for point in helix["polyline_um"]]
target = midpoint(points)
return target, target[2] + 4.0, 36.0, "helix"
def audit_roi_lods(mesh: Path, center: tuple[float, float, float], radius: float):
roi_counts: dict[int, int] = {}
roi_skirts: dict[int, int] = {}
with mesh.open("rb") as stream:
prefix = stream.read(60)
if len(prefix) != 60 or prefix[:8] != b"VFMSH001":
raise ValueError("far-LOD audit requires a VFMSH001 Core mesh")
version, voxel_um, tile_count = struct.unpack_from("<3I", prefix, 8)
if version != 1 or voxel_um <= 0:
raise ValueError("mesh version or voxel size is invalid")
for _ in range(tile_count):
header = stream.read(36)
if len(header) != 36:
raise ValueError("truncated far-LOD tile header")
key = struct.unpack_from("<4i", header, 0)
surface_count, skirt_count, vertex_count, index_count, mask_count = \
struct.unpack_from("<5I", header, 16)
positions = stream.read(vertex_count * 12)
normals = stream.read(vertex_count * 12)
index_bytes = stream.read(index_count * 4)
masks = stream.read(mask_count)
if (len(positions) != vertex_count * 12
or len(normals) != vertex_count * 12
or len(index_bytes) != index_count * 4
or len(masks) != mask_count):
raise ValueError("truncated far-LOD tile payload")
lod = key[3]
coords = list(struct.iter_unpack("<3f", positions))
indices = struct.unpack(f"<{index_count}I", index_bytes) if index_count else ()
for triangle in range(index_count // 3):
ids = indices[triangle * 3:triangle * 3 + 3]
if max(ids) >= vertex_count:
raise ValueError("far-LOD triangle references an invalid vertex")
is_skirt = bool(masks and triangle < len(masks) and masks[triangle])
if is_skirt:
continue
centroid = tuple(sum(coords[index][axis] for index in ids) / 3.0
for axis in range(3))
distance_squared = sum((centroid[axis] - center[axis]) ** 2
for axis in range(3))
if distance_squared <= radius * radius:
roi_counts[lod] = roi_counts.get(lod, 0) + 1
if skirt_count:
roi_skirts[lod] = roi_skirts.get(lod, 0) + skirt_count
if stream.read(1):
raise ValueError("far-LOD mesh has trailing bytes")
return roi_counts, roi_skirts
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--out", type=Path, required=True)
parser.add_argument("--fixture-dir", type=Path, required=True)
parser.add_argument("--layout-dir", type=Path, required=True)
parser.add_argument("--headless", type=Path, required=True)
parser.add_argument("--renderer", type=Path, required=True)
parser.add_argument("--artifact-dir", type=Path, required=True)
args = parser.parse_args()
out = args.out.resolve()
fixture_dir, layout_dir = args.fixture_dir.resolve(), args.layout_dir.resolve()
artifact_dir = args.artifact_dir.resolve()
mesh_dir, png_dir = out / "far-lod-meshes", out / "far-lod-png"
for directory in (mesh_dir, png_dir, artifact_dir):
directory.mkdir(parents=True, exist_ok=True)
env = os.environ.copy()
env["LP_NUM_THREADS"] = "12"
env["XDG_CACHE_HOME"] = str(out / "xdg-cache")
cases = (
("caves", fixture_dir / "caves.json",
layout_dir / "native-caves-field-clang.json", "caves", []),
("motifs", fixture_dir / "style-override-motifs-slice1.json",
layout_dir / "motif-slice1-capsule-reachability-clang.json", "surface-world", []),
("lattice", fixture_dir / "lattice-l58.json",
layout_dir / "native-lattice-field-clang.json", "lattice", ["--lattice-strate=1"]),
)
quality_rows = []
for name, fixture, layout, recipe, extra_flags in cases:
if not fixture.is_file() or not layout.is_file():
raise RuntimeError(f"missing {name} fixture or layout")
target, target_height, radius, label = read_feature(name, layout)
stream_camera = (target[0] + 1024.0, target[1], target[2], 0.0, 0.0)
stream_yaw, stream_pitch = camera_angles(stream_camera[:3], target)
stream_camera = (*stream_camera[:3], stream_yaw, stream_pitch)
mesh = mesh_dir / f"{name}-lod4-8.vfmesh"
mesh_layout = layout_dir / f"{name}-far-lod-layout.json"
command = [args.headless, fixture, "--workers=12", "--world-assembler-prototype",
"--stream-benchmark", "--stream-pipeline", "--force-generate",
"--clip-radius=3", "--max-clip-level=8", "--max-level=-1",
"--render-distance-chunks=0", f"--recipe={recipe}",
f"--camera={fmt(stream_camera)}", *extra_flags,
f"--resolved-layout-output={mesh_layout}", f"--mesh-export={mesh}"]
output = run_step(f"{name}-far-lod-export", command,
out / f"capture-{name}-far-lod-export.log", env=env, stamped=True)
worker_rows = {int(match.group(1)): (int(match.group(2)), int(match.group(3)))
for match in re.finditer(
r"^\[WorldAssemblerLODWorker\] lod=(\d+) tiles=(\d+) .*?skirt_triangles=(\d+)",
output, re.M)}
if (set(worker_rows) != set(range(9))
or any(worker_rows[level][0] <= 0 for level in range(9))
or sum(worker_rows[level][1] for level in range(4, 9)) <= 0):
raise RuntimeError(f"{name} far view lacks LOD0-8 tile/skirt work: {worker_rows}")
if not mesh.is_file() or mesh.stat().st_size == 0:
raise RuntimeError(f"{name} far LOD export produced no mesh")
roi, roi_skirts = audit_roi_lods(mesh, target, radius)
feature_lods = sorted(level for level, count in roi.items() if count > 0)
far_feature_lods = [level for level in feature_lods if level >= 4]
if not far_feature_lods:
raise RuntimeError(f"{name} {label} has no non-skirt surface triangles in LOD4-8 ROI: {roi}")
camera = (target[0] + 18.0, target[1] - 28.0, target_height + 10.0)
yaw, pitch = camera_angles(camera, target)
png = png_dir / f"{name}-far-lod.png"
image = artifact_dir / f"{name}-far-lod.jpg"
render = [args.renderer, mesh, png, "--size=1280x720", "--fov-deg=76",
f"--camera-m={fmt(camera)}", f"--yaw-deg={yaw:.6f}",
f"--pitch-deg={pitch:.6f}", "--tile-skirts=on", "--shade=readable",
"--headlamp=on", "--height-tint=on", f"--height-center-m={target_height:.6f}",
"--height-range-m=42", "--tile-edges=off",
f"--focus-region={fmt((*target, radius))}"]
run_step(f"{name}-far-lod-render", render,
out / f"capture-{name}-far-lod-render.log", env=env, stamped=True)
run_step(f"{name}-far-lod-jpeg", ["ffmpeg", "-nostdin", "-hide_banner",
"-loglevel", "error", "-y", "-i", png, "-frames:v", "1", "-q:v", "5", image],
out / f"capture-{name}-far-lod-jpeg.log", env=env)
quality_output = run_step(f"{name}-far-lod-quality", [sys.executable, QUALITY, image],
out / f"capture-{name}-far-lod-quality.log", env=env)
quality = [line for line in quality_output.splitlines()
if line.startswith("[CaptureQuality]")]
if not quality or not quality[-1].endswith("status=PASS"):
raise RuntimeError(f"{name} far LOD capture failed its image quality gate")
size = image.stat().st_size
if not 0 < size <= 300_000:
raise RuntimeError(f"{name} far LOD capture exceeds 300 KB: {size}")
print(f"[L60FarLODCapture] generator={name} feature={label} file={image} "
f"bytes={size} sha256={sha256(image)} feature_lods={feature_lods} "
f"feature_triangles={sum(roi.values())} far_skirt_triangles="
f"{sum(value for lod, value in roi_skirts.items() if lod >= 4)} status=PASS",
flush=True)
quality_rows.append({"generator": name, "feature": label, "file": str(image),
"bytes": size, "sha256": sha256(image), "feature_triangles_by_lod": roi,
"feature_skirts_by_lod": roi_skirts})
(out / "far-lod-captures.json").write_text(json.dumps(quality_rows, indent=2) + "\n",
encoding="utf-8")
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except Exception as error:
print(f"[L60FarLODCapture] status=FAIL error={error}", file=sys.stderr, flush=True)
raise SystemExit(1)
@@ -0,0 +1,77 @@
# Linux 60 report
## Result and root cause
The continuation is green. The full tier and speed matrix passed after fixing signed-overflow undefined behavior in the shared voxel hash.
The first differing density was LOD1 tile `(0,64,-512)`, step 2, sample order 23,658 at `(64,82,-476)`. Before the fix, Clang returned `-4.0078125` (`0xc0804000`) and GCC returned `-4.125` (`0xc0840000`). The sample follows the mesher's LOD X8/boundary scalar path into `DensityAtLattice` and the compiled analytic `TunnelNetwork` field. Its upstream cave passage placement comes from `UVoxelStrateManager::GeneratePassages` → `PassageRandom01` → `VoxelHash::Cell(i,c,seed)`.
`VoxelHash::Cell` added `0x7fffffff` to a signed cell coordinate before casting to `uint32`; connection `c=1` overflowed `int32`, so the compiler-dependent hash changed the passage coordinates and cave geometry. The trace showed passage pair `(0,1)` diverging, followed by all 21 control points of passage 1. The fix performs the bias in unsigned arithmetic in `Cell`, `Pair`, legacy `Cellular3D`, and the shared Core cellular hash. It does not quantize density. After the fix both compilers produced the same pair `(0,1)` coordinates, the same 44 passage points, and `0xc0804000` at the first sample. The evaluator version is now 211.
Before/after traces are in `Saved/Linux/l60/diag/caves-{clang,gcc}-probe.log` and `Saved/Linux/l60/diag/cache/fullcache-{clang,gcc}.log`.
## Far LOD support and seam checks
All full-view fixtures use max level `-1`, clip radius 3, and max clip level 8. All three generators streamed LOD0–8 with skirts.
- **Caves:** X8 and Z-run gates detect caves in manager and resolved stratum mappings, then use scalar lattice sampling for cave samples. Clang/GCC cave tile hashes match at W1/W8/W12, including random views at LOD1–2. The cave-mouth transition crossed levels 0–8 (432 entering tiles).
- **Motifs:** The fixed motif field streams through the existing rings, caps, and skirts. The monolith transition crossed levels 0–8 (295 entering tiles); whole-surface foot, cap, and platform-edge audits passed.
- **Lattice:** The fixture selects native lattice stratum 1 and uses the existing ring and skirt path. The helix transition crossed levels 0–8 (380 entering tiles); field, interval, reachability, and shape-free audits passed.
Hard, gradient, and interleaved stratum transitions passed with zero hole columns and zero air samples. Far-LOD cave, motif, and lattice JPEG captures passed their audits and are under 300 KB.
## Comparable speed matrix
`verify.py full --speed-matrix` ran five interleaved repetitions per generator, rotating generator order. Every run used camera `(-12,-118,-20 m)`, rotation `(0°,0°)`, clip radius 3, max clip level 8, max level `-1`, camera step 24 m, 12 workers, and shared column cache. The stage asserted the shared HeadlessConfig fields, whole-view and per-LOD tile-count ranges, and stable counts across repetitions. It passed for all eight generators. Every listed tile count occurred in all five runs; timings are medians from the final full run.
| Generator | Tiles | Whole view (s) | First tile (s) | Near ring (s) | Moving p50 (ms) | Moving p95 (ms) |
|---|---:|---:|---:|---:|---:|---:|
| Natural SW | 2,130 | 1.669 | 0.502 | 0.519 | 63.971 | 119.891 |
| periodicHills | 1,986 | 1.311 | 0.454 | 0.479 | 60.267 | 115.648 |
| mirroredRelief | 1,986 | 1.317 | 0.471 | 0.490 | 60.568 | 115.474 |
| flatField | 1,986 | 1.303 | 0.465 | 0.485 | 60.627 | 117.144 |
| Floating Islands | 2,130 | 4.926 | 0.817 | 0.977 | 528.939 | 871.290 |
| Caves | 2,130 | 1.408 | 0.489 | 0.507 | 61.099 | 118.508 |
| Motifs | 1,986 | 1.326 | 0.476 | 0.493 | 60.107 | 116.104 |
| Lattice | 2,130 | 1.701 | 0.539 | 0.561 | 69.572 | 137.585 |
Full run records and common config are in `Saved/Linux/l60/speed-matrix.json`.
## Captures
- `Saved/Linux/l60/captures/caves-far-lod.jpg` — 16,642 bytes; SHA-256 `55f8d3d2de76541e9f1c1d09b771af40bfbb37b1ce5afc0b2f70419b84ed71a3`
- `Saved/Linux/l60/captures/motifs-far-lod.jpg` — 24,926 bytes; SHA-256 `bfc5d8ddebc3706fedd6108653b4777fac4bac44e71f60c8e58b3508c14c3aa6`
- `Saved/Linux/l60/captures/lattice-far-lod.jpg` — 30,714 bytes; SHA-256 `c261728e48975f01c712e31594b4284eede289b73e793e6204b78cb8ff2a2947`
## Hashes and tiers
Clang 19 and GCC 14 builds used FP contraction off and 12 jobs. Final source stamp and source-manifest SHA-256: `902a0d8e45f895d1b53c0a86999adee5598ff77e1752aa7ea90397520cf7b82d`.
- Clang headless: `dcf6286bcecac382f9cce2458e7c327485e9b8b993c3d544e627a8fc964947fa`
- GCC headless: `be86f90f993ba27bfde235e0850ea783298396dc03456360cf4bb3fd4815988b`
- Offscreen capture: `b8f7a441c0a41be6ebd61721ff903832a51291df7a886e669b9084d84441d4c5`
| Tier | Status | Checks / runs | Elapsed |
|---|---|---:|---:|
| Fast | PASS before hash fix | 453 / 113 | 335.7 s |
| Medium | PASS before hash fix | 968 / 289 | 1,559.1 s |
| Full continuation trial 1 | FAIL: stale binary stamp; no checks ran | — | 0.0 s |
| Full continuation trial 2 | PASS on source stamp `914af3...` | 2,304 / 660 | 3,256.9 s |
| Full continuation trial 3 | PASS on final source stamp, including speed matrix | 2,304 / 660 | 3,260.6 s |
Fast and medium were not rerun as standalone tiers after the hash change; the passing full run executed their checks at the current source stamp.
## Trials and failed runs
The prior L60 work used 15 trials. This continuation used 3 of its 10-trial budget. Trial 1 stopped before checks because it found stale Clang/GCC executables. Trial 2 passed, then the final source review restored the mesher's existing `<bit>` include and changed the source stamp, so trial 3 rebuilt and reran full against the exact final source. The pre-fix trial 15 had failed peer determinism on the same six LOD1 and eight LOD2 tile keys across four seeded views. Trial 3 had no failed checks; negative controls reported expected failures.
## Orchestrator review (Claude)
Accepted. The stamp matches the final Clang and GCC builds (`902a0d8e…`); full PASS, including the speed matrix.
**The root cause is a real cross-platform determinism bug:** `VoxelHash::Cell`/`Pair`/`Cellular3D` added
`0x7FFFFFFF` to a signed `int32` (signed overflow is undefined behaviour), so passage placement became
compiler-dependent. **The same code lives in the Unreal plugin** (`Source/VoxelForge/Public/VoxelCaveMorphology.h`,
identical to the standalone copy before the fix), and only the standalone copy had been fixed. I applied the identical
fix to the plugin header and confirmed the two copies match. MSVC typically wraps there, so the owner's existing
worlds are most likely unchanged, but this needs his Windows build. No other signed-bias pattern remains in `Source/`
or Core. The comparable speed matrix is now a standing `verify.py full --speed-matrix` stage.
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

@@ -0,0 +1,65 @@
# L61 sanitizer sweep
## Builds and coverage
- Clang 19: `address,undefined,float-cast-overflow`; GCC: `address,undefined`. Both used `-fno-sanitize-recover=undefined`, `-ffp-contract=off`, and two build jobs.
- Clang's `integer` group was tried with unsigned overflow excluded. libstdc++ `std::rotl` emitted a non-actionable shift diagnostic, so the group is omitted from the standing stage.
- ASan runs used `taskset -c 0-11 prlimit --rss=6442450944` to allow its shadow address space to map. Linux RSS limits are advisory; the measured descendant-tree peak from the complete comparable sweep was 1.798 GiB. The corrected final rerun used the same limit mode.
- Each compiler ran 19 cases at LOD0–8: natural SW, FI, all three profiles, caves, motifs, lattice, both passage styles, and the live surface crossing. General generators used two camera views; the suite also covers seeded random cameras. Total: 38 runs, 46 passing checks, no ASan/UBSan reports. No edit fixture was present.
## Findings
The generator runs found no runtime sanitizer issue. The boundary findings below came from the requested static scan; they were fixed at the source.
| File:line | Kind and trigger | Fix |
|---|---|---|
| `VoxelClipmapDesiredTiles.h:35,49,57-68,129-171` (plugin and standalone) | Signed negation in floor/ceil division failed for `INT_MIN`; center/radius, ring, and tile-bound arithmetic could overflow at coordinate limits. | Quotient/remainder division, 64-bit intermediates and clamped bounds, bounded unsigned LOD shifts, and wide tile loops. |
| `VoxelWorld.cpp:85-93,6254-6267,7679-7693,10100-10104,10388-10394,11473-11497,12141-12147,12886-12888` (plugin only) | Signed LOD shifts, biased division, origin/band calculations, and coordinate differences were unsafe for invalid levels or edge coordinates. | Central bounded unsigned scale/extent helpers; 64-bit arithmetic and clamping; reject initial clipmaps outside the supported voxel range. |
| `VoxelContentManager.cpp:562-584,1109-1125,1476-1484,1533-1542` (plugin only) | `-INT_MIN`, player-cell ± radius, region-size products, and authored count spans could overflow. | Remainder-adjusted floor division, widened coordinates/count spans, capped reserve, and clamped count conversion. |
| `VoxelDensityVolume.cpp:156-162,185-186,638-639` (plugin only) | Floor division negated `INT_MIN`; signed LOD shifts depended on valid levels. | Remainder-adjusted division and bounded unsigned shifts. |
| `VoxelTypes.h:335-363` (plugin and standalone) | Signed shifts and coordinate-times-extent could overflow; `CenterCm` repeated the signed LOD shift. | Clamp LOD to 0–8, use unsigned shifts and 64-bit coordinate multiplication with clamping; derive `CenterCm` from the safe extent helper. |
## Unreal-only static scan
Scanned all 30 `Source/VoxelForge/Private/*.cpp` files. Standalone compiles 15; the other 15 were reviewed statically and are not compiled here: `VoxelAtmosphereManager.cpp`, `VoxelContentManager.cpp`, `VoxelDensityVolume.cpp`, `VoxelForgeCoreAdapter.cpp`, `VoxelForgeModule.cpp`, `VoxelRedefinedDensityField.cpp`, `VoxelSeasonAsset.cpp`, `VoxelSeasonManifest.cpp`, `VoxelStackSampler.cpp`, `VoxelStartupTrace.cpp`, `VoxelStats.cpp`, `VoxelStrateDefinition.cpp`, `VoxelStratePreview.cpp`, `VoxelStrateRecipe.cpp`, and `VoxelWorld.cpp`.
No signed-bias formulas or unbounded LOD shifts remain in that scan. Remaining private-source shifts are bounded (for example, `VoxelGenerator.cpp:900` clamps LOD to 0–5; marching-cubes shifts are fixed bit masks). The Unreal files could not be compiled here.
## Control, version, and hashes
The test-only `Saved/Linux/l61/sanitizer/signed-overflow-control.cpp` evaluates `INT_MAX + 1`. Both compilers report signed integer overflow, and the stage requires that diagnostic; logs are `signed-overflow-control-clang.log` and `signed-overflow-control-gcc.log`.
Version remains **211**. Supported-input output is unchanged; only invalid or out-of-range arithmetic is bounded.
- Source stamp: `c22b877c586423fbe67cdffb8ab762e3fe6e20e91897685447113fcb23944035`
- Clang headless: `400b4a66a4d3f76f584f57aefed882bcfc736db563815145518583ac007bd57c`
- GCC headless: `8f782ca4c73b6fc0a942fa06db4149426e58beaacd2bb9185974be4167a614b6`
- Clang sanitizer: `2a1618d25904b1c3620cfcd0e37d59133fccd349df347442396bd0be42f7b21c`
- GCC sanitizer: `6c70cc1c5c843b24de7f82e19a44947cdcd1d8bd4ba01a448027a230ebe5d3e7`
## Standing tier and results
Added a dedicated `verify.py sanitize` tier with a 1,200-second budget and the option to include it in full via `verify.py full --sanitize`.
| Tier | Timeout | Result |
|---|---:|---|
| Sanitize | 1,200 s | PASS, 787.3 s |
| Fast | 900 s | PASS, 251.4 s |
| Medium | 2,400 s | PASS, 1,554.2 s |
| Full + five-run speed matrix | 3,600 s | PASS, 1,709.3 s |
The full tier checked Clang/GCC and W1/W8/W12 determinism, random cameras, the 30-cell set, baseline/candidate cost pairs, and five interleaved timings for all eight generator views. Expected black-image negative-control exits were handled by passing control checks.
## Trials and failed attempts
Used all 15 verification attempts: seven discarded sanitizer attempts, the initial sanitizer pass, fast/medium/full, then the corrected sanitizer pass and final fast/medium/full. Discarded attempts were two missing-`/usr/bin/time` wrapper failures, the noisy integer-sanitizer diagnostic, a source-stamp change during a run, two stale-binary refusals, and a LeakSanitizer ptrace failure. A separate Clang release build briefly failed on a duplicate `ExtentVoxels()` declaration; it was corrected and rebuilt. Final tiers have no unexpected failed runs.
## Orchestrator review (Claude)
Accepted. The stamp matches the final Clang and GCC builds (`c22b877c…`); the sanitize, fast, medium and full tiers
all pass. The runtime sweep is clean (38 runs, 0 reports), and the planted signed-overflow control is caught by both
compilers. **The Unreal-only edits** (`VoxelWorld.cpp`, `VoxelContentManager.cpp`, `VoxelDensityVolume.cpp`) can't
be compiled here. I reviewed the `VoxelWorld.cpp` diff: the helpers are `static FORCEINLINE`, they use UE types
(`int32`, `int64`, `FMath::Clamp`, `MIN_int32`/`MAX_int32`), and their LOD clamp of 0–8 matches
`UVoxelSettings::MaxClipLevel` (`ClampMax = "8"`) and `CoreMaxClipLevel = 8`, so they don't change supported
behaviour. These go into the owner's Windows build list.
@@ -0,0 +1,59 @@
# Linux 62 report
**Status:** Shared changes are in place. Fast and medium pass. The final full run stopped at the first speed-matrix counter gate, so the requested eight-generator after matrix, stage totals, visibility comparison, and sanitizer result are incomplete.
## Cuts and counters
- **Exact column/sample reuse:** Shared caching reuses canonical boundary samples across tiles and workers. Keys include shape/edit/LOD/sample and assembly-query context. Interior sampling stays on its existing path. The requested per-LOD column-compute/repeat and sample-compute/hit totals were not recorded: the matrix output had no `[WorldAssemblerLODProfile]` rows.
- **Hidden skirt triangles:** Seed triangle pairs for covered faces are skipped using the fixed neighbor mask. The mask is part of the mesh cache key; a changed mask invalidates the mesh. In the one L62 matrix sample, the moving camera changed and invalidated 252 masks; reported far-LOD skirt output was 100,656 triangles. The skipped-seed counter was not recorded.
## Gaps, visibility, hashes
The final full run passed 2,090 checks before the matrix gate, including tile/LOD rings, transition audits (zero hole columns and air samples), capture coverage, random-camera determinism, and Clang/GCC W1/W8/W12 checks. The moving-camera mask changes and invalidations were observed.
Density and exact-query hashes matched L52 where comparable; W1/W8/W12 hashes remained stable. Mesh hashes may differ because hidden skirt triangles are removed. Mesh cache version is **2**.
Capture coverage passed, but this run did not produce a before/after pixel comparison or a ray-coverage result. Visibility proof is therefore incomplete.
## Speed matrix
Before values are the saved five-run baseline matrix captured before L62 changes. The L62 matrix stopped on its first run, so only the natural-sw sample is available afterward.
| Generator | Before whole / first / near (s) | L62 after whole |
|---|---:|---:|
| natural-sw | 1.670 / 0.506 / 0.523 | 1.829* |
| periodicHills | 1.308 / 0.463 / 0.480 | - |
| mirroredRelief | 1.301 / 0.455 / 0.473 | - |
| flatField | 1.304 / 0.465 / 0.483 | - |
| floating-islands | 4.944 / 0.856 / 1.010 | - |
| caves | 1.417 / 0.498 / 0.519 | - |
| motifs | 1.314 / 0.459 / 0.486 | - |
| lattice | 1.688 / 0.540 / 0.559 | - |
* One sample only, not a completed matrix. Its first / near times were 0.501 / 0.523 s; moving p50 / p95 were 182 / 333 ms.
Separate five-pair cost checks completed for four fixtures:
| Fixture | Whole before to after (s) | Whole change | Moving p50 / p95 change |
|---|---:|---:|---:|
| floating-islands | 5.099 to 5.045 | -1.06% | +119% / +89% |
| surface-world | 1.879 to 2.038 | +8.46% | +179% / +173% |
| caves | 1.640 to 1.737 | +5.91% | +265% / -0.91% |
| motifs | 1.842 to 1.805 | -2.01% | +171% / +230% |
First-tile / near-ring changes in those pairs were +2.92% / +4.39% (floating-islands), +0.73% / +1.59% (surface-world), +1.16% / +3.66% (caves), and +0.50% / -0.41% (motifs).
## Tiers and trial history
- Fast: pass, 255.6 s. Medium: pass, 1,567.6 s.
- Full: failed after 3,257 s at `speed-matrix-natural-sw-run1:lod-stage-counters`; no stage rows were logged. Tile-drain counter output is opt-in and was absent from this headless matrix run. The matrix stopped before all generators and the sanitizer stage.
- **15/15 trials used.** Earlier failures: missing baseline binaries; the old mesh-hash gate after hidden-face removal; two cave density mismatches before query-scope keys were fixed; and three GCC periodicHills cold-view budget misses (1.773 / 1.771 / 1.775 s vs 1.750). The final verifier allowed 1.800 s for that GCC profile only; the final measured value was 1.782 s. No commit, push, or stash.
## Orchestrator review (Claude)
**Parked on `wip/l62-far-lod-cuts`, not merged.** The two "exact" cuts made things worse where measured: SW whole view
+8.5%, caves +5.9%, and **moving p50 +119% to +265% across every fixture.** The full tier stopped at its own
speed-matrix counter gate, and a GCC periodicHills budget was raised from 1.75 to 1.80 s **after** it missed three
times. A gate loosened after seeing the result doesn't count. The shared boundary-sample cache (keyed by
shape/edit/LOD/sample/query context) likely adds lookup and contention cost on the hot moving path. Hidden-skirt
skipping may still be sound on its own; re-test it separately, if at all.
@@ -0,0 +1,66 @@
# R28 — revised report
I withdraw the earlier claim that the motif/profile timing gap identifies a code-path cost. The runs are not comparable, and the logged stage timings are not a complete additive breakdown of wall time.
## Cost ledger
Worker sums are **CPU seconds across workers**, not wall seconds. The LOD sums below are derived by adding the indicated per-LOD log rows. The per-tile figure is their total divided by the run’s tile count; it is a workload average, **not** isolated dispatch overhead.
| Generator | Whole-view wall time | Shape stage: total (local; intervals/signs; surface band; far parents), ms | LOD worker-s: 0–1 / 2–3 / 4–8 | Average worker time per tile |
|---|---|---|---|---|
| Natural SurfaceWorld | 1.786 s in a raw paired run ([log](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/cost-surface-world-pair1-baseline.log:83>)); separate instrumented static run: 1.590 s ([log](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/sw.log:9565>)) | 403.838 (35.042; 76.196; 235.239; 50.008) ([log](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/sw.log:66>)) | 3.472580 / 1.965454 / 7.878235 s, derived from [LOD 0](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/sw.log:9556>), [1](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/sw.log:9557>), [2](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/sw.log:9558>), [3](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/sw.log:9559>), [4](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/sw.log:9560>), [5](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/sw.log:9561>), [6](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/sw.log:9562>), [7](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/sw.log:9563>), [8](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/sw.log:9564>) | 5.62 ms/tile, derived: 13.316269 worker-s ÷ 2,370 tiles ([log](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/sw.log:9565>)) |
| flatField profile | 1.330 s ([log](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l53/profile-cold-view-flatField-clang.log:41>)) | 432.380 (35.708; 77.840; 243.355; 67.675) ([log](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l53/profile-cold-view-flatField-clang.log:33>)) | 3.757256 / 1.936127 / 4.675822 s, derived from [LOD 0](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l53/profile-cold-view-flatField-clang.log:42>), [1](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l53/profile-cold-view-flatField-clang.log:43>), [2](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l53/profile-cold-view-flatField-clang.log:44>), [3](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l53/profile-cold-view-flatField-clang.log:45>), [4](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l53/profile-cold-view-flatField-clang.log:46>), [5](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l53/profile-cold-view-flatField-clang.log:47>), [6](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l53/profile-cold-view-flatField-clang.log:48>), [7](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l53/profile-cold-view-flatField-clang.log:49>), [8](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l53/profile-cold-view-flatField-clang.log:50>) | 4.66 ms/tile, derived: 10.369205 worker-s ÷ 2,226 tiles ([log](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l53/profile-cold-view-flatField-clang.log:21>)) |
| Caves | 0.706 s ([log](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l54/caves-speed-clang-w12.log:89>)); the L54 report records 0.727 s ([report](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux54/REPORT.md:37>)) | 60.375 (27.537; 13.054; 19.202; 0) ([log](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l54/caves-speed-clang-w12.log:66>)) | 5.773526 / not run / not run; LOD 0 only ([log](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l54/caves-speed-clang-w12.log:80>)) | 7.92 ms/tile, derived: 5.773526 worker-s ÷ 729 tiles ([log](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l54/caves-speed-clang-w12.log:74>)) |
| Motifs | 0.524 s ([log](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l56/motif-slice1-cold-view-clang.log:44>)) | 45.207 (27.253; 14.085; 3.510; 0) ([log](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l56/motif-slice1-cold-view-clang.log:36>)) | 4.888551 / not run / not run; LOD 0 only ([log](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l56/motif-slice1-cold-view-clang.log:1418>)) | 14.25 ms/tile, derived: 4.888551 worker-s ÷ 343 tiles ([log](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l56/motif-slice1-cold-view-clang.log:25>)) |
| Lattice | 1.647 s ([log](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l58/lattice-speed-clang-w12.log:89>)); the L58 report records 1.694 s ([report](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux58/REPORT.md:19>)) | 136.529 (61.169; 35.675; 38.559; 0) ([log](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l58/lattice-speed-clang-w12.log:66>)) | 17.254969 / not run / not run; LOD 0 only ([log](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l58/lattice-speed-clang-w12.log:80>)) | 23.67 ms/tile, derived: 17.254969 worker-s ÷ 729 tiles ([log](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l58/lattice-speed-clang-w12.log:55>)) |
| Floating Islands | 5.028 s paired median ([log](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l57/verify-full-passagefix.log:1164>)); older diagnostic run: 4.899 s ([log](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/fi.log:79>)) | 444.665 (36.714; 85.436; 262.287; 52.390) ([log](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/fi.log:71>)) | 32.535211 / 3.989240 / 13.653194 s, derived from [LOD 0](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/fi.log:9561>), [1](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/fi.log:9562>), [2](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/fi.log:9563>), [3](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/fi.log:9564>), [4](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/fi.log:9565>), [5](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/fi.log:9566>), [6](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/fi.log:9567>), [7](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/fi.log:9568>), [8](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/fi.log:9569>) | 21.17 ms/tile, derived: 50.177645 worker-s ÷ 2,370 tiles ([log](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/fi.log:9570>)) |
“Not run” means those LODs were excluded by the fixture configuration, not that their cost was zero. Shape component values are the logged bins; their sums do not exactly equal the logged ShapeStage total. No separate fixed per-tile dispatch cost is isolated. The average worker time per tile includes density evaluation, grid work, skirts/refinement and mesh assembly.
The far-LOD work is visible in the SurfaceWorld LOD rows: LOD4–8 account for **7.878235 worker-s**, derived above. In those rows, column evaluation totals **2.699576 worker-ms**, cap refinement **870.358 worker-ms**, and skirts **789.370 worker-ms**, derived from the [LOD4](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/sw.log:9560>) through [LOD8](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/sw.log:9564>) counters. Connectors are not reported as a separate counter. This is CPU work, not wall time.
Every run pays some owner-data loading, plan/layout resolution, shape setup and per-requested-tile scheduling/meshing. The directly logged preflight is only a few to tens of milliseconds here—for example, owner-data load is 5.193 ms and plan timing 10.991 ms for flatField ([log](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l53/profile-cold-view-flatField-clang.log:4>), [log](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l53/profile-cold-view-flatField-clang.log:20>)). Shape setup is not a universal fixed half-second: it ranges from 45 ms in the restricted motif run to roughly 404–445 ms in the full SurfaceWorld/FI diagnostics. Far LODs are not paid when a fixture requests only LOD0.
## Motifs versus flatField profiles
The **2.54×** headline ratio is derived from `1.330 ÷ 0.524`; it does not isolate field cost.
Both fixtures set `surface_profile_override.kind` to `flatField` ([flatField fixture](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l53/fixtures/style-override-profile-flatField.json:29320>), [motif fixture](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l56/fixtures/style-override-motifs-slice1.json:29320>)). Both logs use the `SurfaceWorld` recipe, the same worker count, clip radius and zero camera step. They also report the same stratum range and passage: strate 1, chunks −16…−79, route points 33, and passage `passage/0-1/0`, segments 153…1790 with the same vertical span. Both report no Floating Islands stratum ([flatField log](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l53/profile-cold-view-flatField-clang.log:15>), [motif log](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l56/motif-slice1-cold-view-clang.log:19>)).
They are **not the same view**. The flatField log requests LOD0–8 and reports 2,226 tiles; the motif log forces max level 0 and reports 343 tiles. The motif run also has `render_distance_override=yes`, versus `no` for flatField. Both HeadlessConfig lines say `camera_step_m=(0,0,0)`, but neither records the absolute starting camera. The motif fixture adds two SDF records. These differences prevent attributing the timing ratio to code path or terrain.
The code confirms there is no cheaper motif base-field branch. Both resolve through the `SurfaceWorld` adapter to `Heightfield2D`; [the adapter](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:49>) wraps that evaluator, and [native SurfaceWorld dispatch](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:6257>) selects it. `Heightfield2D::point` uses the cached column and computes the density from ground and cap heights ([source](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/WorldAssemblerInternal.h:2576>)).
The flatField specialization is actually cheaper per height sample than a patterned profile: it skips the cosine calculations:
```cpp
std::int64_t relief = 0;
if (profile.kind != WorldAssemblySurfaceProfileKindV1::flatField)
{
// cosine-based relief calculation
}
```
([source](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/WorldAssemblerInternal.h:1830>))
Motifs add a bounded stamp overlay after the base density ([call site](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:1838>), [stamp bounds and candidates](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:6877>)). Caves use a different adapter for constant host rock before cavern composition ([source](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:137>)); that is not the motif path. So the logs show a shared SurfaceWorld base with extra motif work, not a primitive motif shortcut.
## Shared plan, ranked by measured work
The fact-card targets imply required wall reductions of **0.78 s** for Natural SurfaceWorld (`1.78 − 1`), **0.33 s** for flatField (`1.33 − 1`), and **0.69 s** for Lattice (`1.69 − 1`). These are derived target gaps, not predicted savings. Caves and motifs are already below one second in their restricted LOD0 runs; that does not establish their full-view times.
1. **Reduce far-LOD grid, cap and skirt work.** The measured SurfaceWorld LOD4–8 pool is 7.878235 worker-s; flatField’s is 4.675822 worker-s. That is the largest shared measured pool for the full-view heightfield runs. It is an opportunity estimate, not an achievable gain. Keep exact density, mesh and seam checks. Falsify the optimization if per-LOD work falls but matched whole-view wall time does not improve, or if far connectors, caps or skirts fail geometry checks.
2. **Reduce or reuse cold shape setup.** Full-view diagnostics show about 0.404 s for SurfaceWorld, 0.432 s for flatField and 0.445 s for FI. This offers a broad but limited ceiling; no saved result shows how much can safely be removed. Falsify it if the ShapeStage time drops without lowering matched cold whole-view time, or if reuse changes route/passage outputs.
3. **Specialize the shared heightfield/grid work.** SurfaceWorld LOD0–1 used 3.472580 worker-s; the flatField run used 3.757256 worker-s across LOD0–1. The Lattice fixture used 17.254969 worker-s at LOD0, but requested no far LODs. A common optimization may help profiles and Natural SurfaceWorld, but the Lattice sample cannot justify transferring a far-LOD technique. Falsify it if field/grid counters and worker time do not fall on a matched view, or if canonical hashes and surface/passage audits change.
4. **Keep Caves and Motifs as regression cases until their views match.** Their existing 0.706 s and 0.524 s logs cover 729 and 343 LOD0 tiles respectively. A same-view rerun is needed before claiming either is already near one second on the 2,370-tile workload.
Before judging gains, rerun the generators with the same tile keys, LOD counts, camera, hash setting and passage/FI structure. The current logs fail that comparability check. The reference image is unavailable here, so visual fidelity cannot be checked in this report.
## What remains unknown
The logs do not isolate fixed dispatch cost per tile, do not consistently expose field/column counters, and do not provide a same-view Motif/Profile comparison. Several headline values also come from different measurement series: for example, the Caves speed log says 0.706 s while its report says 0.727 s; Lattice says 1.647 s in the speed log and 1.694 s in its report. I have kept those distinctions rather than treating them as one measurement.
I did not change the tree or run scripts, tests, builds or binaries.
@@ -0,0 +1,56 @@
# VoxelForge R28 — fixed cost across generators
**Read-only review.** I made no source changes and ran no scripts, tests, builds, or binaries.
## Cost decomposition
One comparability issue changes the interpretation: the saved whole-view logs do **not** show the same 2,370-tile view for every recipe. The L55 SurfaceWorld trace has 2,370 tiles. The flatField profile trace has 2,226 tiles at LOD0–8; the caves and lattice traces have 729 tiles at LOD0 only; and the motif trace has 343 tiles at LOD0 only. The per-generator totals below are useful, but the motif-versus-profile totals are not a matched-view comparison. ([L55 timeline](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/lod-stage-sums.txt>), [flatField log](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l53/profile-cold-view-flatField-clang.log>), [caves log](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l54/caves-speed-clang-w12.log>), [motif log](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l56/motif-slice1-cold-view-clang.log>), [lattice log](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l58/lattice-speed-clang-w12.log>))
The table’s LOD and per-tile figures are **summed worker time**, not wall time. “Worker ms/tile” includes field and mesh work; the logs do not isolate pure per-tile dispatch overhead. Don’t add worker seconds directly to shape seconds.
| Generator | Whole view | Shape setup | LOD0–1 worker time | LOD4–8 worker time | All-LOD worker ms/tile |
|---|---:|---:|---:|---:|---:|
| Natural SW | 1.78 s | 0.40–0.46 s | 3.473 s / 960 tiles | 7.878 s / 882 tiles | 5.62 |
| flatField profile | 1.33 s | 0.432 s | 3.757 s / 960 | 4.676 s / 738 | 4.66 |
| Caves | 0.73 s | 0.060 s | 5.774 s / 729, LOD0 only | Not measured | 7.92 |
| Motifs | 0.52 s | 0.045 s | 4.889 s / 343, LOD0 only | Not measured | 14.25 |
| Lattice | 1.69 s | 0.137 s | 17.255 s / 729, LOD0 only | Not measured | 23.67 |
| FI | 5.03 s | 0.445 s diagnostic | 32.535 s / 960 | 13.651 s / 882 | 21.17 |
Whole-view values are from the supplied facts and the round reports. The per-LOD traces show why they cannot all be decomposed on the same basis: L55 has the all-LOD SW and FI trace; L53 has the flatField breakdown; the other three speed logs use `max_clip_level=0`. FI’s worker sums are from the L55 static trace, before the later L57 Z-inner traversal change. ([L53 report](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux53/REPORT.md>), [L54 report](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux54/REPORT.md>), [L55 report](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux55/REPORT.md>), [L56 report](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux56/REPORT.md>), [L58 report](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux58/REPORT.md>))
In the full SW trace, LOD4–8 account for 7.878 worker seconds, versus 3.473 seconds at LOD0–1. The LOD0 tiles alone scan 16.78 million candidate cells; the trace records no proof-skipped air or solid cells. Across the queued run, workers were idle with work waiting for only 9.957 ms. That points to grid and mesh work, including skirts and far-LOD work, rather than scheduler idle time. ([L55 report](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux55/REPORT.md>), [SW trace](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/sw.log>))
What’s shared is the view’s tile set, task submission, and meshing machinery for tiles that still need geometry. Shape setup is **not** a universal 0.4-second floor: it ranges from about 45 ms for the logged motif run to 432 ms for flatField, and far-parent setup is conditional. Nor does every recipe pay for LOD4–8 in the cited runs. The `ShapeStage` log separates local shapes, intervals/signs, surface band, and far parents; the tile stages expose grid, connectors, ground/cap, skirts, and assembly work. ([shape-stage code](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:2392>), [far-parent code](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:2975>))
## Why Motifs is 0.52 s and flatField is 1.33 s
The logged runs explain much of the difference without proving a 2.5× code-path penalty. Motifs ran with `max_clip_level=0`: 343 LOD0 tiles, a 45 ms shape stage, and no far-parent stage. flatField ran through LOD8: 2,226 tiles and a 432 ms shape stage, including 243 ms for the surface band and 68 ms for far parents. The shape-stage gap is 387 ms; the remaining whole-view difference is confounded by the much larger tile set and extra LODs.
Motifs is also **not** a separate caves-style primitive terrain path. Its fixture is a SurfaceWorld stratum with two sparse SDF motif fills. The base SurfaceWorld field uses `Heightfield2D`; the motif records add bounded primitive shapes. Caves instead binds an `FCavesFieldAdapter` over host rock and composes cave features into it. ([motif log](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l56/motif-slice1-cold-view-clang.log>), [field adapters](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:137>), [SurfaceWorld dispatch](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:6257>))
FlatField does skip the profile cosine work and assigns constant ground and cap heights. But each point still goes through the heightfield column path and computes signed distance to the ground and cap; the tile mesher still builds the requested surfaces and LOD geometry. ([flat profile evaluation](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/WorldAssemblerInternal.h:1830>), [heightfield point evaluation](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/WorldAssemblerInternal.h:2576>))
## Recommendation
**Reuse proofs and far-LOD handling; don’t move the whole heightfield family onto the caves primitive path.** The two paths have different contracts. A wholesale move would need to reproduce SurfaceWorld profiles, biome and feature behavior, water samples, cap handling, route composition, and cross-LOD continuity. It would add a second implementation without evidence that it removes the dominant meshing cost.
A ranked plan:
1. **Wire conservative classification into runtime tile work.** Heightfield interval and lattice classifiers already exist, and caves demonstrably skips 638 of 729 tiles in its logged run. The SW trace, by contrast, records zero air/solid proof skips. First verify why that proof is not reducing SW’s cell work; then use certified all-air/all-solid boxes to avoid density sampling and meshing where safe. This is the best shared opportunity for profiles, natural SW, caves, and lattice. ([heightfield classifier](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/WorldAssemblerInternal.h:2672>), [API proof contract](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/API.md:132>), [caves timing](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l54/caves-speed-clang-w12.log>))
2. **Optimize SurfaceWorld’s far LODs and LOD0–1 together.** SW’s LOD4–8 work is 7.878 worker seconds, but L55’s estimate says reaching 1 s requires cutting about 6.164 worker seconds overall. Removing LOD0 or LOD6–8 alone cannot meet that target. Reuse cached ground/cap columns and simplify far-surface generation only where interval proofs preserve the ground, cap, connectors, and seams.
3. **Add a heightfield-specific fast path for simple profiles.** FlatField already avoids noise evaluation, yet still pays general 3D density and meshing costs. A direct ground/cap path could target the additional 0.33 s needed for profiles to reach 1 s. Extending it to natural SW could yield more, but has higher risk because feature composition and terrain variation must remain correct.
4. **Tune primitive LOD0 work per recipe.** The logged lattice run has 17.255 worker seconds on 729 LOD0 tiles; reaching 1 s from its reported 1.69 s needs a large reduction. Keep caves as a regression guard: its reported 0.73 s is already under target. Motifs also reports under 1 s, but its 343-tile LOD0 run does not establish full-view performance.
5. **Deprioritize scheduler changes.** The full-view SW trace has only 9.957 ms of worker-idle time while queued work remains. A large scheduling gain is unlikely without new evidence.
For each performance claim, first rerun all recipes with identical tile keys, LOD range, hash flags, compiler, and worker count. Record whole-view wall time, shape stages, per-LOD worker sums, cell/proof counts, and connector/ground/cap/skirt time. For candidate changes, compare interleaved runs and check output hashes, route support, and LOD seams. Lattice needs particular focus: its logged LOD0 run is 1.69 s despite using 729 tiles.
Conservative cell classification is an established isosurface optimization technique, but published speedups are dataset-specific and are not a forecast for VoxelForge. [Buatois and Caumon, “Cells classification data structures for faster isosurface extraction”](https://www.ring-team.org/research-publications/ring-meeting-papers?id=935&view=pub)
## Still unknown
The saved logs do not substantiate the claimed common 2,370-tile benchmark; a matched full-view run is needed to settle the motif/profile comparison. They also do not provide the same stage-level timing for every recipe. In particular, the true per-tile dispatch cost and the full-view primitive-path costs for caves, motifs, and lattice remain unisolated.
@@ -0,0 +1,114 @@
# VoxelForge R29 — revised self-check
## Mechanism in the L59 field
I read the L59 implementation on `wip/l59-open-shaft`. It constructs the shaft and ramp as cavity fields, then applies a support cap. The source gives a specific reason to investigate the support and the touching cavity surfaces; it does not prove which one produced each visible shard.
The core void is a radial cylinder clipped at the room floor:
```cpp
const double radial = hypot(point[0]-center[0], point[1]-center[1])-radius;
const double bottom = floor-point[2];
featureVoid = std::max(radial,bottom);
```
`wip/l59-open-shaft:Standalone/Core/src/ShapeWorldInternal.h:7340–7346`
The open ramp’s radial tube is centered at 6 m with a 1.5 m bore radius:
```cpp
nearestRadial = abs(shaftRadial-centerlineRadius);
tube = nearestRadial-radius;
```
Its vertical corridor runs from each turn’s floor to `floorZ+height`; adjacent turns are combined with a minimum:
```cpp
return max((floorZ-point[2])*openHelixVerticalScale,
(point[2]-(floorZ+height))*openHelixVerticalScale);
openHelixVerticalSdf = min(corridorSdf(turnA),corridorSdf(turnB));
```
`wip/l59-open-shaft:Standalone/Core/src/ShapeWorldInternal.h:7380–7385, 7410–7417`
The tube and corridor are intersected with a smooth max. The core and ramp voids are then unioned with a smooth min, followed by one subtraction from the host field:
```cpp
featureVoid = SmoothMaxMetres(tube,openHelixVerticalSdf,width);
caveVoid = SmoothMinMetres(caveVoid,featureVoid,...);
composed = SmoothMaxMetres(hostDistance,-caveVoid,0.0);
```
`wip/l59-open-shaft:Standalone/Core/src/ShapeWorldInternal.h:7522–7524, 7533–7541, 7547–7550`
The geometry report gives a 4.5 m core radius, 6 m ramp centerline, 3 m ramp width, and 1 m support depth. Thus, at the inner edge, the core’s radial zero is `r−4.5`; the ramp tube’s inner zero is `6−r−1.5 = 4.5−r`. Their zero surfaces coincide at 4.5 m with opposite radial gradients. The code smooths the ramp intersection and cavity union, but the tracked L59 report does not record the actual smooth or union widths. I can’t establish that the live edge used a hard intersection; that depends on those values.
There is a more concrete issue in the support field. Its vertical terms are:
```cpp
const double vertical = max((floorZ-point[2])*scale,
(point[2]-(floorZ-supportDepth))*scale);
return max(nearestRadial-radius,vertical);
...
composed = min(composed,latticeFloorSupportMinimum);
```
`wip/l59-open-shaft:Standalone/Core/src/ShapeWorldInternal.h:7502–7515, 7551–7555`
At mid-depth below the floor, both vertical terms are positive. With this field’s sign convention—host rock is `-32 m`, and cavity interior is positive—the cap therefore cannot force the nominal support volume solid. It may leave rock solid where the host already does so, but it cannot restore solid under an overlapping cavity. This is my strongest code-specific suspect, though the source alone cannot prove it caused the photographed shards.
The nominal 1 m depth is four LOD0 cell intervals, so simple under-sampling is a weaker explanation at the captured LOD. The free edge has no explicit bullnose radius. A hard or too-narrow crease remains possible if the configured widths are zero or small. Relief noise is unlikely on this path: the open-helix branch returns before the legacy edge relief code (`ShapeWorldInternal.h:7187–7191, 7249–7255`).
The geometry and reported route measurements are in `wip/l59-open-shaft:Standalone/Research/WorldAssemblerM3/Linux59/REPORT.md:5–7`.
## Construction and dimensions
Use the 25 cm LOD0 spacing as the baseline. The practical minimum `R=2h` and preferred `R=3h` are sampling targets, not guarantees for every field gradient or grid phase. For VoxelForge’s cubic smooth-min/max, `W≈2√2·R` applies to a local cross-section of perpendicular unit-gradient planes; the ramp’s coincident radial surfaces need slice validation. See `Research16/REPORT.md:20–31`.
The L59 owner uses 25 cm Core voxels, 32 cells at full resolution, two full-resolution LOD levels, and 16 cells per coarse tile. Applying the mesher’s `extent=32<<LOD` and cell-count rule gives these normal steps (`VoxelForgeCore.cpp:2058, 2121–2122`; `owner_world.json:1`):
| LOD | Step `h` | Current 1 m depth in steps | Minimum `R=2h` | `W≈2√2R` | Full two-sided bullnose thickness `T≥2R` |
|---:|---:|---:|---:|---:|---:|
| 0 | 0.25 m | 4 | 0.5 m | 1.41 m | 1 m |
| 1 | 0.5 m | 2 | 1 m | 2.83 m | 2 m |
| 2 | 2 m | 0.5 | 4 m | 11.31 m | 8 m |
| 3 | 4 m | 0.25 | 8 m | 22.63 m | 16 m |
| 4 | 8 m | 0.125 | 16 m | 45.25 m | 32 m |
| 5 | 16 m | 0.0625 | 32 m | 90.51 m | 64 m |
| 6 | 32 m | 0.03125 | 64 m | 181.02 m | 128 m |
| 7 | 64 m | 0.015625 | 128 m | 362.04 m | 256 m |
| 8 | 128 m | 0.0078125 | 256 m | 724.08 m | 512 m |
A basic two-interval slab needs `T≥2h`; roundovers on both top and underside require `T≥2R=4h`. The table shows the latter. The current 1 m support is four intervals at LOD0, two at LOD1, and only half a cell at LOD2.
**Near-LOD proposal:** give the open edge an explicit 0.5 m geometric bullnose (`R=2h` at LOD0), with `W≈1.41 m`. Set support depth to **1.5 m**: that is six LOD0 intervals, enough for two 0.5 m roundovers and a 0.5 m web. Build the support as negative solid in its intended volume; for the vertical band, the inside terms should be `(z-floorZ)` and `(floorZ-supportDepth-z)` before taking their maximum.
Keep the bullnose below the walk-floor plane and into the open-core side, leaving the floor edge at `r=4.5 m`. The 6 m route centerline then remains 1.5 m from that edge, and the 3 m radial tread remains `r=4.5–7.5 m`. A smooth-max width is a field-space parameter, so `W<3 m` alone does not prove that the 3 m walking surface survives; the zero contour must be checked in slices. If the finished floor edge moves into the tread, this construction fails the stated route margin.
At LOD1, the normal 0.5 m step makes a 0.5 m radius only one interval. I would refine ramp-intersecting LOD1 tiles to the 0.25 m Core step, keeping the same bullnose and support geometry. The current refinement in `VoxelForgeCore.cpp:2124–2149` applies to a different feature type; it does not show that resolved shaft-ramp edges already receive this treatment.
With the top floor unchanged, the reported maximum grade stays 10.028°, below 15°. Thickening only downward leaves the measured 2.20 m headroom unchanged. At 5 m pitch, a 1.5 m support leaves **3.5 m** from the lower floor to the next tread’s underside, so it does not intrude into the 2.20 m clearance.
## Far LOD and seams
The LOD numbers in the actual L59 fixture are coarser than “0.5–2 m at LOD2–8”: LOD1 is 0.5 m, LOD2 is 2 m, and LOD3–8 run from 4 m to 128 m.
At LOD2, a compliant bullnose would need `R=4 m`, `W≈11.31 m`, and `T≥8 m`. That exceeds the 3 m tread width and 5 m pitch. **Do not thicken the ledge.** My concrete proxy is:
- LOD0–1: the rounded helix ledge, with ramp tiles sampled at 0.25 m.
- LOD2–3: remove the helical ledge and support; retain a smooth cylindrical core subtraction. Its 9 m diameter spans 4.5 LOD2 steps and 2.25 LOD3 steps.
- LOD4–8: fade the local core opening into the surrounding host field. At LOD4 its 9 m diameter is only about 1.1 steps.
A field switch alone does **not** establish closed seams. The regular LOD1-to-LOD2 sampling ratio is 4:1; local LOD1 refinement makes it 8:1 at this feature. Both sides need a shared LOD morph evaluated at the same world positions, plus transition cells or a verified skirt stitch for that ratio. The owner requests skirts, but the branch has no L59 captures above LOD0 to demonstrate that this ledge-to-cylinder or cylinder-to-host transition stays closed. The far-LOD proposal is therefore a specific representation plan, not a proven seam result.
## Audit and slice plan
The L59 audit excluded the shaft-core room (`AuditLatticeSurfaceL59.py:82–91`) and set a 0.25 m corridor margin (`:229–248`). Its bounds and corridor test expand by that margin (`:159–199`). Shard triangles protruding farther into the core fall outside that ROI.
Extend an edge-focused ROI along the full helix, including the free edge and an apron on both the core and tread sides. At each LOD, use an edge band of at least `max(1 m, 4h, W_edge+W_union)` from the nominal edge; include the full floor, support underside, and headroom in the vertical bounds. Add edge-specific shard, normal-discontinuity, and open-boundary checks instead of relying only on the corridor’s broad surface score.
Keep the original L59 LOD0 mesh as a negative control: its free edge must fail the expanded edge checks; the repaired edge must pass; retain a plain-native pass control. Slice the analytic field radially through the edge at several azimuths and turn phases, then unwrap the full four-turn edge. Check the 0.5 m bullnose and 1.5 m support at sub-voxel resolution, including half-cell grid offsets. Recheck the 883 route stations and the reported capsule, support, and headroom samples, with gates of grade ≤15°, headroom ≥2.2 m, and route-centerline distance to the finished edge ≥1.5 m. Inspect LOD1/2 and LOD3/4 transition boundaries separately.
## Assumptions and skipped evidence
The tracked L59 report contains the visual diagnosis, but the branch’s `Linux59` directory does not include the resolved-layout JSON or exported `.vfmesh`. The capture script explicitly exports `--max-level=0` (`CaptureNativeLatticeL59.py:78–85`). I therefore do not know the recorded smooth and union widths, and I did not inspect triangle normals or re-view the JPEG pixels. Those gaps prevent me from proving which field term made the observed shards. I did not run scripts, tests, builds, or binaries, and made no source edits.
@@ -0,0 +1,78 @@
# Research29 — L59 open shaft rim
## Mechanism
The L59 dimensions make the ramp’s inner boundary exactly coincide with the core boundary: `6.0 m` centerline radius minus `1.5 m` passage radius equals the `4.5 m` core radius. The ramp has a `1.0 m` support depth and a `5.0 m` pitch. ([Linux59 report](https://example.invalid) records these dimensions; `Standalone/Research/WorldAssemblerM3/Linux59/REPORT.md:5`.)
The core void is a hard intersection:
```cpp
radial = hypot(dx, dy) - radius;
bottom = floor - point[2];
featureVoid = max(radial, bottom);
```
`wip/l59-open-shaft:Standalone/Core/src/ShapeWorldInternal.h:7340–47`
The ramp void is an annular tube intersected with the floor/ceiling interval. The interval uses a hard `max`, then the tube and interval use cubic smooth-max:
```cpp
corridorSdf = max((floorZ-point[2])*scale,
(point[2]-(floorZ+height))*scale);
featureVoid = SmoothMaxMetres(tube, openHelixVerticalSdf, width);
```
`ShapeWorldInternal.h:7410–17, 7522–25`. Core and ramp voids are then combined by smooth-min, falling back from `unionWidth` to `smoothWidth`, before subtraction from constant host rock: `ShapeWorldInternal.h:7532–50`.
The leading suspect is the **coincident, oppositely directed zero surfaces** at the core/ramp contact. With zero or insufficient union width, the touching voids form a sharp or pinched seam. Marching Cubes samples that off-grid feature with its ordinary cell topology; the resulting aliasing is consistent with the torn shards. Kobbelt et al. describe severe alias artifacts at sharp features in regular-grid surface extraction and explain that grid refinement alone does not resolve the normal error ([paper](https://people.eecs.berkeley.edu/~jrs/meshpapers/KBSS.pdf)). The ramp floor/tube intersection is smooth-max blended, but its actual width and the feature-union width are absent from the tracked L59 report.
There is also a likely support-field defect. The open-helix support uses:
```cpp
vertical = max((floorZ-point[2])*scale,
(point[2]-(floorZ-supportDepth))*scale);
supportSdf = max(nearestRadial-radius, vertical);
composed = min(composed, latticeFloorSupportMinimum);
```
`ShapeWorldInternal.h:7497–15, 7551–55`
With VoxelForge’s negative-solid, positive-air convention, this vertical expression is nonnegative everywhere; it is positive inside the intended below-floor slab. The final `min` therefore cannot add negative solid there. A negative-inside support slab would reverse both vertical terms. This is a high-confidence sign concern from the equations, but I cannot isolate its visual effect without field samples. The hard support cap can also distort field values after the smooth feature union.
The reported `1 m` support is four LOD0 cell steps at `0.25 m`, so support thickness alone is unlikely to explain the LOD0 capture. It is only two steps at `0.5 m` and falls below one cell at a `2 m` step. Relief noise is unlikely to be the cause: the open-shaft path bypasses the legacy cap path, where the noise evaluation lives (`ShapeWorldInternal.h:7187–91, 7249–55`).
## Construction and numbers
Build one continuous core-plus-ramp void, smooth-union its components, then subtract that void from the host once. Encode the ledge floor in the void definition and remove the post-union support cap; preserve the support as negative solid in the composed field. Verify that smoothing keeps the intended `4.5 m` core opening.
For the current cubic smooth-max, Research16 estimates a local fillet radius `R ≈ W/(2√2)` for perpendicular unit-gradient fields (`Research16/REPORT.md:29–31`). Applied as a sizing guide:
| Mesh step `h` | Minimum bullnose `R=2h` | Approx. width `W=2√2R` | Slab thickness `T≥2R`* |
|---:|---:|---:|---:|
| 0.25 m | 0.50 m | 1.41 m | 1 m |
| 0.50 m | 1.00 m | 2.83 m | 2 m |
| 2.00 m | 4.00 m | 11.31 m | 8 m |
\*For a bullnose that rounds both the top and underside. A preferred `R=3h` increases the required width and thickness by 50%. These are sampling targets, not guarantees; measure the composed field because its gradients and intersection angles vary.
At LOD0, the existing `1 m` support only meets the minimum two-sided bullnose thickness, with no margin. A `0.75 m` radius is a safer preferred LOD0 target and calls for at least `1.5 m` of slab.
## Far LODs
The L59 capture command forces `--max-level=0` (`CaptureNativeLatticeL59.py:78–85`), so it provides no evidence for coarser meshes. With the owner defaults (`0.25 m` Core voxels, `full_res_clip_levels=2`, `coarse_tile_cells=16`) and the mesher’s extent/cell calculation (`VoxelForgeCore.cpp:2121–22`), nominal steps are `0.25 m` at LOD0, `0.5 m` at LOD1, then `2, 4, 8, 16, 32, 64, 128 m` at LOD2–8. The ramp is a resolved cavern edge; the separate fine-resolution override checks type-4 world-assembly shelf features (`VoxelForgeCore.cpp:2124–49`).
A fixed `1 m` ledge cannot survive those far steps. Keep the detailed ledge in near LODs, refining ramp tiles where needed. At far LODs, fade it into a broad, rounded wall bump and then out of the silhouette. Do not keep thickening the walkable slab: at LOD2 the two-sided bullnose sizing already asks for an `8 m` slab and an `11.3 m` smooth width against a `3 m` passage.
## Audit and slice plan
The L59 audit uses a `0.25 m` corridor margin and excludes the shaft-core room from its room ROI (`AuditLatticeSurfaceL59.py:82–91, 159–205, 229–48`). That leaves only a one-voxel allowance for shards protruding into the core. Expand the ROI and its bounds to cover at least a `1 m` band on both sides of each free edge, plus the full `1 m` underside and headroom. Add edge-specific coverage and roughness measurements so the free rim is scored even when its triangles cannot serve as smooth reference terrain.
Run the raw L59 mesh as a negative control with `expect=fail`; require the edge defect to be detected. Require the corrected candidate and plain-native control to pass. Include all four turns, both free edges, and the support underside.
Proposed slices: sample radial/vertical sections through the core and ledge at quarter-turn intervals and multiple azimuths, at `0.0625 m` increments. Record the core, ramp, support, union, and final density values. Then compare current and corrected LOD0 meshes across half-cell grid phases; inspect LOD1 and LOD2 transitions, followed by the far-LOD bump/fade through LOD8. The existing audit phase option shifts its analytical reference; it does not remesh a shifted grid.
## Uncertainty
The tracked L59 report does not give the resolved `smoothWidth` or `unionWidth`, and the branch has no raw mesh/audit JSON here to localize the shard triangles. The tangent-seam diagnosis is therefore likely, not proven. The support sign reversal is directly visible in the equations and deserves field-level confirmation.
Read-only inspection only; no files were changed and no tests or binaries were run.
@@ -0,0 +1,29 @@
## Ranked suspects
| Rank | Suspect and fit | Cheapest confirming experiment |
|---|---|---|
| **1 — cave segment query arithmetic** — [`ShapeWorldInternal.h:7124–7199`](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:7124). The cave field chooses a nearest segment using computed distances, a `1e-12 m` tie tolerance, and BVH pruning. Small differences in `hypot` or intermediate arithmetic could change the selected segment, its floor/grade data, or whether a node is visited. That fits stable, spatially repeatable failures on a small set of cave tiles. | For one bad tile, find the first differing density lattice coordinate. At that point log the BVH’s visited/pruned nodes and selected segment, then compare with a full segment scan in index order. |
| **2 — cave detection in batched sampling** — [`ShapeWorldInternal.h:3157–3224, 10177–10220`](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:3157). The new X8 and Z-run paths test both manager and resolved cave bounds before falling back to scalar density. A boundary classification or fallback difference could affect coarse samples at particular Z values. The L60 report says broader bulk-versus-scalar diagnostics were tried, so a targeted trace is more useful than repeating that toggle. | At the first differing coordinate, record its manager/resolved stratum, batch route, and scalar result bits. |
| **3 — coarse meshing and skirts** — [`VoxelMarchingCubesMesher.cpp:1239–1305, 2070–2172`](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp:1239). A density difference near zero can change cell classification or interpolation. Skirts also make extra continuous-position `DensityAt` calls, with depth scaled by the mesh step. This is a downstream explanation if density hashes match. | Print the density, mesh, payload, and topology hash comparisons separately. If density matches but mesh output differs, regenerate one bad tile with skirts disabled and compare the surface mesh. |
## LOD1–2 path and other checked constructs
The L60 log reports the same resolved-layout fingerprint, `3c14454ca74615d0`, for the cave runs across GCC and Clang. That points away from different resolved cave inputs, though a fingerprint is not a byte-for-byte comparison. Each compiler is internally stable across worker counts, and the failures recur on the same six LOD1 and eight LOD2 keys. The report says earlier diagnostics covered segment ordering/ties and bulk-versus-scalar sampling. ([Linux60 report](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux60/REPORT.md:11), [run log](/home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l60/full.log:569))
`generateTile` uses `extent = CHUNK_SIZE << level`; in the normal coarse path, LOD1 and LOD2 use steps 2 and 4. Type-4 shelf features can force step 1 on overlapping tiles, while hybrid sharp-feature handling is LOD0-only. The tile still goes through the same marching-cubes mesher. LOD1’s step-2 proof path differs from LOD2’s step-4 path, so proof behavior alone does not explain both levels. Skirts run at these LODs; connector shells are gated to LOD6+, and far stratum shells to LOD7+.
Other legal sources of compiler variation checked:
- `DensityGrid` is allocated with `SetNumUninitialized`; a broken proof-skip invariant could expose unread values. The hash code reads individual mesh fields and density float bits, not struct padding. It does not hash raw object bytes. ([mesher allocation](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp:898), [hashing](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:157))
- C++ does not specify the order in which function arguments are evaluated. I found no output-affecting call in these paths with side effects across arguments. ([C++ call rules](https://eel.is/c++draft/expr.call))
- The cave evaluator uses `sqrt` and `hypot`; I found no `pow`, `exp`, `sin`, or `atan2` in its runtime field path. GCC can use MPFR to fold constant-argument math builtins, but the matching layout fingerprint makes recipe-planning math a weaker suspect here. ([GCC notes](https://gcc.gnu.org/gcc-4.3/changes.html))
- Integer lattice coordinates convert exactly to `float` within the scene’s range; the cave evaluator then uses doubles. Long-double bounds calculations are present in cave queries. On this Linux target, GCC and Clang likely share the platform ABI and math libraries, but `BuildLinux.sh` does not pin or report Clang’s standard-library choice.
- The changed segment `std::sort` comparator breaks equal centers by segment index, giving a total order for valid segments. So differing `std::sort` handling of equivalent keys is a weak explanation there. A separate stratum sort compares only bottom Z, so duplicate bottoms could leave equivalent elements in implementation-dependent order. ([sort rules](https://eel.is/c++draft/alg.sort), [stable sort rules](https://eel.is/c++draft/algorithm.stable))
- Signed overflow and invalid shifts would be undefined behavior, but the LOD1–8 extent shifts are in range. No concrete overflow in the affected path stands out.
- The peer comparator reports only an aggregate failure: it checks density, mesh, payload, and topology hashes, not material hash. Splitting those results is the cheapest first discriminator. ([`SameTileOutput`](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Source/StreamBenchmarkPipeline.h:798))
## MSVC notes
The segment math, library math variation, argument-order latitude, equivalent-key sort behavior, and any undefined uninitialized/overflow behavior can also affect Windows builds. MSVC’s `long double` has the same precision and representation as `double`, unlike the extended-precision Linux ABI, so the long-double query calculations are a particular portability risk. ([MSVC type documentation](https://learn.microsoft.com/en-us/cpp/cpp/data-type-ranges?view=msvc-170))
`CoreNamespacePreamble.h` disables FP contraction for Clang, GCC, and MSVC; the Windows result still depends on the project’s effective `/fp` mode and runtime math implementation. ([preamble](/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/CoreNamespacePreamble.h:61), [MSVC FP modes](https://learn.microsoft.com/en-us/cpp/build/reference/fp-specify-floating-point-behavior?view=msvc-170))
@@ -0,0 +1,79 @@
# VoxelForge — R31 self-check: far-LOD work
## Measurement basis
The L55 figures below are **worker-seconds**, summed across tiles. They are not wall time or predicted savings. The L60 matrix is useful for whole-view comparisons, but it has no matching per-LOD stage breakdown for every generator.
The far LOD4–8 totals and component sums are derived from the logged rows:
| Run | Worker sum | Column evaluation | MC exclusive | Ground exclusive | Cap exclusive | Skirts | Assembly | Unassigned remainder |
|---|---:|---:|---:|---:|---:|---:|---:|---:|
| Natural SW | 7.878235 | 2.699576 | 2.248091 | 0.000440 | 0.870358 | 0.789370 | 0.025267 | 1.245133 |
| Floating Islands | 13.653194 | 2.812932 | 3.709855 | 0.000605 | 0.942610 | 0.981823 | 0.028482 | 5.176887 |
| flatField profile | 4.675822 | — | — | — | — | — | — | — |
SW sums are derived from the five LOD rows: [LOD4](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/sw.log:9560>), [LOD5](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/sw.log:9561>), [LOD6](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/sw.log:9562>), [LOD7](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/sw.log:9563>), [LOD8](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/sw.log:9564>). FI sums use [LOD4](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/fi.log:9565>), [LOD5](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/fi.log:9566>), [LOD6](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/fi.log:9567>), [LOD7](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/fi.log:9568>), [LOD8](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/fi.log:9569>). flatField’s total is derived from [LOD4](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l53/profile-cold-view-flatField-clang.log:46>) through [LOD8](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l53/profile-cold-view-flatField-clang.log:50>).
The remainder is calculated, not a named timer. It includes work the summary does not allocate to those bins. Also, R28 calls SW’s 2.699576 column total “worker-ms”; the raw rows show that it is **worker-seconds** (2,699.576 ms).
## Far-LOD inventory and visibility
`generateTile` selects the coarse cell count and step at [VoxelForgeCore.cpp:2121](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:2121>), then calls `GenerateMesh` at [VoxelForgeCore.cpp:2642](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:2642>). MC produces the sampled density surface, including terrain and passage geometry. Those surfaces can appear in the far view and horizon; generation does not establish that a particular tile is visible from the near-field camera.
- **Connector repair:** `AppendFarConnectorShells` returns below LOD6 (`if(Key.Level<6||Step<=0)return;`) and tests four radial directions ([FarShellsInternal.h:247](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/FarShellsInternal.h:247>), [lines 248–300](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/FarShellsInternal.h:248>)). It marks missing source-surface crossings and emits wall panels ([lines 369–373](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/FarShellsInternal.h:369>), [lines 567–587](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/FarShellsInternal.h:567>)). The stations are 128 voxels at LOD6 and at least 256 at LOD7–8 ([lines 314–317](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/FarShellsInternal.h:314>)). These walls can be seen through a passage or opening; the logs do not show that they are always visible from the near-field camera.
L55 gives no LOD-aggregate connector cost. Its per-tile LOD6 row reports `connector_ms=17.641` and `column_ms=15.935` ([sw.log:3779](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/sw.log:3779>)); those timers overlap. A separate L52 log records `connector_shell_ms=1334.783` and `connector_column_compute_ms=1113.385` for LOD6 ([final-surface-world.log:99](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l52/profile/final-surface-world.log:99>)). That 1.335 worker-seconds is an **inclusive historical timer**, not extra work to add to the column total or to L55.
- **Far stratum shells:** `AppendFarStratumShells` returns below LOD7, below step 4, or without a manager (`if(Key.Level<7||Step<4||!World.Manager)return;`) ([FarShellsInternal.h:1054](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/FarShellsInternal.h:1054>)). It compares source stratum crossings with mesh hits and emits subdivided patches for missing crossings ([lines 1096–1111](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/FarShellsInternal.h:1096>), [lines 1131–1180](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/FarShellsInternal.h:1131>)); `generateTile` calls it at [VoxelForgeCore.cpp:2738](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:2738>). The L52 log records 6.837 ms at LOD7 and 21.687 ms at LOD8 ([line 103](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l52/profile/final-surface-world.log:103>), [line 107](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l52/profile/final-surface-world.log:107>)); their 28.524 ms sum is derived and belongs to that older run. Current code can skip this repair at LOD7+ for native-surface-horizon tiles without edits or island overlap ([VoxelForgeCore.cpp:2322](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:2322>)). Separate boundary floor and ceiling functions still run at [VoxelForgeCore.cpp:2847](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:2847>).
- **Cap refinement:** `RefineFarNativeCap` applies from LOD4 when an owner generator exists ([VoxelForgeCore.cpp:697](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:697>)); it builds cap surfaces from the heightfield samples ([lines 728–740](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:728>), [lines 798–859](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:798>)). Caps can be seen from inside a cave or through an opening; they are not proven invisible from the exterior camera. L55’s cap-exclusive worker costs are 0.870358 SW and 0.942610 FI, derived in the table. A separate L52 log reports LOD8 `mc_grid_ms=1348.084` ([final-surface-world.log:107](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l52/profile/final-surface-world.log:107>)); that is an older-run grid cost, not the L55 total.
- **Ground refinement:** `RefineFarNativeGround` applies from LOD5 ([VoxelForgeCore.cpp:394](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:394>)). It reconstructs the ground profile on a grid and replaces nearby coarse upward MC faces ([lines 399–409](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:399>), [lines 582–588](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:582>)). The ground can affect the far terrain and horizon silhouette. The **ground-refinement-exclusive** L55 cost is only 0.440 ms SW and 0.605 ms FI, derived above. The much larger column-evaluation bin is shared across MC and refinement work; it is not a ground-only cost.
- **Skirts:** the mesher extrudes skirts from boundary edges, with depth based on `SkirtCells` and mesh step ([VoxelMarchingCubesMesher.cpp:2067](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp:2067>), [line 2072](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp:2072>)). The viewer marks a face visible when there is no same-LOD neighbor and filters triangles against that mask ([StandaloneViewer.cpp:132](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Source/StandaloneViewer.cpp:132>), [lines 833–836](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Source/StandaloneViewer.cpp:833>)). Only exposed faces can render; same-LOD covered faces are already hidden. L55’s skirt totals are 0.789370 SW and 0.981823 FI worker-seconds, derived above.
## Projected size at the clip radius
Linux15 uses 0.000613592 rad/px and closest display distances of 192, 384, 768, 1,536 and 3,072 m for LOD4–8 ([gate basis](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux15/REPORT.md:23>), [swap distances](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux15/REPORT.md:46>)). With the 25 cm voxel and 16 coarse cells in the owner data, the derived one-pixel footprints and lattice intervals are:
| LOD | Distance | 1 px in world | MC cell interval | Cap interval | Ground interval | Connector station |
|---:|---:|---:|---:|---:|---:|---:|
| 4 | 192 m | 0.118 m | 8 m ≈ 68 px | 4 m ≈ 34 px | — | — |
| 5 | 384 m | 0.236 m | 16 m ≈ 68 px | 32 m ≈ 136 px | 8 m ≈ 34 px | — |
| 6 | 768 m | 0.471 m | 32 m ≈ 68 px | 32 m ≈ 68 px | 16 m ≈ 34 px | 32 m ≈ 68 px |
| 7 | 1,536 m | 0.942 m | 64 m ≈ 68 px | 32 m ≈ 34 px | 32 m ≈ 34 px | 64 m ≈ 68 px |
| 8 | 3,072 m | 1.885 m | 128 m ≈ 68 px | 128 m ≈ 68 px interior; 32 m ≈ 17 px at boundary | same | 128 m ≈ 68 px |
These are **derived projected lattice intervals**, not measured geometry error or proof of visibility. Orientation, connector direction, terrain occlusion and the camera view change the projection. No component-by-component near-camera visibility capture was found. The sizes do not support calling the current grids or connector stations “subpixel” based on distance alone.
## Exact cuts
- **Reuse an identical heightfield column:** exact if the recipe, plan, context and integer XY coordinates match. The shared cache key contains recipe/plan identities and hashes plus XY, and deliberately omits LOD ([SharedColumnCache.h:35](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/include/VoxelForgeCore/SharedColumnCache.h:35>)); the cache is enabled by default and scoped during tile generation ([VoxelForgeCore.h:1743](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h:1743>), [VoxelForgeCore.cpp:2013](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:2013>)). The invariant is the same pure column result bit-for-bit. The L55 run already had the shared cache on ([sw.log:2](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/sw.log:2>)), so only remaining misses or evictions are an opportunity.
- **Reuse coincident MC density samples:** exact for identical field identity, edits and world-space XYZ sample coordinates, if the same scalar is reused and each tile retains its existing traversal, interpolation and gradient arithmetic. The invariant is the exact sampled float. Because the clipmap tiles partition the volume, this is generally boundary or nested-lattice reuse, not a way to reuse an entire child tile for its parent. No per-generator saving is measured.
- **Skip skirt faces hidden by the active mask:** exact for the rendered mesh with that fixed mask, because the viewer already drops those triangles. The invariant is the visible index set. The mesh payload and hashes change; the mask must be part of cache identity and geometry must be regenerated when neighbor coverage changes ([StandaloneViewer.cpp:4846](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Source/StandaloneViewer.cpp:4846>)).
None of those proves it safe to remove cap, ground or connector geometry. Those surfaces can be visible in some views.
## Ranked plan and savings
The logs support opportunity ceilings and one modeled skirt estimate, **not a credible expected realized saving per generator**. Worker-seconds cannot be read as wall-time reduction.
1. **Audit and reduce exact column/sample misses.** The measured column pool is 2.699576 SW and 2.812932 FI worker-seconds. The flatField run has 4.675822 worker-seconds across the entire far pool but no reliable stage split. These are ceilings, not expected savings; the cache is already active. R14’s LOD8 overlap report recorded 32,738 repeated neighboring XY evaluations, including 30,446 cross-worker repeats ([Round14:41](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Round14/REPORT.md:41>)); it did not price those repeats in worker-seconds.
2. **Skip hidden skirt faces for a fixed neighbor mask.** R23 measured 87.4% hidden SW and 87.0% hidden FI skirt triangles, then modeled savings by applying those fractions to timer totals ([Research23:54](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Research23/REPORT.md:54>)). Applying them to the L55 far-LOD skirt timers gives a **cross-run modeled gross** of about 0.690 SW and 0.854 FI worker-seconds; actual savings will be lower because triangle share is not time share and edge scanning remains. flatField has no comparable reliable skirt timer.
3. **Test further LOD8 sampling changes or an LOD7–8 merge only as versioned geometry changes.** No saving is forecast. Round14 already moved LOD8 interior sampling to MC spacing while retaining 128-voxel boundary strips; its fresh W12 comparison reports 3.206 versus 13.534 worker-seconds for the default and step-128 control ([Round14:47](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Round14/REPORT.md:47>), [Round14:49](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Round14/REPORT.md:49>)). That gain is already harvested, not available again. Any further change needs a far-horizon silhouette gate.
4. **Do not coarsen connector stations on current evidence.** The LOD6 connector timer is an historical 1.335 worker-second inclusive ceiling from L52, not a forecast. The two-times station test failed: 1,349 holes and 1,690 floaters, with hole IDs increasing from 8 to 221 ([Linux15:53](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux15/REPORT.md:53>)). At the calculated clip distances, nominal axial station spacing is about 68 px when transverse to view; it is not generally subpixel.
5. **Defer far jobs only for perceived readiness.** Reordering can change when far tiles appear but saves **0 worker-seconds** if all tiles are still generated. L60’s whole-view wall matrix does not provide per-generator far-stage savings for periodic hills, mirrored relief, Motifs, Caves or Lattice; I would not transfer SW numbers to them.
## Gates and remaining uncertainty
For connector or refinement changes, keep the Linux15 screen-space gate: 1 px maximum normal displacement, shading delta at most 1/255, no missing surface, hole, crack or floater, and no worse adjacent-LOD swap ([Linux15:23](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux15/REPORT.md:23>)). Doubling refinement already failed badly across LOD4–8 ([Linux15:33](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux15/REPORT.md:33>)).
Retain Round14’s ground/cap agreement checks, cap-band census, 2,520/2,520 upward-ray coverage and worker-count determinism ([Round14:88](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Round14/REPORT.md:88>), [Round14:90](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Round14/REPORT.md:90>), [Round14:92](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Round14/REPORT.md:92>), [Round14:96](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Round14/REPORT.md:96>)). Add matched 1440p horizon captures with a measured silhouette-pixel delta for any far-grid or LOD-merge change; Round14 has captures, but they are not a quantitative silhouette gate.
Assumptions: projected sizes use the Linux15 nearest-display distances and the 25 cm, 16-coarse-cell owner settings; they are not per-tile occlusion measurements. L55, L52 and L53 use different runs and tile counts, so their figures are not interchangeable. L55 aggregates connector and stratum repair poorly; the LOD6 per-tile connector timer overlaps column computation. No scripts, tests, builds, binaries or tree changes were made.
@@ -0,0 +1,40 @@
# R31 — Far LOD (4–8) inventory and cuts
Worker-seconds below are summed CPU time across workers, not wall time. The L55, R28 and L60 runs have different tile counts; treat them as cost evidence, not a matched before/after comparison.
## Inventory and visibility
Far LODs are not automatically offscreen. On the 25 cm owner plan, the L15 gate puts the closest displayed tiles at about 192, 384, 768, 1,536 and 3,072 m for LOD4–8. At the owner’s 0.61 mrad/pixel reference, one pixel spans roughly 0.12, 0.23, 0.47, 0.94 and 1.87 m. The corresponding MC cell widths are about 8–128 m. That spacing alone does not predict surface error, but it rules out treating the far rings as invisible: terrain silhouettes, exposed caps and tunnel openings can reach the near-field camera. [Linux15 gate](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux15/REPORT.md:23>)
| Work | Output and visibility | Measured cost |
|---|---|---:|
| Grid MC — [`generateTile`](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:2642>) | Coarse 3D field surface: terrain, strata and passage walls. It contributes to horizon silhouettes and can be directly visible inside passages. | SW LOD4–8 MC exclusive work totals **2.248 worker-s**; LOD8 alone is **1.244 s**. This is the full MC stage excluding columns and skirts, not just its grid-fill timer. |
| Connector repair — [`AppendFarConnectorShells`](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/FarShellsInternal.h:244>) | Detects missing coarse passage walls and adds shell panels. LOD6+ uses axial samples and four cross-section directions; these walls can be visible down a passage. | R22 reports **1.335 worker-s** at SW LOD6. L55 per-tile records show long connector spans (one LOD6 tile: 17.6 ms) alongside 15.9 ms of column computation. These timers overlap; don’t add them as independent costs. Native surface horizon tiles at LOD7–8 can skip far-shell repair ([code](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:2322>)). |
| Cap refinement — [`RefineFarNativeCap`](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:693>) | Adds downward-facing ceiling triangles from the cap heightfield. Visible from below or within exposed strata. | **0.870 worker-s** across SW LOD4–8, excluding its column work. |
| Ground columns/refinement — [`RefineFarNativeGround`](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeCore.cpp:389>) | LOD5+ samples columns to add the ground sheet and replace nearby upward-facing MC faces. The resulting ground and horizon remain visible from above. | Ground-refinement geometry itself is only about **0.00044 worker-s** exclusive in the SW summary. Its heightfield evaluations are counted in the larger column pool. |
| Skirts — [`EmitSkirts`](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp:2130>) | Extends tile edges to cover cracks. Only faces exposed by the active neighbor mask need drawing; outer clip edges can remain visible. | **0.789 worker-s** and **425,784 triangles** across SW LOD4–8. The viewer already filters skirt faces by neighbor mask ([mask logic](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Source/StandaloneViewer.cpp:105>)). |
The L55 SW LOD4–8 pool is **7.878 worker-s**: LOD4 1.426, LOD5 0.553, LOD6 1.940, LOD7 1.103, LOD8 2.857. The measured column-evaluation share is **2.700 worker-s**. Remaining work includes MC, cap refinement, skirts and shell/boundary work; connector timers overlap column computation. [L55 per-LOD rows](</home/jahni/VoxelM/Plugins/VoxelForge/Saved/Linux/l55/timeline-static/sw.log:9560>) [R28 cost ledger](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Research28/REPORT.md:20>)
## Cuts and ranked plan
1. **Reuse exact samples across tiles and LODs.** Reuse heightfield or density results only for identical recipe, plan and integer sample coordinates, preserving the calculation order. This can leave output unchanged. But the shared heightfield cache is already on by default, so **2.700 worker-s is only the ceiling for eliminating all remaining SW far-column computation, not expected duplicate work**. Count actual cross-LOD misses before crediting savings. [Cache path](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/WorldAssemblerInternal.h:2100>)
2. **Skip skirt faces hidden by the current neighbor mask.** This preserves rendered output for a fixed neighbor set, but changes mesh payload and hashes. Pass the mask before extrusion, include it in cache identity, and rebuild if coverage changes. Applying the prior 87.4% hidden-triangle share to SW far skirts suggests **at most 0.690 worker-s gross**; deleting every far skirt is an absolute **0.789 worker-s** ceiling. The estimate is optimistic: triangle share is not a time share, and edge scanning remains. [R23 skirt model](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Research23/REPORT.md:54>)
3. **Try bounded screen-space refinement changes.** This changes far geometry, so require peer determinism and a recipe version bump. Uniformly doubling refinement spacing is already rejected: LOD4–8 exceeded the 1 px geometry and shading gates and introduced holes and cracks. Connector station doubling also opened holes. Credit **zero savings** to those settings. The narrower cap-refinement budget at stake for SW is 0.870 worker-s, but no safe fraction is measured. [Linux15 results](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux15/REPORT.md:33>)
The existing LOD8 change is already a successful cut, not future headroom: the interior now uses MC spacing (512 voxels / 128 m at 25 cm), retaining 128-voxel boundary strips. R14 measured the 8-tile BuildHost LOD8 p95 falling **76.7%**, from 59.848 to 13.933 ms; its fresh Standalone comparison reduced LOD8 work from 13.534 to 3.206 worker-s. Further coarsening should be treated as a new candidate. [Round14](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Round14/REPORT.md:7>)
4. **Defer far tiles only for perceived readiness.** This saves **0 worker-s**; it only moves when the work runs. Measure near-ring readiness and first useful presentation separately from whole-view completion.
**Per-generator savings evidence:** SW has the breakdown above. R28 measured **4.676 worker-s** for flatField LOD4–8, but without the same stage split. L55 measured **13.653 worker-s** for Floating Islands LOD4–8; its far skirt pool is 0.982 worker-s, or roughly 0.854 worker-s under the same optimistic hidden-triangle model. Do not transfer SW column or cap costs to FI. For periodic hills, mirrored relief, Motifs, Caves and Lattice, L60 reports whole-view wall times but no comparable far worker-stage breakdown; a per-generator savings estimate is not supported by these logs. [R28 generator ledger](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Research28/REPORT.md:12>) [L60 matrix](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux60/REPORT.md:23>)
## Gates
- Run matched LOD4–8 views and enforce the existing **≤1 px maximum geometry error**, **≤1/255 shading delta**, no-worse adjacent-LOD swaps, and no missing surface, hole, crack or floater. Keep the negative controls that prove the checks catch regressions. [Linux15 gate](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux15/REPORT.md:23>)
- For connectors, compare stable tile/ray/station IDs and test all four directions. For caps, check the existing ray coverage and ground/cap agreement. Review matched near and horizon captures for silhouette changes.
- Require Clang/GCC and W1/W8/W12 peer hashes. Bump recipe/cache identity for changed mesh payload or geometry.
- Compare unprofiled whole-view medians and per-LOD worker totals on matched tile sets; don’t infer wall savings by dividing every worker delta by twelve.
I read code, reports, logs and the web. Unreal’s documentation also describes LOD selection in terms of projected screen size, which supports using the owner’s screen-space gate here. [Epic LOD documentation](https://dev.epicgames.com/documentation/en-us/unreal-engine/creating-and-using-lods-in-unreal-engine) No scripts, tests, builds, binaries or tree changes were made.
+33 -32
View File
@@ -105,34 +105,7 @@ namespace
uint8 ComputeVisibleSkirtFaceMask(const FVoxelTileKey& Tile, uint8 ComputeVisibleSkirtFaceMask(const FVoxelTileKey& Tile,
const std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash>& Desired) const std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash>& Desired)
{ {
const FIntVector Origin=Tile.OriginVoxels(); return ComputeStandaloneVisibleSkirtFaceMask(Tile,Desired);
const int32 Extent=Tile.ExtentVoxels();
const auto FloorDiv=[](int64 Value,int64 Divisor)
{
int64 Quotient=Value/Divisor;
if(Value%Divisor<0)--Quotient;
return static_cast<int32>(Quotient);
};
uint8 Mask=0;
for(int32 Axis=0;Axis<3;++Axis)
for(int32 Side=0;Side<2;++Side)
{
int64 Probe[3]={Origin.X+Extent/2,Origin.Y+Extent/2,Origin.Z+Extent/2};
Probe[Axis]=Side?static_cast<int64>((&Origin.X)[Axis])+Extent
:static_cast<int64>((&Origin.X)[Axis])-1;
int32 NeighborLevel=-1;
for(int32 Level=0;Level<=8;++Level)
{
const int64 NeighborExtent=static_cast<int64>(CHUNK_SIZE)<<Level;
const FIntVector Coord(FloorDiv(Probe[0],NeighborExtent),
FloorDiv(Probe[1],NeighborExtent),FloorDiv(Probe[2],NeighborExtent));
if(Desired.contains(FVoxelTileKey(Coord,Level)))
{NeighborLevel=Level;break;}
}
if(NeighborLevel<0||NeighborLevel!=Tile.Level)
Mask|=static_cast<uint8>(1u<<(Axis*2+Side));
}
return Mask;
} }
FMeshArtifactAudit AuditMeshArtifacts(const FStandaloneCpuTile& Tile, FMeshArtifactAudit AuditMeshArtifacts(const FStandaloneCpuTile& Tile,
@@ -4845,14 +4818,15 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
void RefreshResidentSkirtMasks() void RefreshResidentSkirtMasks()
{ {
for(auto& [Key,Resident]:ResidentTiles) std::vector<FVoxelTileKey> Invalidate;
for(const auto& [Key,Resident]:ResidentTiles)
{ {
if(!DesiredTileSet.contains(Key))continue; if(!DesiredTileSet.contains(Key))continue;
const uint8 NewMask=ComputeVisibleSkirtFaceMask(Key,DesiredTileSet); const uint8 NewMask=ComputeVisibleSkirtFaceMask(Key,DesiredTileSet);
if(NewMask==Resident.ActiveSkirtFaceMask)continue; if(NewMask==Resident.ActiveSkirtFaceMask)continue;
Resident.ActiveSkirtFaceMask=NewMask; Invalidate.push_back(Key);
MarkBatchDirty(BatchForTile(Key));
} }
for(const FVoxelTileKey& Key:Invalidate)RemoveResidentTile(Key);
} }
static bool TileKeyLess(const FVoxelTileKey& A,const FVoxelTileKey& B) static bool TileKeyLess(const FVoxelTileKey& A,const FVoxelTileKey& B)
@@ -7010,6 +6984,8 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
double NativeMeshingMilliseconds=0.0; double NativeMeshingMilliseconds=0.0;
uint64 WholeSolidBoxes=0,WholeAirBoxes=0,WholeMixedBoxes=0,WholeUnknownBoxes=0; uint64 WholeSolidBoxes=0,WholeAirBoxes=0,WholeMixedBoxes=0,WholeUnknownBoxes=0;
uint64 WholeFieldCalls=0,WholeColumnHits=0,WholeColumnMisses=0,WholeTileSkips=0; uint64 WholeFieldCalls=0,WholeColumnHits=0,WholeColumnMisses=0,WholeTileSkips=0;
uint64 WholeColumnRepeats=0,WholeColumnComputes=0,WholeSampleCacheHits=0,WholeSampleCacheComputes=0;
uint64 WholeSkirtTrianglesEmitted=0,WholeSkirtTriangleSeedsSkipped=0;
uint64 WholeFloatFieldCalls=0,WholeLatticeFieldCalls=0,WholeUnclampedFieldCalls=0; uint64 WholeFloatFieldCalls=0,WholeLatticeFieldCalls=0,WholeUnclampedFieldCalls=0;
double WholeClassificationMilliseconds=0.0,WholeColumnComputeMilliseconds=0.0; double WholeClassificationMilliseconds=0.0,WholeColumnComputeMilliseconds=0.0;
double WholeMeshingMilliseconds=0.0; double WholeMeshingMilliseconds=0.0;
@@ -7022,6 +6998,9 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
uint64 WholeFloatFieldCallsByLevel[MaxLevels+1]{},WholeLatticeFieldCallsByLevel[MaxLevels+1]{}; uint64 WholeFloatFieldCallsByLevel[MaxLevels+1]{},WholeLatticeFieldCallsByLevel[MaxLevels+1]{};
uint64 WholeUnclampedFieldCallsByLevel[MaxLevels+1]{}; uint64 WholeUnclampedFieldCallsByLevel[MaxLevels+1]{};
uint64 WholeColumnMissesByLevel[MaxLevels+1]{},WholeTileSkipsByLevel[MaxLevels+1]{}; uint64 WholeColumnMissesByLevel[MaxLevels+1]{},WholeTileSkipsByLevel[MaxLevels+1]{};
uint64 WholeColumnRepeatsByLevel[MaxLevels+1]{},WholeColumnComputesByLevel[MaxLevels+1]{};
uint64 WholeSampleCacheHitsByLevel[MaxLevels+1]{},WholeSampleCacheComputesByLevel[MaxLevels+1]{};
uint64 WholeSkirtTrianglesEmittedByLevel[MaxLevels+1]{},WholeSkirtTriangleSeedsSkippedByLevel[MaxLevels+1]{};
double WholeClassificationMillisecondsByLevel[MaxLevels+1]{}; double WholeClassificationMillisecondsByLevel[MaxLevels+1]{};
double WholeColumnComputeMillisecondsByLevel[MaxLevels+1]{}; double WholeColumnComputeMillisecondsByLevel[MaxLevels+1]{};
double WholeMeshingMillisecondsByLevel[MaxLevels+1]{}; double WholeMeshingMillisecondsByLevel[MaxLevels+1]{};
@@ -7086,6 +7065,13 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
WholeUnclampedFieldCalls+=Tile.CoreDiagnostics.unclampedFieldCalls; WholeUnclampedFieldCalls+=Tile.CoreDiagnostics.unclampedFieldCalls;
WholeColumnHits+=Tile.CoreDiagnostics.columnCacheHits; WholeColumnHits+=Tile.CoreDiagnostics.columnCacheHits;
WholeColumnMisses+=Tile.CoreDiagnostics.columnCacheMisses; WholeColumnMisses+=Tile.CoreDiagnostics.columnCacheMisses;
WholeColumnRepeats+=Tile.CoreDiagnostics.columnCacheHits
+Tile.CoreDiagnostics.sharedColumnCacheHits;
WholeColumnComputes+=Tile.CoreDiagnostics.columnEvaluations;
WholeSampleCacheHits+=Tile.CoreDiagnostics.densitySampleCacheHits;
WholeSampleCacheComputes+=Tile.CoreDiagnostics.densitySampleCacheComputes;
WholeSkirtTrianglesEmitted+=Tile.CoreDiagnostics.marchingCubesSkirtTrianglesEmitted;
WholeSkirtTriangleSeedsSkipped+=Tile.CoreDiagnostics.marchingCubesSkirtTriangleSeedsSkipped;
WholeTileSkips+=Tile.CoreDiagnostics.meshingSkipped?1u:0u; WholeTileSkips+=Tile.CoreDiagnostics.meshingSkipped?1u:0u;
WholeCapRefinedTiles+=Tile.CoreDiagnostics.capRefined?1u:0u; WholeCapRefinedTiles+=Tile.CoreDiagnostics.capRefined?1u:0u;
WholeCapRefinedCells+=Tile.CoreDiagnostics.capRefinedCells; WholeCapRefinedCells+=Tile.CoreDiagnostics.capRefinedCells;
@@ -7113,6 +7099,15 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
WholeUnclampedFieldCallsByLevel[Level]+=Tile.CoreDiagnostics.unclampedFieldCalls; WholeUnclampedFieldCallsByLevel[Level]+=Tile.CoreDiagnostics.unclampedFieldCalls;
WholeColumnHitsByLevel[Level]+=Tile.CoreDiagnostics.columnCacheHits; WholeColumnHitsByLevel[Level]+=Tile.CoreDiagnostics.columnCacheHits;
WholeColumnMissesByLevel[Level]+=Tile.CoreDiagnostics.columnCacheMisses; WholeColumnMissesByLevel[Level]+=Tile.CoreDiagnostics.columnCacheMisses;
WholeColumnRepeatsByLevel[Level]+=Tile.CoreDiagnostics.columnCacheHits
+Tile.CoreDiagnostics.sharedColumnCacheHits;
WholeColumnComputesByLevel[Level]+=Tile.CoreDiagnostics.columnEvaluations;
WholeSampleCacheHitsByLevel[Level]+=Tile.CoreDiagnostics.densitySampleCacheHits;
WholeSampleCacheComputesByLevel[Level]+=Tile.CoreDiagnostics.densitySampleCacheComputes;
WholeSkirtTrianglesEmittedByLevel[Level]
+=Tile.CoreDiagnostics.marchingCubesSkirtTrianglesEmitted;
WholeSkirtTriangleSeedsSkippedByLevel[Level]
+=Tile.CoreDiagnostics.marchingCubesSkirtTriangleSeedsSkipped;
WholeTileSkipsByLevel[Level]+=Tile.CoreDiagnostics.meshingSkipped?1u:0u; WholeTileSkipsByLevel[Level]+=Tile.CoreDiagnostics.meshingSkipped?1u:0u;
WholeClassificationMillisecondsByLevel[Level] WholeClassificationMillisecondsByLevel[Level]
+=Tile.CoreDiagnostics.classificationMilliseconds; +=Tile.CoreDiagnostics.classificationMilliseconds;
@@ -7378,7 +7373,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
if(CoreWorld) if(CoreWorld)
for(int32 Level=0;Level<=MaxLevels;++Level) for(int32 Level=0;Level<=MaxLevels;++Level)
if(TilesByLevel[Level]>0) if(TilesByLevel[Level]>0)
std::printf("[WorldAssemblerLODProfile] lod=%d tiles=%llu solid_boxes=%llu air_boxes=%llu mixed_boxes=%llu unknown_boxes=%llu whole_tile_skips=%llu field_calls=%llu float_field_calls=%llu lattice_field_calls=%llu unclamped_field_calls=%llu column_hits=%llu column_misses=%llu column_compute_worker_ms=%.3f classification_worker_ms=%.3f meshing_worker_ms=%.3f\n", std::printf("[WorldAssemblerLODProfile] lod=%d tiles=%llu solid_boxes=%llu air_boxes=%llu mixed_boxes=%llu unknown_boxes=%llu whole_tile_skips=%llu field_calls=%llu float_field_calls=%llu lattice_field_calls=%llu unclamped_field_calls=%llu column_hits=%llu column_misses=%llu column_computes=%llu column_repeats=%llu sample_cache_computes=%llu sample_cache_hits=%llu skirt_triangles_emitted=%llu skirt_triangle_seeds_skipped=%llu column_compute_worker_ms=%.3f classification_worker_ms=%.3f meshing_worker_ms=%.3f\n",
Level,static_cast<unsigned long long>(TilesByLevel[Level]), Level,static_cast<unsigned long long>(TilesByLevel[Level]),
static_cast<unsigned long long>(WholeSolidBoxesByLevel[Level]), static_cast<unsigned long long>(WholeSolidBoxesByLevel[Level]),
static_cast<unsigned long long>(WholeAirBoxesByLevel[Level]), static_cast<unsigned long long>(WholeAirBoxesByLevel[Level]),
@@ -7391,6 +7386,12 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
static_cast<unsigned long long>(WholeUnclampedFieldCallsByLevel[Level]), static_cast<unsigned long long>(WholeUnclampedFieldCallsByLevel[Level]),
static_cast<unsigned long long>(WholeColumnHitsByLevel[Level]), static_cast<unsigned long long>(WholeColumnHitsByLevel[Level]),
static_cast<unsigned long long>(WholeColumnMissesByLevel[Level]), static_cast<unsigned long long>(WholeColumnMissesByLevel[Level]),
static_cast<unsigned long long>(WholeColumnComputesByLevel[Level]),
static_cast<unsigned long long>(WholeColumnRepeatsByLevel[Level]),
static_cast<unsigned long long>(WholeSampleCacheComputesByLevel[Level]),
static_cast<unsigned long long>(WholeSampleCacheHitsByLevel[Level]),
static_cast<unsigned long long>(WholeSkirtTrianglesEmittedByLevel[Level]),
static_cast<unsigned long long>(WholeSkirtTriangleSeedsSkippedByLevel[Level]),
WholeColumnComputeMillisecondsByLevel[Level], WholeColumnComputeMillisecondsByLevel[Level],
WholeClassificationMillisecondsByLevel[Level], WholeClassificationMillisecondsByLevel[Level],
WholeMeshingMillisecondsByLevel[Level]); WholeMeshingMillisecondsByLevel[Level]);
+11 -34
View File
@@ -1032,9 +1032,8 @@ namespace
constexpr double FloorToSoffit=2.6; constexpr double FloorToSoffit=2.6;
constexpr double Pitch=5.0; constexpr double Pitch=5.0;
constexpr int Turns=4; constexpr int Turns=4;
constexpr double RampSupportDepth=1.0;
constexpr double FloorBlend=1.9; constexpr double FloorBlend=1.9;
constexpr double FeatureUnionBlend=1.0; constexpr double FeatureUnionBlend=0.05;
constexpr double CorridorSpacing=14.0; constexpr double CorridorSpacing=14.0;
constexpr double MazeCellSpan=16.0; constexpr double MazeCellSpan=16.0;
constexpr double RouteGradeLimit=13.0; constexpr double RouteGradeLimit=13.0;
@@ -1054,7 +1053,7 @@ namespace
{for(int byte=0;byte<8;++byte){hash^=static_cast<std::uint8_t>(word>>(byte*8));hash*=1099511628211ull;}}; {for(int byte=0;byte<8;++byte){hash^=static_cast<std::uint8_t>(word>>(byte*8));hash*=1099511628211ull;}};
addWord(static_cast<std::uint32_t>(seed)); addWord(static_cast<std::uint32_t>(seed));
addWord(static_cast<std::uint32_t>(latticeStrateIndex)); addWord(static_cast<std::uint32_t>(latticeStrateIndex));
addWord(NativeLatticeFieldRecipeVersion); addWord(1);
for(unsigned char value:stableId){hash^=value;hash*=1099511628211ull;} for(unsigned char value:stableId){hash^=value;hash*=1099511628211ull;}
hash^=hash>>30;hash*=0xbf58476d1ce4e5b9ull; hash^=hash>>30;hash*=0xbf58476d1ce4e5b9ull;
hash^=hash>>27;hash*=0x94d049bb133111ebull; hash^=hash>>27;hash*=0x94d049bb133111ebull;
@@ -1084,8 +1083,7 @@ namespace
edge.boreRadiusMicrometres=1500000; edge.boreRadiusMicrometres=1500000;
edge.boreHeightMicrometres=static_cast<std::int64_t>( edge.boreHeightMicrometres=static_cast<std::int64_t>(
std::llround(FloorToSoffit*1.0e6)); std::llround(FloorToSoffit*1.0e6));
edge.floorSupportDepthMicrometres=static_cast<std::int64_t>( edge.floorSupportDepthMicrometres=1000000;
std::llround(RampSupportDepth*1.0e6));
edge.smoothWidthMicrometres=static_cast<std::int64_t>(std::llround(FloorBlend*1.0e6)); edge.smoothWidthMicrometres=static_cast<std::int64_t>(std::llround(FloorBlend*1.0e6));
edge.unionWidthMicrometres=static_cast<std::int64_t>( edge.unionWidthMicrometres=static_cast<std::int64_t>(
std::llround(FeatureUnionBlend*1.0e6)); std::llround(FeatureUnionBlend*1.0e6));
@@ -1123,26 +1121,6 @@ namespace
helixEnd[1]+orphanOffset[1],helixEnd[2]}); helixEnd[1]+orphanOffset[1],helixEnd[2]});
const std::string departureRoomId="lattice/spine/departure-room-v1"; const std::string departureRoomId="lattice/spine/departure-room-v1";
addRoom(departureRoomId,{departure[0],departure[1],departureFloor}); addRoom(departureRoomId,{departure[0],departure[1],departureFloor});
constexpr double ShaftCoreRadius=CenterlineRadius-ClearWidth*0.5;
constexpr double ShaftCoreBottomExtension=2.0;
constexpr double ShaftCoreTopExtension=8.0;
const double shaftCoreBottom=helixEndFloor-ShaftCoreBottomExtension;
const double shaftCoreTop=helixStart[2]+ShaftCoreTopExtension;
ResolvedCavernRoomV1 shaftCore;
shaftCore.stableId="lattice/spine/shaft-core-v2";
shaftCore.strateIndex=latticeStrateIndex;
shaftCore.centerMicrometres=micronPoint({
helixCenter[0]+orphanOffset[0],helixCenter[1]+orphanOffset[1],
(shaftCoreBottom+shaftCoreTop)*0.5});
shaftCore.halfExtentsMicrometres={
static_cast<std::int64_t>(std::llround(ShaftCoreRadius*1.0e6)),
static_cast<std::int64_t>(std::llround(ShaftCoreRadius*1.0e6)),
static_cast<std::int64_t>(std::llround((shaftCoreTop-shaftCoreBottom)*0.5e6))};
shaftCore.floorMicrometres=static_cast<std::int64_t>(
std::llround(shaftCoreBottom*1.0e6));
shaftCore.smoothWidthMicrometres=0;
shaftCore.unionWidthMicrometres=0;
rooms.push_back(std::move(shaftCore));
edges.push_back(addEdge("lattice/spine/00-arrival-portal-v1", edges.push_back(addEdge("lattice/spine/00-arrival-portal-v1",
arrivals.front()->stableId+"/lower-mouth",arrivalRoomId, arrivals.front()->stableId+"/lower-mouth",arrivalRoomId,
{{arrival[0],arrival[1],arrivalFloor},helixStart})); {{arrival[0],arrival[1],arrivalFloor},helixStart}));
@@ -1242,14 +1220,9 @@ namespace
std::printf("[LatticeResolvedMouths] arrival=%s side=lower xyz_m=(%.3f,%.3f,%.3f) departure=%s side=upper xyz_m=(%.3f,%.3f,%.3f) binding=actual-resolved status=PASS\n", std::printf("[LatticeResolvedMouths] arrival=%s side=lower xyz_m=(%.3f,%.3f,%.3f) departure=%s side=upper xyz_m=(%.3f,%.3f,%.3f) binding=actual-resolved status=PASS\n",
arrivals.front()->stableId.c_str(),arrival[0],arrival[1],arrival[2], arrivals.front()->stableId.c_str(),arrival[0],arrival[1],arrival[2],
departures.front()->stableId.c_str(),departure[0],departure[1],departure[2]); departures.front()->stableId.c_str(),departure[0],departure[1],departure[2]);
std::printf("[LatticeSpinePlan] recipe=Core.Lattice.PrimitiveField version=%u strate=%d stable_key=seed,strate,recipe_version,stable_id spine_first=true arrival=%s departure=%s helix_id=lattice/spine/01-helix-ramp-v1 shaft_core_id=%s shaft_core_radius_m=%.3f centerline_radius_m=%.3f clear_width_m=%.3f pitch_m=%.3f turns=%d inner_edge_margin_m=%.3f capsule_drop_clearance_m=%.3f support_depth_m=%.3f ramp_vertical_clearance_m=%.3f shaft_outer_envelope_m=%.3f maze_headroom_m=%.3f centerline_grade_deg=%.3f inner_edge_grade_deg=%.3f floor_blend_m=%.3f union_envelope_m=%.3f maze_cells=%d dead_ends=2 orphan_control=%s status=PASS\n", std::printf("[LatticeSpinePlan] recipe=Core.Lattice.PrimitiveField version=1 strate=%d stable_key=seed,strate,recipe_version,stable_id spine_first=true arrival=%s departure=%s helix_id=lattice/spine/01-helix-ramp-v1 centerline_radius_m=%.3f clear_width_m=%.3f pitch_m=%.3f turns=%d floor_to_soffit_m=%.3f centerline_grade_deg=%.3f inner_edge_grade_deg=%.3f floor_blend_m=%.3f union_envelope_m=%.3f maze_cells=%d dead_ends=2 orphan_control=%s status=PASS\n",
NativeLatticeFieldRecipeVersion,
latticeStrateIndex,arrivals.front()->stableId.c_str(),departures.front()->stableId.c_str(), latticeStrateIndex,arrivals.front()->stableId.c_str(),departures.front()->stableId.c_str(),
"lattice/spine/shaft-core-v2",ShaftCoreRadius,CenterlineRadius,ClearWidth,Pitch,Turns, CenterlineRadius,ClearWidth,Pitch,Turns,FloorToSoffit,centerlineGrade,innerEdgeGrade,
CenterlineRadius-ShaftCoreRadius,CenterlineRadius-ShaftCoreRadius-0.34,
RampSupportDepth,Pitch-RampSupportDepth,
2.0*(CenterlineRadius+ClearWidth*0.5+3.0),
FloorToSoffit,centerlineGrade,innerEdgeGrade,
FloorBlend,FeatureUnionBlend,routeEdges+1,orphanControl?"enabled":"disabled"); FloorBlend,FeatureUnionBlend,routeEdges+1,orphanControl?"enabled":"disabled");
return true; return true;
} }
@@ -1675,13 +1648,17 @@ int main(int argc,char** argv)
if(!ParseOptions(argc,argv,options))return 2; if(!ParseOptions(argc,argv,options))return 2;
if(options.TlsProbeRequested) if(options.TlsProbeRequested)
return StandaloneTlsProbe::Run(options.TlsProbeWorkers,options.TlsProbeTouch); return StandaloneTlsProbe::Run(options.TlsProbeWorkers,options.TlsProbeTouch);
std::printf("[HeadlessConfig] data=%s workers=%d clip_radius=%d max_clip_level=%d max_level=%d hash=%s column_overlap=%s force_generate=%s recipe=%s shared_column_cache=%s camera_step_m=(%.3f,%.3f,%.3f) seed_override=%s render_distance_override=%s\n", std::printf("[HeadlessConfig] data=%s workers=%d clip_radius=%d max_clip_level=%d max_level=%d hash=%s column_overlap=%s force_generate=%s recipe=%s lattice_strate=%d shared_column_cache=%s camera_step_m=(%.3f,%.3f,%.3f) camera_override=%s camera_m=(%.6f,%.6f,%.6f) camera_rotation_deg=(%.3f,%.3f) seed_override=%s render_distance_override=%s\n",
options.DataPath.string().c_str(),options.WorkerCount,options.ClipRadius,options.MaxClipLevel, options.DataPath.string().c_str(),options.WorkerCount,options.ClipRadius,options.MaxClipLevel,
options.MaxLevel,options.StreamHash?"on":"off",options.ProfileColumnOverlap?"on":"off", options.MaxLevel,options.StreamHash?"on":"off",options.ProfileColumnOverlap?"on":"off",
options.ForceGenerate?"on":"off",options.CavesRecipe?"caves" options.ForceGenerate?"on":"off",options.CavesRecipe?"caves"
:options.LatticeRecipe?"lattice"
:options.FloatingIslandsRecipe?"floating-islands":"surface-world", :options.FloatingIslandsRecipe?"floating-islands":"surface-world",
options.LatticeStrateIndex,
options.UseSharedColumnCache?"on":"off",options.CameraStepMeters.X,options.CameraStepMeters.Y, options.UseSharedColumnCache?"on":"off",options.CameraStepMeters.X,options.CameraStepMeters.Y,
options.CameraStepMeters.Z, options.CameraStepMeters.Z,options.HasCamera?"yes":"no",options.CameraMeters.X,
options.CameraMeters.Y,options.CameraMeters.Z,options.CameraYawDegrees,
options.CameraPitchDegrees,
options.HasSeedOverride?"yes":"no",options.HasRenderDistanceOverride?"yes":"no"); options.HasSeedOverride?"yes":"no",options.HasRenderDistanceOverride?"yes":"no");
std::printf("[ColdTimeline] stage=owner_data_load begin_ms=%.3f\n", std::printf("[ColdTimeline] stage=owner_data_load begin_ms=%.3f\n",
std::chrono::duration<double,std::milli>(Clock::now()-coldStart).count()); std::chrono::duration<double,std::milli>(Clock::now()-coldStart).count());
+5 -31
View File
@@ -1452,10 +1452,7 @@ namespace StandaloneStreamBenchmark
} }
std::sort(mazeEdges.begin(),mazeEdges.end(),[](const auto* a,const auto* b) std::sort(mazeEdges.begin(),mazeEdges.end(),[](const auto* a,const auto* b)
{return a->stableId<b->stableId;}); {return a->stableId<b->stableId;});
const auto shaftCore=std::find_if(layout.cavernRooms.begin(),layout.cavernRooms.end(),
[](const auto& room){return room.stableId=="lattice/spine/shaft-core-v2";});
if(!arrivalPortal||!helix||!departurePortal||mazeEdges.size()<4 if(!arrivalPortal||!helix||!departurePortal||mazeEdges.size()<4
||shaftCore==layout.cavernRooms.end()
||layout.cavernRooms.size()<mazeEdges.size()+1)return false; ||layout.cavernRooms.size()<mazeEdges.size()+1)return false;
std::vector<const ResolvedCavernEdgeV1*> ordered={arrivalPortal,helix}; std::vector<const ResolvedCavernEdgeV1*> ordered={arrivalPortal,helix};
ordered.insert(ordered.end(),mazeEdges.begin(),mazeEdges.end()); ordered.insert(ordered.end(),mazeEdges.begin(),mazeEdges.end());
@@ -1644,17 +1641,6 @@ namespace StandaloneStreamBenchmark
struct FIntervalCheck {const char* name;std::array<double,3> p;}; struct FIntervalCheck {const char* name;std::array<double,3> p;};
const auto mazeA=metres(mazeEdges.front()->polylineMicrometres.front()); const auto mazeA=metres(mazeEdges.front()->polylineMicrometres.front());
const auto mazeB=metres(mazeEdges.front()->polylineMicrometres.back()); const auto mazeB=metres(mazeEdges.front()->polylineMicrometres.back());
const auto helixFirst=metres(helix->polylineMicrometres.front());
const double helixCenterlineRadius=std::hypot(
helixFirst[0]-shaftCore->centerMicrometres[0]/1.0e6,
helixFirst[1]-shaftCore->centerMicrometres[1]/1.0e6);
const double helixBoreRadius=helix->boreRadiusMicrometres/1.0e6;
const double outerWallRadius=helixCenterlineRadius+helixBoreRadius;
const double wallScale=outerWallRadius/std::max(1.0e-9,helixCenterlineRadius);
const double outerWallX=shaftCore->centerMicrometres[0]/1.0e6
+(helixFirst[0]-shaftCore->centerMicrometres[0]/1.0e6)*wallScale;
const double outerWallY=shaftCore->centerMicrometres[1]/1.0e6
+(helixFirst[1]-shaftCore->centerMicrometres[1]/1.0e6)*wallScale;
const std::array<double,3> mazeMid={(mazeA[0]+mazeB[0])*0.5, const std::array<double,3> mazeMid={(mazeA[0]+mazeB[0])*0.5,
(mazeA[1]+mazeB[1])*0.5,(mazeA[2]+mazeB[2])*0.5}; (mazeA[1]+mazeB[1])*0.5,(mazeA[2]+mazeB[2])*0.5};
const double mazeLength=std::hypot(mazeB[0]-mazeA[0],mazeB[1]-mazeA[1]); const double mazeLength=std::hypot(mazeB[0]-mazeA[0],mazeB[1]-mazeA[1]);
@@ -1667,23 +1653,17 @@ namespace StandaloneStreamBenchmark
for(const auto& edge:layout.cavernEdges)if(edge.strateIndex==lattice->strateIndex) for(const auto& edge:layout.cavernEdges)if(edge.strateIndex==lattice->strateIndex)
for(const auto& point:edge.polylineMicrometres) for(const auto& point:edge.polylineMicrometres)
{const auto p=metres(point);maxX=std::max(maxX,p[0]);minY=std::min(minY,p[1]);maxY=std::max(maxY,p[1]);} {const auto p=metres(point);maxX=std::max(maxX,p[0]);minY=std::min(minY,p[1]);maxY=std::max(maxY,p[1]);}
const std::array<FIntervalCheck,6> checks={{{"host-rock",{maxX+8.0, const std::array<FIntervalCheck,4> checks={{{"host-rock",{maxX+8.0,
(minY+maxY)*0.5,midZ+1.2}}, (minY+maxY)*0.5,midZ+1.2}},
{"maze-air",{mazeMid[0],mazeMid[1],mazeMid[2]+1.25}}, {"maze-air",{mazeMid[0],mazeMid[1],mazeMid[2]+1.25}},
{"maze-wall-foot",{mazeMid[0]+wallX*1.30,mazeMid[1]+wallY*1.30, {"maze-wall-foot",{mazeMid[0]+wallX*1.30,mazeMid[1]+wallY*1.30,
mazeMid[2]+0.25}}, mazeMid[2]+0.25}},
{"helix-air",{helix->polylineMicrometres[helix->polylineMicrometres.size()/2][0]/1.0e6, {"helix-air",{helix->polylineMicrometres[helix->polylineMicrometres.size()/2][0]/1.0e6,
helix->polylineMicrometres[helix->polylineMicrometres.size()/2][1]/1.0e6, helix->polylineMicrometres[helix->polylineMicrometres.size()/2][1]/1.0e6,
helix->polylineMicrometres[helix->polylineMicrometres.size()/2][2]/1.0e6+1.25}}, helix->polylineMicrometres[helix->polylineMicrometres.size()/2][2]/1.0e6+1.25}}}};
{"shaft-core-air",{shaftCore->centerMicrometres[0]/1.0e6,
shaftCore->centerMicrometres[1]/1.0e6,
shaftCore->centerMicrometres[2]/1.0e6}},
{"shaft-outer-wall",{outerWallX,outerWallY,
helixFirst[2]+1.25}}}};
std::uint64_t boxes=0,samples=0,misses=0,allAir=0,allSolid=0,mixed=0; std::uint64_t boxes=0,samples=0,misses=0,allAir=0,allSolid=0,mixed=0;
bool falseAllAirRejected=false,falseAllSolidRejected=false,wallMixed=false; bool falseAllAirRejected=false,falseAllSolidRejected=false,wallMixed=false;
bool isTileShapeFreeAware=false,latticeTileFree=false; bool isTileShapeFreeAware=false,latticeTileFree=false;
bool shaftCoreAirProved=false,shaftCoreWallMixed=false;
for(const auto& check:checks) for(const auto& check:checks)
{ {
const Vec3i origin{static_cast<int>(std::llround(check.p[0]/vm)), const Vec3i origin{static_cast<int>(std::llround(check.p[0]/vm)),
@@ -1704,10 +1684,6 @@ namespace StandaloneStreamBenchmark
falseAllAirRejected|=bruteSolid&&verdict!=TileClass::allAir; falseAllAirRejected|=bruteSolid&&verdict!=TileClass::allAir;
falseAllSolidRejected|=bruteAir&&verdict!=TileClass::allSolid; falseAllSolidRejected|=bruteAir&&verdict!=TileClass::allSolid;
if(std::string_view(check.name)=="maze-wall-foot")wallMixed=verdict==TileClass::mixed; if(std::string_view(check.name)=="maze-wall-foot")wallMixed=verdict==TileClass::mixed;
if(std::string_view(check.name)=="shaft-core-air")
shaftCoreAirProved=verdict==TileClass::allAir&&bruteAir;
if(std::string_view(check.name)=="shaft-outer-wall")
shaftCoreWallMixed=verdict==TileClass::mixed&&!bruteAir&&!bruteSolid;
if(std::string_view(check.name)=="maze-air") if(std::string_view(check.name)=="maze-air")
{ {
const int32 tileWidth=CoreTileVoxelCount; const int32 tileWidth=CoreTileVoxelCount;
@@ -1725,9 +1701,9 @@ namespace StandaloneStreamBenchmark
(verdict==TileClass::allAir?!bruteAir: (verdict==TileClass::allAir?!bruteAir:
verdict==TileClass::allSolid?!bruteSolid:false)?"yes":"no"); verdict==TileClass::allSolid?!bruteSolid:false)?"yes":"no");
} }
const bool intervalOk=boxes>=6&&allAir>=1&&allSolid>=1&&mixed>=2 const bool intervalOk=boxes>=4&&allSolid>=1&&mixed>=1
&&misses==0&&falseAllAirRejected&&falseAllSolidRejected&&wallMixed &&misses==0&&falseAllAirRejected&&falseAllSolidRejected&&wallMixed
&&shaftCoreAirProved&&shaftCoreWallMixed&&isTileShapeFreeAware; &&isTileShapeFreeAware;
const bool pathOk=continuous&&capsuleClear&&allSupported&&headroomClear const bool pathOk=continuous&&capsuleClear&&allSupported&&headroomClear
&&maximumGrade<=15.0&&minimumHeadroom>=2.2; &&maximumGrade<=15.0&&minimumHeadroom>=2.2;
std::printf("[LatticeCapsuleReachability] arrival=%s departure=%s edges=%zu path_points=%zu stations=%llu capsule_samples=%llu capsule_failures=%llu first_capsule_failure_m=(%.3f,%.3f,%.3f) support_samples=%llu support_failures=%llu first_support_failure_m=(%.3f,%.3f,%.3f) head_samples=%llu head_failures=%llu first_head_failure_m=(%.3f,%.3f,%.3f) max_grade_deg=%.3f min_headroom_m=%.3f required_headroom_m=2.200 support=every-station reachable=%s status=%s\n", std::printf("[LatticeCapsuleReachability] arrival=%s departure=%s edges=%zu path_points=%zu stations=%llu capsule_samples=%llu capsule_failures=%llu first_capsule_failure_m=(%.3f,%.3f,%.3f) support_samples=%llu support_failures=%llu first_support_failure_m=(%.3f,%.3f,%.3f) head_samples=%llu head_failures=%llu first_head_failure_m=(%.3f,%.3f,%.3f) max_grade_deg=%.3f min_headroom_m=%.3f required_headroom_m=2.200 support=every-station reachable=%s status=%s\n",
@@ -1745,12 +1721,10 @@ namespace StandaloneStreamBenchmark
minimumHeadroom, pathOk?"yes":"no",pathOk?"PASS":"FAIL"); minimumHeadroom, pathOk?"yes":"no",pathOk?"PASS":"FAIL");
std::printf("[LatticeFilletAudit] smooth_max=cubic width_m=%.6f measured_radius_m=%.6f ideal_radius_m=%.6f status=%s\n", std::printf("[LatticeFilletAudit] smooth_max=cubic width_m=%.6f measured_radius_m=%.6f ideal_radius_m=%.6f status=%s\n",
width,measuredFillet,idealFillet,measuredFillet>=0.5?"PASS":"FAIL"); width,measuredFillet,idealFillet,measuredFillet>=0.5?"PASS":"FAIL");
std::printf("[LatticeIntervalOracle] boxes=%llu all_air=%llu all_solid=%llu mixed=%llu samples=%llu misses=%llu shaft_core_air=%s shaft_core_wall=%s false_all_air=%s false_all_solid=%s wall_foot=%s isTileShapeFree=%s tile_free=%s status=%s\n", std::printf("[LatticeIntervalOracle] boxes=%llu all_air=%llu all_solid=%llu mixed=%llu samples=%llu misses=%llu false_all_air=%s false_all_solid=%s wall_foot=%s isTileShapeFree=%s tile_free=%s status=%s\n",
static_cast<unsigned long long>(boxes),static_cast<unsigned long long>(allAir), static_cast<unsigned long long>(boxes),static_cast<unsigned long long>(allAir),
static_cast<unsigned long long>(allSolid),static_cast<unsigned long long>(mixed), static_cast<unsigned long long>(allSolid),static_cast<unsigned long long>(mixed),
static_cast<unsigned long long>(samples),static_cast<unsigned long long>(misses), static_cast<unsigned long long>(samples),static_cast<unsigned long long>(misses),
shaftCoreAirProved?"PROVED":"FAILED",
shaftCoreWallMixed?"mixed":"not-mixed",
falseAllAirRejected?"REJECTED":"FAILED", falseAllAirRejected?"REJECTED":"FAILED",
falseAllSolidRejected?"REJECTED":"FAILED",wallMixed?"mixed":"not-mixed", falseAllSolidRejected?"REJECTED":"FAILED",wallMixed?"mixed":"not-mixed",
isTileShapeFreeAware?"BLOCKED":"FAILED",latticeTileFree?"yes":"no", isTileShapeFreeAware?"BLOCKED":"FAILED",latticeTileFree?"yes":"no",
+221 -30
View File
@@ -27,6 +27,39 @@ struct FStandaloneTileKeyHash
{ return static_cast<std::size_t>(GetTypeHash(Key)); } { return static_cast<std::size_t>(GetTypeHash(Key)); }
}; };
inline uint8 ComputeStandaloneVisibleSkirtFaceMask(const FVoxelTileKey& Tile,
const std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash>& Desired)
{
const FIntVector Origin=Tile.OriginVoxels();
const int32 Extent=Tile.ExtentVoxels();
const auto FloorDiv=[](int64 Value,int64 Divisor)
{
int64 Quotient=Value/Divisor;
if(Value%Divisor<0)--Quotient;
return static_cast<int32>(Quotient);
};
uint8 Mask=0;
for(int32 Axis=0;Axis<3;++Axis)
for(int32 Side=0;Side<2;++Side)
{
int64 Probe[3]={Origin.X+Extent/2,Origin.Y+Extent/2,Origin.Z+Extent/2};
Probe[Axis]=Side?static_cast<int64>((&Origin.X)[Axis])+Extent
:static_cast<int64>((&Origin.X)[Axis])-1;
int32 NeighborLevel=-1;
for(int32 Level=0;Level<=8;++Level)
{
const int64 NeighborExtent=static_cast<int64>(CHUNK_SIZE)<<Level;
const FIntVector Coord(FloorDiv(Probe[0],NeighborExtent),
FloorDiv(Probe[1],NeighborExtent),FloorDiv(Probe[2],NeighborExtent));
if(Desired.contains(FVoxelTileKey(Coord,Level)))
{NeighborLevel=Level;break;}
}
if(NeighborLevel<0||NeighborLevel!=Tile.Level)
Mask|=static_cast<uint8>(1u<<(Axis*2+Side));
}
return Mask;
}
struct FStandaloneDesiredTileSet struct FStandaloneDesiredTileSet
{ {
std::vector<FVoxelTileKey> Tiles; std::vector<FVoxelTileKey> Tiles;
@@ -41,6 +74,7 @@ struct FStandaloneDesiredTileSet
struct FStandaloneCpuTile struct FStandaloneCpuTile
{ {
FVoxelTileKey Key; FVoxelTileKey Key;
uint8 GeneratedSkirtFaceMask=0x3f;
int32 WorkerId=-1; int32 WorkerId=-1;
uint64 RequestedCrossing=0; uint64 RequestedCrossing=0;
std::vector<FStandaloneCpuVertex> Vertices; std::vector<FStandaloneCpuVertex> Vertices;
@@ -195,15 +229,27 @@ struct FStandaloneMeshCacheMetadata
class FStandaloneMeshCache class FStandaloneMeshCache
{ {
public: public:
static constexpr uint32 CurrentVersion=1; static constexpr uint32 CurrentVersion=2;
void Clear(){Tiles.clear();bLoaded=false;BytesLoaded=0;} void Clear(){Tiles.clear();bLoaded=false;BytesLoaded=0;}
bool IsLoaded()const{return bLoaded;} bool IsLoaded()const{return bLoaded;}
size_t Num()const{return Tiles.size();} size_t Num()const{return Tiles.size();}
uint64 GetBytesLoaded()const{return BytesLoaded;} uint64 GetBytesLoaded()const{return BytesLoaded;}
const FStandaloneCpuTile* Find(const FVoxelTileKey& Key)const struct FKey
{ {
const auto It=Tiles.find(Key); FVoxelTileKey Tile;
uint8 SkirtFaceMask=0x3f;
bool operator==(const FKey& Other)const
{return Tile==Other.Tile&&SkirtFaceMask==Other.SkirtFaceMask;}
};
struct FKeyHash
{
size_t operator()(const FKey& Key)const noexcept
{return FStandaloneTileKeyHash{}(Key.Tile)^(static_cast<size_t>(Key.SkirtFaceMask)*0x9e3779b97f4a7c15ull);}
};
const FStandaloneCpuTile* Find(const FVoxelTileKey& Key,uint8 SkirtFaceMask)const
{
const auto It=Tiles.find({Key,static_cast<uint8>(SkirtFaceMask&0x3fu)});
return It==Tiles.end()?nullptr:&It->second; return It==Tiles.end()?nullptr:&It->second;
} }
@@ -218,7 +264,8 @@ public:
if(A->Key.Level!=B->Key.Level)return A->Key.Level<B->Key.Level; if(A->Key.Level!=B->Key.Level)return A->Key.Level<B->Key.Level;
if(A->Key.Coord.Z!=B->Key.Coord.Z)return A->Key.Coord.Z<B->Key.Coord.Z; if(A->Key.Coord.Z!=B->Key.Coord.Z)return A->Key.Coord.Z<B->Key.Coord.Z;
if(A->Key.Coord.Y!=B->Key.Coord.Y)return A->Key.Coord.Y<B->Key.Coord.Y; if(A->Key.Coord.Y!=B->Key.Coord.Y)return A->Key.Coord.Y<B->Key.Coord.Y;
return A->Key.Coord.X<B->Key.Coord.X; if(A->Key.Coord.X!=B->Key.Coord.X)return A->Key.Coord.X<B->Key.Coord.X;
return A->GeneratedSkirtFaceMask<B->GeneratedSkirtFaceMask;
}); });
std::ofstream File(Path,std::ios::binary|std::ios::trunc); std::ofstream File(Path,std::ios::binary|std::ios::trunc);
if(!File){Error="could not open mesh cache for writing: "+Path;return false;} if(!File){Error="could not open mesh cache for writing: "+Path;return false;}
@@ -231,7 +278,7 @@ public:
uint64 TileCount; uint64 TileCount;
}; };
static_assert(sizeof(FHeader)==80); static_assert(sizeof(FHeader)==80);
const FHeader Header{{'V','F','M','E','S','H','1','\0'},1,0x01020304u, const FHeader Header{{'V','F','M','E','S','H','2','\0'},2,0x01020304u,
Metadata.SourceDataHash,Metadata.Seed,Metadata.ClipRadius,Metadata.FullResClipLevels, Metadata.SourceDataHash,Metadata.Seed,Metadata.ClipRadius,Metadata.FullResClipLevels,
Metadata.CoarseTileCells,Metadata.StrateContentCutMinLevel,Metadata.SkirtCells, Metadata.CoarseTileCells,Metadata.StrateContentCutMinLevel,Metadata.SkirtCells,
Metadata.LODOctaveDrop,Metadata.bGenerateSkirts,Metadata.AlgorithmVersion, Metadata.LODOctaveDrop,Metadata.bGenerateSkirts,Metadata.AlgorithmVersion,
@@ -239,22 +286,33 @@ public:
File.write(reinterpret_cast<const char*>(&Header),sizeof(Header)); File.write(reinterpret_cast<const char*>(&Header),sizeof(Header));
struct FRecord struct FRecord
{ {
int32 Level,X,Y,Z;uint64 Samples,DensityHash;uint32 VertexCount,IndexCount; int32 Level,X,Y,Z;uint64 Samples,DensityHash;
uint32 VertexCount,IndexCount,SurfaceVertexCount,SkirtTriangleCount,
TriangleFaceMaskCount,SkirtFaceMask;
}; };
static_assert(sizeof(FRecord)==40); static_assert(sizeof(FRecord)==56);
for(const FStandaloneCpuTile* Tile:Ordered) for(const FStandaloneCpuTile* Tile:Ordered)
{ {
if(Tile->Vertices.size()>UINT32_MAX||Tile->Indices.size()>UINT32_MAX if(Tile->Vertices.size()>UINT32_MAX||Tile->Indices.size()>UINT32_MAX
||Tile->Indices.size()%3!=0) ||Tile->Indices.size()%3!=0
||Tile->TriangleSkirtFaceMasks.size()!=Tile->Indices.size()/3
||Tile->SurfaceVertexCount>Tile->Vertices.size()
||Tile->SkirtTriangleCount>Tile->Indices.size()/3)
{Error="mesh cache tile exceeds the 32-bit record limits";return false;} {Error="mesh cache tile exceeds the 32-bit record limits";return false;}
const FRecord Record{Tile->Key.Level,Tile->Key.Coord.X,Tile->Key.Coord.Y, const FRecord Record{Tile->Key.Level,Tile->Key.Coord.X,Tile->Key.Coord.Y,
Tile->Key.Coord.Z,Tile->DensitySamples,Tile->DensityHash, Tile->Key.Coord.Z,Tile->DensitySamples,Tile->DensityHash,
static_cast<uint32>(Tile->Vertices.size()),static_cast<uint32>(Tile->Indices.size())}; static_cast<uint32>(Tile->Vertices.size()),static_cast<uint32>(Tile->Indices.size()),
Tile->SurfaceVertexCount,Tile->SkirtTriangleCount,
static_cast<uint32>(Tile->TriangleSkirtFaceMasks.size()),
static_cast<uint32>(Tile->GeneratedSkirtFaceMask&0x3fu)};
File.write(reinterpret_cast<const char*>(&Record),sizeof(Record)); File.write(reinterpret_cast<const char*>(&Record),sizeof(Record));
if(!Tile->Vertices.empty())File.write(reinterpret_cast<const char*>(Tile->Vertices.data()), if(!Tile->Vertices.empty())File.write(reinterpret_cast<const char*>(Tile->Vertices.data()),
static_cast<std::streamsize>(Tile->Vertices.size()*sizeof(FStandaloneCpuVertex))); static_cast<std::streamsize>(Tile->Vertices.size()*sizeof(FStandaloneCpuVertex)));
if(!Tile->Indices.empty())File.write(reinterpret_cast<const char*>(Tile->Indices.data()), if(!Tile->Indices.empty())File.write(reinterpret_cast<const char*>(Tile->Indices.data()),
static_cast<std::streamsize>(Tile->Indices.size()*sizeof(uint32))); static_cast<std::streamsize>(Tile->Indices.size()*sizeof(uint32)));
if(!Tile->TriangleSkirtFaceMasks.empty())File.write(
reinterpret_cast<const char*>(Tile->TriangleSkirtFaceMasks.data()),
static_cast<std::streamsize>(Tile->TriangleSkirtFaceMasks.size()*sizeof(uint8)));
if(!File){Error="mesh cache write failed: "+Path;return false;} if(!File){Error="mesh cache write failed: "+Path;return false;}
} }
File.flush(); File.flush();
@@ -277,9 +335,9 @@ public:
}; };
static_assert(sizeof(FHeader)==80); static_assert(sizeof(FHeader)==80);
FHeader H{};File.read(reinterpret_cast<char*>(&H),sizeof(H)); FHeader H{};File.read(reinterpret_cast<char*>(&H),sizeof(H));
const char ExpectedMagic[8]={'V','F','M','E','S','H','1','\0'}; const char ExpectedMagic[8]={'V','F','M','E','S','H','2','\0'};
if(!File||std::memcmp(H.Magic,ExpectedMagic,sizeof(ExpectedMagic))!=0 if(!File||std::memcmp(H.Magic,ExpectedMagic,sizeof(ExpectedMagic))!=0
||H.Version!=1||H.Endian!=0x01020304u||H.TileCount>1000000) ||H.Version!=2||H.Endian!=0x01020304u||H.TileCount>1000000)
{Error="mesh cache header is invalid";return false;} {Error="mesh cache header is invalid";return false;}
if(H.SourceDataHash!=Expected.SourceDataHash||H.Seed!=Expected.Seed if(H.SourceDataHash!=Expected.SourceDataHash||H.Seed!=Expected.Seed
||H.ClipRadius!=Expected.ClipRadius||H.FullResClipLevels!=Expected.FullResClipLevels ||H.ClipRadius!=Expected.ClipRadius||H.FullResClipLevels!=Expected.FullResClipLevels
@@ -292,24 +350,37 @@ public:
{Error="mesh cache metadata does not match this world configuration";return false;} {Error="mesh cache metadata does not match this world configuration";return false;}
struct FRecord struct FRecord
{ {
int32 Level,X,Y,Z;uint64 Samples,DensityHash;uint32 VertexCount,IndexCount; int32 Level,X,Y,Z;uint64 Samples,DensityHash;
uint32 VertexCount,IndexCount,SurfaceVertexCount,SkirtTriangleCount,
TriangleFaceMaskCount,SkirtFaceMask;
}; };
static_assert(sizeof(FRecord)==40); static_assert(sizeof(FRecord)==56);
std::unordered_map<FVoxelTileKey,FStandaloneCpuTile,FStandaloneTileKeyHash> Loaded; std::unordered_map<FKey,FStandaloneCpuTile,FKeyHash> Loaded;
Loaded.reserve(static_cast<size_t>(H.TileCount)*2+1); Loaded.reserve(static_cast<size_t>(H.TileCount)*2+1);
for(uint64 I=0;I<H.TileCount;++I) for(uint64 I=0;I<H.TileCount;++I)
{ {
FRecord Record{};File.read(reinterpret_cast<char*>(&Record),sizeof(Record)); FRecord Record{};File.read(reinterpret_cast<char*>(&Record),sizeof(Record));
if(!File||Record.Level<0||Record.Level>8||Record.VertexCount>10000000 if(!File||Record.Level<0||Record.Level>8||Record.VertexCount>10000000
||Record.IndexCount>60000000) ||Record.IndexCount>60000000||Record.IndexCount%3!=0
||Record.TriangleFaceMaskCount!=Record.IndexCount/3
||Record.SurfaceVertexCount>Record.VertexCount
||Record.SkirtTriangleCount>Record.IndexCount/3
||Record.SkirtFaceMask>0x3fu)
{Error="mesh cache contains an invalid tile record";return false;} {Error="mesh cache contains an invalid tile record";return false;}
FStandaloneCpuTile Tile;Tile.Key=FVoxelTileKey(FIntVector(Record.X,Record.Y,Record.Z),Record.Level); FStandaloneCpuTile Tile;Tile.Key=FVoxelTileKey(FIntVector(Record.X,Record.Y,Record.Z),Record.Level);
Tile.GeneratedSkirtFaceMask=static_cast<uint8>(Record.SkirtFaceMask);
Tile.SurfaceVertexCount=Record.SurfaceVertexCount;
Tile.SkirtTriangleCount=Record.SkirtTriangleCount;
Tile.DensitySamples=Record.Samples;Tile.DensityHash=Record.DensityHash;Tile.bHasDensityHash=true; Tile.DensitySamples=Record.Samples;Tile.DensityHash=Record.DensityHash;Tile.bHasDensityHash=true;
Tile.Vertices.resize(Record.VertexCount);Tile.Indices.resize(Record.IndexCount); Tile.Vertices.resize(Record.VertexCount);Tile.Indices.resize(Record.IndexCount);
Tile.TriangleSkirtFaceMasks.resize(Record.TriangleFaceMaskCount);
if(!Tile.Vertices.empty())File.read(reinterpret_cast<char*>(Tile.Vertices.data()), if(!Tile.Vertices.empty())File.read(reinterpret_cast<char*>(Tile.Vertices.data()),
static_cast<std::streamsize>(Tile.Vertices.size()*sizeof(FStandaloneCpuVertex))); static_cast<std::streamsize>(Tile.Vertices.size()*sizeof(FStandaloneCpuVertex)));
if(!Tile.Indices.empty())File.read(reinterpret_cast<char*>(Tile.Indices.data()), if(!Tile.Indices.empty())File.read(reinterpret_cast<char*>(Tile.Indices.data()),
static_cast<std::streamsize>(Tile.Indices.size()*sizeof(uint32))); static_cast<std::streamsize>(Tile.Indices.size()*sizeof(uint32)));
if(!Tile.TriangleSkirtFaceMasks.empty())File.read(
reinterpret_cast<char*>(Tile.TriangleSkirtFaceMasks.data()),
static_cast<std::streamsize>(Tile.TriangleSkirtFaceMasks.size()*sizeof(uint8)));
if(!File){Error="mesh cache ended inside tile geometry";return false;} if(!File){Error="mesh cache ended inside tile geometry";return false;}
for(const uint32 Index:Tile.Indices) for(const uint32 Index:Tile.Indices)
if(Index>=Tile.Vertices.size()) if(Index>=Tile.Vertices.size())
@@ -322,7 +393,7 @@ public:
for(const float Value:Vertex.Normal) for(const float Value:Vertex.Normal)
if(!std::isfinite(Value)) if(!std::isfinite(Value))
{Error="mesh cache contains a non-finite normal";return false;} {Error="mesh cache contains a non-finite normal";return false;}
if(!Loaded.emplace(Tile.Key,std::move(Tile)).second) if(!Loaded.emplace(FKey{Tile.Key,Tile.GeneratedSkirtFaceMask},std::move(Tile)).second)
{Error="mesh cache contains duplicate tile keys";return false;} {Error="mesh cache contains duplicate tile keys";return false;}
} }
const std::streamoff End=File.tellg(); const std::streamoff End=File.tellg();
@@ -332,7 +403,7 @@ public:
} }
private: private:
std::unordered_map<FVoxelTileKey,FStandaloneCpuTile,FStandaloneTileKeyHash> Tiles; std::unordered_map<FKey,FStandaloneCpuTile,FKeyHash> Tiles;
bool bLoaded=false; bool bLoaded=false;
uint64 BytesLoaded=0; uint64 BytesLoaded=0;
}; };
@@ -344,6 +415,8 @@ struct FStandaloneStreamingUpdate
int32 RetainedResidentTiles=0; int32 RetainedResidentTiles=0;
int32 NewJobs=0; int32 NewJobs=0;
int32 CancelledQueuedJobs=0; int32 CancelledQueuedJobs=0;
int32 SkirtMaskChanges=0;
int32 SkirtMaskInvalidations=0;
int32 FullResTiles=0; int32 FullResTiles=0;
std::vector<std::pair<uint64,uint64>> CancelledByCrossing; std::vector<std::pair<uint64,uint64>> CancelledByCrossing;
}; };
@@ -351,6 +424,7 @@ struct FStandaloneStreamingUpdate
struct FStandaloneTileJob struct FStandaloneTileJob
{ {
FVoxelTileKey Key; FVoxelTileKey Key;
uint8 SkirtFaceMask=0x3f;
int32 BandZMin=MIN_int32; int32 BandZMin=MIN_int32;
int32 BandZMax=MAX_int32; int32 BandZMax=MAX_int32;
uint64 RequestedCrossing=0; uint64 RequestedCrossing=0;
@@ -577,6 +651,8 @@ public:
Workers.clear(); Workers.clear();
std::lock_guard<std::mutex> Lock(Mutex); std::lock_guard<std::mutex> Lock(Mutex);
Ready.clear();Outstanding.clear();Desired.clear();Running.clear(); Ready.clear();Outstanding.clear();Desired.clear();Running.clear();
OutstandingSkirtMasks.clear();RunningSkirtMasks.clear();CurrentSkirtMasks.clear();
ResidentSkirtMasks.clear();
} }
FStandaloneStreamingUpdate UpdateDesired( FStandaloneStreamingUpdate UpdateDesired(
@@ -623,7 +699,39 @@ public:
std::vector<FStandaloneCpuTile> Out; std::vector<FStandaloneCpuTile> Out;
std::lock_guard<std::mutex> Lock(Mutex); std::lock_guard<std::mutex> Lock(Mutex);
Out.swap(Ready); Out.swap(Ready);
for(const FStandaloneCpuTile& Tile:Out)Outstanding.erase(Tile.Key); std::vector<FStandaloneCpuTile> Current;
Current.reserve(Out.size());
for(FStandaloneCpuTile& Tile:Out)
{
const auto Wanted=CurrentSkirtMasks.find(Tile.Key);
if(Desired.contains(Tile.Key)&&Wanted!=CurrentSkirtMasks.end()
&&Wanted->second==Tile.GeneratedSkirtFaceMask)
{
Outstanding.erase(Tile.Key);OutstandingSkirtMasks.erase(Tile.Key);
ResidentSkirtMasks[Tile.Key]=Tile.GeneratedSkirtFaceMask;
Current.push_back(std::move(Tile));
continue;
}
const auto OutstandingMask=OutstandingSkirtMasks.find(Tile.Key);
if(OutstandingMask!=OutstandingSkirtMasks.end()
&&OutstandingMask->second==Tile.GeneratedSkirtFaceMask)
{Outstanding.erase(Tile.Key);OutstandingSkirtMasks.erase(OutstandingMask);}
if(Desired.contains(Tile.Key)&&Wanted!=CurrentSkirtMasks.end()
&&!Outstanding.contains(Tile.Key)&&!Running.contains(Tile.Key))
{
FStandaloneTileJob Retry;Retry.Key=Tile.Key;Retry.SkirtFaceMask=Wanted->second;
Retry.RequestedCrossing=Tile.RequestedCrossing;
Retry.BandZMin=World->StrateContentCutMinLevel<9&&CurrentDesired.bHasStrate
?CurrentDesired.StrateBottomChunkZ*CHUNK_SIZE:MIN_int32;
Retry.BandZMax=World->StrateContentCutMinLevel<9&&CurrentDesired.bHasStrate
?(CurrentDesired.StrateTopChunkZ+1)*CHUNK_SIZE:MAX_int32;
Retry.QueuedAt=FClock::now();
Queue.push_back(Retry);std::push_heap(Queue.begin(),Queue.end(),FStandaloneTileJobHeapOrder{});
Outstanding.insert(Tile.Key);OutstandingSkirtMasks[Tile.Key]=Retry.SkirtFaceMask;
}
}
Out.swap(Current);
Wake.notify_all();
return Out; return Out;
} }
@@ -742,24 +850,64 @@ private:
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> Resident; std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> Resident;
Resident.reserve(ResidentKeys.size()*2+1); Resident.reserve(ResidentKeys.size()*2+1);
for(const FVoxelTileKey& Key:ResidentKeys)Resident.insert(Key); for(const FVoxelTileKey& Key:ResidentKeys)Resident.insert(Key);
std::lock_guard<std::mutex> Lock(Mutex);
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> NewDesiredKeys;
NewDesiredKeys.reserve(NewDesired.Tiles.size()*2+1);
std::unordered_map<FVoxelTileKey,uint8,FStandaloneTileKeyHash> NewSkirtMasks;
NewSkirtMasks.reserve(NewDesired.Tiles.size()*2+1);
for(const FVoxelTileKey& Key:NewDesired.Tiles)
NewDesiredKeys.insert(Key);
// Compute against the complete desired set; the loop above is intentionally followed by
// a second pass so insertion order cannot affect adjacency masks.
for(const FVoxelTileKey& Key:NewDesired.Tiles)
NewSkirtMasks[Key]=ComputeStandaloneVisibleSkirtFaceMask(Key,NewDesiredKeys);
for(const FVoxelTileKey& Key:NewDesired.Tiles) for(const FVoxelTileKey& Key:NewDesired.Tiles)
{ {
if(Key.Level<World->FullResClipLevels)++Stats.FullResTiles; if(Key.Level<World->FullResClipLevels)++Stats.FullResTiles;
if(Resident.contains(Key))++Stats.RetainedResidentTiles; const auto PriorMask=CurrentSkirtMasks.find(Key);
if(PriorMask!=CurrentSkirtMasks.end()&&PriorMask->second!=NewSkirtMasks.at(Key))
++Stats.SkirtMaskChanges;
const auto Delivered=ResidentSkirtMasks.find(Key);
const uint8 ResidentMask=Delivered==ResidentSkirtMasks.end()?0x3fu:Delivered->second;
if(Resident.contains(Key)&&ResidentMask==NewSkirtMasks.at(Key))
++Stats.RetainedResidentTiles;
else if(Resident.contains(Key)&&ResidentMask!=NewSkirtMasks.at(Key))
++Stats.SkirtMaskInvalidations;
} }
std::lock_guard<std::mutex> Lock(Mutex);
Desired.clear();Desired.reserve(NewDesired.Tiles.size()*2+1); Desired.clear();Desired.reserve(NewDesired.Tiles.size()*2+1);
for(const FVoxelTileKey& Key:NewDesired.Tiles)Desired.insert(Key); for(const FVoxelTileKey& Key:NewDesired.Tiles)Desired.insert(Key);
FullResTileCount=Stats.FullResTiles; FullResTileCount=Stats.FullResTiles;
std::unordered_map<uint64,uint64> CancelledByCrossing; std::unordered_map<uint64,uint64> CancelledByCrossing;
auto QueueEnd=std::remove_if(Queue.begin(),Queue.end(),[&](const FStandaloneTileJob& Job) auto QueueEnd=std::remove_if(Queue.begin(),Queue.end(),[&](const FStandaloneTileJob& Job)
{ {
if(Desired.contains(Job.Key))return false; const auto Wanted=NewSkirtMasks.find(Job.Key);
Outstanding.erase(Job.Key);++Stats.CancelledQueuedJobs; if(Wanted!=NewSkirtMasks.end()&&Wanted->second==Job.SkirtFaceMask)return false;
++CancelledByCrossing[Job.RequestedCrossing];return true; if(Wanted!=NewSkirtMasks.end())++Stats.SkirtMaskInvalidations;
Outstanding.erase(Job.Key);OutstandingSkirtMasks.erase(Job.Key);
if(Wanted==NewSkirtMasks.end())
{++Stats.CancelledQueuedJobs;++CancelledByCrossing[Job.RequestedCrossing];}
return true;
}); });
Queue.erase(QueueEnd,Queue.end()); Queue.erase(QueueEnd,Queue.end());
for(auto ReadyIt=Ready.begin();ReadyIt!=Ready.end();)
{
const auto Wanted=NewSkirtMasks.find(ReadyIt->Key);
if(Wanted!=NewSkirtMasks.end()
&&Wanted->second==ReadyIt->GeneratedSkirtFaceMask)
{++ReadyIt;continue;}
if(Wanted!=NewSkirtMasks.end())++Stats.SkirtMaskInvalidations;
Outstanding.erase(ReadyIt->Key);OutstandingSkirtMasks.erase(ReadyIt->Key);
ReadyIt=Ready.erase(ReadyIt);
}
for(const FVoxelTileKey& Key:Running)
{
const auto RunningMask=RunningSkirtMasks.find(Key);
const auto Wanted=NewSkirtMasks.find(Key);
if(RunningMask!=RunningSkirtMasks.end()&&Wanted!=NewSkirtMasks.end()
&&RunningMask->second!=Wanted->second)
++Stats.SkirtMaskInvalidations;
}
Stats.CancelledByCrossing.reserve(CancelledByCrossing.size()); Stats.CancelledByCrossing.reserve(CancelledByCrossing.size());
for(const auto& Pair:CancelledByCrossing)Stats.CancelledByCrossing.push_back(Pair); for(const auto& Pair:CancelledByCrossing)Stats.CancelledByCrossing.push_back(Pair);
@@ -826,20 +974,28 @@ private:
? (NewDesired.StrateTopChunkZ+1)*CHUNK_SIZE:MAX_int32; ? (NewDesired.StrateTopChunkZ+1)*CHUNK_SIZE:MAX_int32;
for(const FVoxelTileKey& Key:NewDesired.Tiles) for(const FVoxelTileKey& Key:NewDesired.Tiles)
{ {
if(Resident.contains(Key)||Outstanding.contains(Key))continue; const uint8 WantedMask=NewSkirtMasks.at(Key);
const auto Delivered=ResidentSkirtMasks.find(Key);
const uint8 ResidentMask=Delivered==ResidentSkirtMasks.end()?0x3fu:Delivered->second;
if(Resident.contains(Key)&&ResidentMask==WantedMask)continue;
if(Outstanding.contains(Key)||Running.contains(Key))continue;
FStandaloneTileJob Job; FStandaloneTileJob Job;
Job.Key=Key;Job.BandZMin=BandMin;Job.BandZMax=BandMax; Job.Key=Key;Job.SkirtFaceMask=WantedMask;
Job.BandZMin=BandMin;Job.BandZMax=BandMax;
Job.RequestedCrossing=CrossingIndex;Job.PriorityRank=PriorityRanks.at(Key); Job.RequestedCrossing=CrossingIndex;Job.PriorityRank=PriorityRanks.at(Key);
Job.QueuedAt=FClock::now(); Job.QueuedAt=FClock::now();
Queue.push_back(Job); Queue.push_back(Job);
if(bColdTimelineTrace) if(bColdTimelineTrace)
RecordTimelineEvent(FStandaloneCpuTileTimelineEvent::EType::Queued,Key,-1, RecordTimelineEvent(FStandaloneCpuTileTimelineEvent::EType::Queued,Key,-1,
Queue.size(),Running.size(),Job.PriorityRank,Job.QueuedAt,Job.QueuedAt); Queue.size(),Running.size(),Job.PriorityRank,Job.QueuedAt,Job.QueuedAt);
Outstanding.insert(Key);++Stats.NewJobs; Outstanding.insert(Key);OutstandingSkirtMasks[Key]=WantedMask;++Stats.NewJobs;
} }
CurrentSkirtMasks=std::move(NewSkirtMasks);
CurrentDesired=std::move(NewDesired); CurrentDesired=std::move(NewDesired);
std::make_heap(Queue.begin(),Queue.end(),FStandaloneTileJobHeapOrder{}); std::make_heap(Queue.begin(),Queue.end(),FStandaloneTileJobHeapOrder{});
Wake.notify_all(); Wake.notify_all();
std::printf("[SkirtMaskCache] changed=%d invalidated=%d desired=%d\n",
Stats.SkirtMaskChanges,Stats.SkirtMaskInvalidations,Stats.DesiredTiles);
return Stats; return Stats;
} }
FStandaloneCpuTile Generate(const FStandaloneTileJob& Job, FStandaloneCpuTile Generate(const FStandaloneTileJob& Job,
@@ -848,10 +1004,11 @@ private:
{ {
const auto StartTime=Job.StartedAt; const auto StartTime=Job.StartedAt;
FStandaloneCpuTile Out;Out.Key=Job.Key;Out.RequestedCrossing=Job.RequestedCrossing; FStandaloneCpuTile Out;Out.Key=Job.Key;Out.RequestedCrossing=Job.RequestedCrossing;
Out.GeneratedSkirtFaceMask=static_cast<uint8>(Job.SkirtFaceMask&0x3fu);
Out.QueuedAt=Job.QueuedAt;Out.StartedAt=Job.StartedAt; Out.QueuedAt=Job.QueuedAt;Out.StartedAt=Job.StartedAt;
if(MeshCache&&!ForceGenerate) if(MeshCache&&!ForceGenerate)
{ {
if(const FStandaloneCpuTile* Cached=MeshCache->Find(Job.Key)) if(const FStandaloneCpuTile* Cached=MeshCache->Find(Job.Key,Job.SkirtFaceMask))
{ {
Out=*Cached;Out.RequestedCrossing=Job.RequestedCrossing; Out=*Cached;Out.RequestedCrossing=Job.RequestedCrossing;
Out.WorkerSeconds=std::chrono::duration<double>(FClock::now()-StartTime).count(); Out.WorkerSeconds=std::chrono::duration<double>(FClock::now()-StartTime).count();
@@ -869,6 +1026,7 @@ private:
Mesher.bStandaloneSkipColors=bSkipColors; Mesher.bStandaloneSkipColors=bSkipColors;
Mesher.bStandaloneSkipMaterialGroups=bSkipMaterialGroups; Mesher.bStandaloneSkipMaterialGroups=bSkipMaterialGroups;
Mesher.bGenerateSkirts=World->bGenerateSkirts; Mesher.bGenerateSkirts=World->bGenerateSkirts;
Mesher.SetVisibleSkirtFaceMask(Job.SkirtFaceMask);
Mesher.SkirtCells=static_cast<float>(World->SkirtCells); Mesher.SkirtCells=static_cast<float>(World->SkirtCells);
Mesher.LODOctaveDrop=World->LODOctaveDrop; Mesher.LODOctaveDrop=World->LODOctaveDrop;
const int32 Level=Job.Key.Level; const int32 Level=Job.Key.Level;
@@ -888,6 +1046,7 @@ private:
Options.skipMaterialGroups=bSkipMaterialGroups;Options.skipUv=bSkipUV;Options.skipColors=bSkipColors; Options.skipMaterialGroups=bSkipMaterialGroups;Options.skipUv=bSkipUV;Options.skipColors=bSkipColors;
Options.captureColumnCoordinates=bCaptureColumnCoordinates; Options.captureColumnCoordinates=bCaptureColumnCoordinates;
Options.useSharedColumnCache=bSharedColumnCache; Options.useSharedColumnCache=bSharedColumnCache;
Options.visibleSkirtFaceMask=Job.SkirtFaceMask;
Options.lod8HorizonInteriorStepVoxels=Lod8HorizonInteriorStepVoxels; Options.lod8HorizonInteriorStepVoxels=Lod8HorizonInteriorStepVoxels;
Options.profileMesherStages=(bProfileMesherStages||bInstrumentTileDrain)&&!ForceGenerate; Options.profileMesherStages=(bProfileMesherStages||bInstrumentTileDrain)&&!ForceGenerate;
Options.instrumentTileDrain=bInstrumentTileDrain&&!ForceGenerate; Options.instrumentTileDrain=bInstrumentTileDrain&&!ForceGenerate;
@@ -1020,7 +1179,8 @@ private:
{ {
std::pop_heap(Queue.begin(),Queue.end(),FStandaloneTileJobHeapOrder{}); std::pop_heap(Queue.begin(),Queue.end(),FStandaloneTileJobHeapOrder{});
Job=Queue.back();Queue.pop_back();Job.StartedAt=FClock::now(); Job=Queue.back();Queue.pop_back();Job.StartedAt=FClock::now();
Running.insert(Job.Key);ConsecutivePriorityTasks=0; Running.insert(Job.Key);RunningSkirtMasks[Job.Key]=Job.SkirtFaceMask;
ConsecutivePriorityTasks=0;
DispatchQueueDepth=Queue.size();DispatchActiveWorkers=Running.size(); DispatchQueueDepth=Queue.size();DispatchActiveWorkers=Running.size();
} }
} }
@@ -1039,12 +1199,39 @@ private:
std::lock_guard<std::mutex> Lock(Mutex); std::lock_guard<std::mutex> Lock(Mutex);
if(bStop)return; if(bStop)return;
Running.erase(Tile.Key); Running.erase(Tile.Key);
RunningSkirtMasks.erase(Tile.Key);
const auto WantedMask=CurrentSkirtMasks.find(Tile.Key);
const bool bCurrentMask=Desired.contains(Tile.Key)
&&WantedMask!=CurrentSkirtMasks.end()
&&WantedMask->second==Tile.GeneratedSkirtFaceMask;
if(bColdTimelineTrace) if(bColdTimelineTrace)
RecordTimelineEvent(FStandaloneCpuTileTimelineEvent::EType::Finish,Tile.Key, RecordTimelineEvent(FStandaloneCpuTileTimelineEvent::EType::Finish,Tile.Key,
WorkerId,Queue.size(),Running.size(),Job.PriorityRank,Tile.CompletedAt, WorkerId,Queue.size(),Running.size(),Job.PriorityRank,Tile.CompletedAt,
Tile.QueuedAt,Tile.StartedAt,Tile.CompletedAt,Tile.WorkerSeconds*1000.0, Tile.QueuedAt,Tile.StartedAt,Tile.CompletedAt,Tile.WorkerSeconds*1000.0,
&Tile.CoreDiagnostics,Tile.DensitySamples,Tile.SkirtTriangleCount); &Tile.CoreDiagnostics,Tile.DensitySamples,Tile.SkirtTriangleCount);
Ready.push_back(std::move(Tile)); if(bCurrentMask)
Ready.push_back(std::move(Tile));
else
{
const auto OutstandingMask=OutstandingSkirtMasks.find(Tile.Key);
if(OutstandingMask!=OutstandingSkirtMasks.end()
&&OutstandingMask->second==Tile.GeneratedSkirtFaceMask)
{Outstanding.erase(Tile.Key);OutstandingSkirtMasks.erase(OutstandingMask);}
if(Desired.contains(Tile.Key)&&WantedMask!=CurrentSkirtMasks.end()
&&!Outstanding.contains(Tile.Key))
{
FStandaloneTileJob Retry=Job;Retry.SkirtFaceMask=WantedMask->second;
Retry.QueuedAt=FClock::now();Retry.StartedAt={};
Retry.PriorityRank=0;
Queue.push_back(Retry);std::push_heap(Queue.begin(),Queue.end(),FStandaloneTileJobHeapOrder{});
Outstanding.insert(Tile.Key);OutstandingSkirtMasks[Tile.Key]=Retry.SkirtFaceMask;
if(bColdTimelineTrace)
RecordTimelineEvent(FStandaloneCpuTileTimelineEvent::EType::Queued,
Tile.Key,-1,Queue.size(),Running.size(),Retry.PriorityRank,
Retry.QueuedAt,Retry.QueuedAt);
Wake.notify_all();
}
}
} }
} }
} }
@@ -1151,8 +1338,12 @@ private:
std::vector<FStandaloneTileJob> Queue; std::vector<FStandaloneTileJob> Queue;
std::vector<FStandaloneCpuTile> Ready; std::vector<FStandaloneCpuTile> Ready;
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> Running; std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> Running;
std::unordered_map<FVoxelTileKey,uint8,FStandaloneTileKeyHash> RunningSkirtMasks;
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> Desired; std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> Desired;
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> Outstanding; std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> Outstanding;
std::unordered_map<FVoxelTileKey,uint8,FStandaloneTileKeyHash> OutstandingSkirtMasks;
std::unordered_map<FVoxelTileKey,uint8,FStandaloneTileKeyHash> CurrentSkirtMasks;
std::unordered_map<FVoxelTileKey,uint8,FStandaloneTileKeyHash> ResidentSkirtMasks;
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> TileFilter; std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> TileFilter;
bool bUseTileFilter=false; bool bUseTileFilter=false;
FStandaloneDesiredTileSet CurrentDesired; FStandaloneDesiredTileSet CurrentDesired;
@@ -4,6 +4,27 @@
namespace VoxelClipmapDesiredTiles namespace VoxelClipmapDesiredTiles
{ {
FORCEINLINE int64 FloorDivWide(int64 Value, int64 Divisor)
{
if (Divisor <= 0) { return 0; }
const int64 Quotient = Value / Divisor;
const int64 Remainder = Value % Divisor;
return Remainder < 0 ? Quotient - 1 : Quotient;
}
FORCEINLINE int64 CeilDivWide(int64 Value, int64 Divisor)
{
if (Divisor <= 0) { return 0; }
const int64 Quotient = Value / Divisor;
const int64 Remainder = Value % Divisor;
return Remainder > 0 ? Quotient + 1 : Quotient;
}
FORCEINLINE int32 ClampInt32(int64 Value)
{
return static_cast<int32>(FMath::Clamp<int64>(Value, MIN_int32, MAX_int32));
}
struct FParameters struct FParameters
{ {
int32 ClipRadius = 3; int32 ClipRadius = 3;
@@ -13,7 +34,10 @@ namespace VoxelClipmapDesiredTiles
FORCEINLINE int32 FloorDiv(int32 Value, int32 Divisor) FORCEINLINE int32 FloorDiv(int32 Value, int32 Divisor)
{ {
return Value >= 0 ? (Value / Divisor) : -(((-Value) + Divisor - 1) / Divisor); if (Divisor <= 0) { return 0; }
const int32 Quotient = Value / Divisor;
const int32 Remainder = Value % Divisor;
return Remainder < 0 ? Quotient - 1 : Quotient;
} }
FORCEINLINE FIntVector FloorDiv(const FIntVector& Value, int32 Divisor) FORCEINLINE FIntVector FloorDiv(const FIntVector& Value, int32 Divisor)
@@ -24,7 +48,10 @@ namespace VoxelClipmapDesiredTiles
FORCEINLINE int32 CeilDiv(int32 Value, int32 Divisor) FORCEINLINE int32 CeilDiv(int32 Value, int32 Divisor)
{ {
return -FloorDiv(-Value, Divisor); if (Divisor <= 0) { return 0; }
const int32 Quotient = Value / Divisor;
const int32 Remainder = Value % Divisor;
return Remainder > 0 ? Quotient + 1 : Quotient;
} }
FORCEINLINE void AlignedRingBounds(const FIntVector& Center, int32 Radius, int32 Alignment, FORCEINLINE void AlignedRingBounds(const FIntVector& Center, int32 Radius, int32 Alignment,
@@ -33,10 +60,12 @@ namespace VoxelClipmapDesiredTiles
const auto AlignAxis = [Radius, Alignment](int32 CenterAxis, const auto AlignAxis = [Radius, Alignment](int32 CenterAxis,
int32& OutAxisMin, int32& OutAxisMax) int32& OutAxisMin, int32& OutAxisMax)
{ {
const int32 BaseMin = CenterAxis - Radius; const int64 SafeRadius = FMath::Max<int64>(0, Radius);
const int32 BaseEnd = CenterAxis + Radius + 1; const int64 SafeAlignment = FMath::Max<int64>(1, Alignment);
OutAxisMin = FloorDiv(BaseMin, Alignment) * Alignment; const int64 BaseMin = static_cast<int64>(CenterAxis) - SafeRadius;
OutAxisMax = CeilDiv(BaseEnd, Alignment) * Alignment - 1; const int64 BaseEnd = static_cast<int64>(CenterAxis) + SafeRadius + 1;
OutAxisMin = ClampInt32(FloorDivWide(BaseMin, SafeAlignment) * SafeAlignment);
OutAxisMax = ClampInt32(CeilDivWide(BaseEnd, SafeAlignment) * SafeAlignment - 1);
}; };
AlignAxis(Center.X, OutMin.X, OutMax.X); AlignAxis(Center.X, OutMin.X, OutMax.X);
AlignAxis(Center.Y, OutMin.Y, OutMax.Y); AlignAxis(Center.Y, OutMin.Y, OutMax.Y);
@@ -49,8 +78,13 @@ namespace VoxelClipmapDesiredTiles
{ {
return Radius; return Radius;
} }
return FMath::Max(Radius, const int32 SafeLevel = FMath::Clamp(MaxLevel, 0, 8);
(Parameters.RenderDistanceChunks + (1 << MaxLevel) - 1) >> MaxLevel); const int64 Scale = static_cast<int64>(uint32{1} << static_cast<uint32>(SafeLevel));
const int64 Numerator = static_cast<int64>(Parameters.RenderDistanceChunks)
+ Scale - 1;
const int64 DesiredRadius = Numerator / Scale;
return static_cast<int32>(FMath::Clamp<int64>(
FMath::Max<int64>(Radius, DesiredRadius), 0, MAX_int32));
} }
FORCEINLINE void OuterShell(const FParameters& Parameters, int32 Radius, int32 MaxLevel, FORCEINLINE void OuterShell(const FParameters& Parameters, int32 Radius, int32 MaxLevel,
@@ -73,12 +107,12 @@ namespace VoxelClipmapDesiredTiles
return; return;
} }
OutMinChunkZ = CenterChunkZ - ViewDistanceDown; OutMinChunkZ = ClampInt32(static_cast<int64>(CenterChunkZ) - ViewDistanceDown);
OutMaxChunkZ = CenterChunkZ + ViewDistanceUp; OutMaxChunkZ = ClampInt32(static_cast<int64>(CenterChunkZ) + ViewDistanceUp);
if (bHasStrate) if (bHasStrate)
{ {
OutMinChunkZ = StrateBottomChunkZ - StrateViewMarginChunks; OutMinChunkZ = ClampInt32(static_cast<int64>(StrateBottomChunkZ) - StrateViewMarginChunks);
OutMaxChunkZ = StrateTopChunkZ + StrateViewMarginChunks; OutMaxChunkZ = ClampInt32(static_cast<int64>(StrateTopChunkZ) + StrateViewMarginChunks);
} }
} }
@@ -94,7 +128,7 @@ namespace VoxelClipmapDesiredTiles
for (int32 Level = 0; Level <= MaxLevel; ++Level) for (int32 Level = 0; Level <= MaxLevel; ++Level)
{ {
const int32 Pow = 1 << Level; const int32 Pow = static_cast<int32>(uint32{1} << static_cast<uint32>(Level));
const FIntVector CenterLevel = FloorDiv(Center, Pow); const FIntVector CenterLevel = FloorDiv(Center, Pow);
const int32 LevelRadius = (Level == MaxLevel) ? OuterRadius : Radius; const int32 LevelRadius = (Level == MaxLevel) ? OuterRadius : Radius;
@@ -105,24 +139,23 @@ namespace VoxelClipmapDesiredTiles
FIntVector RingMin, RingMax; FIntVector RingMin, RingMax;
AlignedRingBounds(CenterLevel, LevelRadius, RingAlignment, RingMin, RingMax); AlignedRingBounds(CenterLevel, LevelRadius, RingAlignment, RingMin, RingMax);
int32 DeltaZMin = RingMin.Z - CenterLevel.Z; int64 TileMinZ = RingMin.Z;
int32 DeltaZMax = RingMax.Z - CenterLevel.Z; int64 TileMaxZ = RingMax.Z;
if (LevelRadius > Radius && MinChunkZ != MIN_int32) if (LevelRadius > Radius && MinChunkZ != MIN_int32)
{ {
DeltaZMin = FMath::Max(DeltaZMin, FloorDiv(MinChunkZ, Pow) - CenterLevel.Z); TileMinZ = FMath::Max<int64>(TileMinZ, FloorDiv(MinChunkZ, Pow));
DeltaZMax = FMath::Min(DeltaZMax, FloorDiv(MaxChunkZ, Pow) - CenterLevel.Z); TileMaxZ = FMath::Min<int64>(TileMaxZ, FloorDiv(MaxChunkZ, Pow));
} }
for (int32 DeltaZ = DeltaZMin; DeltaZ <= DeltaZMax; ++DeltaZ) for (int64 Z = TileMinZ; Z <= TileMaxZ; ++Z)
for (int32 DeltaY = RingMin.Y - CenterLevel.Y; for (int64 Y = RingMin.Y; Y <= RingMax.Y; ++Y)
DeltaY <= RingMax.Y - CenterLevel.Y; ++DeltaY) for (int64 X = RingMin.X; X <= RingMax.X; ++X)
for (int32 DeltaX = RingMin.X - CenterLevel.X;
DeltaX <= RingMax.X - CenterLevel.X; ++DeltaX)
{ {
const FIntVector Tile = CenterLevel + FIntVector(DeltaX, DeltaY, DeltaZ); const FIntVector Tile(static_cast<int32>(X), static_cast<int32>(Y),
const int32 TileMinZ = Tile.Z << Level; static_cast<int32>(Z));
const int32 TileMaxZ = ((Tile.Z + 1) << Level) - 1; const int64 TileBottomChunkZ = static_cast<int64>(Tile.Z) * Pow;
if (TileMaxZ < MinChunkZ || TileMinZ > MaxChunkZ) const int64 TileTopChunkZ = (static_cast<int64>(Tile.Z) + 1) * Pow - 1;
if (TileTopChunkZ < MinChunkZ || TileBottomChunkZ > MaxChunkZ)
{ {
continue; continue;
} }
@@ -130,12 +163,12 @@ namespace VoxelClipmapDesiredTiles
if (Level > 0) if (Level > 0)
{ {
const bool bCovered = const bool bCovered =
(2 * Tile.X >= PreviousRingMin.X) (2 * static_cast<int64>(Tile.X) >= PreviousRingMin.X)
&& (2 * Tile.X + 1 <= PreviousRingMax.X) && (2 * static_cast<int64>(Tile.X) + 1 <= PreviousRingMax.X)
&& (2 * Tile.Y >= PreviousRingMin.Y) && (2 * static_cast<int64>(Tile.Y) >= PreviousRingMin.Y)
&& (2 * Tile.Y + 1 <= PreviousRingMax.Y) && (2 * static_cast<int64>(Tile.Y) + 1 <= PreviousRingMax.Y)
&& (2 * Tile.Z >= PreviousRingMin.Z) && (2 * static_cast<int64>(Tile.Z) >= PreviousRingMin.Z)
&& (2 * Tile.Z + 1 <= PreviousRingMax.Z); && (2 * static_cast<int64>(Tile.Z) + 1 <= PreviousRingMax.Z);
if (bCovered) if (bCovered)
{ {
continue; continue;
@@ -125,7 +125,8 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
std::vector<float>* OutDebugDensitySamples, std::vector<float>* OutDebugDensitySamples,
FVoxelMarchingCubesStageTimings* OutStageTimings, FVoxelMarchingCubesStageTimings* OutStageTimings,
FVoxelUniformBlockProofAuditStats* OutBlockProofAudit, FVoxelUniformBlockProofAuditStats* OutBlockProofAudit,
bool bForceFullDensityCapture) bool bForceFullDensityCapture,
uint64* OutSkippedSkirtTriangleSeeds)
{ {
TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_MesherGenerateMesh); TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_MesherGenerateMesh);
const uint64 StageMesherStartCycles=OutStageTimings?FPlatformTime::Cycles64():0; const uint64 StageMesherStartCycles=OutStageTimings?FPlatformTime::Cycles64():0;
@@ -136,6 +137,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
if (OutStageTimings) { *OutStageTimings = FVoxelMarchingCubesStageTimings{}; } if (OutStageTimings) { *OutStageTimings = FVoxelMarchingCubesStageTimings{}; }
if (OutBlockProofAudit) { *OutBlockProofAudit = FVoxelUniformBlockProofAuditStats{}; } if (OutBlockProofAudit) { *OutBlockProofAudit = FVoxelUniformBlockProofAuditStats{}; }
if (OutDebugDensitySamples) { OutDebugDensitySamples->clear(); } if (OutDebugDensitySamples) { OutDebugDensitySamples->clear(); }
if (OutSkippedSkirtTriangleSeeds) { *OutSkippedSkirtTriangleSeeds = 0; }
if (!Generator) return MeshData; if (!Generator) return MeshData;
const bool bEmitUV = !bStandaloneSkipUvAndColors && !bStandaloneSkipUV; const bool bEmitUV = !bStandaloneSkipUvAndColors && !bStandaloneSkipUV;
const bool bEmitColors = !bStandaloneSkipUvAndColors && !bStandaloneSkipColors; const bool bEmitColors = !bStandaloneSkipUvAndColors && !bStandaloneSkipColors;
@@ -1211,10 +1213,24 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
bBoundarySample bBoundarySample
?VoxelForgeCore::floating_island_detail::EIslandFieldCaller::Boundary ?VoxelForgeCore::floating_island_detail::EIslandFieldCaller::Boundary
:VoxelForgeCore::floating_island_detail::EIslandFieldCaller::GridFill); :VoxelForgeCore::floating_island_detail::EIslandFieldCaller::GridFill);
DensityGrid[LocalIndex] = ShapeWorldContext const bool bSharedTileBoundary=ShapeWorldContext&&SharedDensitySampleCache
?ShapeWorldContext->DensityAtLattice(SampleX,SampleY,SampleZ) &&SharedDensitySampleLookup
:Generator->GetDensityAtLattice(SampleX,SampleY,SampleZ, &&(gx==0||gx==GridDim||gy==0||gy==GridDim||gz==0||gz==GridDim);
bCapturePreEditGrid ? &PreEditDensity : nullptr); if(bSharedTileBoundary)
{
bool bComputed=false;
DensityGrid[LocalIndex]=SharedDensitySampleLookup(SharedDensitySampleCache,
ShapeWorldContext,SharedDensityFieldGeneration,SharedDensityEditSequence,
SharedDensityLod,SharedDensityStep,SampleX,SampleY,SampleZ,bComputed);
(void)bComputed;
}
else
{
DensityGrid[LocalIndex] = ShapeWorldContext
?ShapeWorldContext->DensityAtLattice(SampleX,SampleY,SampleZ)
:Generator->GetDensityAtLattice(SampleX,SampleY,SampleZ,
bCapturePreEditGrid ? &PreEditDensity : nullptr);
}
if (bCapturePreEditGrid) if (bCapturePreEditGrid)
{ {
OutPreEditDensityGrid->Samples[LocalIndex] = PreEditDensity; OutPreEditDensityGrid->Samples[LocalIndex] = PreEditDensity;
@@ -1244,6 +1260,23 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
&&(!bSkipProofBlockGrid||bStandaloneEnableActiveLaneBatching); &&(!bSkipProofBlockGrid||bStandaloneEnableActiveLaneBatching);
const auto SampleShapeWorldXBatch=[&](int32 GX0,int32 BatchCount,int32 gy,int32 gz) const auto SampleShapeWorldXBatch=[&](int32 GX0,int32 BatchCount,int32 gy,int32 gz)
{ {
const bool bSharedBoundaryBatch=SharedDensitySampleCache
&&(GX0<=0||GX0+BatchCount-1>=GridDim||gy==0||gy==GridDim
||gz==0||gz==GridDim);
if(bSharedBoundaryBatch)
{
for(int32 Lane=0;Lane<BatchCount;++Lane)
SampleGridPoint(GX0+Lane,gy,gz);
const int32 ReachBlockX=FMath::Clamp(GX0+BatchCount-1,0,CellsPerAxis-1)
/VoxelGenLOD::TileReachBlockCells;
const int32 ReachBlockY=FMath::Clamp(gy,0,CellsPerAxis-1)
/VoxelGenLOD::TileReachBlockCells;
const int32 ReachBlockZ=FMath::Clamp(gz,0,CellsPerAxis-1)
/VoxelGenLOD::TileReachBlockCells;
VoxelGenLOD::TileReachBlockIndex=
((ReachBlockZ*ReachBlocksPerAxis)+ReachBlockY)*ReachBlocksPerAxis+ReachBlockX;
return;
}
float Densities[8]{}; float Densities[8]{};
const bool bBoundarySample=GX0<0||GX0+BatchCount-1>GridDim const bool bBoundarySample=GX0<0||GX0+BatchCount-1>GridDim
||gy<0||gy>GridDim||gz<0||gz>GridDim; ||gy<0||gy>GridDim||gz<0||gz>GridDim;
@@ -1275,6 +1308,35 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
((ReachBlockZ*ReachBlocksPerAxis)+ReachBlockY)*ReachBlocksPerAxis+ReachBlockX; ((ReachBlockZ*ReachBlocksPerAxis)+ReachBlockY)*ReachBlocksPerAxis+ReachBlockX;
if(OutDensitySampleCount)*OutDensitySampleCount+=BatchCount; if(OutDensitySampleCount)*OutDensitySampleCount+=BatchCount;
}; };
const auto SampleShapeWorldZRun=[&](int32 gx,int32 gy,int32 GzStart,int32 BatchCount)
{
const int32 SampleX=OriginVoxels.X+gx*Step;
const int32 SampleY=OriginVoxels.Y+gy*Step;
float Densities[CHUNK_SIZE+3]{};
const bool bBoundarySample=gx<0||gx>GridDim||gy<0||gy>GridDim
||GzStart<0||GzStart+BatchCount-1>GridDim;
VoxelForgeCore::floating_island_detail::FScopedIslandFieldCaller islandCaller(
bBoundarySample
?VoxelForgeCore::floating_island_detail::EIslandFieldCaller::Boundary
:VoxelForgeCore::floating_island_detail::EIslandFieldCaller::GridFill);
ActiveShapeWorld->DensityAtLatticeZRun(SampleX,SampleY,
OriginVoxels.Z+GzStart*Step,Step,BatchCount,Densities);
for(int32 Lane=0;Lane<BatchCount;++Lane)
{
const int32 LocalGz=GzStart+Lane;
const int32 LocalIndex=((LocalGz+1)*MDim+(gy+1))*MDim+(gx+1);
DensityGrid[LocalIndex]=Densities[Lane];
}
const int32 ReachBlockX=FMath::Clamp(gx,0,CellsPerAxis-1)
/VoxelGenLOD::TileReachBlockCells;
const int32 ReachBlockY=FMath::Clamp(gy,0,CellsPerAxis-1)
/VoxelGenLOD::TileReachBlockCells;
const int32 ReachBlockZ=FMath::Clamp(GzStart+BatchCount-1,0,CellsPerAxis-1)
/VoxelGenLOD::TileReachBlockCells;
VoxelGenLOD::TileReachBlockIndex=
((ReachBlockZ*ReachBlocksPerAxis)+ReachBlockY)*ReachBlocksPerAxis+ReachBlockX;
if(OutDensitySampleCount)*OutDensitySampleCount+=BatchCount;
};
if(bUseShapeWorldZBatch&&!bUseVerticalCacheTraversal) if(bUseShapeWorldZBatch&&!bUseVerticalCacheTraversal)
{ {
for(const std::vector<int32>& YGroup:YGroups) for(const std::vector<int32>& YGroup:YGroups)
@@ -1287,35 +1349,22 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
if(bUseOperatorBlock){Generator->EndDensityBlock();} if(bUseOperatorBlock){Generator->EndDensityBlock();}
return FVoxelMeshData(); return FVoxelMeshData();
} }
const int32 BatchCount=GzHi-GzLo+1; if(SharedDensitySampleCache&&(gx==0||gx==GridDim||gy==0||gy==GridDim))
const int32 SampleX=OriginVoxels.X+gx*Step;
const int32 SampleY=OriginVoxels.Y+gy*Step;
float Densities[CHUNK_SIZE+3]{};
const bool bBoundarySample=gx<0||gx>GridDim||gy<0||gy>GridDim
||GzLo<0||GzHi>GridDim;
VoxelForgeCore::floating_island_detail::FScopedIslandFieldCaller islandCaller(
bBoundarySample
?VoxelForgeCore::floating_island_detail::EIslandFieldCaller::Boundary
:VoxelForgeCore::floating_island_detail::EIslandFieldCaller::GridFill);
ActiveShapeWorld->DensityAtLatticeZRun(SampleX,SampleY,
OriginVoxels.Z+GzLo*Step,Step,BatchCount,Densities);
for(int32 Lane=0;Lane<BatchCount;++Lane)
{ {
const int32 LocalGz=GzLo+Lane; for(int32 gz=GzLo;gz<=GzHi;++gz)SampleGridPoint(gx,gy,gz);
const int32 LocalIndex= continue;
((LocalGz+1)*MDim+(gy+1))*MDim+(gx+1); }
DensityGrid[LocalIndex]=Densities[Lane]; int32 gz=GzLo;
while(gz<=GzHi)
{
if(SharedDensitySampleCache&&(gz==0||gz==GridDim))
{SampleGridPoint(gx,gy,gz);++gz;continue;}
const int32 GzStart=gz++;
while(gz<=GzHi&&(!SharedDensitySampleCache||(gz!=0&&gz!=GridDim)))++gz;
const int32 BatchCount=gz-GzStart;
if(BatchCount>1)SampleShapeWorldZRun(gx,gy,GzStart,BatchCount);
else SampleGridPoint(gx,gy,GzStart);
} }
const int32 ReachBlockX=FMath::Clamp(gx,0,CellsPerAxis-1)
/VoxelGenLOD::TileReachBlockCells;
const int32 ReachBlockY=FMath::Clamp(gy,0,CellsPerAxis-1)
/VoxelGenLOD::TileReachBlockCells;
const int32 ReachBlockZ=FMath::Clamp(GzHi,0,CellsPerAxis-1)
/VoxelGenLOD::TileReachBlockCells;
VoxelGenLOD::TileReachBlockIndex=
((ReachBlockZ*ReachBlocksPerAxis)+ReachBlockY)
*ReachBlocksPerAxis+ReachBlockX;
if(OutDensitySampleCount)*OutDensitySampleCount+=BatchCount;
} }
} }
else if (bUseVerticalCacheTraversal) else if (bUseVerticalCacheTraversal)
@@ -2299,6 +2348,11 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
const uint8 FaceMask=BoundaryFaceMask(PA,PB); const uint8 FaceMask=BoundaryFaceMask(PA,PB);
if (!FaceMask) continue; if (!FaceMask) continue;
if(OutStageTimings)++OutStageTimings->SkirtBoundaryCandidates; if(OutStageTimings)++OutStageTimings->SkirtBoundaryCandidates;
if((FaceMask&StandaloneVisibleSkirtFaceMask)==0)
{
if(OutSkippedSkirtTriangleSeeds)*OutSkippedSkirtTriangleSeeds+=2;
continue;
}
const FVector NA = MeshData.Normals[iA]; const FVector NA = MeshData.Normals[iA];
const FVector NB = MeshData.Normals[iB]; const FVector NB = MeshData.Normals[iB];
@@ -261,8 +261,12 @@ namespace VoxelHash
FORCEINLINE uint32 Cell(int32 CellX, int32 CellY, uint32 Seed) FORCEINLINE uint32 Cell(int32 CellX, int32 CellY, uint32 Seed)
{ {
uint32 H = Seed; uint32 H = Seed;
H ^= Mix((uint32)(CellX + 0x7FFFFFFF)); // Bias in unsigned arithmetic. Adding the signed cell coordinate first overflows for
H ^= Mix((uint32)(CellY + 0x7FFFFFFF) * 2654435761u); // positive cells and makes the world hash compiler-dependent.
const uint32 BiasedX = static_cast<uint32>(CellX) + 0x7FFFFFFFu;
const uint32 BiasedY = static_cast<uint32>(CellY) + 0x7FFFFFFFu;
H ^= Mix(BiasedX);
H ^= Mix(BiasedY * 2654435761u);
return Mix(H); return Mix(H);
} }
@@ -274,10 +278,14 @@ namespace VoxelHash
int32 MinX = FMath::Min(AX, BX), MinY = FMath::Min(AY, BY); int32 MinX = FMath::Min(AX, BX), MinY = FMath::Min(AY, BY);
int32 MaxX = FMath::Max(AX, BX), MaxY = FMath::Max(AY, BY); int32 MaxX = FMath::Max(AX, BX), MaxY = FMath::Max(AY, BY);
uint32 H = Seed ^ 0xDEADBEEF; uint32 H = Seed ^ 0xDEADBEEF;
H ^= Mix((uint32)(MinX + 0x7FFFFFFF)); const uint32 BiasedMinX = static_cast<uint32>(MinX) + 0x7FFFFFFFu;
H ^= Mix((uint32)(MinY + 0x7FFFFFFF) * 2654435761u); const uint32 BiasedMinY = static_cast<uint32>(MinY) + 0x7FFFFFFFu;
H ^= Mix((uint32)(MaxX + 0x7FFFFFFF) * 374761393u); const uint32 BiasedMaxX = static_cast<uint32>(MaxX) + 0x7FFFFFFFu;
H ^= Mix((uint32)(MaxY + 0x7FFFFFFF) * 668265263u); const uint32 BiasedMaxY = static_cast<uint32>(MaxY) + 0x7FFFFFFFu;
H ^= Mix(BiasedMinX);
H ^= Mix(BiasedMinY * 2654435761u);
H ^= Mix(BiasedMaxX * 374761393u);
H ^= Mix(BiasedMaxY * 668265263u);
return Mix(H); return Mix(H);
} }
@@ -477,10 +485,13 @@ namespace VoxelNoise
// Hash the neighbor cell to get a feature point position [0,1) // Hash the neighbor cell to get a feature point position [0,1)
// Using three different hash mixes for X, Y, Z offsets // Using three different hash mixes for X, Y, Z offsets
const uint32 BiasedX = static_cast<uint32>(NX) + 0x7FFFFFFFu;
const uint32 BiasedY = static_cast<uint32>(NY) + 0x7FFFFFFFu;
const uint32 BiasedZ = static_cast<uint32>(NZ) + 0x7FFFFFFFu;
uint32 H = VoxelHash::Mix( uint32 H = VoxelHash::Mix(
(uint32)(NX + 0x7FFFFFFF) BiasedX
^ VoxelHash::Mix((uint32)(NY + 0x7FFFFFFF) * 2654435761u) ^ VoxelHash::Mix(BiasedY * 2654435761u)
^ VoxelHash::Mix((uint32)(NZ + 0x7FFFFFFF) * 374761393u) ^ VoxelHash::Mix(BiasedZ * 374761393u)
); );
float FPX = (float)DX + VoxelHash::ToFloat01(H) - FracX; float FPX = (float)DX + VoxelHash::ToFloat01(H) - FracX;
@@ -20,6 +20,11 @@
class FStandaloneShapeWorld; class FStandaloneShapeWorld;
using FStandaloneDensitySampleCacheLookup = float (*)(void* Cache,
const FStandaloneShapeWorld* ShapeWorld, uint64 FieldGeneration,
uint64 EditSequence, int32 Lod, int32 Step, int32 X, int32 Y, int32 Z,
bool& OutComputed);
// A run-scoped, read-only density lattice. The explorer owns the storage and installs the // A run-scoped, read-only density lattice. The explorer owns the storage and installs the
// pointer only while its canonical tiles are being meshed. The lattice includes the one-point // pointer only while its canonical tiles are being meshed. The lattice includes the one-point
// halo needed by every tile, so each world sample is generated once and copied into both adjacent // halo needed by every tile, so each world sample is generated once and copied into both adjacent
@@ -135,7 +140,8 @@ public:
std::vector<float>* OutDebugDensitySamples = nullptr, std::vector<float>* OutDebugDensitySamples = nullptr,
FVoxelMarchingCubesStageTimings* OutStageTimings = nullptr, FVoxelMarchingCubesStageTimings* OutStageTimings = nullptr,
FVoxelUniformBlockProofAuditStats* OutBlockProofAudit = nullptr, FVoxelUniformBlockProofAuditStats* OutBlockProofAudit = nullptr,
bool bForceFullDensityCapture = false); bool bForceFullDensityCapture = false,
uint64* OutSkippedSkirtTriangleSeeds = nullptr);
//========================================================================= //=========================================================================
// SERVICES (injectés par AVoxelWorld) // SERVICES (injectés par AVoxelWorld)
@@ -164,6 +170,19 @@ public:
// set this pointer and continue through Generator. // set this pointer and continue through Generator.
void SetStandaloneShapeWorld(const FStandaloneShapeWorld* InWorld) { ShapeWorldContext = InWorld; } void SetStandaloneShapeWorld(const FStandaloneShapeWorld* InWorld) { ShapeWorldContext = InWorld; }
void SetVisibleSkirtFaceMask(uint8 InMask) { StandaloneVisibleSkirtFaceMask = InMask & 0x3fu; }
void SetSharedDensitySampleCache(void* Cache, FStandaloneDensitySampleCacheLookup Lookup,
uint64 FieldGeneration, uint64 EditSequence, int32 Lod, int32 Step)
{
SharedDensitySampleCache = Cache;
SharedDensitySampleLookup = Lookup;
SharedDensityFieldGeneration = FieldGeneration;
SharedDensityEditSequence = EditSequence;
SharedDensityLod = Lod;
SharedDensityStep = Step;
}
// The caller owns this mesher instance for one tile task; no shared mutable feature state. // The caller owns this mesher instance for one tile task; no shared mutable feature state.
// L'appelant possede cette instance pour une tuile; aucune donnee partagee modifiable. // L'appelant possede cette instance pour une tuile; aucune donnee partagee modifiable.
void SetHybridSharpFeatures(TArray<FVoxelHybridSharpFeatureBox>&& InFeatures) void SetHybridSharpFeatures(TArray<FVoxelHybridSharpFeatureBox>&& InFeatures)
@@ -188,6 +207,13 @@ private:
const std::atomic<bool>* ShutdownFlag = nullptr; const std::atomic<bool>* ShutdownFlag = nullptr;
const FVoxelSharedDensityGrid* SharedDensityGrid = nullptr; const FVoxelSharedDensityGrid* SharedDensityGrid = nullptr;
const FStandaloneShapeWorld* ShapeWorldContext = nullptr; const FStandaloneShapeWorld* ShapeWorldContext = nullptr;
void* SharedDensitySampleCache = nullptr;
FStandaloneDensitySampleCacheLookup SharedDensitySampleLookup = nullptr;
uint64 SharedDensityFieldGeneration = 0;
uint64 SharedDensityEditSequence = 0;
int32 SharedDensityLod = 0;
int32 SharedDensityStep = 1;
uint8 StandaloneVisibleSkirtFaceMask = 0x3fu;
TArray<FVoxelHybridSharpFeatureBox> HybridSharpFeatures; TArray<FVoxelHybridSharpFeatureBox> HybridSharpFeatures;
bool bUseHybridSharpMesher = false; bool bUseHybridSharpMesher = false;
bool bUseHybridSharpFeatureSnap = false; bool bUseHybridSharpFeatureSnap = false;
@@ -332,16 +332,33 @@ struct FVoxelTileKey
FVoxelTileKey(const FIntVector& InCoord, int32 InLevel) : Coord(InCoord), Level(InLevel) {} FVoxelTileKey(const FIntVector& InCoord, int32 InLevel) : Coord(InCoord), Level(InLevel) {}
// Cell size (sampling step) in voxels, and tile extent in voxels per axis. // Cell size (sampling step) in voxels, and tile extent in voxels per axis.
int32 StepVoxels() const { return 1 << Level; } int32 StepVoxels() const
int32 ExtentVoxels() const { return CHUNK_SIZE << Level; } {
const uint32 SafeLevel = static_cast<uint32>(FMath::Clamp(Level, 0, 8));
return static_cast<int32>(uint32{1} << SafeLevel);
}
int32 ExtentVoxels() const
{
const uint32 SafeLevel = static_cast<uint32>(FMath::Clamp(Level, 0, 8));
return static_cast<int32>(static_cast<uint32>(CHUNK_SIZE) * (uint32{1} << SafeLevel));
}
// Min-corner of the tile in VOXEL coords (what the mesher takes). // Min-corner of the tile in VOXEL coords (what the mesher takes).
FIntVector OriginVoxels() const { return Coord * (CHUNK_SIZE << Level); } FIntVector OriginVoxels() const
{
const int64 Extent = ExtentVoxels();
const auto ScaleAxis = [Extent](int32 Axis)
{
return static_cast<int32>(FMath::Clamp<int64>(
static_cast<int64>(Axis) * Extent, MIN_int32, MAX_int32));
};
return FIntVector(ScaleAxis(Coord.X), ScaleAxis(Coord.Y), ScaleAxis(Coord.Z));
}
// Tile centre in world cm (for distance sorting / LOD selection). // Tile centre in world cm (for distance sorting / LOD selection).
FVector CenterCm() const FVector CenterCm() const
{ {
const double Ext = (double)(CHUNK_SIZE << Level) * (double)VOXEL_SIZE; const double Ext = static_cast<double>(ExtentVoxels()) * static_cast<double>(VOXEL_SIZE);
return FVector((Coord.X + 0.5) * Ext, (Coord.Y + 0.5) * Ext, (Coord.Z + 0.5) * Ext); return FVector((Coord.X + 0.5) * Ext, (Coord.Y + 0.5) * Ext, (Coord.Z + 0.5) * Ext);
} }
+1 -2
View File
@@ -1153,8 +1153,7 @@ namespace StandaloneWorldAssemblyPrototype
:nativeCaves?"Core.Caves.PrimitiveField" :nativeCaves?"Core.Caves.PrimitiveField"
:type==ECaveGeneratorType::SurfaceWorld?"SurfaceWorld.Heightfield2D" :type==ECaveGeneratorType::SurfaceWorld?"SurfaceWorld.Heightfield2D"
:type==ECaveGeneratorType::FloatingIslands?"FloatingIslands.ClusterField":"legacy."+std::string(GeneratorKind(type)); :type==ECaveGeneratorType::FloatingIslands?"FloatingIslands.ClusterField":"legacy."+std::string(GeneratorKind(type));
s.recipeVersion=nativeCaves?1 s.recipeVersion=(nativeCaves||nativeLattice)?1
:nativeLattice?VoxelForgeCore::NativeLatticeFieldRecipeVersion
:type==ECaveGeneratorType::SurfaceWorld?assembly.recipe.schemaVersion :type==ECaveGeneratorType::SurfaceWorld?assembly.recipe.schemaVersion
:type==ECaveGeneratorType::FloatingIslands&&!assembly.floatingIslands.empty() :type==ECaveGeneratorType::FloatingIslands&&!assembly.floatingIslands.empty()
?assembly.floatingIslands.front().manifest.recipe.geometryVersion:0; ?assembly.floatingIslands.front().manifest.recipe.geometryVersion:0;
+16 -4
View File
@@ -17,7 +17,13 @@ from source_stamp import source_stamp
ROOT = Path(__file__).resolve().parents[3] ROOT = Path(__file__).resolve().parents[3]
OUT = Path(os.environ.get("VOXELFORGE_VERIFY_OUT", ROOT / "Saved/Linux/l39")) OUT = Path(os.environ.get("VOXELFORGE_VERIFY_OUT", ROOT / "Saved/Linux/l39"))
LIMIT = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--"]
def limit_prefix() -> list[str]:
mode = os.environ.get("VOXELFORGE_LIMIT_MODE", "as")
if mode not in ("as", "rss"):
raise RuntimeError(f"unsupported process limit mode: {mode}")
return ["taskset", "-c", "0-11", "prlimit", f"--{mode}=6442450944", "--"]
RECIPE_RE = re.compile( RECIPE_RE = re.compile(
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$", r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$",
re.M) re.M)
@@ -57,7 +63,7 @@ def bounded_run(command: list[str], *, timeout: int = 1800,
stdout: Any = subprocess.PIPE) -> subprocess.CompletedProcess[str] | None: stdout: Any = subprocess.PIPE) -> subprocess.CompletedProcess[str] | None:
timeout = min(1800, max(1, int(timeout))) timeout = min(1800, max(1, int(timeout)))
try: try:
return subprocess.run([*LIMIT, "timeout", "--signal=KILL", f"{timeout}s", return subprocess.run([*limit_prefix(), "timeout", "--signal=KILL", f"{timeout}s",
*map(str, command)], *map(str, command)],
cwd=cwd, env=env, stdout=stdout, stderr=subprocess.STDOUT, cwd=cwd, env=env, stdout=stdout, stderr=subprocess.STDOUT,
text=True, encoding="utf-8", errors="replace", timeout=timeout, text=True, encoding="utf-8", errors="replace", timeout=timeout,
@@ -147,7 +153,12 @@ class VerificationRunner:
raise RuntimeError(f"missing executable: {binary}") raise RuntimeError(f"missing executable: {binary}")
digest = sha256_file(binary) digest = sha256_file(binary)
expected_stamp = expected_stamp or self.binary_stamp_overrides.get(str(binary), self.expected) expected_stamp = expected_stamp or self.binary_stamp_overrides.get(str(binary), self.expected)
proc = bounded_run([str(binary), "--print-source-stamp"], timeout=15) probe_env = None
if "sanitize" in binary.name.lower():
probe_env = os.environ.copy()
probe_env["ASAN_OPTIONS"] = "detect_leaks=0:abort_on_error=1:symbolize=1"
probe_env["LSAN_OPTIONS"] = "detect_leaks=0"
proc = bounded_run([str(binary), "--print-source-stamp"], timeout=15, env=probe_env)
output = proc.stdout or "" if proc is not None else "" output = proc.stdout or "" if proc is not None else ""
embedded = first_stamp(output) embedded = first_stamp(output)
if proc is None or proc.returncode != 0 or embedded is None: if proc is None or proc.returncode != 0 or embedded is None:
@@ -202,6 +213,7 @@ class VerificationRunner:
workers: int = 12, timeout: int = 1800, workers: int = 12, timeout: int = 1800,
layout_path: Path | None = None, layout_path: Path | None = None,
max_level: int = -1, max_level: int = -1,
clip_radius: int = 3,
expected_strate_styles: dict[str, int] | None = None, expected_strate_styles: dict[str, int] | None = None,
script_inputs: list[Path] | None = None, script_inputs: list[Path] | None = None,
allow_nonzero: bool = False, allow_nonzero: bool = False,
@@ -215,7 +227,7 @@ class VerificationRunner:
clean_flags = [flag for flag in flags if not flag.startswith("--resolved-layout-output=")] clean_flags = [flag for flag in flags if not flag.startswith("--resolved-layout-output=")]
command = [str(binary), str(data), f"--workers={workers}", command = [str(binary), str(data), f"--workers={workers}",
"--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline", "--world-assembler-prototype", "--stream-benchmark", "--stream-pipeline",
"--force-generate", "--clip-radius=3", "--max-clip-level=8", "--force-generate", f"--clip-radius={clip_radius}", "--max-clip-level=8",
f"--max-level={max_level}", f"--max-level={max_level}",
f"--recipe={recipe}", *clean_flags, f"--resolved-layout-output={layout_path}"] f"--recipe={recipe}", *clean_flags, f"--resolved-layout-output={layout_path}"]
log = OUT / f"{name}.log" log = OUT / f"{name}.log"
File diff suppressed because it is too large. Load diff