perf(review): gate never deadlocks, collision radius, 20x faster digs
From the outside performance review (items 1-4; item 5 prototyped and reverted).
1. Collision gate: a support request outside the stamped desired set was cancelled by the rebuild
it forced, and the gate held the pawn with gravity 0. Baseline deep-drop (floor at (0,0,-7),
outside the aligned L0 ring): 1 hold of 30.0 s, never released. Fix: the target and 3 tiles
below are retained through rebuilds at CollisionCritical; hold only when the swept capsule is
about to enter pending support; release restores movement mode, gravity and downward speed.
After: 3 holds, 2.21 s total, 112.8 m horizontal progress; gate off still penetrates (-7.4 cm).
2. Suspected cleanup regression (out-of-layout stacks): not real. 44 paired boundary tiles,
30.9 ms vs 32.6 ms at b4ac996, hashes 44/44 identical. No change.
3. PawnCollisionRadiusChunks (new, default 1): L0 tiles outside the collision set stay visible
without Chaos cooks; anchors and the gate's support column are unioned in. Cook requests
~217/min -> ~30/min (-86%). Critical support ready time did NOT improve (9.5 s -> 10.6 s in the
deep-drop fixture; the wait is the cook itself, ~7.4 s cook-to-ready there).
4. Dig: exact pre-edit L0 density cached (one 35^3 grid, 171.5 KB), carve replays current mods on
it. Sync remesh 99.4 ms -> 4.7 ms. 16/16 random carves hash-identical to full regeneration.
5. Parallel grid fill for the critical tile: 16/16 hash parity, but the waited-on tile was already
meshed; the wait was collision cooking. Reverted.
Review G answered: M_Test1 still ray-marches VolTex0 (custom HLSL mini-sun shadow), so the density
volume is live. Known and left alone: outside the strate layout the fallback density can be solid
while the gate treats those tiles as air.
Tests: VoxelForgeCollisionDesiredTilesTest, VoxelForgePreEditDensityCacheTest; no-overlap, edit
ghost, descent corridor pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
1 parent
116b57f01e
commit
0bcd67a016
13 files changed
+1099
-89
No files matched your search
+14
-9
@@ -254,7 +254,7 @@ Tile identity lives in `FVoxelTileKey` (VoxelTypes.h:326).
|
|||||||
`UVoxelSettings : UPrimaryDataAsset` — the single tuning asset assigned on `AVoxelWorld`.
|
`UVoxelSettings : UPrimaryDataAsset` — the single tuning asset assigned on `AVoxelWorld`.
|
||||||
| Group | Fields (line) |
|
| Group | Fields (line) |
|
||||||
|-------|---------------|
|
|-------|---------------|
|
||||||
| Streaming | `ViewDistanceXY=16`, `ViewDistanceUp/Down=5`, `MaxConcurrentTasks=16`, `MaxMeshAppliesPerFrame=4` (defaults — actual values live on the data asset) |
|
| Streaming | `ViewDistanceXY=16`, `ViewDistanceUp/Down=5`, `MaxConcurrentTasks=16`, `MaxMeshAppliesPerFrame=4`, `PawnCollisionRadiusChunks=1` (defaults — actual values live on the data asset; the collision radius is independent from the render clipmap) |
|
||||||
| Clipmap | `ClipRadius`, `MaxClipLevel=5`, `FullResClipLevels`, `CoarseTileCells`, `RenderDistanceChunks=768` (measured ship defaults; custom horizontal reach: the outermost shell keeps generating until it covers this many chunks; 0 = off), skirts, `LODOctaveDrop` (T2.b octave drop on coarse tiles — 0 = off/byte-identical). Existing serialized data assets retain their authored values until deliberately migrated. |
|
| Clipmap | `ClipRadius`, `MaxClipLevel=5`, `FullResClipLevels`, `CoarseTileCells`, `RenderDistanceChunks=768` (measured ship defaults; custom horizontal reach: the outermost shell keeps generating until it covers this many chunks; 0 = off), skirts, `LODOctaveDrop` (T2.b octave drop on coarse tiles — 0 = off/byte-identical). Existing serialized data assets retain their authored values until deliberately migrated. |
|
||||||
| World bounds | `WorldRadiusVoxels=8192` and `EdgeSealThickness=64` (actor-space XY radial shell; radius 0 = true legacy no-op) |
|
| World bounds | `WorldRadiusVoxels=8192` and `EdgeSealThickness=64` (actor-space XY radial shell; radius 0 = true legacy no-op) |
|
||||||
| Lighting | `bEnableDensityVolume` + DensityVolume* tunables (§3.11 density clipmap / mini-sun shadows) |
|
| Lighting | `bEnableDensityVolume` + DensityVolume* tunables (§3.11 density clipmap / mini-sun shadows) |
|
||||||
@@ -304,18 +304,21 @@ casse cette arithmetique *structurellement* — `BeginPlay` loggue une **Error**
|
|||||||
| `ApplyTileResult` | — | **Shared game-thread apply** for one `FChunkResult` (async drain + sync carve): discards stale generation epochs and keys no longer desired, marks loaded, ingests capture, EMPTY releases the tile's existing component (a re-gen can flip content→empty on band change — old geometry must not linger), else `ApplyMeshToTile`. Returns true iff a visible mesh uploaded (counts the budget). Doesn't touch `PendingTiles` (caller's). |
|
| `ApplyTileResult` | — | **Shared game-thread apply** for one `FChunkResult` (async drain + sync carve): discards stale generation epochs and keys no longer desired, marks loaded, ingests capture, EMPTY releases the tile's existing component (a re-gen can flip content→empty on band change — old geometry must not linger), else `ApplyMeshToTile`. Returns true iff a visible mesh uploaded (counts the budget). Doesn't touch `PendingTiles` (caller's). |
|
||||||
| `FVoxelStackSampler` / `FScopedVoxelStackRegistration` (`VoxelStackSampler.h/.cpp`) | — | **Diagnostic raw stack sampler, off by default.** A sampler thread captures registered generation workers' stacks (`CaptureThreadStackBackTrace`) every N µs and tags each with the tile's LOD. It writes bounded raw PCs to CSV plus a symbolization-free summary under `Saved/`; symbol resolution is deferred until after process exit so the measured process never initializes/calls DbgHelp for this diagnostic. Lifecycle teardown is serialized and registration TLS is session-tagged. Registration is per tile TASK in `LoadTile` (and the export's tasks), never per sample; disabled costs one relaxed atomic load. Game `-voxel.SampleStacks=<us>`, commandlet `-samplestacks[=us]`. |
|
| `FVoxelStackSampler` / `FScopedVoxelStackRegistration` (`VoxelStackSampler.h/.cpp`) | — | **Diagnostic raw stack sampler, off by default.** A sampler thread captures registered generation workers' stacks (`CaptureThreadStackBackTrace`) every N µs and tags each with the tile's LOD. It writes bounded raw PCs to CSV plus a symbolization-free summary under `Saved/`; symbol resolution is deferred until after process exit so the measured process never initializes/calls DbgHelp for this diagnostic. Lifecycle teardown is serialized and registration TLS is session-tagged. Registration is per tile TASK in `LoadTile` (and the export's tasks), never per sample; disabled costs one relaxed atomic load. Game `-voxel.SampleStacks=<us>`, commandlet `-samplestacks[=us]`. |
|
||||||
| Room-cache performance (2026-09-13): `VF_FindPlayerFitPointForRoomMemoized` / `GPlayerFitMemo` (`VoxelCaveMorphology.cpp`), `voxel.TileCacheWindow` + `VoxelGenLOD::TileOriginVoxels/TileCellsPerAxis` (`VoxelGenerator.h`), `FChunkSDFSpatialIndex` | — | **Player-fit memo:** exact 97-word key, process-wide, `voxel.PlayerFitMemo`. **Tile cache window:** the room cache covers the requesting tile's footprint plus a fixed collect margin. It is set via `TGuardValue` in `GenerateMesh`/classify, and is window-invariant by construction (the margin does not grow with the tile). **LOD-gated:** fused path at LOD ≥ `voxel.TileCacheWindowMinLOD` (3), op-stack at LOD ≥ `voxel.TileCacheWindowOpStackMinLOD` (4); lower LODs keep the 4-chunk worker window, because the tile window slowed LOD0 evaluation ~6%. Strates whose authored terrain ops exceed the proven 256-voxel extent (or are malformed/unloaded) fall back to the legacy window (`VF_ComputeTerrainOpEnvelope`). `voxel.SpatialIndex` is -1 auto (off at fused LOD0, on elsewhere), 0 or 1. **Spatial index:** ordered XY buckets over cache primitives, exact rejection. The `static_assert` in `VoxelStrateTypes.h` ties `VF_STRATE_PARAM_FIELDS` to `FStrateGenerationParams` (83 fields + 8 B padding). |
|
| Room-cache performance (2026-09-13): `VF_FindPlayerFitPointForRoomMemoized` / `GPlayerFitMemo` (`VoxelCaveMorphology.cpp`), `voxel.TileCacheWindow` + `VoxelGenLOD::TileOriginVoxels/TileCellsPerAxis` (`VoxelGenerator.h`), `FChunkSDFSpatialIndex` | — | **Player-fit memo:** exact 97-word key, process-wide, `voxel.PlayerFitMemo`. **Tile cache window:** the room cache covers the requesting tile's footprint plus a fixed collect margin. It is set via `TGuardValue` in `GenerateMesh`/classify, and is window-invariant by construction (the margin does not grow with the tile). **LOD-gated:** fused path at LOD ≥ `voxel.TileCacheWindowMinLOD` (3), op-stack at LOD ≥ `voxel.TileCacheWindowOpStackMinLOD` (4); lower LODs keep the 4-chunk worker window, because the tile window slowed LOD0 evaluation ~6%. Strates whose authored terrain ops exceed the proven 256-voxel extent (or are malformed/unloaded) fall back to the legacy window (`VF_ComputeTerrainOpEnvelope`). `voxel.SpatialIndex` is -1 auto (off at fused LOD0, on elsewhere), 0 or 1. **Spatial index:** ordered XY buckets over cache primitives, exact rejection. The `static_assert` in `VoxelStrateTypes.h` ties `VF_STRATE_PARAM_FIELDS` to `FStrateGenerationParams` (83 fields + 8 B padding). |
|
||||||
| `GenerateTileResult` | — | **Shared worker-side gen** for one tile (async `LoadTile` task + sync `SyncRemeshTile`): [ClassifyTile (T1.d) + exact validation, only when `voxel.OuterClassifierMode=1`; **default 0 since 2026-09-13**] → `GenerateMesh` → `BuildTileStreamSet`; records vertex count and resident stream allocation bytes for the horizon trace. Reads Generator/Mesher only → safe on a worker or the game thread; fills `FChunkResult`, no enqueue. |
|
| `GenerateTileResult` | — | **Shared worker-side gen** for one tile (async `LoadTile` task + sync `SyncRemeshTile`): sealed-solid proof, out-of-layout air proof, optional ClassifyTile (T1.d) + exact validation only when `voxel.OuterClassifierMode=1`; **default 0 since 2026-09-13** → `GenerateMesh` → `BuildTileStreamSet`; records vertex count and resident stream allocation bytes for the horizon trace. Near the player it can capture the exact pre-edit L0 lattice into the result; a cache-hit sync remesh passes an edited per-call shared lattice. Reads Generator/Mesher only → safe on a worker or the game thread; fills `FChunkResult`, no enqueue. |
|
||||||
| `RecordTileHash` / `WriteTileHashDump` | — | Headless `-voxel.TileHashDump=<absolute path>` parity instrument. Records the exact `FVoxelMeshData` used by the streaming result, including empty/classified tiles, then writes sorted absolute tile keys, LOD step/cells, content-band limits, settings digest context, and the shared `VoxelGeometryHash` at `EndPlay`. |
|
| `RecordTileHash` / `WriteTileHashDump` | — | Headless `-voxel.TileHashDump=<absolute path>` parity instrument. Records the exact `FVoxelMeshData` used by the streaming result, including empty/classified tiles, then writes sorted absolute tile keys, LOD step/cells, content-band limits, settings digest context, and the shared `VoxelGeometryHash` at `EndPlay`. |
|
||||||
| `SyncRemeshTile` | — | **INSTANT DIG**: level-0 same-frame re-mesh on the game thread (`GenerateTileResult` + `ApplyTileResult` inline, Cells=CHUNK_SIZE/Step=1 + strate band, no capture). Used for the tile under the brush centre so a carve is visible THIS frame; one full-res gen on the game thread. |
|
| `SyncRemeshTile` | — | **INSTANT DIG**: level-0 same-frame re-mesh on the game thread. Uses `PreEditDensityCache` when the tile is resident in the bounded local cache, replays the current DiffLayer over that exact procedural lattice, then meshes/applies inline so a carve is visible THIS frame. A cache miss uses normal generation; it captures the unedited lattice if the tile needs meshing, while uniform-tile proofs remain enabled. |
|
||||||
|
| `PreEditDensityCache` / `VoxelForgePreEditDensityCache::BuildEditedGrid` | — | Per-world cache of the pawn's current L0 tile: exact 35³ pre-edit samples, max **171,500 bytes**. It is pruned when the pawn crosses chunk boundaries and cleared on `RegenerateAllChunks`. `BuildEditedGrid` snapshots touched diff chunks once and reproduces `GetDensityAt`'s final `pre-edit - EvaluateMods` operation before the per-call shared-grid mesher path. |
|
||||||
| `UpdateChunksAroundPosition` | 6870 | Builds desired set, cancels pending keys that left it, sorts from the **true pawn position**, submits the occupied/support LOD0 tile and the next tile along the pawn heading as an absolute `BackgroundHigh` prefix, then loads/unloads and handles LOD changes. **Delta cull**: `BuildDesiredTiles` returns the LEAVERS (stamped `DesiredStamped` map, one sweep) — only those + `TransitionHold` are considered per crossing, not every loaded tile. `BuildDesiredTiles` also applies `RenderDistanceChunks`: the outermost shell widens to cover the distance as level-MaxClipLevel MC tiles (`VF_OuterShell` shared with `IsTileInClipRange` so the cull sees the same horizon), dz pre-clamped to the vertical band. **§9.3 anchors:** also prunes dead `StreamingAnchors` + detects their chunk crossings → rebuilds the desired set when an anchor moves/(un)registers (`bAnchorsMoved`/`bForceDesiredRebuild`), same cadence as player movement. §8.10. |
|
| `UpdateChunksAroundPosition` | 6870 | Builds desired set, cancels pending keys that left it, sorts from the **true pawn position**, submits the occupied/support LOD0 tile and the next tile along the pawn heading as an absolute `BackgroundHigh` prefix, then loads/unloads and handles LOD changes. **Delta cull**: `BuildDesiredTiles` returns the LEAVERS (stamped `DesiredStamped` map, one sweep) — only those + `TransitionHold` are considered per crossing, not every loaded tile. `BuildDesiredTiles` also applies `RenderDistanceChunks`: the outermost shell widens to cover the distance as level-MaxClipLevel MC tiles (`VF_OuterShell` shared with `IsTileInClipRange` so the cull sees the same horizon), dz pre-clamped to the vertical band. **§9.3 anchors:** also prunes dead `StreamingAnchors` + detects their chunk crossings → rebuilds the desired set when an anchor moves/(un)registers (`bAnchorsMoved`/`bForceDesiredRebuild`), same cadence as player movement. §8.10. |
|
||||||
| `AdvanceHeadlessCollisionGateStressTest` / `ObserveHeadlessCollisionGateStressTest` / `MaybeFinishHeadlessCollisionGateStressTest` | — | Explicit `-voxel.TestCollisionGateStress=1` harness only. `voxel.TestCollisionGateStressSpawnHeightVoxels` sets the drop above the measured density surface (default 10 voxels); optional `voxel.TestCollisionGateStressDeepDrop=1` carves a process-local shaft for a fall deeper than the level-0 ring. The `GetDensityAt` column at capsule feet defines the local air/solid surface. Each frame records actor position, velocity, direct foot tile and gate support readiness, gate state, signed feet clearance, and a ≤0.5-voxel sweep between positions. PASS requires no measured clearance below −5 cm; support submission/readiness, travel, and holds are diagnostics. Result logs include the terminal density column and first crossing below spawn height. |
|
| `AdvanceHeadlessCollisionGateStressTest` / `ObserveHeadlessCollisionGateStressTest` / `MaybeFinishHeadlessCollisionGateStressTest` / `MeasureHeadlessCollisionGateStressTerrain` | — | Explicit `-voxel.TestCollisionGateStress=1` harness only. The normal spawn-height cvar offsets from the measured density surface; `voxel.TestCollisionGateStressDeepDrop=1` carves a process-local box shaft from above the top to just above the floor tile, extending along the post-landing route so the pawn does not run into the shaft sidewall, temporarily raises only the transient test DiffLayer brush-radius allowance for that fixture, removes only the local pawn's redundant streaming anchor for this isolated repro, proves every sample in two complete outside-ring level-0 tiles is air before seeding their known-empty status, prewarms the known-empty in-ring column, and asserts that the support walk reaches the in-layout floor outside the aligned level-0 render ring before the pawn drops. The log reports whether the fixed gate retains that otherwise out-of-ring target. Other streaming anchors remain active. The fixture rejects a target or floor outside the layout, a non-air empty-tile proof, or a floor inside the aligned level-0 ring. The `GetDensityAt` column at capsule feet defines the local air/solid surface; the deep-drop column crossing is cached after its first exact scan. Each frame records actor position, velocity, direct foot tile and gate support readiness, gate state, signed feet clearance, and a ≤0.5-voxel sweep between positions. PASS requires no measured clearance below −5 cm; deep-drop gate-on also requires horizontal travel during a hold. Result logs include the terminal density column, first crossing below spawn height, hold count/time, and gated horizontal progress. |
|
||||||
| `BuildDesiredTiles` | 6482 | Builds `DesiredSorted`+`DesiredStamped` (player clipmap shells), then adds streaming anchors and the pending player's collision-only support column before the leaver sweep. It orders from the true local pawn position and builds `CriticalDesiredTiles` for the unresolved support target, occupied/support and heading tiles. Also rebuilds `DesiredTransitionDescendants` (ancestor → desired descendants) for the reconcile pass. |
|
| `BuildDesiredTiles` | 6482 | Builds `DesiredSorted`+`DesiredStamped` (player clipmap shells), then adds streaming anchors and the pending player's collision-only support column before the leaver sweep. It orders from the true local pawn position and builds `CriticalDesiredTiles` for the unresolved support target, occupied/support and heading tiles. Also rebuilds `DesiredTransitionDescendants` (ancestor → desired descendants) for the reconcile pass. |
|
||||||
| `CollisionGateDesiredTiles` / `RequestCollisionGateSupportTile` | 5062 | Retains an unresolved support target and up to three level-0 tiles below it across desired-set rebuilds. The additional keys are hidden collision-only tiles, queued ahead of ordinary streaming, and pruned once resolved or outside the active column. |
|
| `CollisionGateDesiredTiles` / `RequestCollisionGateSupportTile` | 5062 | Retains an unresolved support target and up to three level-0 tiles below it across desired-set rebuilds. The additional keys are hidden collision-only tiles, queued ahead of ordinary streaming, and pruned once resolved or outside the active column. |
|
||||||
| `IsPawnCapsuleOverlappingPendingFloor` / `UpdatePawnCollisionGate` | 4987 / 5195 | Uses the current-to-next-step swept capsule bounds to start a hold only when the pending support tile is about to intersect the pawn. A hold preserves horizontal input and restores downward speed after collision becomes ready. |
|
| `IsPawnCapsuleOverlappingPendingFloor` / `UpdatePawnCollisionGate` | 4987 / 5195 | Uses the current-to-next-step swept capsule bounds to start a hold only when the pending support tile is about to intersect the pawn. When current support is ready, it also requests and checks the next-step support tile before a horizontal boundary crossing. Character holds use gravity-free `MOVE_Flying` with horizontal input live, then restore movement mode, gravity, and saved downward speed when collision is ready. |
|
||||||
| `VoxelClipmapDesiredTiles::Build` / `AlignedRingBounds` (`Private/VoxelClipmapDesiredTiles.h`) | — | The pure clipmap selector. **Every finer ring snaps outward to a child-pair boundary** (`AlignedRingBounds`, alignment 2; the outermost ring needs none), so a coarser tile is wholly inside or wholly outside the finer ring: **no two desired tiles of different levels overlap** (an unaligned 7-wide odd ring leaves one half-covered coarse tile per axis side, drawn over the fine one: 635 pairs at the owner config). Cost: LOD0 ring 343 → 512 tiles. Guarded by `VoxelForge.Streaming.ClipmapDesiredTilesNoOverlapCoverage`. Anchor tiles appended later are outside this guarantee. |
|
| `VoxelClipmapDesiredTiles::Build` / `AlignedRingBounds` (`Private/VoxelClipmapDesiredTiles.h`) | — | The pure clipmap selector. **Every finer ring snaps outward to a child-pair boundary** (`AlignedRingBounds`, alignment 2; the outermost ring needs none), so a coarser tile is wholly inside or wholly outside the finer ring: **no two desired tiles of different levels overlap** (an unaligned 7-wide odd ring leaves one half-covered coarse tile per axis side, drawn over the fine one: 635 pairs at the owner config). Cost: LOD0 ring 343 → 512 tiles. Guarded by `VoxelForge.Streaming.ClipmapDesiredTilesNoOverlapCoverage`. Anchor tiles appended later are outside this guarantee. |
|
||||||
| `ReconcileReadyTransitionVisibility` | — | Visibility-only pass while streaming is unsettled: hides a retired tile once all desired tiles over its footprint are loaded, reveals new tiles once no visible retired tile overlaps them. **Indexed** (dyadic ancestors + `DesiredTransitionDescendants`, visible retired tiles collected once per pass): moving p95 32 ms → 0.31 ms. `voxel.MeasureReadyTransitionVisibility=1` logs per-frame samples. |
|
| `ReconcileReadyTransitionVisibility` | — | Visibility-only pass while streaming is unsettled: hides a retired tile once all desired tiles over its footprint are loaded, reveals new tiles once no visible retired tile overlaps them. **Indexed** (dyadic ancestors + `DesiredTransitionDescendants`, visible retired tiles collected once per pass): moving p95 32 ms → 0.31 ms. `voxel.MeasureReadyTransitionVisibility=1` logs per-frame samples. |
|
||||||
| `RequestCollisionGateSupportTile` / `UpdatePawnCollisionGate` | — | The gate submits the pawn's level-0 support tile **immediately** as `CollisionCritical`, even before the first desired set exists (the desired rebuild then adopts the same pending key). The gate holds downward Z only, with **no timeout** (a 2 s release would fire at every startup, while the game thread applies nothing for ~2.2 s). |
|
| `IsTileCollisionDesired` / `ReconcileCollisionDesiredTiles` / `ApplyMeshToTile` | — | Collision is independent of rendering: the player's level-0 `PawnCollisionRadiusChunks` box, each anchor's authored box, and the gate's retained support column union into `CollisionDesiredTiles`. Level-0 meshes outside that set remain visible with `NoCollision` and do not submit an RMC cook. Entering the set queues a remesh for already-loaded render tiles; leaving disables their component immediately; a late cook cannot restore readiness outside the set. |
|
||||||
| `AddAnchorDesiredTiles` | — | **§9.3 multi-anchor:** folds each `FVoxelStreamingAnchor`'s thin level-0 box (`XYRadiusChunks`/`ZBelowChunks`/`ZAboveChunks`) into the SAME desired set (deduped by stamp) so AI/remote players keep collision loaded around them; delta cull releases them on move/unregister. **§9.4:** a tile only a CollisionOnly anchor wants (clipmap didn't stamp it) → `CollisionOnlyTiles` → hidden at apply. Zero cost when no anchors. |
|
| `VoxelCollisionDesiredTiles::AddBox` (`Private/VoxelCollisionDesiredTiles.h`) | — | Shared pure box selector for the pawn collision radius and anchor extents; tested for pawn radius bounds and an independent anchor box. |
|
||||||
|
| `RequestCollisionGateSupportTile` / `UpdatePawnCollisionGate` | — | The gate submits the pawn's level-0 support tile **immediately** as `CollisionCritical`, even before the first desired set exists, and retains up to three more support-column tiles across desired-set rebuilds. A previously rendered no-collision tile is remeshed before the gate can accept it. The gate holds only at swept contact, keeps horizontal input live, has no timeout, and restores movement mode, gravity and saved fall speed on release. |
|
||||||
|
| `AddAnchorDesiredTiles` | — | **§9.3 multi-anchor:** folds each `FVoxelStreamingAnchor`'s thin level-0 box (`XYRadiusChunks`/`ZBelowChunks`/`ZAboveChunks`) into the SAME desired set and the independent collision set, so AI/remote players keep collision loaded around them; delta cull releases them on move/unregister. **§9.4:** a tile only a CollisionOnly anchor wants (clipmap didn't stamp it) → `CollisionOnlyTiles` → hidden at apply. Zero cost when no anchors. |
|
||||||
| `ReconcileAnchorTileVisibility` | — | **§9.4:** after each rebuild, toggle `SetVisibility` on ALREADY-LOADED tiles that flipped render↔collision-only (diff `CollisionOnlyTiles` vs prev — bounded, no O(loaded) scan; unhide only if still desired). No-op without CollisionOnly anchors. |
|
| `ReconcileAnchorTileVisibility` | — | **§9.4:** after each rebuild, toggle `SetVisibility` on ALREADY-LOADED tiles that flipped render↔collision-only (diff `CollisionOnlyTiles` vs prev — bounded, no O(loaded) scan; unhide only if still desired). No-op without CollisionOnly anchors. |
|
||||||
| `RegisterStreamingAnchor` / `UnregisterStreamingAnchor` | — | **BlueprintCallable §9.3:** add/remove an actor as a streaming anchor (`EVoxelAnchorPolicy` CollisionOnly/FullVisual + XY/ZBelow/ZAbove box). Idempotent; forces a rebuild next Tick. §9.4: CollisionOnly tiles cook collision but are hidden (no draw/VSM) unless the player clipmap wants them too. |
|
| `RegisterStreamingAnchor` / `UnregisterStreamingAnchor` | — | **BlueprintCallable §9.3:** add/remove an actor as a streaming anchor (`EVoxelAnchorPolicy` CollisionOnly/FullVisual + XY/ZBelow/ZAbove box). Idempotent; forces a rebuild next Tick. §9.4: CollisionOnly tiles cook collision but are hidden (no draw/VSM) unless the player clipmap wants them too. |
|
||||||
| `LoadTile` | 7291 | Budget check → `UE::Tasks::Launch` background gen+mesh; RAII task guard. Worker runs `Generator->ClassifyTile` first (T1.d): AllSolid/AllAir ⇒ skip `GenerateMesh`, tile stays empty (capture tiles always generate). It carries `GenerationEpoch`/desired epoch and checks `bShuttingDown`, generation pause, and its read-only cancellation token between expensive stages; obsolete work returns through `ProcessQueue`. STRATE CONTENT CUT: tiles ≥ `StrateContentCutMinLevel` pass the player-strate band (`MeshBandChunkLo/Hi` → voxels) to `GenerateMesh` + stamp it on `FChunkResult::BandChunkLo/Hi`; band change re-queues via `BandRemeshQueue` (see `UpdateChunksAroundPosition`). TOO-COARSE SKIP: if one cell is taller than the band (`Step > band height` — level ≥7 territory) the tile is enqueued EMPTY without launching a task (cell-granular cut could only render garbage). §8.10. |
|
| `LoadTile` | 7291 | Budget check → `UE::Tasks::Launch` background gen+mesh; RAII task guard. Worker runs `Generator->ClassifyTile` first (T1.d): AllSolid/AllAir ⇒ skip `GenerateMesh`, tile stays empty (capture tiles always generate). It carries `GenerationEpoch`/desired epoch and checks `bShuttingDown`, generation pause, and its read-only cancellation token between expensive stages; obsolete work returns through `ProcessQueue`. STRATE CONTENT CUT: tiles ≥ `StrateContentCutMinLevel` pass the player-strate band (`MeshBandChunkLo/Hi` → voxels) to `GenerateMesh` + stamp it on `FChunkResult::BandChunkLo/Hi`; band change re-queues via `BandRemeshQueue` (see `UpdateChunksAroundPosition`). TOO-COARSE SKIP: if one cell is taller than the band (`Step > band height` — level ≥7 territory) the tile is enqueued EMPTY without launching a task (cell-granular cut could only render garbage). §8.10. |
|
||||||
@@ -347,7 +350,7 @@ This is **where terrain shape lives.**
|
|||||||
| `CellularNoise3D` (static) | 976 | Worley/cellular — grotto/scallop. |
|
| `CellularNoise3D` (static) | 976 | Worley/cellular — grotto/scallop. |
|
||||||
| `ApplyBoundarySeal` (static) | 996 | Solidifies strate top/bottom shells. |
|
| `ApplyBoundarySeal` (static) | 996 | Solidifies strate top/bottom shells. |
|
||||||
| `InitializeSettings` | 2691 | Copies the effective seed, spine radius, and global XY world-bound settings from `UVoxelSettings` (the season owns all three when assigned); radius 0 preserves the unbounded field. |
|
| `InitializeSettings` | 2691 | Copies the effective seed, spine radius, and global XY world-bound settings from `UVoxelSettings` (the season owns all three when assigned); radius 0 preserves the unbounded field. |
|
||||||
| **`GetDensityAt`** | 2712 | **Entry point.** Picks strate + generator type and, on the per-chunk refetch, builds the chunk's operator stack (editor composer → cooked-season recipe → native archetype stack); evaluates it (TunnelNetwork/Underwater through the fused `GetDensityWithParams`), then adds disturbances, the structural tail and the diff layer. A degenerate strate has no stack and is air. Its `CP_*` state is keyed by `(DensityCacheOwnerId, ChunkCoord, LayoutVersion)`; workers copy immutable recipe/vector data and never read editor composer state. |
|
| **`GetDensityAt`** | 2712 | **Entry point.** Picks strate + generator type and, on the per-chunk refetch, builds the chunk's operator stack (editor composer → cooked-season recipe → native archetype stack); evaluates it (TunnelNetwork/Underwater through the fused `GetDensityWithParams`), then adds disturbances and structural posts. Optional `OutPreEditDensity` receives the exact final procedural field immediately before the player DiffLayer tail, allowing the near-player dig cache to replay edits without regenerating the operator stack. Its ordinary one-argument behavior is unchanged. A degenerate strate has no stack and is air; `CP_*` state remains keyed by `(DensityCacheOwnerId, ChunkCoord, LayoutVersion)`. |
|
||||||
| **`GetDensityWithParams`** | 3749 | TunnelNetwork pipeline (~1000 lines) — the fused evaluator of the TunnelNetwork/Underwater stack in `GetDensityAt`. See §4. ⚠️ Takes **required** `ParamsFingerprint` + `LayoutVersion` since the AUDIT §C2 fix (2026-07-28) — they go into the SDF cache key so a chunk can no longer be evaluated against a neighbour's rooms. Callers compute the CRC **once per chunk** (`CP_TunnelFP`), never per voxel. The optional `voxel.WormBlockSkip` path (default 0) uses `VF_TryGetWormBlockSkip` + a worker-local 4³ lattice-block cache to prove `|N1| >= WormThreshold` and remove both worm noise calls only; it never changes `NetworkMask` or the fallback field path. |
|
| **`GetDensityWithParams`** | 3749 | TunnelNetwork pipeline (~1000 lines) — the fused evaluator of the TunnelNetwork/Underwater stack in `GetDensityAt`. See §4. ⚠️ Takes **required** `ParamsFingerprint` + `LayoutVersion` since the AUDIT §C2 fix (2026-07-28) — they go into the SDF cache key so a chunk can no longer be evaluated against a neighbour's rooms. Callers compute the CRC **once per chunk** (`CP_TunnelFP`), never per voxel. The optional `voxel.WormBlockSkip` path (default 0) uses `VF_TryGetWormBlockSkip` + a worker-local 4³ lattice-block cache to prove `|N1| >= WormThreshold` and remove both worm noise calls only; it never changes `NetworkMask` or the fallback field path. |
|
||||||
| `SampleSurfaceStructuralZ` | — | **F20:** the RAW SurfaceWorld heightfield (continents+mountains+detail), BEFORE any terrain op; returns terrain Z + relief M. Cliff re-samples it at an XY offset for a cheap analytic slope. |
|
| `SampleSurfaceStructuralZ` | — | **F20:** the RAW SurfaceWorld heightfield (continents+mountains+detail), BEFORE any terrain op; returns terrain Z + relief M. Cliff re-samples it at an XY offset for a cheap analytic slope. |
|
||||||
| `ComputeSurfaceTerrainZ` / `ComputeSurfaceColumn` | — | SurfaceWorld heightfield → terrain Z (+ sky-cap ceiling and overhang gate per column); biome **output-blend** lerps dominant/neighbour heights (`ParamsD`/`ParamsN`/weight). **F20 surface ops** (`FSurfaceGenerationParams`, biome-selected + slope/relief-conditioned, all default off): Cliff (slope-gated STEEPENING — push height from local mean where steep ⇒ sheer walls; 4 structural resamples only when on), Terrace (relief-gated + `TerraceHardness`), LayerLines (sedimentary shelves) — pure per-column height REMAPS applied here so the single height oracle stays consistent (MC/ClassifyTile/deco/BP bridge). **Phase 2 OVERHANG** (volumetric — real jutting shelves): in the stack's `FOverhangShelfMod`, for AIR voxels in a window `(TerrainZ, TerrainZ+OverhangHeight]` above a steep slope, the heightfield is re-sampled UPHILL (toward the cliff) by a reach that GROWS with height (tiny low ⇒ air over the void, full high ⇒ borrows the far cliff rock) and unioned in ⇒ a shelf attached to the cliff, tapering out over the void with air beneath (the sketch). Per-column `OverhangAmp`(=strength·slope-gate) + unit uphill `(DirX,DirY)` resolved once in `ComputeSurfaceColumn` (gradient sampled at the REACH scale so a spot over the void can see the cliff), cached on `FSurfaceColumn`. Genuine 3D (per-voxel structural re-eval, gated to steep overhang columns). Off ⇒ byte-identical. The field-preserving LOD0 pass removes only needless temporaries/default construction; every output remains bit-identical. §8.14. |
|
| `ComputeSurfaceTerrainZ` / `ComputeSurfaceColumn` | — | SurfaceWorld heightfield → terrain Z (+ sky-cap ceiling and overhang gate per column); biome **output-blend** lerps dominant/neighbour heights (`ParamsD`/`ParamsN`/weight). **F20 surface ops** (`FSurfaceGenerationParams`, biome-selected + slope/relief-conditioned, all default off): Cliff (slope-gated STEEPENING — push height from local mean where steep ⇒ sheer walls; 4 structural resamples only when on), Terrace (relief-gated + `TerraceHardness`), LayerLines (sedimentary shelves) — pure per-column height REMAPS applied here so the single height oracle stays consistent (MC/ClassifyTile/deco/BP bridge). **Phase 2 OVERHANG** (volumetric — real jutting shelves): in the stack's `FOverhangShelfMod`, for AIR voxels in a window `(TerrainZ, TerrainZ+OverhangHeight]` above a steep slope, the heightfield is re-sampled UPHILL (toward the cliff) by a reach that GROWS with height (tiny low ⇒ air over the void, full high ⇒ borrows the far cliff rock) and unioned in ⇒ a shelf attached to the cliff, tapering out over the void with air beneath (the sketch). Per-column `OverhangAmp`(=strength·slope-gate) + unit uphill `(DirX,DirY)` resolved once in `ComputeSurfaceColumn` (gradient sampled at the REACH scale so a spot over the void can see the cliff), cached on `FSurfaceColumn`. Genuine 3D (per-voxel structural re-eval, gated to steep overhang columns). Off ⇒ byte-identical. The field-preserving LOD0 pass removes only needless temporaries/default construction; every output remains bit-identical. §8.14. |
|
||||||
@@ -686,7 +689,7 @@ text plus independently copied metadata/hash, and retains soft references to any
|
|||||||
The pre-sampled grid supplies positions AND gradients inline (T1.b).
|
The pre-sampled grid supplies positions AND gradients inline (T1.b).
|
||||||
| Method | .cpp line | Role |
|
| Method | .cpp line | Role |
|
||||||
|--------|-----------|------|
|
|--------|-----------|------|
|
||||||
| **`GenerateMesh`** | 96 | The MC loop over cells; `Step` controls LOD sampling. Sets the T2.b octave bias for the tile (`TGuardValue` on `VoxelGenLOD::OctaveBias`, from `LODOctaveDrop` × log2(Step); 0 at LOD0/off). Edge `t` + grid-gradient normals computed inline (`SampleG`/`GradAt`). Optional `OutCaptureGrid` (4th arg) = CAPTURE-DURING-MESHING: when non-null + full-res (`CellsPerAxis==CHUNK_SIZE`), copies the already-sampled `CHUNK_SIZE³` density grid (quantized via `VF_QuantizeDensity`, VoxelTypes.h) so the density clipmap reuses it instead of re-sampling `GetDensityAt`. Pure read of the grid — §8.10 untouched. **F17 surface class**: each unique vertex is classified sol/sky-cap in `GetOrCreateVertex` (down-facing only → memoized `GetSurfaceHeightAt`, nearer `CeilSurf` = cap); triangles bucket by majority into `GroundTris`/`CapTris` (thread_local), skirts emit per bucket, then `Triangles = ground‖cap` + `FVoxelMeshData::NumCeilingTriangles` (→ polygroups in `BuildTileStreamSet`). Same tris, only index ORDER changes. STRATE CONTENT CUT: optional `BandZMin/MaxVox` params restrict the cz cell loop (+ gz sampling rows) to the player-strate band — coarse straddling tiles mesh ONE strate (kills far-LOD inter-strate aliasing holes); meshed cells bit-identical. |
|
| **`GenerateMesh`** | 96 | The MC loop over cells; `Step` controls LOD sampling. Sets the T2.b octave bias for the tile (`TGuardValue` on `VoxelGenLOD::OctaveBias`, from `LODOctaveDrop` × log2(Step); 0 at LOD0/off). Edge `t` + grid-gradient normals computed inline (`SampleG`/`GradAt`). Optional `OutCaptureGrid` (4th arg) captures the already-sampled `CHUNK_SIZE³` R8 density clipmap. Optional `OutPreEditDensityGrid` captures exact float samples before player edits for full-res L0 tiles; optional `InSharedDensityGrid` is per-call (not the mesher's shared mutable setting) so sync carve reuse cannot race async tile tasks. Capture/reuse leaves §8.10's thread-local scratch, two-pass MC and density-grid semantics intact. **F17 surface class**: each unique vertex is classified sol/sky-cap in `GetOrCreateVertex` (down-facing only → memoized `GetSurfaceHeightAt`, nearer `CeilSurf` = cap); triangles bucket by majority into `GroundTris`/`CapTris` (thread_local), skirts emit per bucket, then `Triangles = ground‖cap` + `FVoxelMeshData::NumCeilingTriangles` (→ polygroups in `BuildTileStreamSet`). Same tris, only index ORDER changes. STRATE CONTENT CUT: optional `BandZMin/MaxVox` params restrict the cz cell loop (+ gz sampling rows) to the player-strate band — coarse straddling tiles mesh ONE strate; meshed cells bit-identical. |
|
||||||
|
|
||||||
**`Public/MarchingCubesTables.h`** — `EdgeTable` + `TriTable` reference data (Paul
|
**`Public/MarchingCubesTables.h`** — `EdgeTable` + `TriTable` reference data (Paul
|
||||||
Bourke). Cube corner/edge layout documented at top (lines 7-37). Rarely needs editing.
|
Bourke). Cube corner/edge layout documented at top (lines 7-37). Rarely needs editing.
|
||||||
@@ -738,6 +741,8 @@ second density path.
|
|||||||
| `VoxelForgeStrateConnectivityTest.cpp` | `VoxelForge.Generation.StrateConnectivity` / `VoxelForge.Generation.StrateConnectivityRefinement` / `VoxelForge.Generation.VerticalShaftSeamFreedom` | Bounded strate metrics with density-polarity and solid-gap controls, deterministic route rechecks, and refinement sweeps. The refinement test uses the bounded 4-chunk density fixture, fits each measurement AABB to the arrival/departure mouth pair plus three shaft spacings (the prescribed local tree fallback), and caps the fitted control at 120,000,000 cells. It reports exact before/after seed-6 cell counts, reruns every negative with the fitted margin doubled, measures tree orphan candidates/path reachability and roughness-bubble proxies, asserts mouths remain inside the deterministic shaft feature core (the exact axis is an unsupported open cylinder), checks source/mirror physical paths, and requires 16/16 effective arrival→departure results across the 16 VerticalShafts seeds. Margin-binding negatives are reported as measurement limits, never as gap findings. The shaft seam test re-evaluates cell-boundary positions after warming distinct neighbouring chunk contexts. |
|
| `VoxelForgeStrateConnectivityTest.cpp` | `VoxelForge.Generation.StrateConnectivity` / `VoxelForge.Generation.StrateConnectivityRefinement` / `VoxelForge.Generation.VerticalShaftSeamFreedom` | Bounded strate metrics with density-polarity and solid-gap controls, deterministic route rechecks, and refinement sweeps. The refinement test uses the bounded 4-chunk density fixture, fits each measurement AABB to the arrival/departure mouth pair plus three shaft spacings (the prescribed local tree fallback), and caps the fitted control at 120,000,000 cells. It reports exact before/after seed-6 cell counts, reruns every negative with the fitted margin doubled, measures tree orphan candidates/path reachability and roughness-bubble proxies, asserts mouths remain inside the deterministic shaft feature core (the exact axis is an unsupported open cylinder), checks source/mirror physical paths, and requires 16/16 effective arrival→departure results across the 16 VerticalShafts seeds. Margin-binding negatives are reported as measurement limits, never as gap findings. The shaft seam test re-evaluates cell-boundary positions after warming distinct neighbouring chunk contexts. |
|
||||||
| `VoxelForgeMazeSeamTest.cpp` | `VoxelForge.Generation.MazeSeamFreedom` | Re-evaluates **42** cell-boundary probes through `GetDensityAt` after warming six different neighbouring chunk contexts; must report **0 mismatches**. The Maze source's 2×2×2 lower-node window is deliberately local; no wide collect is involved. |
|
| `VoxelForgeMazeSeamTest.cpp` | `VoxelForge.Generation.MazeSeamFreedom` | Re-evaluates **42** cell-boundary probes through `GetDensityAt` after warming six different neighbouring chunk contexts; must report **0 mismatches**. The Maze source's 2×2×2 lower-node window is deliberately local; no wide collect is involved. |
|
||||||
| `VoxelForgeClipmapNoOverlapTest.cpp` | `VoxelForge.Streaming.ClipmapDesiredTilesNoOverlapCoverage` | Sweeps 5,145 cases (343 centres with negative coords and every parity × 5 radius/level/render-distance settings × 3 vertical clamps): **zero overlapping desired pairs AND coverage ⊇ the frozen `7486c29` selector**. Failed on `7486c29` (1,022,679 overlapping pairs) as intended. |
|
| `VoxelForgeClipmapNoOverlapTest.cpp` | `VoxelForge.Streaming.ClipmapDesiredTilesNoOverlapCoverage` | Sweeps 5,145 cases (343 centres with negative coords and every parity × 5 radius/level/render-distance settings × 3 vertical clamps): **zero overlapping desired pairs AND coverage ⊇ the frozen `7486c29` selector**. Failed on `7486c29` (1,022,679 overlapping pairs) as intended. |
|
||||||
|
| `VoxelForgeCollisionDesiredTilesTest.cpp` | `VoxelForge.Streaming.CollisionDesiredTiles.PawnAndAnchors` | Checks the pawn collision box stays within its configured radius, includes support tiles below, and unions an independent anchor box at its authored XY/Z extents. |
|
||||||
|
| `VoxelForgePreEditDensityCacheTest.cpp` | `VoxelForge.Determinism.PreEditDensityCacheCarveParity` | Captures one exact 35³ L0 pre-edit lattice, applies 16 deterministic random carves cumulatively, and requires each cached remesh's complete geometry hash to match full `GetDensityAt` regeneration; also requires non-empty carve geometry. |
|
||||||
| `VoxelForgeDensityBlockEquivalenceTest.cpp` | `VoxelForge.Density.OperatorBlockMatchesScalarGrid` | Block evaluation (`EvalBlock` after `BuildActiveOpList` pruning) vs scalar, **bit-identical**: 5.1M mesher-grid samples (6 seeds × 8 archetypes × levels 0–5, with edits) + 300k direct `EvalBlock` samples. Scopes `voxel.UseFusedEvaluator=0` so the real block path runs. An injected optimistic `FPassageCarveOp::EffectOverBox` produced 5,612 mismatches. |
|
| `VoxelForgeDensityBlockEquivalenceTest.cpp` | `VoxelForge.Density.OperatorBlockMatchesScalarGrid` | Block evaluation (`EvalBlock` after `BuildActiveOpList` pruning) vs scalar, **bit-identical**: 5.1M mesher-grid samples (6 seeds × 8 archetypes × levels 0–5, with edits) + 300k direct `EvalBlock` samples. Scopes `voxel.UseFusedEvaluator=0` so the real block path runs. An injected optimistic `FPassageCarveOp::EffectOverBox` produced 5,612 mismatches. |
|
||||||
| `VoxelForgeWorldEdgeSealTest.cpp` | `VoxelForge.Generation.WorldEdgeSeal` | Positive samples outside the radius across every fixture strate, bit-identical interior negative control against radius 0, monotonic smooth-ramp check, radius-0 no-op, radial `ClassifyBox`/T1.d proofs brute-forced for every reported voxel, and synthetic plus generated near-rim passage coverage. Emits one aggregate summary with proved-box and voxel counts. |
|
| `VoxelForgeWorldEdgeSealTest.cpp` | `VoxelForge.Generation.WorldEdgeSeal` | Positive samples outside the radius across every fixture strate, bit-identical interior negative control against radius 0, monotonic smooth-ramp check, radius-0 no-op, radial `ClassifyBox`/T1.d proofs brute-forced for every reported voxel, and synthetic plus generated near-rim passage coverage. Emits one aggregate summary with proved-box and voxel counts. |
|
||||||
| `VoxelForgeCorrectnessRoundTest.cpp` | `VoxelForge.Determinism.DiffApiBoundary` / `DiffLayerIdentity` / `EmptyPassageVersion` / `SurfaceBiomeCacheIdentity` / `TileCacheContextSwitch` / `ZeroTransitionBlend` | Regression coverage for the cold-audit correctness fixes (diff-layer API edges, cache identities, empty-passage layout version, zero transition blend). |
|
| `VoxelForgeCorrectnessRoundTest.cpp` | `VoxelForge.Determinism.DiffApiBoundary` / `DiffLayerIdentity` / `EmptyPassageVersion` / `SurfaceBiomeCacheIdentity` / `TileCacheContextSwitch` / `ZeroTransitionBlend` | Regression coverage for the cold-audit correctness fixes (diff-layer API edges, cache identities, empty-passage layout version, zero transition blend). |
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
#if WITH_DEV_AUTOMATION_TESTS
|
||||||
|
|
||||||
|
#include "Misc/AutomationTest.h"
|
||||||
|
|
||||||
|
#include "VoxelCollisionDesiredTiles.h"
|
||||||
|
|
||||||
|
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||||
|
FVoxelForgeCollisionDesiredTilesPawnAndAnchorsTest,
|
||||||
|
"VoxelForge.Streaming.CollisionDesiredTiles.PawnAndAnchors",
|
||||||
|
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
|
||||||
|
|
||||||
|
bool FVoxelForgeCollisionDesiredTilesPawnAndAnchorsTest::RunTest(const FString& Parameters)
|
||||||
|
{
|
||||||
|
(void)Parameters;
|
||||||
|
TSet<FVoxelTileKey> CollisionTiles;
|
||||||
|
const FIntVector PawnChunk(2, -3, 4);
|
||||||
|
VoxelCollisionDesiredTiles::AddBox(CollisionTiles, PawnChunk, 1, 1, 1);
|
||||||
|
|
||||||
|
TestTrue(TEXT("pawn center remains collidable"),
|
||||||
|
CollisionTiles.Contains(FVoxelTileKey(PawnChunk, 0)));
|
||||||
|
TestTrue(TEXT("pawn radius includes horizontal edge"),
|
||||||
|
CollisionTiles.Contains(FVoxelTileKey(PawnChunk + FIntVector(1, -1, 0), 0)));
|
||||||
|
TestTrue(TEXT("pawn radius includes support below"),
|
||||||
|
CollisionTiles.Contains(FVoxelTileKey(PawnChunk + FIntVector(0, 0, -1), 0)));
|
||||||
|
TestFalse(TEXT("pawn radius does not follow the full render ring"),
|
||||||
|
CollisionTiles.Contains(FVoxelTileKey(PawnChunk + FIntVector(2, 0, 0), 0)));
|
||||||
|
|
||||||
|
const FIntVector AnchorChunk(-12, 8, -5);
|
||||||
|
VoxelCollisionDesiredTiles::AddBox(CollisionTiles, AnchorChunk, 2, 1, 3);
|
||||||
|
TestTrue(TEXT("anchor box includes its far horizontal edge"),
|
||||||
|
CollisionTiles.Contains(FVoxelTileKey(AnchorChunk + FIntVector(-2, 2, 0), 0)));
|
||||||
|
TestTrue(TEXT("anchor box includes its upper extent"),
|
||||||
|
CollisionTiles.Contains(FVoxelTileKey(AnchorChunk + FIntVector(0, 0, 3), 0)));
|
||||||
|
TestFalse(TEXT("anchor box keeps authored bounds"),
|
||||||
|
CollisionTiles.Contains(FVoxelTileKey(AnchorChunk + FIntVector(3, 0, 0), 0)));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // WITH_DEV_AUTOMATION_TESTS
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
#if WITH_DEV_AUTOMATION_TESTS
|
||||||
|
|
||||||
|
#include "Misc/AutomationTest.h"
|
||||||
|
#include "VoxelForgeTestFixture.h"
|
||||||
|
#include "VoxelMarchingCubesMesher.h"
|
||||||
|
#include "VoxelGeometryHash.h"
|
||||||
|
#include "VoxelStackSampler.h"
|
||||||
|
#include "../VoxelPreEditDensityCache.h"
|
||||||
|
|
||||||
|
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||||
|
FVoxelForgePreEditDensityCacheParityTest,
|
||||||
|
"VoxelForge.Determinism.PreEditDensityCacheCarveParity",
|
||||||
|
EAutomationTestFlags_ApplicationContextMask | EAutomationTestFlags::EngineFilter)
|
||||||
|
|
||||||
|
bool FVoxelForgePreEditDensityCacheParityTest::RunTest(const FString& Parameters)
|
||||||
|
{
|
||||||
|
using namespace VoxelForgeTest;
|
||||||
|
|
||||||
|
FTestWorld World;
|
||||||
|
World.Build(/*Seed*/1337, /*GapChunks*/2);
|
||||||
|
if (!World.IsValid())
|
||||||
|
{
|
||||||
|
AddError(World.WhyInvalid());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 SlotTop = 0;
|
||||||
|
int32 SlotBottom = 0;
|
||||||
|
if (!World.GetSlotVoxelZRange(FTestWorld::SlotTunnelNetwork, SlotTop, SlotBottom))
|
||||||
|
{
|
||||||
|
AddError(TEXT("TunnelNetwork slot range was unavailable."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const int32 MidVoxelZ = (SlotTop + SlotBottom) / 2;
|
||||||
|
const int32 TileZ = FMath::FloorToInt(
|
||||||
|
static_cast<float>(MidVoxelZ) / CHUNK_SIZE) * CHUNK_SIZE;
|
||||||
|
const FIntVector TileOrigin(0, 0, TileZ);
|
||||||
|
TestEqual(TEXT("the runtime pre-edit cache holds only the pawn's current tile"),
|
||||||
|
VoxelForgePreEditDensityCache::MaxCachedTiles, 1);
|
||||||
|
TestEqual(TEXT("the cache target radius matches its one-tile memory bound"),
|
||||||
|
VoxelForgePreEditDensityCache::PawnRadiusChunks, 0);
|
||||||
|
|
||||||
|
TStrongObjectPtr<UVoxelMarchingCubesMesher> Mesher(
|
||||||
|
NewObject<UVoxelMarchingCubesMesher>(GetTransientPackage(), NAME_None, RF_Transient));
|
||||||
|
Mesher->SetGenerator(World.Generator.Get());
|
||||||
|
|
||||||
|
FScopedVoxelStackRegistration StackRegistration(/*InLODLevel*/0);
|
||||||
|
FVoxelSharedDensityGrid PreEditGrid;
|
||||||
|
int64 BaseDensitySamples = 0;
|
||||||
|
Mesher->GenerateMesh(TileOrigin, 1, CHUNK_SIZE, nullptr, INT32_MIN, INT32_MAX,
|
||||||
|
&BaseDensitySamples, &PreEditGrid);
|
||||||
|
TestEqual(TEXT("the cache captured the complete 35³ lattice"),
|
||||||
|
PreEditGrid.Samples.Num(), (CHUNK_SIZE + 3) * (CHUNK_SIZE + 3) * (CHUNK_SIZE + 3));
|
||||||
|
TestEqual(TEXT("the cache uses the one-voxel mesh halo"), PreEditGrid.Dim, CHUNK_SIZE + 3);
|
||||||
|
TestTrue(TEXT("the initial tile sampled its density lattice"), BaseDensitySamples > 0);
|
||||||
|
if (PreEditGrid.Samples.Num() == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FRandomStream Random(0x4D17);
|
||||||
|
int32 ComparedCarves = 0;
|
||||||
|
int32 NonEmptyPairs = 0;
|
||||||
|
for (int32 CarveIndex = 0; CarveIndex < 16; ++CarveIndex)
|
||||||
|
{
|
||||||
|
FVoxelModification Modification;
|
||||||
|
Modification.Center = FVector(
|
||||||
|
TileOrigin.X + Random.RandRange(5, 27),
|
||||||
|
TileOrigin.Y + Random.RandRange(5, 27),
|
||||||
|
TileOrigin.Z + Random.RandRange(5, 27));
|
||||||
|
Modification.Radius = static_cast<float>(Random.RandRange(3, 7));
|
||||||
|
Modification.Strength = -50.0f;
|
||||||
|
const TArray<FIntVector> Affected = World.DiffLayer->ApplyModification(Modification);
|
||||||
|
TestTrue(FString::Printf(TEXT("random carve %d affected at least one chunk"), CarveIndex),
|
||||||
|
Affected.Num() > 0);
|
||||||
|
|
||||||
|
FVoxelSharedDensityGrid EditedGrid;
|
||||||
|
TestTrue(FString::Printf(TEXT("random carve %d replayed over pre-edit samples"), CarveIndex),
|
||||||
|
VoxelForgePreEditDensityCache::BuildEditedGrid(
|
||||||
|
PreEditGrid, World.DiffLayer.Get(), EditedGrid));
|
||||||
|
|
||||||
|
const FVoxelMeshData FullRegeneration = Mesher->GenerateMesh(
|
||||||
|
TileOrigin, 1, CHUNK_SIZE, nullptr, INT32_MIN, INT32_MAX);
|
||||||
|
const FVoxelMeshData CachedRemesh = Mesher->GenerateMesh(
|
||||||
|
TileOrigin, 1, CHUNK_SIZE, nullptr, INT32_MIN, INT32_MAX,
|
||||||
|
nullptr, nullptr, &EditedGrid);
|
||||||
|
const FString FullHash = VoxelForgeGeometryHash::Compute(FullRegeneration);
|
||||||
|
const FString CachedHash = VoxelForgeGeometryHash::Compute(CachedRemesh);
|
||||||
|
TestEqual(FString::Printf(TEXT("carve %d cached/full tile hash"), CarveIndex),
|
||||||
|
CachedHash, FullHash);
|
||||||
|
if (CachedHash == FullHash)
|
||||||
|
{
|
||||||
|
++ComparedCarves;
|
||||||
|
}
|
||||||
|
if (!FullRegeneration.IsEmpty() && !CachedRemesh.IsEmpty())
|
||||||
|
{
|
||||||
|
++NonEmptyPairs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TestEqual(TEXT("all random carves matched a full tile regeneration"), ComparedCarves, 16);
|
||||||
|
TestTrue(TEXT("the parity set exercised actual carve geometry"), NonEmptyPairs > 0);
|
||||||
|
AddInfo(FString::Printf(
|
||||||
|
TEXT("pre_edit_density_cache: carves=%d hash_matches=%d nonempty_pairs=%d samples=%d bytes=%d"),
|
||||||
|
16, ComparedCarves, NonEmptyPairs, PreEditGrid.Samples.Num(),
|
||||||
|
PreEditGrid.Samples.Num() * static_cast<int32>(sizeof(float))));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // WITH_DEV_AUTOMATION_TESTS
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "VoxelTypes.h"
|
||||||
|
|
||||||
|
namespace VoxelCollisionDesiredTiles
|
||||||
|
{
|
||||||
|
inline void AddBox(TSet<FVoxelTileKey>& OutTiles, const FIntVector& Center,
|
||||||
|
int32 XYRadius, int32 ZBelow, int32 ZAbove)
|
||||||
|
{
|
||||||
|
const int32 SafeXYRadius = FMath::Max(0, XYRadius);
|
||||||
|
const int32 SafeZBelow = FMath::Max(0, ZBelow);
|
||||||
|
const int32 SafeZAbove = FMath::Max(0, ZAbove);
|
||||||
|
for (int32 Z = -SafeZBelow; Z <= SafeZAbove; ++Z)
|
||||||
|
{
|
||||||
|
for (int32 Y = -SafeXYRadius; Y <= SafeXYRadius; ++Y)
|
||||||
|
{
|
||||||
|
for (int32 X = -SafeXYRadius; X <= SafeXYRadius; ++X)
|
||||||
|
{
|
||||||
|
OutTiles.Add(FVoxelTileKey(Center + FIntVector(X, Y, Z), 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2709,7 +2709,8 @@ void UVoxelGenerator::EndDensityBlock() const
|
|||||||
GVoxelDensityBlockSession.End();
|
GVoxelDensityBlockSession.End();
|
||||||
}
|
}
|
||||||
|
|
||||||
float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) const
|
float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ,
|
||||||
|
float* OutPreEditDensity) const
|
||||||
{
|
{
|
||||||
// Soundness mode: for a sample in a block whose core or passage-carving reach bit is clear,
|
// Soundness mode: for a sample in a block whose core or passage-carving reach bit is clear,
|
||||||
// evaluate the complete canonical field once with the bypass bit set. The normal call then
|
// evaluate the complete canonical field once with the bypass bit set. The normal call then
|
||||||
@@ -3696,6 +3697,13 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
// Fill (diff > 0) → Result -= positive → Result decreases → more solid ✓
|
// Fill (diff > 0) → Result -= positive → Result decreases → more solid ✓
|
||||||
VoxelDensityProfile::FScopedTimer DensityDiffLayerTimer(
|
VoxelDensityProfile::FScopedTimer DensityDiffLayerTimer(
|
||||||
VoxelDensityProfile::EBucket::DensityDiffLayer);
|
VoxelDensityProfile::EBucket::DensityDiffLayer);
|
||||||
|
// The sync-remesh cache keeps the exact procedural field after every structural post, but
|
||||||
|
// before player edits. The cached lattice is replayed with the current DiffLayer snapshot so
|
||||||
|
// a carve sees the same subtraction order and float result as this canonical path.
|
||||||
|
if (OutPreEditDensity != nullptr)
|
||||||
|
{
|
||||||
|
*OutPreEditDensity = Result;
|
||||||
|
}
|
||||||
if (DiffLayer && DiffLayer->HasAnyMods())
|
if (DiffLayer && DiffLayer->HasAnyMods())
|
||||||
{
|
{
|
||||||
// ── PER-CHUNK MOD SNAPSHOT ──
|
// ── PER-CHUNK MOD SNAPSHOT ──
|
||||||
|
|||||||
@@ -96,11 +96,14 @@ void UVoxelMarchingCubesMesher::InitializeConsoleSwitches()
|
|||||||
FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels, int32 Step, int32 InCellsPerAxis,
|
FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels, int32 Step, int32 InCellsPerAxis,
|
||||||
TArray<uint8>* OutCaptureGrid,
|
TArray<uint8>* OutCaptureGrid,
|
||||||
int32 BandZMinVox, int32 BandZMaxVox,
|
int32 BandZMinVox, int32 BandZMaxVox,
|
||||||
int64* OutDensitySampleCount)
|
int64* OutDensitySampleCount,
|
||||||
|
FVoxelSharedDensityGrid* OutPreEditDensityGrid,
|
||||||
|
const FVoxelSharedDensityGrid* InSharedDensityGrid)
|
||||||
{
|
{
|
||||||
TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_MesherGenerateMesh);
|
TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_MesherGenerateMesh);
|
||||||
FVoxelMeshData MeshData;
|
FVoxelMeshData MeshData;
|
||||||
if (OutCaptureGrid) { OutCaptureGrid->Reset(); }
|
if (OutCaptureGrid) { OutCaptureGrid->Reset(); }
|
||||||
|
if (OutPreEditDensityGrid) { OutPreEditDensityGrid->Reset(); }
|
||||||
if (OutDensitySampleCount) { *OutDensitySampleCount = 0; }
|
if (OutDensitySampleCount) { *OutDensitySampleCount = 0; }
|
||||||
if (!Generator) return MeshData;
|
if (!Generator) return MeshData;
|
||||||
VoxelDensityProfile::FScopedTimer MesherProfileTimer(
|
VoxelDensityProfile::FScopedTimer MesherProfileTimer(
|
||||||
@@ -383,7 +386,11 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
|||||||
// COUPE DE CONTENU PAR STRATE — restreint le maillage (et l'échantillonnage) aux cellules
|
// COUPE DE CONTENU PAR STRATE — restreint le maillage (et l'échantillonnage) aux cellules
|
||||||
// dont l'intervalle Z chevauche la bande [BandZMinVox, BandZMaxVox] (voxels inclusifs).
|
// dont l'intervalle Z chevauche la bande [BandZMinVox, BandZMaxVox] (voxels inclusifs).
|
||||||
// Les tuiles à capture ne sont jamais bandées (niveau 0 — garde-fou ci-dessous).
|
// Les tuiles à capture ne sont jamais bandées (niveau 0 — garde-fou ci-dessous).
|
||||||
if (OutCaptureGrid) { BandZMinVox = INT32_MIN; BandZMaxVox = INT32_MAX; }
|
if (OutCaptureGrid || OutPreEditDensityGrid)
|
||||||
|
{
|
||||||
|
BandZMinVox = INT32_MIN;
|
||||||
|
BandZMaxVox = INT32_MAX;
|
||||||
|
}
|
||||||
int32 CzLo = 0, CzHi = CellsPerAxis - 1;
|
int32 CzLo = 0, CzHi = CellsPerAxis - 1;
|
||||||
if (BandZMinVox > INT32_MIN || BandZMaxVox < INT32_MAX)
|
if (BandZMinVox > INT32_MIN || BandZMaxVox < INT32_MAX)
|
||||||
{
|
{
|
||||||
@@ -660,7 +667,8 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
|||||||
// capacité, donc plus de malloc/free de ~170 Ko (35³ floats) par tuile.
|
// capacité, donc plus de malloc/free de ~170 Ko (35³ floats) par tuile.
|
||||||
static thread_local TArray<float> DensityGrid;
|
static thread_local TArray<float> DensityGrid;
|
||||||
DensityGrid.SetNumUninitialized(MDim * MDim * MDim);
|
DensityGrid.SetNumUninitialized(MDim * MDim * MDim);
|
||||||
const FVoxelSharedDensityGrid* ReuseGrid = SharedDensityGrid;
|
const FVoxelSharedDensityGrid* ReuseGrid = InSharedDensityGrid != nullptr
|
||||||
|
? InSharedDensityGrid : SharedDensityGrid;
|
||||||
const int32 SharedDeltaX = ReuseGrid
|
const int32 SharedDeltaX = ReuseGrid
|
||||||
? (OriginVoxels.X - ReuseGrid->OriginVoxels.X) / Step : 0;
|
? (OriginVoxels.X - ReuseGrid->OriginVoxels.X) / Step : 0;
|
||||||
const int32 SharedDeltaY = ReuseGrid
|
const int32 SharedDeltaY = ReuseGrid
|
||||||
@@ -680,6 +688,17 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
|||||||
&& SharedDeltaX + GridDim < ReuseGrid->Dim
|
&& SharedDeltaX + GridDim < ReuseGrid->Dim
|
||||||
&& SharedDeltaY + GridDim < ReuseGrid->Dim
|
&& SharedDeltaY + GridDim < ReuseGrid->Dim
|
||||||
&& SharedDeltaZ + GridDim < ReuseGrid->Dim;
|
&& SharedDeltaZ + GridDim < ReuseGrid->Dim;
|
||||||
|
const bool bCapturePreEditGrid = OutPreEditDensityGrid != nullptr
|
||||||
|
&& !bUseSharedDensityGrid
|
||||||
|
&& Step == 1 && CellsPerAxis == CHUNK_SIZE
|
||||||
|
&& CzLo == 0 && CzHi == CellsPerAxis - 1;
|
||||||
|
if (bCapturePreEditGrid)
|
||||||
|
{
|
||||||
|
OutPreEditDensityGrid->OriginVoxels = OriginVoxels + FIntVector(-Step, -Step, -Step);
|
||||||
|
OutPreEditDensityGrid->Step = Step;
|
||||||
|
OutPreEditDensityGrid->Dim = MDim;
|
||||||
|
OutPreEditDensityGrid->Samples.SetNumUninitialized(MDim * MDim * MDim);
|
||||||
|
}
|
||||||
// Bande de strate : seules les rangées Z réellement lues (cellules CzLo..CzHi + marges de
|
// Bande de strate : seules les rangées Z réellement lues (cellules CzLo..CzHi + marges de
|
||||||
// gradient) sont échantillonnées — le reste du tampon reste non initialisé et non lu.
|
// gradient) sont échantillonnées — le reste du tampon reste non initialisé et non lu.
|
||||||
VoxelDensityProfile::FScopedTimer MesherDensityGridTimer(
|
VoxelDensityProfile::FScopedTimer MesherDensityGridTimer(
|
||||||
@@ -731,10 +750,16 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
|||||||
VoxelGenLOD::TileReachBlockIndex =
|
VoxelGenLOD::TileReachBlockIndex =
|
||||||
((ReachBlockZ * ReachBlocksPerAxis) + ReachBlockY)
|
((ReachBlockZ * ReachBlocksPerAxis) + ReachBlockY)
|
||||||
* ReachBlocksPerAxis + ReachBlockX;
|
* ReachBlocksPerAxis + ReachBlockX;
|
||||||
|
float PreEditDensity = 0.0f;
|
||||||
DensityGrid[LocalIndex] = Generator->GetDensityAt(
|
DensityGrid[LocalIndex] = Generator->GetDensityAt(
|
||||||
OriginVoxels.X + gx * Step,
|
OriginVoxels.X + gx * Step,
|
||||||
OriginVoxels.Y + gy * Step,
|
OriginVoxels.Y + gy * Step,
|
||||||
OriginVoxels.Z + gz * Step);
|
OriginVoxels.Z + gz * Step,
|
||||||
|
bCapturePreEditGrid ? &PreEditDensity : nullptr);
|
||||||
|
if (bCapturePreEditGrid)
|
||||||
|
{
|
||||||
|
OutPreEditDensityGrid->Samples[LocalIndex] = PreEditDensity;
|
||||||
|
}
|
||||||
if (OutDensitySampleCount)
|
if (OutDensitySampleCount)
|
||||||
{
|
{
|
||||||
++(*OutDensitySampleCount);
|
++(*OutDensitySampleCount);
|
||||||
@@ -848,6 +873,11 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (OutPreEditDensityGrid && !bCapturePreEditGrid)
|
||||||
|
{
|
||||||
|
OutPreEditDensityGrid->Reset();
|
||||||
|
}
|
||||||
|
|
||||||
// Lecture grille (avec offset de marge) + gradient central depuis la grille.
|
// Lecture grille (avec offset de marge) + gradient central depuis la grille.
|
||||||
auto SampleG = [&](int32 gx, int32 gy, int32 gz) -> float
|
auto SampleG = [&](int32 gx, int32 gy, int32 gz) -> float
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
// Bounded helpers for replaying player edits over the pre-edit L0 density lattice.
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "VoxelDiffLayer.h"
|
||||||
|
#include "VoxelMarchingCubesMesher.h"
|
||||||
|
|
||||||
|
namespace VoxelForgePreEditDensityCache
|
||||||
|
{
|
||||||
|
// Keep only the tile containing the pawn: 35³ floats is a fixed 171,500-byte ceiling. The
|
||||||
|
// current tile is the synchronous brush-centre remesh target and avoids prewarming empty neighbors.
|
||||||
|
constexpr int32 PawnRadiusChunks = 0;
|
||||||
|
constexpr int32 MaxCachedTiles = 1;
|
||||||
|
|
||||||
|
inline bool IsPawnRadiusTile(const FVoxelTileKey& Tile, const FIntVector& PawnChunk)
|
||||||
|
{
|
||||||
|
return Tile.Level == 0
|
||||||
|
&& FMath::Abs(Tile.Coord.X - PawnChunk.X) <= PawnRadiusChunks
|
||||||
|
&& FMath::Abs(Tile.Coord.Y - PawnChunk.Y) <= PawnRadiusChunks
|
||||||
|
&& FMath::Abs(Tile.Coord.Z - PawnChunk.Z) <= PawnRadiusChunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool BuildEditedGrid(const FVoxelSharedDensityGrid& PreEditGrid,
|
||||||
|
const UVoxelDiffLayer* DiffLayer,
|
||||||
|
FVoxelSharedDensityGrid& OutEditedGrid)
|
||||||
|
{
|
||||||
|
OutEditedGrid.Reset();
|
||||||
|
if (PreEditGrid.Step != 1 || PreEditGrid.Dim != CHUNK_SIZE + 3
|
||||||
|
|| PreEditGrid.Samples.Num() != PreEditGrid.Dim * PreEditGrid.Dim * PreEditGrid.Dim)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
OutEditedGrid = PreEditGrid;
|
||||||
|
if (DiffLayer == nullptr || !DiffLayer->HasAnyMods())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FIntVector LastVoxel = PreEditGrid.OriginVoxels
|
||||||
|
+ FIntVector(PreEditGrid.Dim - 1, PreEditGrid.Dim - 1, PreEditGrid.Dim - 1)
|
||||||
|
* PreEditGrid.Step;
|
||||||
|
const FIntVector MinChunk(
|
||||||
|
FMath::FloorToInt(static_cast<float>(PreEditGrid.OriginVoxels.X) / CHUNK_SIZE),
|
||||||
|
FMath::FloorToInt(static_cast<float>(PreEditGrid.OriginVoxels.Y) / CHUNK_SIZE),
|
||||||
|
FMath::FloorToInt(static_cast<float>(PreEditGrid.OriginVoxels.Z) / CHUNK_SIZE));
|
||||||
|
const FIntVector MaxChunk(
|
||||||
|
FMath::FloorToInt(static_cast<float>(LastVoxel.X) / CHUNK_SIZE),
|
||||||
|
FMath::FloorToInt(static_cast<float>(LastVoxel.Y) / CHUNK_SIZE),
|
||||||
|
FMath::FloorToInt(static_cast<float>(LastVoxel.Z) / CHUNK_SIZE));
|
||||||
|
|
||||||
|
TMap<FIntVector, TArray<FVoxelModification>> ChunkMods;
|
||||||
|
for (int32 CZ = MinChunk.Z;; ++CZ)
|
||||||
|
{
|
||||||
|
for (int32 CY = MinChunk.Y;; ++CY)
|
||||||
|
{
|
||||||
|
for (int32 CX = MinChunk.X;; ++CX)
|
||||||
|
{
|
||||||
|
TArray<FVoxelModification> Mods;
|
||||||
|
DiffLayer->GetChunkModsSnapshot(FIntVector(CX, CY, CZ), Mods);
|
||||||
|
ChunkMods.Add(FIntVector(CX, CY, CZ), MoveTemp(Mods));
|
||||||
|
if (CX == MaxChunk.X) { break; }
|
||||||
|
}
|
||||||
|
if (CY == MaxChunk.Y) { break; }
|
||||||
|
}
|
||||||
|
if (CZ == MaxChunk.Z) { break; }
|
||||||
|
}
|
||||||
|
|
||||||
|
const int32 Dim = PreEditGrid.Dim;
|
||||||
|
for (int32 Z = 0; Z < Dim; ++Z)
|
||||||
|
{
|
||||||
|
const int32 WorldZ = PreEditGrid.OriginVoxels.Z + Z * PreEditGrid.Step;
|
||||||
|
const int32 ChunkZ = FMath::FloorToInt(static_cast<float>(WorldZ) / CHUNK_SIZE);
|
||||||
|
for (int32 Y = 0; Y < Dim; ++Y)
|
||||||
|
{
|
||||||
|
const int32 WorldY = PreEditGrid.OriginVoxels.Y + Y * PreEditGrid.Step;
|
||||||
|
const int32 ChunkY = FMath::FloorToInt(static_cast<float>(WorldY) / CHUNK_SIZE);
|
||||||
|
for (int32 X = 0; X < Dim; ++X)
|
||||||
|
{
|
||||||
|
const int32 WorldX = PreEditGrid.OriginVoxels.X + X * PreEditGrid.Step;
|
||||||
|
const int32 ChunkX = FMath::FloorToInt(static_cast<float>(WorldX) / CHUNK_SIZE);
|
||||||
|
const TArray<FVoxelModification>* Mods = ChunkMods.Find(
|
||||||
|
FIntVector(ChunkX, ChunkY, ChunkZ));
|
||||||
|
if (Mods == nullptr || Mods->Num() == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int32 Index = (Z * Dim + Y) * Dim + X;
|
||||||
|
// Match GetDensityAt's canonical post-edit operation exactly.
|
||||||
|
OutEditedGrid.Samples[Index] -= UVoxelDiffLayer::EvaluateMods(
|
||||||
|
*Mods, static_cast<float>(WorldX), static_cast<float>(WorldY),
|
||||||
|
static_cast<float>(WorldZ));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -164,7 +164,8 @@ public:
|
|||||||
*
|
*
|
||||||
* @param WorldX, WorldY, WorldZ - coordonnées monde en voxels (pas cm)
|
* @param WorldX, WorldY, WorldZ - coordonnées monde en voxels (pas cm)
|
||||||
*/
|
*/
|
||||||
float GetDensityAt(float WorldX, float WorldY, float WorldZ) const;
|
float GetDensityAt(float WorldX, float WorldY, float WorldZ,
|
||||||
|
float* OutPreEditDensity = nullptr) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open a worker-local lattice hand-off for the following GetDensityAt calls. The mesher uses
|
* Open a worker-local lattice hand-off for the following GetDensityAt calls. The mesher uses
|
||||||
|
|||||||
@@ -76,7 +76,9 @@ public:
|
|||||||
FVoxelMeshData GenerateMesh(FIntVector OriginVoxels, int32 Step = 1, int32 CellsPerAxis = CHUNK_SIZE,
|
FVoxelMeshData GenerateMesh(FIntVector OriginVoxels, int32 Step = 1, int32 CellsPerAxis = CHUNK_SIZE,
|
||||||
TArray<uint8>* OutCaptureGrid = nullptr,
|
TArray<uint8>* OutCaptureGrid = nullptr,
|
||||||
int32 BandZMinVox = INT32_MIN, int32 BandZMaxVox = INT32_MAX,
|
int32 BandZMinVox = INT32_MIN, int32 BandZMaxVox = INT32_MAX,
|
||||||
int64* OutDensitySampleCount = nullptr);
|
int64* OutDensitySampleCount = nullptr,
|
||||||
|
FVoxelSharedDensityGrid* OutPreEditDensityGrid = nullptr,
|
||||||
|
const FVoxelSharedDensityGrid* InSharedDensityGrid = nullptr);
|
||||||
|
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
// SERVICES (injectés par AVoxelWorld)
|
// SERVICES (injectés par AVoxelWorld)
|
||||||
|
|||||||
@@ -81,6 +81,12 @@ public:
|
|||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Streaming")
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Streaming")
|
||||||
int32 MaxUnloadsPerFrame = 6;
|
int32 MaxUnloadsPerFrame = 6;
|
||||||
|
|
||||||
|
// Independent level-0 collision radius around the locally controlled pawn. Render tiles outside
|
||||||
|
// this cube remain visible but do not queue Chaos cooks; registered streaming anchors keep their
|
||||||
|
// own authored collision boxes, and the collision gate can extend a landing-support column.
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Streaming", meta = (ClampMin = "1", ClampMax = "4"))
|
||||||
|
int32 PawnCollisionRadiusChunks = 1;
|
||||||
|
|
||||||
// Strate-aware VERTICAL streaming: don't load chunks into a strate you can't see.
|
// Strate-aware VERTICAL streaming: don't load chunks into a strate you can't see.
|
||||||
// Seals + the inter-strate bedrock gap are light-tight (§8.7), so the strate above/below
|
// Seals + the inter-strate bedrock gap are light-tight (§8.7), so the strate above/below
|
||||||
// the one you're in is fully occluded — loading it just wastes scene primitives (the
|
// the one you're in is fully occluded — loading it just wastes scene primitives (the
|
||||||
|
|||||||
@@ -149,6 +149,9 @@ struct FChunkResult
|
|||||||
// it to UVoxelDensityVolume::IngestTileCapture so the density clipmap reuses the mesher's samples
|
// it to UVoxelDensityVolume::IngestTileCapture so the density clipmap reuses the mesher's samples
|
||||||
// instead of re-sampling. Moved (not copied) through the MPSC queue. See UVoxelDensityVolume.
|
// instead of re-sampling. Moved (not copied) through the MPSC queue. See UVoxelDensityVolume.
|
||||||
TArray<uint8> CaptureGrid;
|
TArray<uint8> CaptureGrid;
|
||||||
|
// Exact pre-edit level-0 lattice for the bounded local-player dig cache. Sampled on the worker
|
||||||
|
// with the tile's normal generator context and moved through the result queue.
|
||||||
|
FVoxelSharedDensityGrid PreEditDensityGrid;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** One worker-produced mesh identity emitted only by the explicit parity harness. */
|
/** One worker-produced mesh identity emitted only by the explicit parity harness. */
|
||||||
@@ -550,7 +553,7 @@ private:
|
|||||||
bool MeasureHeadlessCollisionGateStressTerrain(
|
bool MeasureHeadlessCollisionGateStressTerrain(
|
||||||
const FVector& WorldPosition, float CapsuleHalfHeightCm,
|
const FVector& WorldPosition, float CapsuleHalfHeightCm,
|
||||||
float& OutFeetDensity, float& OutCenterDensity,
|
float& OutFeetDensity, float& OutCenterDensity,
|
||||||
float& OutSurfaceLocalVoxelZ, float& OutClearanceCm) const;
|
float& OutSurfaceLocalVoxelZ, float& OutClearanceCm);
|
||||||
void AdvanceHeadlessSurfaceFallTest(FVector& InOutPlayerPosition,
|
void AdvanceHeadlessSurfaceFallTest(FVector& InOutPlayerPosition,
|
||||||
FVector& InOutPlayerHeading, APawn* PlayerPawn);
|
FVector& InOutPlayerHeading, APawn* PlayerPawn);
|
||||||
void InitializeHeadlessStrateCrossingTest();
|
void InitializeHeadlessStrateCrossingTest();
|
||||||
@@ -769,7 +772,9 @@ public:
|
|||||||
int32 Step, int32 Cells, uint32 Epoch, bool bWantCapture,
|
int32 Step, int32 Cells, uint32 Epoch, bool bWantCapture,
|
||||||
int32 BandVoxLo, int32 BandVoxHi, int32 BandChunkLo, int32 BandChunkHi,
|
int32 BandVoxLo, int32 BandVoxHi, int32 BandChunkLo, int32 BandChunkHi,
|
||||||
FChunkResult& Result,
|
FChunkResult& Result,
|
||||||
const std::atomic<bool>* ObsoleteFlag = nullptr);
|
const std::atomic<bool>* ObsoleteFlag = nullptr,
|
||||||
|
bool bWantPreEditDensityGrid = false,
|
||||||
|
const FVoxelSharedDensityGrid* SharedDensityOverride = nullptr);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Game-thread apply for one gen result (shared by ProcessPendingChunks + SyncRemeshTile):
|
* Game-thread apply for one gen result (shared by ProcessPendingChunks + SyncRemeshTile):
|
||||||
@@ -859,6 +864,7 @@ public:
|
|||||||
FVoxelTileKey& OutSupportTile) const;
|
FVoxelTileKey& OutSupportTile) const;
|
||||||
|
|
||||||
bool IsTileCollisionReady(const FVoxelTileKey& Tile) const;
|
bool IsTileCollisionReady(const FVoxelTileKey& Tile) const;
|
||||||
|
bool IsTileCollisionDesired(const FVoxelTileKey& Tile) const;
|
||||||
bool IsCollisionTileUnavailable(const FVoxelTileKey& Tile,
|
bool IsCollisionTileUnavailable(const FVoxelTileKey& Tile,
|
||||||
const TCHAR*& OutReason) const;
|
const TCHAR*& OutReason) const;
|
||||||
bool IsCollisionReadyFromSupportTile(const FVoxelTileKey& CandidateTile,
|
bool IsCollisionReadyFromSupportTile(const FVoxelTileKey& CandidateTile,
|
||||||
@@ -872,10 +878,10 @@ public:
|
|||||||
FVoxelTileKey* OutPendingTile = nullptr) const;
|
FVoxelTileKey* OutPendingTile = nullptr) const;
|
||||||
void RequestCollisionGateSupportTile(const FVoxelTileKey& SupportTile);
|
void RequestCollisionGateSupportTile(const FVoxelTileKey& SupportTile);
|
||||||
|
|
||||||
/** Cancel only downward pawn motion while the tile beneath its feet is unresolved. Horizontal
|
/** Hold vertical character motion in gravity-free MOVE_Flying while a swept pending support tile
|
||||||
* input and movement remain live. The world tick is installed as the pawn prerequisite in the
|
* intersects the capsule, preserving horizontal movement until collision is ready. Restore the
|
||||||
* implementation (and directly on its movement component) so this check runs before
|
* previous mode, gravity, and downward speed on release. The world tick is a prerequisite of the
|
||||||
* CharacterMovementComponent physics. */
|
* pawn and movement component so the gate check runs before movement physics. */
|
||||||
void UpdatePawnCollisionGate(APawn* Pawn, const FVector& PlayerPosition, float DeltaTime);
|
void UpdatePawnCollisionGate(APawn* Pawn, const FVector& PlayerPosition, float DeltaTime);
|
||||||
void EngagePawnCollisionGate(APawn* Pawn);
|
void EngagePawnCollisionGate(APawn* Pawn);
|
||||||
void ReleasePawnCollisionGate();
|
void ReleasePawnCollisionGate();
|
||||||
@@ -951,6 +957,9 @@ public:
|
|||||||
|
|
||||||
// Player's level-0 tile coord (= chunk coord). The desired set is rebuilt when this changes.
|
// Player's level-0 tile coord (= chunk coord). The desired set is rebuilt when this changes.
|
||||||
FIntVector CurrentCenterChunk = FIntVector::ZeroValue;
|
FIntVector CurrentCenterChunk = FIntVector::ZeroValue;
|
||||||
|
// Exact unedited L0 lattices retained around the player for same-frame carve remeshes.
|
||||||
|
// The radius helper bounds this to 27 × 35³ floats (4.42 MiB).
|
||||||
|
TMap<FVoxelTileKey, FVoxelSharedDensityGrid> PreEditDensityCache;
|
||||||
|
|
||||||
// Non-player streaming anchors (AI now, remote players later — ARCHITECTURE §9.3). BuildDesiredTiles
|
// Non-player streaming anchors (AI now, remote players later — ARCHITECTURE §9.3). BuildDesiredTiles
|
||||||
// folds each anchor's small level-0 box into the desired set (same DesiredStamped machinery → the
|
// folds each anchor's small level-0 box into the desired set (same DesiredStamped machinery → the
|
||||||
@@ -960,6 +969,7 @@ public:
|
|||||||
// Adds each anchor's level-0 tiles to DesiredSorted/DesiredStamped (dedup vs the player clipmap)
|
// Adds each anchor's level-0 tiles to DesiredSorted/DesiredStamped (dedup vs the player clipmap)
|
||||||
// and records the ones ONLY a CollisionOnly anchor wants in CollisionOnlyTiles.
|
// and records the ones ONLY a CollisionOnly anchor wants in CollisionOnlyTiles.
|
||||||
void AddAnchorDesiredTiles();
|
void AddAnchorDesiredTiles();
|
||||||
|
void ReconcileCollisionDesiredTiles();
|
||||||
// Forces a desired-set rebuild next Tick even if neither the player nor an anchor crossed a tile
|
// Forces a desired-set rebuild next Tick even if neither the player nor an anchor crossed a tile
|
||||||
// boundary (set by UnregisterStreamingAnchor so a removed anchor's tiles get culled).
|
// boundary (set by UnregisterStreamingAnchor so a removed anchor's tiles get culled).
|
||||||
bool bForceDesiredRebuild = false;
|
bool bForceDesiredRebuild = false;
|
||||||
@@ -971,6 +981,9 @@ public:
|
|||||||
// without an O(loaded) scan. Both empty when there are no CollisionOnly anchors → zero cost.
|
// without an O(loaded) scan. Both empty when there are no CollisionOnly anchors → zero cost.
|
||||||
TSet<FVoxelTileKey> CollisionOnlyTiles;
|
TSet<FVoxelTileKey> CollisionOnlyTiles;
|
||||||
TSet<FVoxelTileKey> PrevCollisionOnlyTiles;
|
TSet<FVoxelTileKey> PrevCollisionOnlyTiles;
|
||||||
|
// Independent from DesiredSorted: rendered LOD0 tiles outside the pawn radius remain visible
|
||||||
|
// without a Chaos body. Pawn radius, anchor boxes, and collision-gate support are unioned here.
|
||||||
|
TSet<FVoxelTileKey> CollisionDesiredTiles;
|
||||||
// A short collision-only column below an unresolved player support tile. Keeping these keys
|
// A short collision-only column below an unresolved player support tile. Keeping these keys
|
||||||
// stamped prevents a critical request outside the render clipmap from being canceled on rebuild.
|
// stamped prevents a critical request outside the render clipmap from being canceled on rebuild.
|
||||||
TSet<FVoxelTileKey> CollisionGateDesiredTiles;
|
TSet<FVoxelTileKey> CollisionGateDesiredTiles;
|
||||||
@@ -1103,14 +1116,18 @@ public:
|
|||||||
bool bHeadlessCollisionGateStressTestFailed = false;
|
bool bHeadlessCollisionGateStressTestFailed = false;
|
||||||
bool bHeadlessCollisionGateStressTestExitRequested = false;
|
bool bHeadlessCollisionGateStressTestExitRequested = false;
|
||||||
bool bHeadlessCollisionGateStressTestGateWasEngaged = false;
|
bool bHeadlessCollisionGateStressTestGateWasEngaged = false;
|
||||||
|
bool bHeadlessCollisionGateStressTestHorizontalGateProbeApplied = false;
|
||||||
bool bHeadlessCollisionGateStressTestFullStop = false;
|
bool bHeadlessCollisionGateStressTestFullStop = false;
|
||||||
bool bHeadlessCollisionGateStressTestStartHeightCrossed = false;
|
bool bHeadlessCollisionGateStressTestStartHeightCrossed = false;
|
||||||
bool bHeadlessCollisionGateStressTestTerrainViolation = false;
|
bool bHeadlessCollisionGateStressTestTerrainViolation = false;
|
||||||
bool bHeadlessCollisionGateStressTestTerrainMeasurementMissing = false;
|
bool bHeadlessCollisionGateStressTestTerrainMeasurementMissing = false;
|
||||||
bool bHeadlessCollisionGateStressTestLanded = false;
|
bool bHeadlessCollisionGateStressTestLanded = false;
|
||||||
bool bHeadlessCollisionGateStressTestLaunchSupportTimingCaptured = false;
|
bool bHeadlessCollisionGateStressTestLaunchSupportTimingCaptured = false;
|
||||||
|
bool bHeadlessCollisionGateStressTestSettingsReported = false;
|
||||||
bool bHeadlessCollisionGateStressDeepDrop = false;
|
bool bHeadlessCollisionGateStressDeepDrop = false;
|
||||||
bool bHeadlessCollisionGateStressDeepDropPrepared = false;
|
bool bHeadlessCollisionGateStressDeepDropPrepared = false;
|
||||||
|
bool bHeadlessCollisionGateStressDeepDropPrewarmReported = false;
|
||||||
|
bool bHeadlessCollisionGateStressDeepDropSurfaceCached = false;
|
||||||
float HeadlessCollisionGateStressTestSpeedCmPerSecond = 6000.0f;
|
float HeadlessCollisionGateStressTestSpeedCmPerSecond = 6000.0f;
|
||||||
float HeadlessCollisionGateStressTestSpawnHeightVoxels = 10.0f;
|
float HeadlessCollisionGateStressTestSpawnHeightVoxels = 10.0f;
|
||||||
float HeadlessCollisionGateStressTestDurationSeconds = 2.0f;
|
float HeadlessCollisionGateStressTestDurationSeconds = 2.0f;
|
||||||
@@ -1139,6 +1156,8 @@ public:
|
|||||||
FVector HeadlessCollisionGateStressTestLastActualPosition = FVector::ZeroVector;
|
FVector HeadlessCollisionGateStressTestLastActualPosition = FVector::ZeroVector;
|
||||||
FVector HeadlessCollisionGateStressTestStartPosition = FVector::ZeroVector;
|
FVector HeadlessCollisionGateStressTestStartPosition = FVector::ZeroVector;
|
||||||
FVector HeadlessCollisionGateStressTestMinClearancePosition = FVector::ZeroVector;
|
FVector HeadlessCollisionGateStressTestMinClearancePosition = FVector::ZeroVector;
|
||||||
|
FVector2D HeadlessCollisionGateStressDeepDropSurfaceXYVox = FVector2D::ZeroVector;
|
||||||
|
float HeadlessCollisionGateStressDeepDropSurfaceZVox = 0.0f;
|
||||||
FVoxelTileKey HeadlessCollisionGateStressTestLaunchSupportTile;
|
FVoxelTileKey HeadlessCollisionGateStressTestLaunchSupportTile;
|
||||||
FString HeadlessCollisionGateStressTestFailureReason;
|
FString HeadlessCollisionGateStressTestFailureReason;
|
||||||
|
|
||||||
@@ -1235,6 +1254,9 @@ public:
|
|||||||
bool bCollisionGateEnabled = true;
|
bool bCollisionGateEnabled = true;
|
||||||
float SavedCharacterGravityScale = 1.0f;
|
float SavedCharacterGravityScale = 1.0f;
|
||||||
bool bSavedCharacterGravityScale = false;
|
bool bSavedCharacterGravityScale = false;
|
||||||
|
uint8 SavedCharacterMovementMode = 0;
|
||||||
|
uint8 SavedCharacterCustomMovementMode = 0;
|
||||||
|
bool bSavedCharacterMovementMode = false;
|
||||||
float SavedGateVerticalVelocity = 0.0f;
|
float SavedGateVerticalVelocity = 0.0f;
|
||||||
bool bSavedGateVerticalVelocity = false;
|
bool bSavedGateVerticalVelocity = false;
|
||||||
bool bPawnCollisionGateEngaged = false;
|
bool bPawnCollisionGateEngaged = false;
|
||||||
|
|||||||
@@ -216,6 +216,40 @@ function Assert-NoForeignUnreal([string]$When) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (-not ('VoxelForgeTestMemoryStatus' -as [type])) {
|
||||||
|
Add-Type -TypeDefinition @'
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
public static class VoxelForgeTestMemoryStatus {
|
||||||
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
||||||
|
public class MEMORYSTATUSEX {
|
||||||
|
public uint dwLength = (uint)Marshal.SizeOf(typeof(MEMORYSTATUSEX));
|
||||||
|
public uint dwMemoryLoad;
|
||||||
|
public ulong ullTotalPhys;
|
||||||
|
public ulong ullAvailPhys;
|
||||||
|
public ulong ullTotalPageFile;
|
||||||
|
public ulong ullAvailPageFile;
|
||||||
|
public ulong ullTotalVirtual;
|
||||||
|
public ulong ullAvailVirtual;
|
||||||
|
public ulong ullAvailExtendedVirtual;
|
||||||
|
}
|
||||||
|
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||||
|
public static extern bool GlobalMemoryStatusEx([In, Out] MEMORYSTATUSEX lpBuffer);
|
||||||
|
}
|
||||||
|
'@
|
||||||
|
}
|
||||||
|
|
||||||
|
function Assert-EnoughFreeMemory([string]$When) {
|
||||||
|
$status = New-Object VoxelForgeTestMemoryStatus+MEMORYSTATUSEX
|
||||||
|
if (-not [VoxelForgeTestMemoryStatus]::GlobalMemoryStatusEx($status)) {
|
||||||
|
throw "Could not query free physical memory before $When; refusing to launch."
|
||||||
|
}
|
||||||
|
$freeGiB = [double]$status.ullAvailPhys / 1GB
|
||||||
|
if ($freeGiB -lt 6.0) {
|
||||||
|
throw ("Only {0:N2} GiB free before {1}; refusing to launch (minimum 6 GiB)." -f $freeGiB, $When)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function Get-CrashSnapshot {
|
function Get-CrashSnapshot {
|
||||||
$snapshot = [ordered]@{}
|
$snapshot = [ordered]@{}
|
||||||
$roots = @(
|
$roots = @(
|
||||||
@@ -330,6 +364,7 @@ function Wait-OwnedProcess($Process, [int]$TimeoutSeconds, [switch]$EnforceMemor
|
|||||||
function Invoke-UnrealLaunch([string]$Kind, [string[]]$Arguments, [string]$LogPath,
|
function Invoke-UnrealLaunch([string]$Kind, [string[]]$Arguments, [string]$LogPath,
|
||||||
[string]$StdoutPath, [string]$StderrPath) {
|
[string]$StdoutPath, [string]$StderrPath) {
|
||||||
Assert-NoForeignUnreal "before $Kind launch"
|
Assert-NoForeignUnreal "before $Kind launch"
|
||||||
|
Assert-EnoughFreeMemory "$Kind launch"
|
||||||
Ensure-Directory (Split-Path -Parent $LogPath)
|
Ensure-Directory (Split-Path -Parent $LogPath)
|
||||||
Ensure-Directory (Split-Path -Parent $StdoutPath)
|
Ensure-Directory (Split-Path -Parent $StdoutPath)
|
||||||
Ensure-Directory (Split-Path -Parent $StderrPath)
|
Ensure-Directory (Split-Path -Parent $StderrPath)
|
||||||
@@ -399,6 +434,8 @@ function Invoke-UnrealLaunch([string]$Kind, [string[]]$Arguments, [string]$LogPa
|
|||||||
|
|
||||||
function Invoke-BuildProcess([string[]]$Arguments, [string]$LogPath, [string]$ErrorPath,
|
function Invoke-BuildProcess([string[]]$Arguments, [string]$LogPath, [string]$ErrorPath,
|
||||||
[string]$StdoutPath = $LogPath) {
|
[string]$StdoutPath = $LogPath) {
|
||||||
|
Assert-NoForeignUnreal 'before staged build launch'
|
||||||
|
Assert-EnoughFreeMemory 'staged build launch'
|
||||||
Ensure-Directory (Split-Path -Parent $LogPath)
|
Ensure-Directory (Split-Path -Parent $LogPath)
|
||||||
$dotnetHome = Join-Path $RunRoot 'DotnetHome'
|
$dotnetHome = Join-Path $RunRoot 'DotnetHome'
|
||||||
Ensure-Directory $dotnetHome
|
Ensure-Directory $dotnetHome
|
||||||
@@ -991,7 +1028,8 @@ function Get-CollisionGateStressMetrics([string]$LogPath) {
|
|||||||
foreach ($key in @('reason', 'elapsed_s', 'distance_m', 'expected_distance_m', 'speed_cm_s',
|
foreach ($key in @('reason', 'elapsed_s', 'distance_m', 'expected_distance_m', 'speed_cm_s',
|
||||||
'spawn_height_vox', 'deep_drop',
|
'spawn_height_vox', 'deep_drop',
|
||||||
'gate_holds', 'gate_total_s', 'max_gate_s', 'full_stops',
|
'gate_holds', 'gate_total_s', 'max_gate_s', 'full_stops',
|
||||||
'full_stop_total_s', 'collision_gate', 'gate_progress_m', 'support_tile',
|
'full_stop_total_s', 'collision_gate', 'gate_progress_m',
|
||||||
|
'horizontal_gate_probe', 'support_tile',
|
||||||
'support_first_check_to_submit_s',
|
'support_first_check_to_submit_s',
|
||||||
'support_first_check_to_collision_ready_s', 'test_support_tile',
|
'support_first_check_to_collision_ready_s', 'test_support_tile',
|
||||||
'test_support_first_check_to_submit_s',
|
'test_support_first_check_to_submit_s',
|
||||||
|
|||||||
Reference in new issue
Block a user