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
This commit is contained in:
1 parent
c0c204ba50
commit
135957831b
13 files changed
+760
-132
No files matched your search
@@ -13,6 +13,8 @@ COMPILER="clang++-19"
|
||||
VARIANT="clang19"
|
||||
FP_CONTRACT="off"
|
||||
JOBS=12
|
||||
SANITIZERS=""
|
||||
EXCLUDE_UNSIGNED_OVERFLOW=0
|
||||
|
||||
while (($#)); do
|
||||
case "$1" in
|
||||
@@ -20,6 +22,8 @@ while (($#)); do
|
||||
--variant=*) VARIANT="${1#*=}" ;;
|
||||
--fp-contract=*) FP_CONTRACT="${1#*=}" ;;
|
||||
--jobs=*) JOBS="${1#*=}" ;;
|
||||
--sanitize=*) SANITIZERS="${1#*=}" ;;
|
||||
--exclude-unsigned-integer-overflow) EXCLUDE_UNSIGNED_OVERFLOW=1 ;;
|
||||
*) printf 'Unknown build option: %s\n' "$1" >&2; exit 2 ;;
|
||||
esac
|
||||
shift
|
||||
@@ -60,11 +64,22 @@ INCLUDES=(
|
||||
"-I$ROOT/Source/VoxelForge/Public"
|
||||
"-I$ROOT/Source/VoxelForge/Private"
|
||||
)
|
||||
OPT_LEVEL=-O2
|
||||
LINK_FLAGS=()
|
||||
if [[ -n "$SANITIZERS" ]]; then
|
||||
OPT_LEVEL=-O1
|
||||
LINK_FLAGS+=("-fsanitize=$SANITIZERS" -fno-sanitize-recover=undefined)
|
||||
if ((EXCLUDE_UNSIGNED_OVERFLOW)); then
|
||||
LINK_FLAGS+=(-fno-sanitize=unsigned-integer-overflow)
|
||||
fi
|
||||
fi
|
||||
|
||||
FLAGS=(
|
||||
-std=c++20 -O2 -mavx2 -mfma -fno-fast-math
|
||||
-std=c++20 "$OPT_LEVEL" -mavx2 -mfma -fno-fast-math
|
||||
-fexceptions -pthread "${INCLUDES[@]}"
|
||||
"-DVOXELFORGE_SOURCE_STAMP=\"$SOURCE_STAMP\""
|
||||
)
|
||||
FLAGS+=("${LINK_FLAGS[@]}")
|
||||
if [[ "$FP_CONTRACT" != "default" ]]; then
|
||||
FLAGS+=("-ffp-contract=$FP_CONTRACT")
|
||||
fi
|
||||
@@ -131,7 +146,7 @@ PIDS=()
|
||||
CORE_LIBRARY="$BUILD_DIR/libVoxelForgeCore.a"
|
||||
"$(command -v ar)" rcs "$CORE_LIBRARY" "${CORE_OBJECTS[@]}"
|
||||
OUTPUT="$BIN_DIR/VoxelForgeCoreHeadless-$VARIANT"
|
||||
"$COMPILER" -pthread "${LEGACY_OBJECTS[@]}" "$CORE_LIBRARY" -o "$OUTPUT"
|
||||
"$COMPILER" -pthread "${LINK_FLAGS[@]}" "${LEGACY_OBJECTS[@]}" "$CORE_LIBRARY" -o "$OUTPUT"
|
||||
CAPTURE_OUTPUT="$BIN_DIR/VoxelForgeOffscreenCapture-$VARIANT"
|
||||
"$COMPILER" -std=c++20 -O2 -fno-fast-math -pthread \
|
||||
"-DVOXELFORGE_SOURCE_STAMP=\"$SOURCE_STAMP\"" -I"$ROOT/Source" \
|
||||
@@ -145,4 +160,5 @@ if [[ "$POST_BUILD_STAMP" != "$SOURCE_STAMP" ]]; then
|
||||
fi
|
||||
printf 'Built Core library: %s\nBuilt headless benchmark: %s\n' "$CORE_LIBRARY" "$OUTPUT"
|
||||
printf 'Built EGL offscreen capture: %s\n' "$CAPTURE_OUTPUT"
|
||||
printf 'Compiler: %s | variant: %s | fp-contract: %s | jobs: %s\n' "$COMPILER" "$VARIANT" "$FP_CONTRACT" "$JOBS"
|
||||
printf 'Compiler: %s | variant: %s | fp-contract: %s | sanitizers: %s | jobs: %s\n' \
|
||||
"$COMPILER" "$VARIANT" "$FP_CONTRACT" "${SANITIZERS:-off}" "$JOBS"
|
||||
@@ -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,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.
|
||||
@@ -4,6 +4,27 @@
|
||||
|
||||
namespace VoxelClipmapDesiredTiles
|
||||
{
|
||||
FORCEINLINE int64 FloorDivWide(int64 Value, int64 Divisor)
|
||||
{
|
||||
if (Divisor <= 0) { return 0; }
|
||||
const int64 Quotient = Value / Divisor;
|
||||
const int64 Remainder = Value % Divisor;
|
||||
return Remainder < 0 ? Quotient - 1 : Quotient;
|
||||
}
|
||||
|
||||
FORCEINLINE int64 CeilDivWide(int64 Value, int64 Divisor)
|
||||
{
|
||||
if (Divisor <= 0) { return 0; }
|
||||
const int64 Quotient = Value / Divisor;
|
||||
const int64 Remainder = Value % Divisor;
|
||||
return Remainder > 0 ? Quotient + 1 : Quotient;
|
||||
}
|
||||
|
||||
FORCEINLINE int32 ClampInt32(int64 Value)
|
||||
{
|
||||
return static_cast<int32>(FMath::Clamp<int64>(Value, MIN_int32, MAX_int32));
|
||||
}
|
||||
|
||||
struct FParameters
|
||||
{
|
||||
int32 ClipRadius = 3;
|
||||
@@ -13,7 +34,10 @@ namespace VoxelClipmapDesiredTiles
|
||||
|
||||
FORCEINLINE int32 FloorDiv(int32 Value, int32 Divisor)
|
||||
{
|
||||
return Value >= 0 ? (Value / Divisor) : -(((-Value) + Divisor - 1) / Divisor);
|
||||
if (Divisor <= 0) { return 0; }
|
||||
const int32 Quotient = Value / Divisor;
|
||||
const int32 Remainder = Value % Divisor;
|
||||
return Remainder < 0 ? Quotient - 1 : Quotient;
|
||||
}
|
||||
|
||||
FORCEINLINE FIntVector FloorDiv(const FIntVector& Value, int32 Divisor)
|
||||
@@ -24,7 +48,10 @@ namespace VoxelClipmapDesiredTiles
|
||||
|
||||
FORCEINLINE int32 CeilDiv(int32 Value, int32 Divisor)
|
||||
{
|
||||
return -FloorDiv(-Value, Divisor);
|
||||
if (Divisor <= 0) { return 0; }
|
||||
const int32 Quotient = Value / Divisor;
|
||||
const int32 Remainder = Value % Divisor;
|
||||
return Remainder > 0 ? Quotient + 1 : Quotient;
|
||||
}
|
||||
|
||||
FORCEINLINE void AlignedRingBounds(const FIntVector& Center, int32 Radius, int32 Alignment,
|
||||
@@ -33,10 +60,12 @@ namespace VoxelClipmapDesiredTiles
|
||||
const auto AlignAxis = [Radius, Alignment](int32 CenterAxis,
|
||||
int32& OutAxisMin, int32& OutAxisMax)
|
||||
{
|
||||
const int32 BaseMin = CenterAxis - Radius;
|
||||
const int32 BaseEnd = CenterAxis + Radius + 1;
|
||||
OutAxisMin = FloorDiv(BaseMin, Alignment) * Alignment;
|
||||
OutAxisMax = CeilDiv(BaseEnd, Alignment) * Alignment - 1;
|
||||
const int64 SafeRadius = FMath::Max<int64>(0, Radius);
|
||||
const int64 SafeAlignment = FMath::Max<int64>(1, Alignment);
|
||||
const int64 BaseMin = static_cast<int64>(CenterAxis) - SafeRadius;
|
||||
const int64 BaseEnd = static_cast<int64>(CenterAxis) + SafeRadius + 1;
|
||||
OutAxisMin = ClampInt32(FloorDivWide(BaseMin, SafeAlignment) * SafeAlignment);
|
||||
OutAxisMax = ClampInt32(CeilDivWide(BaseEnd, SafeAlignment) * SafeAlignment - 1);
|
||||
};
|
||||
AlignAxis(Center.X, OutMin.X, OutMax.X);
|
||||
AlignAxis(Center.Y, OutMin.Y, OutMax.Y);
|
||||
@@ -49,8 +78,13 @@ namespace VoxelClipmapDesiredTiles
|
||||
{
|
||||
return Radius;
|
||||
}
|
||||
return FMath::Max(Radius,
|
||||
(Parameters.RenderDistanceChunks + (1 << MaxLevel) - 1) >> MaxLevel);
|
||||
const int32 SafeLevel = FMath::Clamp(MaxLevel, 0, 8);
|
||||
const int64 Scale = 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,
|
||||
@@ -73,12 +107,12 @@ namespace VoxelClipmapDesiredTiles
|
||||
return;
|
||||
}
|
||||
|
||||
OutMinChunkZ = CenterChunkZ - ViewDistanceDown;
|
||||
OutMaxChunkZ = CenterChunkZ + ViewDistanceUp;
|
||||
OutMinChunkZ = ClampInt32(static_cast<int64>(CenterChunkZ) - ViewDistanceDown);
|
||||
OutMaxChunkZ = ClampInt32(static_cast<int64>(CenterChunkZ) + ViewDistanceUp);
|
||||
if (bHasStrate)
|
||||
{
|
||||
OutMinChunkZ = StrateBottomChunkZ - StrateViewMarginChunks;
|
||||
OutMaxChunkZ = StrateTopChunkZ + StrateViewMarginChunks;
|
||||
OutMinChunkZ = ClampInt32(static_cast<int64>(StrateBottomChunkZ) - StrateViewMarginChunks);
|
||||
OutMaxChunkZ = ClampInt32(static_cast<int64>(StrateTopChunkZ) + StrateViewMarginChunks);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +128,7 @@ namespace VoxelClipmapDesiredTiles
|
||||
|
||||
for (int32 Level = 0; Level <= MaxLevel; ++Level)
|
||||
{
|
||||
const int32 Pow = 1 << Level;
|
||||
const int32 Pow = static_cast<int32>(uint32{1} << static_cast<uint32>(Level));
|
||||
const FIntVector CenterLevel = FloorDiv(Center, Pow);
|
||||
const int32 LevelRadius = (Level == MaxLevel) ? OuterRadius : Radius;
|
||||
|
||||
@@ -105,24 +139,23 @@ namespace VoxelClipmapDesiredTiles
|
||||
FIntVector RingMin, RingMax;
|
||||
AlignedRingBounds(CenterLevel, LevelRadius, RingAlignment, RingMin, RingMax);
|
||||
|
||||
int32 DeltaZMin = RingMin.Z - CenterLevel.Z;
|
||||
int32 DeltaZMax = RingMax.Z - CenterLevel.Z;
|
||||
int64 TileMinZ = RingMin.Z;
|
||||
int64 TileMaxZ = RingMax.Z;
|
||||
if (LevelRadius > Radius && MinChunkZ != MIN_int32)
|
||||
{
|
||||
DeltaZMin = FMath::Max(DeltaZMin, FloorDiv(MinChunkZ, Pow) - CenterLevel.Z);
|
||||
DeltaZMax = FMath::Min(DeltaZMax, FloorDiv(MaxChunkZ, Pow) - CenterLevel.Z);
|
||||
TileMinZ = FMath::Max<int64>(TileMinZ, FloorDiv(MinChunkZ, Pow));
|
||||
TileMaxZ = FMath::Min<int64>(TileMaxZ, FloorDiv(MaxChunkZ, Pow));
|
||||
}
|
||||
|
||||
for (int32 DeltaZ = DeltaZMin; DeltaZ <= DeltaZMax; ++DeltaZ)
|
||||
for (int32 DeltaY = RingMin.Y - CenterLevel.Y;
|
||||
DeltaY <= RingMax.Y - CenterLevel.Y; ++DeltaY)
|
||||
for (int32 DeltaX = RingMin.X - CenterLevel.X;
|
||||
DeltaX <= RingMax.X - CenterLevel.X; ++DeltaX)
|
||||
for (int64 Z = TileMinZ; Z <= TileMaxZ; ++Z)
|
||||
for (int64 Y = RingMin.Y; Y <= RingMax.Y; ++Y)
|
||||
for (int64 X = RingMin.X; X <= RingMax.X; ++X)
|
||||
{
|
||||
const FIntVector Tile = CenterLevel + FIntVector(DeltaX, DeltaY, DeltaZ);
|
||||
const int32 TileMinZ = Tile.Z << Level;
|
||||
const int32 TileMaxZ = ((Tile.Z + 1) << Level) - 1;
|
||||
if (TileMaxZ < MinChunkZ || TileMinZ > MaxChunkZ)
|
||||
const FIntVector Tile(static_cast<int32>(X), static_cast<int32>(Y),
|
||||
static_cast<int32>(Z));
|
||||
const int64 TileBottomChunkZ = static_cast<int64>(Tile.Z) * Pow;
|
||||
const int64 TileTopChunkZ = (static_cast<int64>(Tile.Z) + 1) * Pow - 1;
|
||||
if (TileTopChunkZ < MinChunkZ || TileBottomChunkZ > MaxChunkZ)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -130,12 +163,12 @@ namespace VoxelClipmapDesiredTiles
|
||||
if (Level > 0)
|
||||
{
|
||||
const bool bCovered =
|
||||
(2 * Tile.X >= PreviousRingMin.X)
|
||||
&& (2 * Tile.X + 1 <= PreviousRingMax.X)
|
||||
&& (2 * Tile.Y >= PreviousRingMin.Y)
|
||||
&& (2 * Tile.Y + 1 <= PreviousRingMax.Y)
|
||||
&& (2 * Tile.Z >= PreviousRingMin.Z)
|
||||
&& (2 * Tile.Z + 1 <= PreviousRingMax.Z);
|
||||
(2 * static_cast<int64>(Tile.X) >= PreviousRingMin.X)
|
||||
&& (2 * static_cast<int64>(Tile.X) + 1 <= PreviousRingMax.X)
|
||||
&& (2 * static_cast<int64>(Tile.Y) >= PreviousRingMin.Y)
|
||||
&& (2 * static_cast<int64>(Tile.Y) + 1 <= PreviousRingMax.Y)
|
||||
&& (2 * static_cast<int64>(Tile.Z) >= PreviousRingMin.Z)
|
||||
&& (2 * static_cast<int64>(Tile.Z) + 1 <= PreviousRingMax.Z);
|
||||
if (bCovered)
|
||||
{
|
||||
continue;
|
||||
|
||||
@@ -332,16 +332,33 @@ struct FVoxelTileKey
|
||||
FVoxelTileKey(const FIntVector& InCoord, int32 InLevel) : Coord(InCoord), Level(InLevel) {}
|
||||
|
||||
// Cell size (sampling step) in voxels, and tile extent in voxels per axis.
|
||||
int32 StepVoxels() const { return 1 << Level; }
|
||||
int32 ExtentVoxels() const { return CHUNK_SIZE << Level; }
|
||||
int32 StepVoxels() const
|
||||
{
|
||||
const uint32 SafeLevel = static_cast<uint32>(FMath::Clamp(Level, 0, 8));
|
||||
return static_cast<int32>(uint32{1} << SafeLevel);
|
||||
}
|
||||
int32 ExtentVoxels() const
|
||||
{
|
||||
const uint32 SafeLevel = static_cast<uint32>(FMath::Clamp(Level, 0, 8));
|
||||
return static_cast<int32>(static_cast<uint32>(CHUNK_SIZE) * (uint32{1} << SafeLevel));
|
||||
}
|
||||
|
||||
// Min-corner of the tile in VOXEL coords (what the mesher takes).
|
||||
FIntVector OriginVoxels() const { return Coord * (CHUNK_SIZE << Level); }
|
||||
FIntVector OriginVoxels() const
|
||||
{
|
||||
const int64 Extent = ExtentVoxels();
|
||||
const auto ScaleAxis = [Extent](int32 Axis)
|
||||
{
|
||||
return static_cast<int32>(FMath::Clamp<int64>(
|
||||
static_cast<int64>(Axis) * Extent, MIN_int32, MAX_int32));
|
||||
};
|
||||
return FIntVector(ScaleAxis(Coord.X), ScaleAxis(Coord.Y), ScaleAxis(Coord.Z));
|
||||
}
|
||||
|
||||
// Tile centre in world cm (for distance sorting / LOD selection).
|
||||
FVector CenterCm() const
|
||||
{
|
||||
const double Ext = (double)(CHUNK_SIZE << Level) * (double)VOXEL_SIZE;
|
||||
const double Ext = static_cast<double>(ExtentVoxels()) * static_cast<double>(VOXEL_SIZE);
|
||||
return FVector((Coord.X + 0.5) * Ext, (Coord.Y + 0.5) * Ext, (Coord.Z + 0.5) * Ext);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,13 @@ from source_stamp import source_stamp
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[3]
|
||||
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(
|
||||
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$",
|
||||
re.M)
|
||||
@@ -57,7 +63,7 @@ def bounded_run(command: list[str], *, timeout: int = 1800,
|
||||
stdout: Any = subprocess.PIPE) -> subprocess.CompletedProcess[str] | None:
|
||||
timeout = min(1800, max(1, int(timeout)))
|
||||
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)],
|
||||
cwd=cwd, env=env, stdout=stdout, stderr=subprocess.STDOUT,
|
||||
text=True, encoding="utf-8", errors="replace", timeout=timeout,
|
||||
@@ -147,7 +153,12 @@ class VerificationRunner:
|
||||
raise RuntimeError(f"missing executable: {binary}")
|
||||
digest = sha256_file(binary)
|
||||
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 ""
|
||||
embedded = first_stamp(output)
|
||||
if proc is None or proc.returncode != 0 or embedded is None:
|
||||
@@ -202,6 +213,7 @@ class VerificationRunner:
|
||||
workers: int = 12, timeout: int = 1800,
|
||||
layout_path: Path | None = None,
|
||||
max_level: int = -1,
|
||||
clip_radius: int = 3,
|
||||
expected_strate_styles: dict[str, int] | None = None,
|
||||
script_inputs: list[Path] | None = None,
|
||||
allow_nonzero: bool = False,
|
||||
@@ -215,7 +227,7 @@ class VerificationRunner:
|
||||
clean_flags = [flag for flag in flags if not flag.startswith("--resolved-layout-output=")]
|
||||
command = [str(binary), str(data), f"--workers={workers}",
|
||||
"--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"--recipe={recipe}", *clean_flags, f"--resolved-layout-output={layout_path}"]
|
||||
log = OUT / f"{name}.log"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run the reusable VoxelForge verification tiers: fast, medium, or full."""
|
||||
"""Run the reusable VoxelForge verification tiers: fast, medium, full, or sanitize."""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
@@ -33,7 +33,7 @@ OWNER = ROOT / "Standalone/data/owner_world.json"
|
||||
OUT_DEFAULT = ROOT / "Saved/Linux/l42"
|
||||
|
||||
PARSER = argparse.ArgumentParser(description=__doc__)
|
||||
PARSER.add_argument("tier", choices=("fast", "medium", "full"))
|
||||
PARSER.add_argument("tier", choices=("fast", "medium", "full", "sanitize"))
|
||||
PARSER.add_argument("--out", type=Path, default=OUT_DEFAULT,
|
||||
help="artifact directory (default: Saved/Linux/l41)")
|
||||
PARSER.add_argument("--baseline", default="HEAD",
|
||||
@@ -42,7 +42,11 @@ PARSER.add_argument("--overall-timeout", type=int, default=7200,
|
||||
help="maximum tier wall time in seconds (default: 7200; each child is capped at 1800)")
|
||||
PARSER.add_argument("--speed-matrix", action="store_true",
|
||||
help="run five interleaved full-view timings for every generator")
|
||||
PARSER.add_argument("--sanitize", action="store_true",
|
||||
help="in the full tier, also run the standalone short sanitizer sweep")
|
||||
ARGS = PARSER.parse_args()
|
||||
if ARGS.sanitize and ARGS.tier != "full":
|
||||
PARSER.error("--sanitize is available only with the full tier; use the sanitize tier on its own")
|
||||
OUT = ARGS.out.resolve()
|
||||
ROUND_NAME = OUT.name
|
||||
os.environ["VOXELFORGE_VERIFY_OUT"] = str(OUT)
|
||||
@@ -2548,6 +2552,253 @@ def run_gcc_supplement(runner: VerificationRunner) -> None:
|
||||
run_motif_determinism(runner, GCC, compare_reference=True)
|
||||
|
||||
|
||||
SANITIZER_STAGE_SECONDS = 1200
|
||||
# Clang's integer group diagnoses the defined unsigned left shift inside libstdc++ std::rotl.
|
||||
# Keep the Clang sweep on UBSan/ASan plus float-cast checks; GCC still runs the requested integer core.
|
||||
CLANG_SANITIZERS = "address,undefined,float-cast-overflow"
|
||||
|
||||
|
||||
def _sanitize_camera_flag(camera: tuple[float, float, float, float, float]) -> str:
|
||||
return "--camera=" + ",".join(
|
||||
[*(f"{value:.6f}" for value in camera[:3]),
|
||||
*(f"{value:.3f}" for value in camera[3:])])
|
||||
|
||||
|
||||
def _sanitize_build(runner: VerificationRunner, compiler: str, variant: str,
|
||||
sanitizer_flags: str, *, exclude_unsigned: bool,
|
||||
deadline: float) -> Path:
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
(OUT / "tmp").mkdir(parents=True, exist_ok=True)
|
||||
build_root = OUT / "sanitizer-build" / variant
|
||||
binary_dir = OUT / "bin"
|
||||
env = os.environ.copy()
|
||||
env.update({
|
||||
"TMPDIR": str(OUT / "tmp"),
|
||||
"VOXELFORGE_VERIFY_OUT": str(OUT),
|
||||
"VOXELFORGE_BUILD_ROOT": str(build_root),
|
||||
"VOXELFORGE_BIN_DIR": str(binary_dir),
|
||||
"VOXELFORGE_LIMITED_BUILD": "1",
|
||||
})
|
||||
command = ["bash", str(ROOT / "Standalone/BuildLinux.sh"),
|
||||
f"--compiler={compiler}", f"--variant={variant}",
|
||||
"--fp-contract=off", "--jobs=2", f"--sanitize={sanitizer_flags}"]
|
||||
if exclude_unsigned:
|
||||
command.append("--exclude-unsigned-integer-overflow")
|
||||
log = OUT / f"sanitizer-build-{variant}.log"
|
||||
remaining = max(1, int(deadline - time.monotonic()))
|
||||
with log.open("w", encoding="utf-8") as stream:
|
||||
proc = bounded_run(command, timeout=min(600, remaining), env=env, stdout=stream)
|
||||
ok = proc is not None and proc.returncode == 0
|
||||
runner.require(f"sanitizer-build-{variant}", ok,
|
||||
f"compiler={compiler} flags={sanitizer_flags} jobs=2 fp-contract=off "
|
||||
f"limit={os.environ.get('VOXELFORGE_LIMIT_MODE', 'as')} log={log}")
|
||||
binary = binary_dir / f"VoxelForgeCoreHeadless-{variant}"
|
||||
runner.probe_binary(binary)
|
||||
return binary
|
||||
|
||||
|
||||
def _sanitizer_control(runner: VerificationRunner, compiler: str,
|
||||
binary_tag: str, sanitizer_flags: str,
|
||||
*, exclude_unsigned: bool, deadline: float) -> None:
|
||||
control_dir = OUT / "sanitizer"
|
||||
control_dir.mkdir(parents=True, exist_ok=True)
|
||||
source = control_dir / "signed-overflow-control.cpp"
|
||||
source.write_text(
|
||||
"#include <climits>\n"
|
||||
"int main() { volatile int maximum = INT_MAX; volatile int one = 1; "
|
||||
"volatile int result = maximum + one; return result == 0; }\n",
|
||||
encoding="utf-8")
|
||||
executable = control_dir / f"signed-overflow-control-{binary_tag}"
|
||||
compile_command = [compiler, "-std=c++20", "-O1", "-fno-fast-math",
|
||||
"-ffp-contract=off", f"-fsanitize={sanitizer_flags}",
|
||||
"-fno-sanitize-recover=undefined", str(source), "-o", str(executable)]
|
||||
if exclude_unsigned:
|
||||
compile_command.insert(-2, "-fno-sanitize=unsigned-integer-overflow")
|
||||
compile_log = control_dir / f"signed-overflow-control-{binary_tag}-build.log"
|
||||
compile_remaining = max(1, int(deadline - time.monotonic()))
|
||||
with compile_log.open("w", encoding="utf-8") as stream:
|
||||
built = bounded_run(compile_command, timeout=min(60, compile_remaining), stdout=stream)
|
||||
runner.require(f"sanitizer-control-build-{binary_tag}",
|
||||
built is not None and built.returncode == 0,
|
||||
f"test-only INT_MAX+1 compiled with {sanitizer_flags}")
|
||||
run_log = control_dir / f"signed-overflow-control-{binary_tag}.log"
|
||||
run_remaining = max(1, int(deadline - time.monotonic()))
|
||||
with run_log.open("w", encoding="utf-8") as stream:
|
||||
caught = bounded_run([str(executable)], timeout=min(30, run_remaining), stdout=stream)
|
||||
output = run_log.read_text(encoding="utf-8", errors="replace")
|
||||
runner.require(f"sanitizer-control-caught-{binary_tag}",
|
||||
caught is not None and caught.returncode != 0
|
||||
and "signed integer overflow" in output,
|
||||
f"signed overflow exited {caught.returncode if caught else 'TIMEOUT'}; log={run_log}")
|
||||
|
||||
|
||||
def run_sanitizer_stage(runner: VerificationRunner) -> None:
|
||||
"""Run small random-camera LOD 0..8 sweeps through both instrumented builds."""
|
||||
previous = {key: os.environ.get(key) for key in (
|
||||
"VOXELFORGE_LIMIT_MODE", "ASAN_OPTIONS", "LSAN_OPTIONS", "UBSAN_OPTIONS")}
|
||||
os.environ["VOXELFORGE_LIMIT_MODE"] = "rss"
|
||||
os.environ["ASAN_OPTIONS"] = "detect_leaks=0:abort_on_error=1:symbolize=1"
|
||||
os.environ["LSAN_OPTIONS"] = "detect_leaks=0"
|
||||
os.environ["UBSAN_OPTIONS"] = "print_stacktrace=1:halt_on_error=1"
|
||||
deadline = time.monotonic() + SANITIZER_STAGE_SECONDS
|
||||
failures: list[str] = []
|
||||
try:
|
||||
clang_binary = _sanitize_build(runner, "clang++-19",
|
||||
f"{ROUND_NAME}-clang-sanitize",
|
||||
CLANG_SANITIZERS,
|
||||
exclude_unsigned=True, deadline=deadline)
|
||||
gcc_binary = _sanitize_build(runner, "g++",
|
||||
f"{ROUND_NAME}-gcc-sanitize", "address,undefined",
|
||||
exclude_unsigned=False, deadline=deadline)
|
||||
_sanitizer_control(runner, "clang++-19", "clang",
|
||||
CLANG_SANITIZERS,
|
||||
exclude_unsigned=True, deadline=deadline)
|
||||
_sanitizer_control(runner, "g++", "gcc", "address,undefined",
|
||||
exclude_unsigned=False, deadline=deadline)
|
||||
|
||||
profile_paths = {profile: OUT / "fixtures" / f"style-override-profile-{profile}.json"
|
||||
for profile in PROFILE_NAMES}
|
||||
if not all(path.is_file() for path in profile_paths.values()):
|
||||
runner.run_stage("sanitizer-build-profile-fixtures", LINUX53 / "BuildProfileFixturesL53.py",
|
||||
["--out", str(OUT / "fixtures")], binary=CLANG, data=OWNER,
|
||||
recipe="surface-world", style=None, env=env_for(),
|
||||
script_inputs=[OWNER])
|
||||
for profile, fixture in profile_paths.items():
|
||||
manifest = OUT / "fixtures" / f"style-override-profile-{profile}-manifest.json"
|
||||
if not manifest.is_file():
|
||||
name = f"sanitizer-profile-camera-manifest-{profile}"
|
||||
runner.run_cpp(name, CLANG, fixture, "surface-world",
|
||||
[f"--world-assembler-manifest-output={manifest}",
|
||||
"--render-distance-chunks=0"], expected_style=None,
|
||||
expected_strate_styles={"0": 2, "1": 1}, workers=1,
|
||||
max_level=-1, clip_radius=0,
|
||||
layout_path=OUT / "layouts" / f"{name}.json")
|
||||
caves_fixture = OUT / "fixtures" / "caves.json"
|
||||
if not caves_fixture.is_file():
|
||||
caves_fixture = build_caves_fixture(runner, CLANG)
|
||||
lattice_fixture = OUT / "fixtures" / "lattice-l58.json"
|
||||
if not lattice_fixture.is_file():
|
||||
lattice_fixture = build_lattice_fixture(runner, CLANG)
|
||||
motif_fixture = OUT / "fixtures" / "style-override-motifs-slice1.json"
|
||||
if not motif_fixture.is_file():
|
||||
motif_fixture = build_motif_fixture(runner)
|
||||
|
||||
cave_layout = OUT / "layouts" / "native-caves-field-clang.json"
|
||||
lattice_layout = OUT / "layouts" / "native-lattice-field-clang.json"
|
||||
motif_layout = OUT / "layouts" / "motif-slice1-capsule-reachability-clang.json"
|
||||
if not cave_layout.is_file():
|
||||
_, cave_layout = run_native_caves_field_audit(runner, CLANG)
|
||||
if not lattice_layout.is_file():
|
||||
_, lattice_layout = run_native_lattice_field_audit(runner, CLANG)
|
||||
if not motif_layout.is_file():
|
||||
_, motif_layout = run_motif_fixture_audits(runner, CLANG)
|
||||
|
||||
owner_cameras = seeded_camera_positions(5)
|
||||
cases: list[tuple[str, Path, str, list[str], int | None,
|
||||
dict[str, int] | None, list[tuple[float, float, float, float, float]]]] = [
|
||||
("natural-sw", OWNER, "surface-world", [], 2, {"0": 2, "1": 1},
|
||||
[owner_cameras[0], owner_cameras[3]]),
|
||||
("floating-islands", OWNER, "floating-islands", [], 2, {"0": 2, "1": 1},
|
||||
[owner_cameras[2], owner_cameras[4]]),
|
||||
]
|
||||
for profile in PROFILE_NAMES:
|
||||
cases.append((f"profile-{profile}", profile_paths[profile], "surface-world",
|
||||
[], 2, {"0": 2, "1": 1}, profile_camera_positions(profile, count=2)))
|
||||
cases.extend([
|
||||
("caves", caves_fixture, "caves", [], 2, {"0": 2, "1": 1},
|
||||
cave_camera_positions(cave_layout, count=2)),
|
||||
("motifs", motif_fixture, "surface-world", [], None, {"0": 2, "1": 1},
|
||||
[row[1] for row in motif_camera_rows(motif_layout) if row[0].startswith("random")]),
|
||||
("lattice", lattice_fixture, "lattice", ["--lattice-strate=1"], None,
|
||||
{"0": 2, "1": 1, "2": 1}, lattice_camera_positions(lattice_layout, count=2)),
|
||||
])
|
||||
|
||||
binaries = (("clang", clang_binary), ("gcc", gcc_binary))
|
||||
for compiler_tag, binary in binaries:
|
||||
for generator, data, recipe, base_flags, style, strate_styles, cameras in cases:
|
||||
for camera_index, camera in enumerate(cameras):
|
||||
if time.monotonic() >= deadline:
|
||||
failures.append("sanitizer stage time budget expired")
|
||||
break
|
||||
name = (f"sanitize-{compiler_tag}-{generator}-camera{camera_index}-"
|
||||
f"lod0-8")
|
||||
flags = [*base_flags, "--stream-hash-tiles", "--render-distance-chunks=0",
|
||||
_sanitize_camera_flag(camera)]
|
||||
try:
|
||||
_, output = runner.run_cpp(name, binary, data, recipe, flags,
|
||||
expected_style=style, expected_strate_styles=strate_styles,
|
||||
workers=1, max_level=-1, clip_radius=0,
|
||||
timeout=min(180, max(1, int(deadline - time.monotonic()))),
|
||||
layout_path=OUT / "layouts" / f"{name}.json")
|
||||
lod_rows = full_lod_stream_rows(output)
|
||||
lod_ok = (set(lod_rows) == set(range(9))
|
||||
and all(lod_rows[level]["tiles"] > 0 for level in range(9)))
|
||||
runner.require(f"{name}:lod0-8-coverage", lod_ok,
|
||||
"positive tile sample at each of LOD0 through LOD8")
|
||||
except VerificationFailure as error:
|
||||
failures.append(f"{name}: {error}")
|
||||
|
||||
# Exercise passage resolution and a live 24 m camera crossing under both runtimes.
|
||||
for recipe, flags, style in (
|
||||
("floating-islands", ["--audit-resolved-layout-passages"], 2),
|
||||
("surface-world", ["--landmark-chamber-demo", "--audit-resolved-layout-passages"], 2),
|
||||
):
|
||||
name = f"sanitize-{compiler_tag}-passages-{recipe}-lod0-8"
|
||||
camera = owner_cameras[0 if recipe == "surface-world" else 2]
|
||||
try:
|
||||
_, output = runner.run_cpp(name, binary, OWNER, recipe,
|
||||
[*flags, "--stream-hash-tiles", "--render-distance-chunks=0",
|
||||
_sanitize_camera_flag(camera)], expected_style=style,
|
||||
expected_strate_styles={"0": 2, "1": 1}, workers=1,
|
||||
max_level=-1, clip_radius=0,
|
||||
timeout=min(180, max(1, int(deadline - time.monotonic()))),
|
||||
layout_path=OUT / "layouts" / f"{name}.json")
|
||||
lod_rows = full_lod_stream_rows(output)
|
||||
runner.require(f"{name}:lod0-8-coverage",
|
||||
set(lod_rows) == set(range(9))
|
||||
and all(lod_rows[level]["tiles"] > 0 for level in range(9)),
|
||||
"passage path streamed at each of LOD0 through LOD8")
|
||||
except VerificationFailure as error:
|
||||
failures.append(f"{name}: {error}")
|
||||
|
||||
crossing_name = f"sanitize-{compiler_tag}-surface-crossing-lod0-8"
|
||||
try:
|
||||
_, crossing_output = runner.run_cpp(crossing_name, binary, OWNER,
|
||||
"surface-world", ["--stream-hash-tiles", "--render-distance-chunks=0",
|
||||
"--camera=-12,-118,-20,0,0", "--camera-step=24,0,0", "--region-crossing"],
|
||||
expected_style=2, expected_strate_styles={"0": 2, "1": 1},
|
||||
workers=1, max_level=-1, clip_radius=0,
|
||||
timeout=min(180, max(1, int(deadline - time.monotonic()))),
|
||||
layout_path=OUT / "layouts" / f"{crossing_name}.json")
|
||||
lod_rows = full_lod_stream_rows(crossing_output)
|
||||
moving = re.search(r"^\[MovingTileLatency\] entering_tiles=(\d+)",
|
||||
crossing_output, re.M)
|
||||
crossing_ok = (set(lod_rows) == set(range(9))
|
||||
and all(lod_rows[level]["tiles"] > 0 for level in range(9))
|
||||
and moving is not None and int(moving.group(1)) > 0)
|
||||
runner.require(f"{crossing_name}:crossing-and-lod0-8", crossing_ok,
|
||||
f"entering_tiles={moving.group(1) if moving else 'MISSING'}")
|
||||
except VerificationFailure as error:
|
||||
failures.append(f"{crossing_name}: {error}")
|
||||
|
||||
edit_fixtures = [path for base in (ROOT / "Standalone/data", OUT / "fixtures")
|
||||
if base.is_dir() for path in base.rglob("*.json")
|
||||
if "edit" in path.name.lower()]
|
||||
runner.require("sanitizer-edit-fixture", True,
|
||||
(f"found {len(edit_fixtures)} edit fixture(s): "
|
||||
+ ", ".join(str(path) for path in edit_fixtures)) if edit_fixtures
|
||||
else "no edit fixture exists under Standalone/data or this run's fixtures")
|
||||
runner.require("sanitizer-stage-findings", not failures,
|
||||
"; ".join(failures) if failures else
|
||||
"all Clang/GCC ASan+UBSan generator, passage, crossing, and random-camera cases passed")
|
||||
finally:
|
||||
for key, value in previous.items():
|
||||
if value is None:
|
||||
os.environ.pop(key, None)
|
||||
else:
|
||||
os.environ[key] = value
|
||||
|
||||
|
||||
def _tier_deadline(_signum: int, _frame: object) -> None:
|
||||
raise VerificationFailure(
|
||||
f"{ARGS.tier} tier exceeded its {ARGS.overall_timeout}s overall timeout")
|
||||
@@ -2566,7 +2817,9 @@ def main() -> int:
|
||||
for binary in (CLANG, GCC, RENDERER):
|
||||
if binary.is_file():
|
||||
runner.probe_binary(binary)
|
||||
if ARGS.tier == "fast":
|
||||
if ARGS.tier == "sanitize":
|
||||
run_sanitizer_stage(runner)
|
||||
elif ARGS.tier == "fast":
|
||||
owners = run_owner_passages(runner, (CLANG,))
|
||||
run_landmark_reachability(runner, (CLANG,))
|
||||
run_regression_controls(runner)
|
||||
@@ -2594,6 +2847,9 @@ def main() -> int:
|
||||
if ARGS.speed_matrix:
|
||||
run_speed_matrix(runner)
|
||||
|
||||
if ARGS.sanitize:
|
||||
run_sanitizer_stage(runner)
|
||||
|
||||
runner.assert_binaries_current()
|
||||
runner.assert_current_tree("at tier completion")
|
||||
elapsed = time.monotonic() - started
|
||||
|
||||
Reference in new issue
Block a user