measure(step1): cook is 1.5-2.3 ms, RMC's route is 8-11 s; CMC cannot take a field floor; cap 16 uploads
Rebuild plan step 1 (RESEARCH-SPEED-2026-09.md), all behind default-off switches. A. Direct cook on a dedicated thread, 27-tile support cohort: core cook p50 1.50 ms, p95 2.28 ms; a 3,949-tri tile 1.67 ms extract+cook. PASSES the 10 ms gate. Through RMC's route the same support bodies became ready 8.46 s p50 / 10.80 s p95 after their meshes existed: FAILS 150 ms. Exact-target capsule sweep confirmed (0.75 ms after body update). B. 100 rapid underfoot digs on CMC with a field override: 883 false-support frames, 0 false-air, 0 invented velocity. CMC's floor comes from collision-scene traces -> step 4 = field-aware Mover. C. 341 prequeued tiles, render on: cap 4 = 238 applies/s, cap 8 = 476, cap 16 = 639 (drain p95 2.22 ms vs 2 ms gate); first visible 1 frame after release; no RT/RHI backlog. Tune drain, no grouping needed yet. D. Same 128^3 region, 64/64 tile hashes equal in export and game. The "263 ns/sample" was export WALL time with 15 workers sharing a lattice (~3.4 us CPU/sample); game tile time includes meshing and duplicated borders (+22%); contention cap 1 vs 14: 74 vs 112 ms per tile. Profiling is not the cause. 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
97a1874498
commit
41b001b652
5 files changed
+481
-55
No files matched your search
+12
-9
@@ -117,9 +117,9 @@ remains `0`, and lateral regions remain gated off.
|
||||
|------|------|
|
||||
| `../../VoxelForge.uplugin` | Plugin manifest. Runtime module `VoxelForge` plus the Editor-only `VoxelForgeEditor` commandlet module. Beta. |
|
||||
| `VoxelForge.Build.cs` | Deps: Core, CoreUObject, Engine, **GameplayTags**, **RealtimeMeshComponent**. |
|
||||
| `Tools/VoxelForgeTest.ps1` | The single guarded test entry point. `-Scenario canonical|owner|probe|perf|tests|surface-fall|crossing|gate-stress|visual-edit|visual-lod|visual-gap-walk|visual-gap-inject|dig-motion|parity|horizon|horizon-render|determinism`, optional `-Build`, `-Assets owner|default`, `-Cvars`, absolute `-Out`, `-Label`, `-HorizonLevel`, `-HorizonRenderDistanceChunks`, and `-SharedDDC`; stages the host/assets, isolates user/DDC/config state, serializes one Unreal launch at a time, enforces the 8 GiB/30-minute guards, and emits compact `result.json`/`summary.txt` plus full logs. `visual-gap-walk` records camera-frustum surface-point coverage at 6000 cm/s; `visual-gap-inject` proves an early component hide is detected; `dig-motion` logs per-frame vertical velocity, gate, support readiness, and movement mode. `-Cvars @{ 'voxel.TestGapAuditLegacyHandoff'=1 }` replays the old visibility handoff for A/B audit baselines. Horizon traces report desired/loaded tiles, resident stream geometry, worker seconds, steady wall time, and peak private memory; `horizon-render` captures the owner view; `determinism` compares per-tile hashes across the default CRT path and `-voxel.CrtFma3=0`. |
|
||||
| `Tools/VoxelForgeTest.ps1` | The single guarded test entry point. `-Scenario canonical|owner|probe|perf|tests|surface-fall|crossing|gate-stress|visual-edit|visual-lod|visual-gap-walk|visual-gap-inject|dig-motion|parity|horizon|horizon-render|determinism`, optional `-Build`, `-Assets owner|default`, `-Cvars`, absolute `-Out`, `-Label`, `-HorizonLevel`, `-HorizonRenderDistanceChunks`, `-ParityExportSize=32|64|128`, `-ParityExportTile=x,y,z`, and `-SharedDDC`; stages the host/assets, isolates user/DDC/config state, serializes one Unreal launch at a time, enforces the 8 GiB/30-minute guards, and emits compact `result.json`/`summary.txt` plus full logs. Parity can render its game leg with `-EnableRender` and compares the selected export region against the exact streamed tile hashes. `visual-gap-walk` records camera-frustum surface-point coverage at 6000 cm/s; `visual-gap-inject` proves an early component hide is detected; `dig-motion` logs per-frame vertical velocity, gate, support readiness, and movement mode. `-Cvars @{ 'voxel.TestGapAuditLegacyHandoff'=1 }` replays the old visibility handoff for A/B audit baselines. Horizon traces report desired/loaded tiles, resident stream geometry, worker seconds, steady wall time, and peak private memory; `horizon-render` captures the owner view; `determinism` compares per-tile hashes across the default CRT path and `-voxel.CrtFma3=0`. |
|
||||
| `Public/VoxelForgeModule.h` / `Private/VoxelForgeModule.cpp` | `FVoxelForgeModule` boilerplate (Startup/Shutdown just log). |
|
||||
| `Source/VoxelForgeEditor/VoxelForgeEditor.Build.cs` / `Private/VoxelForgeExploreCommandlet.*` | Editor/commandlet-only `-run=VoxelForgeExplore`; owns bounded render/`VF_` walk/OBJ export orchestration and is not linked by Game/Shipping. |
|
||||
| `Source/VoxelForgeEditor/VoxelForgeEditor.Build.cs` / `Private/VoxelForgeExploreCommandlet.*` | Editor/commandlet-only `-run=VoxelForgeExplore`; owns bounded render/`VF_` walk/OBJ export orchestration and is not linked by Game/Shipping. Optional `-exporttilex/y/z` sets an exact L0 tile origin for controlled export/game parity; omitting all three preserves the existing center-derived origin. |
|
||||
| `Public/VoxelGeometryHash.h` | Header-only geometry digest shared by commandlet export and runtime tile-hash dump; hashes mesh counts and the vertex/normal/UV/color/triangle arrays with the historical CRC order. |
|
||||
| `Public/VoxelStats.h` / `Private/VoxelStats.cpp` | `stat VoxelForge` DWORD counters for tile classification, skipping, meshing, operator-stack verdicts, and cave-bail diagnosis. Each `Cave Bail *` counter names one guard; `Cave Bail No Stack` counts slots with no buildable stack (no definition, or a degenerate strate). |
|
||||
|
||||
@@ -305,9 +305,10 @@ casse cette arithmetique *structurellement* — `BeginPlay` loggue une **Error**
|
||||
| `RebuildStrates` | 724 | Reinitializes the volume only when enabled; when disabled, shuts down and clears any existing volume and reapplies neutral terrain material params. |
|
||||
| `Tick` | 5715 | `UpdateChunksAroundPosition(player, heading)` + `ProcessPendingChunks()`; the clean LOD0 latency summary is flushed at `EndPlay`. Density-volume update/material work is inside the non-null volume branch, so it is absent when disabled. |
|
||||
| `GetPlayerPosition` | 4831 | Pawn position or zero. |
|
||||
| `AdvanceHeadlessDigMotionTest` / `RecordHeadlessDigMotionFrame` | 3060 / 3217 | Explicit `-voxel.TestDigMotion=1` repro: digs eight times beside a walking pawn, records each frame's vertical velocity, gate, support tile/readiness, movement mode, then digs underfoot and requires ordinary falling. The accompanying gap audit is scoped to the dig session. |
|
||||
| `AdvanceHeadlessDigMotionTest` / `RecordHeadlessDigMotionFrame` | 3060 / 3217 | Explicit `-voxel.TestDigMotion=1` repro: optional near digs, then repeats underfoot digs and samples `Generator->GetDensityAt` immediately after each edit, on its first subsequent simulated frame, and on every following observation frame. `-voxel.TestDigMotionFieldOverride=1` is a measurement-only prototype that clears `CurrentFloor` and switches CMC to falling when the sampled post-edit density is air; it is inactive by default. The test counts false support, false air and downward velocity beyond one gravity step. `-voxel.TestDigMotionUnderfootDigCount` is clamped to 1–100; `-voxel.TestDigMotionUnderfootIntervalSeconds=0` performs per-frame rapid edits. The accompanying gap audit is scoped to the dig session. |
|
||||
| `MaybeFinishHeadlessStreamingTest` | — | The existing `voxel.TestExitSeconds` diagnostic emits `VoxelForgeStreamingExitDiag` at most every 30 elapsed seconds, including threshold and early-return test flags; no output when the timer is unconfigured. |
|
||||
| `UpdateVisualStalenessAudit` | 5034 | Test-only camera-frustum coverage audit: tracks up to 24 real pre-skirt isosurface points per visible component, then counts frames where no visible tile component covers a previously visible, still-desired point (excluding edited bounds). `voxel.TestVisualGapInjectEarlyHide=1` hides a valid component for three frames and must be caught. `voxel.TestGapAuditLegacyHandoff=1` restores old handoff culling for A/B baselines. |
|
||||
| `ProcessPendingChunks` | 7435 | Drains critical, edited, near, then ordinary result queues under the per-frame budget; reads the per-request cancellation token before removing its map entry, drops obsolete/aborted results, then applies each live result via `ApplyTileResult`. Collision profiling timestamps the result's arrival here to separate worker completion from game-thread apply wait. Step 1's `voxel.Step1PrequeueVisibleLOD0` holds completed results until the requested non-empty L0 count is ready, then releases them through this same drain. |
|
||||
| `ProcessPendingChunks` | 7435 | Drains critical, edited, near, then ordinary result queues under the per-frame budget; reads the per-request cancellation token before removing its map entry, drops obsolete/aborted results, then applies each live result via `ApplyTileResult`. Collision profiling timestamps the result's arrival here to separate worker completion from game-thread apply wait. Step 1's `voxel.Step1PrequeueVisibleLOD0` holds completed results until the requested non-empty L0 count is ready, then releases them through this same drain and emits the `VFStep1UploadCorpusReleased` CSV event for UE's delayed CSV capture. |
|
||||
| `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]`. |
|
||||
| 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). |
|
||||
@@ -331,9 +332,9 @@ casse cette arithmetique *structurellement* — `BeginPlay` loggue une **Error**
|
||||
| `LoadTile` | 9033 | 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`). `voxel.TestFullResClipLevels` is a measurement-only runtime override; default -1 uses the authored setting. 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. |
|
||||
| `UnloadTile` | — | Clears tile state; marks any matching generation request obsolete without removing its pending slot, so a late result cannot clear a replacement request; the component is PARKED in the pool (T2.c), not destroyed. |
|
||||
| `ApplyMeshToTile` | 9838 | Upload geometry. One component per tile (clipmap keeps count low; supersedes the old region batching); worker-built streams (T1.f) → `CreateSectionGroup(MoveTemp)`. Reuses the component's existing `URealtimeMesh` (no per-apply mesh alloc). **F17: two polygroups** (0 ground / 1 sky-cap, per-triangle class from the mesher) → RMC auto-section per non-empty group; slot 0 = override/default material, slot 1 = `CeilingMaterial` (fallback ground); config gated by `FChunkResult::bHasGroundTris/bHasCeilingTris`. Takes `FChunkResult&`; strate lookups clamp Z into `Result.BandChunkLo/Hi` (strate content cut) — ground at clamped bottom chunk, cap at clamped top (mid = gap fallback). Collision level-0 only (T1.c) both groups; shadow per SECTION: ground casts at level≤1, cap never. Ordinary same-tile re-cooks retain the installed body while it is valid. A carve intersecting the pawn's current support tile and capsule bounds suppresses that old body until the replacement cook is installed. `voxel.ProfileCollisionCookStages` reports generation/apply waits, combined RMC time, completion hops, triangle/skirt counts and collision settings. With `voxel.Step1PrequeueVisibleLOD0>0`, logs apply frame and waits for `GetLastRenderTime` to advance for first-visible frame; `voxel.Step1UploadDisableCollision=1` isolates render upload. Routes each terrain base through shared MIDs; disabled volume MIDs bind neutral `VolP*` and null `VolTex*`. **§9.4:** `SetVisibility(false)` when the tile is in `CollisionOnlyTiles` (anchor-only, hidden — collision still cooks). §8.10 + ARCHITECTURE SurfaceWorld row. |
|
||||
| `HandleRealtimeMeshCollisionBodyUpdated` / `HandleTileCollisionCookComplete` / `PollStep1SupportSweepProbes` | 6964 / 7192 / — | Correlate RMC body-install event and collision-future completion. `voxel.ProfileCollisionCookStages=1` logs worker queue/gen, result wait, apply/upload/config submission, RMC submit→body update, body event→future, future→game-thread hop, submit frames, pending VoxelForge cooks, priority, collision flags, and triangle/skirt totals. `voxel.Step1CollisionProbe=1` adds the support component's physics-state-ready timestamp and polls a real downward capsule sweep until that component is hit. RMC's end-frame dequeue, async worker start, geometry extraction and core cook remain inside the external plugin and are not individually timestampable from this module. |
|
||||
| `FVoxelStep1CoreCookWork` / `FStep1CoreCookPoolOwner` | — | Optional `voxel.Step1CoreCookBenchmark=1` copies critical L0 streams after meshing, appends collision triangles and calls RMC's public `CookComplexMesh` on a dedicated one-thread high-priority pool. It records queue delay, extraction and core cook separately. The benchmark sidecar is not installed as the tile BodySetup and is disabled by default. |
|
||||
| Step 1 measurement CVars | — | `voxel.Step1GenerationCap=-1` uses the normal cap (`0` pauses, positive values cap workers); `voxel.Step1PauseGenerationAfterSeconds=0` disables the timed pause; `voxel.Step1MaxMeshApplies=0` uses the asset cap; `voxel.Step1PrequeueVisibleLOD0=0` disables the held upload corpus; `voxel.Step1UploadDisableCollision=0`, `voxel.Step1CollisionProbe=0`, and `voxel.Step1CoreCookBenchmark=0`. All default off. |
|
||||
| `HandleRealtimeMeshCollisionBodyUpdated` / `HandleTileCollisionCookComplete` / `PollStep1SupportSweepProbes` | 6964 / 7192 / — | Correlate RMC body-install event and collision-future completion. `voxel.ProfileCollisionCookStages=1` logs worker queue/gen, result wait, apply/upload/config submission, RMC submit→body update, body event→future, future→game-thread hop, submit frames, pending VoxelForge cooks, priority, collision flags, and triangle/skirt totals; it also reports mesh-generation-end→ready. `voxel.Step1CollisionProbe=1` sweeps the pawn capsule against the exact component pointer, logs both the first hit and whether it is the target body, and checks every body in the captured support cohort. RMC's end-frame dequeue, async worker start, geometry extraction and core cook remain inside the external plugin and are not individually timestampable from this module. |
|
||||
| `FVoxelStep1CoreCookWork` / `FStep1CoreCookPoolOwner` | — | Optional `voxel.Step1CoreCookBenchmark=1` captures the pawn's first exact radius box (normally 27 L0 tiles) on the game thread, tags those results through worker meshing, and sends their streams plus any out-of-box critical support tile to a dedicated one-thread high-priority pool. It records queue delay, extraction and core cook separately. One approximately 4k-triangle cohort stream is retained and replayed after `Step1PauseGenerationAfterSeconds` pauses generation. The benchmark sidecar is not installed as the tile BodySetup and is disabled by default. |
|
||||
| Step 1 measurement CVars | — | `voxel.Step1GenerationCap=-1` uses the normal cap (`0` pauses, positive values cap worker tasks); `voxel.Step1ExitAfterFrames=0` disables a frame-counted clean test exit; `voxel.Step1PauseGenerationAfterSeconds=0` disables the timed pause; `voxel.Step1MaxMeshApplies=0` uses the asset cap; `voxel.Step1PrequeueVisibleLOD0=0` disables the held upload corpus; `voxel.Step1UploadDisableCollision=0`, `voxel.Step1CollisionProbe=0`, `voxel.Step1CoreCookBenchmark=0`, and `voxel.Step1CsvCaptureOnBeginPlay=0`. All default off. The launch-only `voxel.TestClipRadius` transiently duplicates settings and clamps a positive requested radius to 1–16; it is absent by default and leaves the authored asset unchanged. The prequeue release emits the default-global `VFStep1UploadCorpusReleased` CSV event; `voxel.Step1CsvCaptureOnBeginPlay=1` emits `VFStep1GameBeginPlay`. Use UE's `-csvStartOnEvent=<event>` to begin capture after RHI initialization. |
|
||||
| `AcquireTileComponent` / `ReleaseTileComponent` | — | **T2.c component pool** (`TileComponentPool`, bounded): park on unload (geometry+collision stripped, hidden, stays registered), pop on apply — no `NewObject`/`RegisterComponent`/GC churn during travel & regen. §8.10. |
|
||||
| `GetStrateAtPosition` | 8351 | Gameplay query → strate index. |
|
||||
| `GetBiomeAtWorldLocation` | — | **BlueprintCallable** biome probe at a world point (undoes actor xf → voxel → `Generator::QueryBiomeAt`). Returns `FVoxelBiomeQuery` for BP debug ("what biome / how many decos under the cursor?"). |
|
||||
@@ -443,8 +444,10 @@ serialization before they are written. The commandlet keeps `WorldRadiusVoxels=0
|
||||
authored asset or diff layer, and enforces the default 25-minute / maximum 30-minute wall-clock
|
||||
budget, reporting partial artifacts and completed modes on truncation. `-gameconfig=1` opts into the
|
||||
authored settings' skirts/LOD octave drop and permits export-only parity of a single-strate asset;
|
||||
the historical interior-slot requirement remains in force for walk/probe runs. `VoxelGeometryHash.h`
|
||||
is the shared digest used by `Tools/VoxelForgeTest.ps1 -Scenario parity`.
|
||||
the optional `-exporttilex/y/z` arguments choose a specific level-0 tile for export/game parity,
|
||||
while omitting them preserves center-derived region selection. The historical interior-slot
|
||||
requirement remains in force for walk/probe runs. `VoxelGeometryHash.h` is the shared digest used
|
||||
by `Tools/VoxelForgeTest.ps1 -Scenario parity`.
|
||||
|
||||
### 3.7 Cave morphology (SDF rooms/tunnels) — `Public/VoxelCaveMorphology.h` + `.cpp`
|
||||
Header is rich with inline docs. Two namespaces + a per-chunk cache system.
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
#include "Policies/CondensedJsonPrintPolicy.h"
|
||||
#include "Serialization/JsonWriter.h"
|
||||
#include "ProfilingDebugging/CpuProfilerTrace.h" // Unreal Insights scopes (Perf 0)
|
||||
#include "ProfilingDebugging/CsvProfiler.h" // Delayed CSV capture for Step 1 upload runs
|
||||
|
||||
static bool VF_TileOverlapsVoxelBounds(const FVoxelTileKey& Tile,
|
||||
const FVector& BoundsMin, const FVector& BoundsMax);
|
||||
@@ -101,6 +102,20 @@ namespace
|
||||
GVoxelForgeStep1GenerationCap,
|
||||
TEXT("Step 1 measurement override: -1 uses the normal cap, 0 pauses generation, positive values cap worker tasks."));
|
||||
|
||||
int32 GVoxelForgeStep1ExitAfterFrames = 0;
|
||||
FAutoConsoleVariableRef CVarVoxelForgeStep1ExitAfterFrames(
|
||||
TEXT("voxel.Step1ExitAfterFrames"),
|
||||
GVoxelForgeStep1ExitAfterFrames,
|
||||
TEXT("Step 1 measurement: request clean exit after this many engine frames; 0 disables."));
|
||||
uint64 GVoxelForgeStep1ExitStartFrame = 0;
|
||||
bool bVoxelForgeStep1ExitRequested = false;
|
||||
|
||||
int32 GVoxelForgeStep1CsvCaptureOnBeginPlay = 0;
|
||||
FAutoConsoleVariableRef CVarVoxelForgeStep1CsvCaptureOnBeginPlay(
|
||||
TEXT("voxel.Step1CsvCaptureOnBeginPlay"),
|
||||
GVoxelForgeStep1CsvCaptureOnBeginPlay,
|
||||
TEXT("Step 1 measurement: emit a delayed CSV start event from BeginPlay; 0 disables."));
|
||||
|
||||
float GVoxelForgeStep1PauseGenerationAfterSeconds = 0.0f;
|
||||
FAutoConsoleVariableRef CVarVoxelForgeStep1PauseGenerationAfterSeconds(
|
||||
TEXT("voxel.Step1PauseGenerationAfterSeconds"),
|
||||
@@ -137,7 +152,7 @@ namespace
|
||||
FAutoConsoleVariableRef CVarVoxelForgeStep1CoreCookBenchmark(
|
||||
TEXT("voxel.Step1CoreCookBenchmark"),
|
||||
GVoxelForgeStep1CoreCookBenchmark,
|
||||
TEXT("Step 1 measurement: directly extract and CookComplexMesh for critical L0 support tiles on a dedicated one-thread pool; does not install this sidecar body."));
|
||||
TEXT("Step 1 measurement: directly extract and CookComplexMesh for the pawn's exact L0 support cohort plus critical support tiles on a dedicated one-thread pool; does not install this sidecar body."));
|
||||
|
||||
struct FStep1CoreCookPoolOwner
|
||||
{
|
||||
@@ -182,8 +197,10 @@ namespace
|
||||
public:
|
||||
FVoxelStep1CoreCookWork(
|
||||
TSharedPtr<RealtimeMesh::FRealtimeMeshStreamSet> InStreams,
|
||||
FVoxelTileKey InTile, int32 InTriangleCount, uint64 InQueueCycles)
|
||||
FVoxelTileKey InTile, int32 InTriangleCount, bool bInSupportCohort,
|
||||
bool bInPausedReplay, uint64 InQueueCycles)
|
||||
: Streams(MoveTemp(InStreams)), Tile(InTile), TriangleCount(InTriangleCount),
|
||||
bSupportCohort(bInSupportCohort), bPausedReplay(bInPausedReplay),
|
||||
QueueCycles(InQueueCycles)
|
||||
{
|
||||
}
|
||||
@@ -204,11 +221,13 @@ namespace
|
||||
}
|
||||
const uint64 CookEndCycles = FPlatformTime::Cycles64();
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeStep1CoreCook] tile=(%d,%d,%d) level=%d extracted=%d "
|
||||
TEXT("[VoxelForgeStep1CoreCook] tile=(%d,%d,%d) level=%d cohort=%d paused_replay=%d extracted=%d "
|
||||
"triangles=%d collision_vertices=%d queue_to_worker_ms=%.3f "
|
||||
"stream_extract_ms=%.3f cook_ms=%.3f total_ms=%.3f thread_id=%u"),
|
||||
Tile.Coord.X, Tile.Coord.Y, Tile.Coord.Z, Tile.Level,
|
||||
bExtracted ? 1 : 0, TriangleCount, CollisionMesh.GetVertices().Num(),
|
||||
bSupportCohort ? 1 : 0, bPausedReplay ? 1 : 0,
|
||||
bExtracted ? 1 : 0, TriangleCount,
|
||||
CollisionMesh.GetVertices().Num(),
|
||||
FPlatformTime::ToSeconds64(WorkerStartCycles - QueueCycles) * 1000.0,
|
||||
FPlatformTime::ToSeconds64(ExtractionEndCycles - ExtractionStartCycles) * 1000.0,
|
||||
FPlatformTime::ToSeconds64(CookEndCycles - CookStartCycles) * 1000.0,
|
||||
@@ -226,6 +245,8 @@ namespace
|
||||
TSharedPtr<RealtimeMesh::FRealtimeMeshStreamSet> Streams;
|
||||
FVoxelTileKey Tile;
|
||||
int32 TriangleCount = 0;
|
||||
bool bSupportCohort = false;
|
||||
bool bPausedReplay = false;
|
||||
uint64 QueueCycles = 0;
|
||||
};
|
||||
|
||||
@@ -361,10 +382,19 @@ namespace
|
||||
bool bEnabled = false;
|
||||
bool bStarted = false;
|
||||
bool bUnderDigIssued = false;
|
||||
bool bUnderfootFieldOverride = false;
|
||||
bool bUnderfootProbePending = false;
|
||||
bool bSawFalling = false;
|
||||
bool bResultLogged = false;
|
||||
int32 NearDigCount = 8;
|
||||
int32 NearDigsIssued = 0;
|
||||
int32 UnderfootDigCount = 1;
|
||||
int32 UnderfootDigsIssued = 0;
|
||||
int32 UnderfootFirstFrameChecks = 0;
|
||||
int32 UnderfootSupportObservedFrames = 0;
|
||||
int32 UnderfootFalseSupportFrames = 0;
|
||||
int32 UnderfootFalseAirFrames = 0;
|
||||
int32 UnderfootInventedVelocityEvents = 0;
|
||||
int32 Frame = 0;
|
||||
int32 NearFrames = 0;
|
||||
int32 NearGateFrames = 0;
|
||||
@@ -379,6 +409,12 @@ namespace
|
||||
double UnderDigSeconds = 0.0;
|
||||
double LastFrameSeconds = 0.0;
|
||||
double DigIntervalSeconds = 0.35;
|
||||
double UnderfootDigIntervalSeconds = 0.1;
|
||||
double NextUnderfootDigSeconds = 0.0;
|
||||
uint64 UnderfootEditFrame = 0;
|
||||
uint64 UnderfootLastObservedFrame = 0;
|
||||
float UnderfootVelocityBefore = 0.0f;
|
||||
float UnderfootFieldAfter = 0.0f;
|
||||
FVector StartPosition = FVector::ZeroVector;
|
||||
FVector UnderDigStartPosition = FVector::ZeroVector;
|
||||
};
|
||||
@@ -1778,6 +1814,8 @@ void AVoxelWorld::BeginPlay()
|
||||
int32 CommandLineSampleStacks = GVoxelForgeSampleStacks;
|
||||
int32 CommandLineCollisionGate = GVoxelForgeCollisionGate;
|
||||
int32 CommandLineStep1GenerationCap = GVoxelForgeStep1GenerationCap;
|
||||
int32 CommandLineStep1ExitAfterFrames = GVoxelForgeStep1ExitAfterFrames;
|
||||
int32 CommandLineStep1CsvCaptureOnBeginPlay = GVoxelForgeStep1CsvCaptureOnBeginPlay;
|
||||
float CommandLineStep1PauseGenerationAfterSeconds =
|
||||
GVoxelForgeStep1PauseGenerationAfterSeconds;
|
||||
int32 CommandLineStep1MaxMeshApplies = GVoxelForgeStep1MaxMeshApplies;
|
||||
@@ -1816,6 +1854,19 @@ void AVoxelWorld::BeginPlay()
|
||||
{
|
||||
GVoxelForgeStep1GenerationCap = CommandLineStep1GenerationCap;
|
||||
}
|
||||
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.Step1ExitAfterFrames="),
|
||||
CommandLineStep1ExitAfterFrames))
|
||||
{
|
||||
GVoxelForgeStep1ExitAfterFrames = FMath::Max(0, CommandLineStep1ExitAfterFrames);
|
||||
}
|
||||
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.Step1CsvCaptureOnBeginPlay="),
|
||||
CommandLineStep1CsvCaptureOnBeginPlay))
|
||||
{
|
||||
GVoxelForgeStep1CsvCaptureOnBeginPlay =
|
||||
CommandLineStep1CsvCaptureOnBeginPlay != 0 ? 1 : 0;
|
||||
}
|
||||
GVoxelForgeStep1ExitStartFrame = GFrameCounter;
|
||||
bVoxelForgeStep1ExitRequested = false;
|
||||
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.Step1PauseGenerationAfterSeconds="),
|
||||
CommandLineStep1PauseGenerationAfterSeconds))
|
||||
{
|
||||
@@ -1848,9 +1899,11 @@ void AVoxelWorld::BeginPlay()
|
||||
GVoxelForgeStep1CoreCookBenchmark = CommandLineStep1CoreCookBenchmark;
|
||||
}
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeStep1] gen_cap=%d pause_after_s=%.3f max_applies=%d prequeue_l0=%d "
|
||||
TEXT("[VoxelForgeStep1] gen_cap=%d exit_after_frames=%d pause_after_s=%.3f "
|
||||
"max_applies=%d prequeue_l0=%d "
|
||||
"disable_collision=%d collision_probe=%d core_cook_benchmark=%d"),
|
||||
GVoxelForgeStep1GenerationCap, GVoxelForgeStep1PauseGenerationAfterSeconds,
|
||||
GVoxelForgeStep1GenerationCap, GVoxelForgeStep1ExitAfterFrames,
|
||||
GVoxelForgeStep1PauseGenerationAfterSeconds,
|
||||
GVoxelForgeStep1MaxMeshApplies,
|
||||
GVoxelForgeStep1PrequeueVisibleLOD0, GVoxelForgeStep1UploadDisableCollision,
|
||||
GVoxelForgeStep1CollisionProbe, GVoxelForgeStep1CoreCookBenchmark);
|
||||
@@ -1923,6 +1976,30 @@ void AVoxelWorld::BeginPlay()
|
||||
VoxelDensityProfile::DefaultSampleInterval);
|
||||
}
|
||||
|
||||
if (GVoxelForgeProfileTileGeneration != 0
|
||||
|| GVoxelForgeStep1CsvCaptureOnBeginPlay != 0)
|
||||
{
|
||||
const VoxelDensityProfile::EMode DensityMode = VoxelDensityProfile::GetMode();
|
||||
const TCHAR* DensityModeName = DensityMode == VoxelDensityProfile::EMode::Disabled
|
||||
? TEXT("disabled")
|
||||
: DensityMode == VoxelDensityProfile::EMode::Sampled
|
||||
? TEXT("sampled")
|
||||
: DensityMode == VoxelDensityProfile::EMode::Attribution
|
||||
? TEXT("attribution") : TEXT("full");
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeProfileState] density_mode=%s profile_density=%d "
|
||||
"profile_density_full=%d perf_attribution=%d profile_tile_generation=%d"),
|
||||
DensityModeName, GVoxelForgeProfileDensity, GVoxelForgeProfileDensityFull,
|
||||
GVoxelForgePerfAttribution, GVoxelForgeProfileTileGeneration);
|
||||
}
|
||||
if (GVoxelForgeStep1CsvCaptureOnBeginPlay != 0)
|
||||
{
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeStep1Csv] stage=beginplay_event frame=%llu"),
|
||||
static_cast<unsigned long long>(GFrameCounter));
|
||||
CSV_EVENT_GLOBAL(TEXT("VFStep1GameBeginPlay"));
|
||||
}
|
||||
|
||||
if (!Settings)
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("[VoxelWorld] No Settings assigned — world won't generate."));
|
||||
@@ -1934,19 +2011,28 @@ void AVoxelWorld::BeginPlay()
|
||||
FParse::Value(FCommandLine::Get(), TEXT("voxel.TestRenderDistanceChunks="),
|
||||
TestRenderDistanceChunks)
|
||||
&& TestRenderDistanceChunks > 0;
|
||||
int32 TestClipRadius = 0;
|
||||
const bool bTestClipRadius =
|
||||
FParse::Value(FCommandLine::Get(), TEXT("voxel.TestClipRadius="), TestClipRadius)
|
||||
&& TestClipRadius > 0;
|
||||
int32 TestMaxClipLevel = -1;
|
||||
const bool bTestMaxClipLevel =
|
||||
FParse::Value(FCommandLine::Get(), TEXT("voxel.TestMaxClipLevel="), TestMaxClipLevel);
|
||||
if (bTestRenderDistance || bTestMaxClipLevel)
|
||||
if (bTestRenderDistance || bTestClipRadius || bTestMaxClipLevel)
|
||||
{
|
||||
if (UVoxelSettings* TestSettings = DuplicateObject<UVoxelSettings>(Settings, this))
|
||||
{
|
||||
const int32 AuthoredRenderDistance = Settings->RenderDistanceChunks;
|
||||
const int32 AuthoredClipRadius = Settings->ClipRadius;
|
||||
const int32 AuthoredMaxClipLevel = Settings->MaxClipLevel;
|
||||
if (bTestRenderDistance)
|
||||
{
|
||||
TestSettings->RenderDistanceChunks = TestRenderDistanceChunks;
|
||||
}
|
||||
if (bTestClipRadius)
|
||||
{
|
||||
TestSettings->ClipRadius = FMath::Clamp(TestClipRadius, 1, 16);
|
||||
}
|
||||
if (bTestMaxClipLevel)
|
||||
{
|
||||
TestSettings->MaxClipLevel = FMath::Clamp(TestMaxClipLevel, 0, 8);
|
||||
@@ -1954,8 +2040,10 @@ void AVoxelWorld::BeginPlay()
|
||||
Settings = TestSettings;
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeHorizonMeasure] transient_settings=1 "
|
||||
"render_distance_chunks=%d authored=%d max_clip_level=%d authored=%d"),
|
||||
"render_distance_chunks=%d authored=%d clip_radius=%d authored=%d "
|
||||
"max_clip_level=%d authored=%d"),
|
||||
Settings->RenderDistanceChunks, AuthoredRenderDistance,
|
||||
Settings->ClipRadius, AuthoredClipRadius,
|
||||
Settings->MaxClipLevel, AuthoredMaxClipLevel);
|
||||
}
|
||||
}
|
||||
@@ -2390,13 +2478,26 @@ void AVoxelWorld::ConfigureHeadlessStreamingTest()
|
||||
{
|
||||
FDigMotionTestState& Test = GVoxelForgeDigMotionTests.Add(this);
|
||||
Test.bEnabled = true;
|
||||
int32 FieldOverrideValue = 0;
|
||||
FParse::Value(CommandLine, TEXT("voxel.TestDigMotionNearDigCount="), Test.NearDigCount);
|
||||
FParse::Value(CommandLine, TEXT("voxel.TestDigMotionIntervalSeconds="), Test.DigIntervalSeconds);
|
||||
Test.NearDigCount = FMath::Clamp(Test.NearDigCount, 1, 32);
|
||||
Test.DigIntervalSeconds = FMath::Clamp(Test.DigIntervalSeconds, 0.1, 3.0);
|
||||
FParse::Value(CommandLine, TEXT("voxel.TestDigMotionUnderfootDigCount="),
|
||||
Test.UnderfootDigCount);
|
||||
FParse::Value(CommandLine, TEXT("voxel.TestDigMotionUnderfootIntervalSeconds="),
|
||||
Test.UnderfootDigIntervalSeconds);
|
||||
FParse::Value(CommandLine, TEXT("voxel.TestDigMotionFieldOverride="),
|
||||
FieldOverrideValue);
|
||||
Test.NearDigCount = FMath::Clamp(Test.NearDigCount, 0, 32);
|
||||
Test.UnderfootDigCount = FMath::Clamp(Test.UnderfootDigCount, 1, 100);
|
||||
Test.DigIntervalSeconds = FMath::Clamp(Test.DigIntervalSeconds, 0.0, 3.0);
|
||||
Test.UnderfootDigIntervalSeconds = FMath::Clamp(
|
||||
Test.UnderfootDigIntervalSeconds, 0.0, 1.0);
|
||||
Test.bUnderfootFieldOverride = FieldOverrideValue != 0;
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeDigMotion] configured=1 near_dig_count=%d interval_s=%.3f"),
|
||||
Test.NearDigCount, Test.DigIntervalSeconds);
|
||||
TEXT("[VoxelForgeDigMotion] configured=1 near_dig_count=%d interval_s=%.3f "
|
||||
"underfoot_dig_count=%d underfoot_interval_s=%.3f field_override=%d"),
|
||||
Test.NearDigCount, Test.DigIntervalSeconds, Test.UnderfootDigCount,
|
||||
Test.UnderfootDigIntervalSeconds, Test.bUnderfootFieldOverride ? 1 : 0);
|
||||
}
|
||||
FString RequestedTileHashDumpPath;
|
||||
if (FParse::Value(CommandLine, TEXT("voxel.TileHashDump="), RequestedTileHashDumpPath))
|
||||
@@ -3331,6 +3432,54 @@ void AVoxelWorld::AdvanceHeadlessDigMotionTest(FVector& InOutPlayerPosition, APa
|
||||
}
|
||||
|
||||
const double Now = FPlatformTime::Seconds();
|
||||
if (Test->bUnderfootProbePending && GFrameCounter > Test->UnderfootEditFrame)
|
||||
{
|
||||
const float HalfHeightVoxels = Character->GetCapsuleComponent()
|
||||
->GetScaledCapsuleHalfHeight() / VOXEL_SIZE;
|
||||
FVector ProbePosition = WorldToLocalVoxel(InOutPlayerPosition);
|
||||
ProbePosition.Z -= HalfHeightVoxels + 0.5f;
|
||||
const float FieldDensity = Generator
|
||||
? Generator->GetDensityAt(ProbePosition.X, ProbePosition.Y, ProbePosition.Z)
|
||||
: 0.0f;
|
||||
const bool bFieldSolidAtFeet = VoxelMath::IsFinite(FieldDensity)
|
||||
&& FieldDensity < 0.0f;
|
||||
const bool bCmcHasFloor = Movement->IsMovingOnGround()
|
||||
&& Movement->CurrentFloor.IsWalkableFloor();
|
||||
if (!bFieldSolidAtFeet && bCmcHasFloor)
|
||||
{
|
||||
++Test->UnderfootFalseSupportFrames;
|
||||
}
|
||||
if (bFieldSolidAtFeet && Movement->MovementMode == MOVE_Falling)
|
||||
{
|
||||
++Test->UnderfootFalseAirFrames;
|
||||
}
|
||||
|
||||
const float FrameDelta = GetWorld() ? GetWorld()->GetDeltaSeconds() : 0.0f;
|
||||
const float ExpectedGravityDelta = Movement->GetGravityZ() * FrameDelta;
|
||||
const float ActualVelocityDelta = Movement->Velocity.Z - Test->UnderfootVelocityBefore;
|
||||
const float UnexplainedDownwardDelta = FMath::Max(
|
||||
0.0f, ExpectedGravityDelta - ActualVelocityDelta);
|
||||
if (UnexplainedDownwardDelta > 25.0f)
|
||||
{
|
||||
++Test->UnderfootInventedVelocityEvents;
|
||||
}
|
||||
++Test->UnderfootFirstFrameChecks;
|
||||
++Test->UnderfootSupportObservedFrames;
|
||||
Test->UnderfootLastObservedFrame = GFrameCounter;
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeDigMotionField] dig=%d first_sim_frame=%llu field_density=%.5f "
|
||||
"field_solid=%d movement_mode=%d floor_walkable=%d support=%d "
|
||||
"velocity_z_cm_s=%.3f expected_gravity_delta_cm_s=%.3f "
|
||||
"actual_velocity_delta_cm_s=%.3f unexplained_downward_cm_s=%.3f"),
|
||||
Test->UnderfootDigsIssued,
|
||||
static_cast<unsigned long long>(GFrameCounter), FieldDensity,
|
||||
bFieldSolidAtFeet ? 1 : 0, static_cast<int32>(Movement->MovementMode),
|
||||
Movement->CurrentFloor.IsWalkableFloor() ? 1 : 0, bCmcHasFloor ? 1 : 0,
|
||||
Movement->Velocity.Z, ExpectedGravityDelta, ActualVelocityDelta,
|
||||
UnexplainedDownwardDelta);
|
||||
Test->bUnderfootProbePending = false;
|
||||
}
|
||||
|
||||
if (Now - Test->BeginSeconds > 240.0)
|
||||
{
|
||||
Test->bResultLogged = true;
|
||||
@@ -3398,28 +3547,68 @@ void AVoxelWorld::AdvanceHeadlessDigMotionTest(FVector& InOutPlayerPosition, APa
|
||||
return;
|
||||
}
|
||||
|
||||
if (Test->NearDigsIssued >= Test->NearDigCount && !Test->bUnderDigIssued
|
||||
&& Now >= Test->NextDigSeconds + 0.5)
|
||||
const bool bUnderfootDelayElapsed = Test->bUnderDigIssued
|
||||
? Now >= Test->NextUnderfootDigSeconds
|
||||
: Now >= Test->NextDigSeconds + 0.5;
|
||||
if (Test->NearDigsIssued >= Test->NearDigCount
|
||||
&& Test->UnderfootDigsIssued < Test->UnderfootDigCount
|
||||
&& bUnderfootDelayElapsed)
|
||||
{
|
||||
const float HalfHeightVoxels = Character->GetCapsuleComponent()
|
||||
->GetScaledCapsuleHalfHeight() / VOXEL_SIZE;
|
||||
FVector DigCenterVoxel = WorldToLocalVoxel(InOutPlayerPosition);
|
||||
const FVector LocalPawnPosition = WorldToLocalVoxel(InOutPlayerPosition);
|
||||
FVector DigCenterVoxel = LocalPawnPosition;
|
||||
DigCenterVoxel.Z -= HalfHeightVoxels + 1.0f;
|
||||
Test->UnderDigStartPosition = InOutPlayerPosition;
|
||||
Test->UnderDigSeconds = Now;
|
||||
Test->bUnderDigIssued = true;
|
||||
Test->NextDigSeconds = Now;
|
||||
if (!Test->bUnderDigIssued)
|
||||
{
|
||||
Test->UnderDigStartPosition = InOutPlayerPosition;
|
||||
Test->UnderDigSeconds = Now;
|
||||
Test->bUnderDigIssued = true;
|
||||
}
|
||||
const FVector DigCenterWorld = LocalVoxelToWorld(DigCenterVoxel);
|
||||
FVector FeetProbe = LocalPawnPosition;
|
||||
FeetProbe.Z -= HalfHeightVoxels + 0.5f;
|
||||
const float FieldDensityBefore = Generator
|
||||
? Generator->GetDensityAt(FeetProbe.X, FeetProbe.Y, FeetProbe.Z)
|
||||
: 0.0f;
|
||||
Test->UnderfootVelocityBefore = Movement->Velocity.Z;
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeDigMotion] under_dig=1 center=(%.2f,%.2f,%.2f) pawn=(%.2f,%.2f,%.2f) "
|
||||
"radius_vox=5"),
|
||||
TEXT("[VoxelForgeDigMotion] under_dig=%d center=(%.2f,%.2f,%.2f) "
|
||||
"pawn=(%.2f,%.2f,%.2f) radius_vox=5 field_before=%.5f frame=%llu"),
|
||||
Test->UnderfootDigsIssued + 1,
|
||||
DigCenterWorld.X, DigCenterWorld.Y, DigCenterWorld.Z,
|
||||
InOutPlayerPosition.X, InOutPlayerPosition.Y, InOutPlayerPosition.Z);
|
||||
InOutPlayerPosition.X, InOutPlayerPosition.Y, InOutPlayerPosition.Z,
|
||||
FieldDensityBefore,
|
||||
static_cast<unsigned long long>(GFrameCounter));
|
||||
CarveAtPosition(DigCenterWorld, 5.0f, 100.0f);
|
||||
Test->UnderfootFieldAfter = Generator
|
||||
? Generator->GetDensityAt(FeetProbe.X, FeetProbe.Y, FeetProbe.Z)
|
||||
: 0.0f;
|
||||
++Test->UnderfootDigsIssued;
|
||||
Test->UnderfootEditFrame = GFrameCounter;
|
||||
Test->bUnderfootProbePending = true;
|
||||
Test->NextUnderfootDigSeconds = Now + Test->UnderfootDigIntervalSeconds;
|
||||
Test->LastFrameSeconds = Now;
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeDigMotionField] dig=%d stage=post_edit frame=%llu "
|
||||
"field_before=%.5f field_after=%.5f override=%d"),
|
||||
Test->UnderfootDigsIssued,
|
||||
static_cast<unsigned long long>(GFrameCounter), FieldDensityBefore,
|
||||
Test->UnderfootFieldAfter, Test->bUnderfootFieldOverride ? 1 : 0);
|
||||
if (Test->bUnderfootFieldOverride
|
||||
&& VoxelMath::IsFinite(Test->UnderfootFieldAfter)
|
||||
&& Test->UnderfootFieldAfter >= 0.0f)
|
||||
{
|
||||
// Measurement-only prototype: detach immediately from the removed field floor.
|
||||
// Normal play is unchanged because TestDigMotion is an explicit headless switch.
|
||||
Movement->CurrentFloor.Clear();
|
||||
Movement->SetMovementMode(MOVE_Falling);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (Test->bUnderDigIssued && Now - Test->UnderDigSeconds >= 2.5)
|
||||
if (Test->UnderfootDigsIssued >= Test->UnderfootDigCount
|
||||
&& !Test->bUnderfootProbePending && Now - Test->UnderDigSeconds >= 2.5)
|
||||
{
|
||||
Test->bResultLogged = true;
|
||||
const double FallCm = Test->UnderDigStartPosition.Z - InOutPlayerPosition.Z;
|
||||
@@ -3429,16 +3618,29 @@ void AVoxelWorld::AdvanceHeadlessDigMotionTest(FVector& InOutPlayerPosition, APa
|
||||
&& Test->NearVerticalMotionFrames == 0
|
||||
&& Test->bSawFalling
|
||||
&& FallCm >= 10.0
|
||||
&& Test->UnderMaxDownwardVelocity < 6000.0f;
|
||||
&& Test->UnderMaxDownwardVelocity < 6000.0f
|
||||
&& Test->UnderfootDigsIssued == Test->UnderfootDigCount
|
||||
&& Test->UnderfootFirstFrameChecks == Test->UnderfootDigCount
|
||||
&& Test->UnderfootSupportObservedFrames >= Test->UnderfootFirstFrameChecks
|
||||
&& Test->UnderfootFalseSupportFrames == 0
|
||||
&& Test->UnderfootFalseAirFrames == 0
|
||||
&& Test->UnderfootInventedVelocityEvents == 0;
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeDigMotion] result=%s near_digs=%d near_frames=%d near_gate_frames=%d "
|
||||
"near_falling_frames=%d near_vertical_motion_frames=%d "
|
||||
"under_saw_falling=%d under_gate_frames=%d under_fall_cm=%.2f "
|
||||
"under_max_downward_velocity_cm_s=%.2f"),
|
||||
"under_max_downward_velocity_cm_s=%.2f under_digs=%d "
|
||||
"first_frame_checks=%d support_observed_frames=%d "
|
||||
"false_support_frames=%d false_air_frames=%d "
|
||||
"invented_downward_velocity_events=%d field_override=%d"),
|
||||
bPassed ? TEXT("PASS") : TEXT("FAIL"), Test->NearDigsIssued, Test->NearFrames,
|
||||
Test->NearGateFrames, Test->NearFallingFrames, Test->NearVerticalMotionFrames,
|
||||
Test->bSawFalling ? 1 : 0, Test->UnderGateFrames, FallCm,
|
||||
Test->UnderMaxDownwardVelocity);
|
||||
Test->UnderMaxDownwardVelocity, Test->UnderfootDigsIssued,
|
||||
Test->UnderfootFirstFrameChecks, Test->UnderfootSupportObservedFrames,
|
||||
Test->UnderfootFalseSupportFrames,
|
||||
Test->UnderfootFalseAirFrames, Test->UnderfootInventedVelocityEvents,
|
||||
Test->bUnderfootFieldOverride ? 1 : 0);
|
||||
FPlatformMisc::RequestExitWithStatus(
|
||||
false, bPassed ? 0 : 1, TEXT("VoxelForge dig-motion repro complete"));
|
||||
}
|
||||
@@ -3461,6 +3663,39 @@ void AVoxelWorld::RecordHeadlessDigMotionFrame(
|
||||
const float VerticalVelocity = Movement ? Movement->Velocity.Z : Pawn->GetVelocity().Z;
|
||||
const float GravityZ = Movement ? Movement->GetGravityZ() : 0.0f;
|
||||
const int32 MovementMode = Movement ? static_cast<int32>(Movement->MovementMode) : -1;
|
||||
if (Movement && Test->UnderfootDigsIssued > 0
|
||||
&& GFrameCounter > Test->UnderfootEditFrame
|
||||
&& Test->UnderfootLastObservedFrame != GFrameCounter)
|
||||
{
|
||||
const float HalfHeightVoxels = Character && Character->GetCapsuleComponent()
|
||||
? Character->GetCapsuleComponent()->GetScaledCapsuleHalfHeight() / VOXEL_SIZE
|
||||
: 0.0f;
|
||||
FVector ProbePosition = WorldToLocalVoxel(PlayerPosition);
|
||||
ProbePosition.Z -= HalfHeightVoxels + 0.5f;
|
||||
const float FieldDensity = Generator
|
||||
? Generator->GetDensityAt(ProbePosition.X, ProbePosition.Y, ProbePosition.Z)
|
||||
: 0.0f;
|
||||
const bool bFieldSolidAtFeet = VoxelMath::IsFinite(FieldDensity)
|
||||
&& FieldDensity < 0.0f;
|
||||
const bool bCmcHasFloor = Movement->IsMovingOnGround()
|
||||
&& Movement->CurrentFloor.IsWalkableFloor();
|
||||
if (!bFieldSolidAtFeet && bCmcHasFloor)
|
||||
{
|
||||
++Test->UnderfootFalseSupportFrames;
|
||||
}
|
||||
if (bFieldSolidAtFeet && Movement->MovementMode == MOVE_Falling)
|
||||
{
|
||||
++Test->UnderfootFalseAirFrames;
|
||||
}
|
||||
++Test->UnderfootSupportObservedFrames;
|
||||
Test->UnderfootLastObservedFrame = GFrameCounter;
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeDigMotionField] stage=frame frame=%llu field_density=%.5f "
|
||||
"field_solid=%d movement_mode=%d floor_walkable=%d support=%d"),
|
||||
static_cast<unsigned long long>(GFrameCounter), FieldDensity,
|
||||
bFieldSolidAtFeet ? 1 : 0, MovementMode,
|
||||
Movement->CurrentFloor.IsWalkableFloor() ? 1 : 0, bCmcHasFloor ? 1 : 0);
|
||||
}
|
||||
FVoxelTileKey SupportTile;
|
||||
const bool bSupportReady = IsPlayerSupportCollisionReady(Pawn, PlayerPosition, &SupportTile);
|
||||
const bool bNearPhase = !Test->bUnderDigIssued;
|
||||
@@ -5158,6 +5393,37 @@ void AVoxelWorld::MaybeFinishHeadlessCollisionGateStressTest()
|
||||
|
||||
void AVoxelWorld::MaybeFinishHeadlessStreamingTest()
|
||||
{
|
||||
if (GVoxelForgeStep1ExitAfterFrames > 0 && !bVoxelForgeStep1ExitRequested
|
||||
&& GFrameCounter - GVoxelForgeStep1ExitStartFrame
|
||||
>= static_cast<uint64>(GVoxelForgeStep1ExitAfterFrames))
|
||||
{
|
||||
bVoxelForgeStep1ExitRequested = true;
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeStep1Exit] target_frames=%d elapsed_frames=%llu; requesting clean exit"),
|
||||
GVoxelForgeStep1ExitAfterFrames,
|
||||
static_cast<unsigned long long>(GFrameCounter - GVoxelForgeStep1ExitStartFrame));
|
||||
FPlatformMisc::RequestExit(false, TEXT("VoxelForge step 1 frame-count capture complete"));
|
||||
return;
|
||||
}
|
||||
|
||||
const double ExitDiagnosticNowSeconds = FPlatformTime::Seconds();
|
||||
const double ExitDiagnosticElapsedSeconds = HeadlessStreamingTestBeginSeconds > 0.0
|
||||
? ExitDiagnosticNowSeconds - HeadlessStreamingTestBeginSeconds : -1.0;
|
||||
static double LastExitDiagnosticElapsedSeconds = -30.0;
|
||||
if (ExitDiagnosticElapsedSeconds >= 0.0
|
||||
&& ExitDiagnosticElapsedSeconds - LastExitDiagnosticElapsedSeconds >= 30.0)
|
||||
{
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeStreamingExitDiag] elapsed_s=%.3f threshold_s=%.3f "
|
||||
"exit_requested=%d surface_fall=%d crossing=%d gate_stress=%d"),
|
||||
ExitDiagnosticElapsedSeconds, HeadlessStreamingTestExitSeconds,
|
||||
bHeadlessStreamingTestExitRequested ? 1 : 0,
|
||||
bHeadlessSurfaceFallTest ? 1 : 0,
|
||||
bHeadlessStrateCrossingTest ? 1 : 0,
|
||||
bHeadlessCollisionGateStressTest ? 1 : 0);
|
||||
LastExitDiagnosticElapsedSeconds = ExitDiagnosticElapsedSeconds;
|
||||
}
|
||||
|
||||
if (bHeadlessStrateCrossingTest)
|
||||
{
|
||||
MaybeFinishHeadlessStrateCrossingTest();
|
||||
@@ -5933,6 +6199,26 @@ void AVoxelWorld::Tick(float DeltaTime)
|
||||
TEXT("[VoxelForgeStep1] generation_paused=1 elapsed_s=%.3f pending=%d held_results=%d"),
|
||||
NowSeconds - GVoxelForgeStep1StartSeconds, PendingTiles.Num(),
|
||||
Step1HeldApplyResults.Num());
|
||||
if (GVoxelForgeStep1CoreCookBenchmark != 0
|
||||
&& Step1PauseReplayStreams.IsValid() && !bStep1PauseReplayQueued)
|
||||
{
|
||||
if (FQueuedThreadPool* CookPool = GVoxelForgeStep1CoreCookPool.GetOrCreate())
|
||||
{
|
||||
CookPool->AddQueuedWork(
|
||||
new FVoxelStep1CoreCookWork(
|
||||
Step1PauseReplayStreams, Step1PauseReplayTile,
|
||||
Step1PauseReplayTriangles, true, true,
|
||||
FPlatformTime::Cycles64()),
|
||||
EQueuedWorkPriority::High);
|
||||
bStep1PauseReplayQueued = true;
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeStep1CoreCook] stage=paused_replay_queued "
|
||||
"tile=(%d,%d,%d) triangles=%d"),
|
||||
Step1PauseReplayTile.Coord.X, Step1PauseReplayTile.Coord.Y,
|
||||
Step1PauseReplayTile.Coord.Z, Step1PauseReplayTriangles);
|
||||
Step1PauseReplayStreams.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7195,9 +7481,10 @@ void AVoxelWorld::HandleRealtimeMeshCollisionBodyUpdated(URealtimeMesh* Mesh, UB
|
||||
APawn* Pawn = nullptr;
|
||||
FVector PlayerPosition = FVector::ZeroVector;
|
||||
FVoxelTileKey CurrentSupportTile;
|
||||
if (TryGetPlayerPosition(PlayerPosition, &Pawn)
|
||||
&& GetPlayerSupportTile(Pawn, PlayerPosition, CurrentSupportTile)
|
||||
&& CurrentSupportTile == Pair.Key)
|
||||
const bool bHaveCurrentSupport = TryGetPlayerPosition(PlayerPosition, &Pawn)
|
||||
&& GetPlayerSupportTile(Pawn, PlayerPosition, CurrentSupportTile);
|
||||
if ((bHaveCurrentSupport && CurrentSupportTile == Pair.Key)
|
||||
|| Pending.bStep1SupportCohort)
|
||||
{
|
||||
const URealtimeMeshComponent* Component = TileComponents.FindRef(Pair.Key);
|
||||
Pending.bPhysicsStateReadyAtBodyUpdate = IsValid(Component)
|
||||
@@ -7324,10 +7611,16 @@ void AVoxelWorld::PollStep1SupportSweepProbes()
|
||||
}
|
||||
if (bHit && Hit.GetComponent() == Component)
|
||||
{
|
||||
const UPrimitiveComponent* ExactHitComponent = Hit.GetComponent();
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCollisionProbe] stage=first_support_capsule_sweep tile=(%d,%d,%d) "
|
||||
"request_to_sweep_ms=%.3f body_to_sweep_ms=%.3f hit_z=%.3f"),
|
||||
"pawn_channel_target=1 target_component=%s hit_component=%s "
|
||||
"current_support_match=%d request_to_sweep_ms=%.3f "
|
||||
"body_to_sweep_ms=%.3f hit_z=%.3f"),
|
||||
It.Key().Coord.X, It.Key().Coord.Y, It.Key().Coord.Z,
|
||||
*Component->GetName(),
|
||||
ExactHitComponent ? *ExactHitComponent->GetName() : TEXT("none"),
|
||||
bHaveCurrentSupportTile && CurrentSupportTile == It.Key() ? 1 : 0,
|
||||
FPlatformTime::ToSeconds64(NowCycles - Probe.CollisionRequestCycles) * 1000.0,
|
||||
FPlatformTime::ToSeconds64(NowCycles - Probe.BodyUpdatedCycles) * 1000.0,
|
||||
Hit.ImpactPoint.Z);
|
||||
@@ -7337,7 +7630,7 @@ void AVoxelWorld::PollStep1SupportSweepProbes()
|
||||
}
|
||||
|
||||
if (Probe.BodyUpdatedCycles != 0 && NowCycles >= Probe.BodyUpdatedCycles
|
||||
&& FPlatformTime::ToSeconds64(NowCycles - Probe.BodyUpdatedCycles) > 10.0)
|
||||
&& FPlatformTime::ToSeconds64(NowCycles - Probe.BodyUpdatedCycles) > 30.0)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning,
|
||||
TEXT("[VoxelForgeCollisionProbe] stage=sweep_timeout tile=(%d,%d,%d) "
|
||||
@@ -7656,6 +7949,7 @@ void AVoxelWorld::HandleTileCollisionCookComplete(
|
||||
const uint64 MeasuredGenerationEndCycles = Pending->GenerationEndCycles;
|
||||
const uint64 MeasuredApplyStartCycles = Pending->ApplyStartCycles;
|
||||
const EVoxelTileWorkPriority WorkPriority = Pending->WorkPriority;
|
||||
const bool bMeasuredSupportCohort = Pending->bStep1SupportCohort;
|
||||
const int32 NumVertices = Pending->NumVertices;
|
||||
const int32 NumTriangles = Pending->NumTriangles;
|
||||
const int32 NumSkirtTriangles = Pending->NumSkirtTriangles;
|
||||
@@ -7752,6 +8046,8 @@ void AVoxelWorld::HandleTileCollisionCookComplete(
|
||||
? FPlatformTime::ToSeconds64(EndCycles - StartCycles)
|
||||
: -1.0;
|
||||
};
|
||||
const double MeshGeneratedToReadySeconds = SecondsBetween(
|
||||
ReadyCycles, MeasuredGenerationEndCycles);
|
||||
const double RequestToWorkerStartSeconds = SecondsBetween(
|
||||
MeasuredGenerationStartCycles, MeasuredRequestStartCycles);
|
||||
const double WorkerGenerationSeconds = SecondsBetween(
|
||||
@@ -7787,7 +8083,8 @@ void AVoxelWorld::HandleTileCollisionCookComplete(
|
||||
{
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCollisionReady] tile=(%d,%d,%d) level=%d result=%s body_setup=%d "
|
||||
"ready=%d body_event=%d priority=%d critical=%d vertices=%d triangles=%d "
|
||||
"ready=%d body_event=%d priority=%d critical=%d support_cohort=%d "
|
||||
"vertices=%d triangles=%d "
|
||||
"skirt_triangles=%d pending_cooks=%d async=%d complex_as_simple=%d fast_cook=%d "
|
||||
"merge=%d request_to_worker_s=%.6f worker_gen_s=%.6f worker_to_apply_s=%.6f "
|
||||
"apply_to_mesh_submit_s=%.6f mesh_to_collision_submit_s=%.6f "
|
||||
@@ -7795,6 +8092,7 @@ void AVoxelWorld::HandleTileCollisionCookComplete(
|
||||
"physics_ready_at_body=%d body_update_to_physics_ready_s=%.6f "
|
||||
"body_update_to_future_callback_s=%.6f future_callback_to_gt_s=%.6f "
|
||||
"suppressed_to_ready_s=%.6f mesh_submit_to_ready=%.6f "
|
||||
"mesh_generated_to_ready_s=%.6f "
|
||||
"collision_request_to_ready=%.6f body_event_to_future=%.6f"),
|
||||
Tile.Coord.X, Tile.Coord.Y, Tile.Coord.Z, Tile.Level, ResultName,
|
||||
CompletedMesh->GetBodySetup() != nullptr ? 1 : 0,
|
||||
@@ -7802,6 +8100,7 @@ void AVoxelWorld::HandleTileCollisionCookComplete(
|
||||
bBodyUpdatedEventSeen ? 1 : 0,
|
||||
static_cast<int32>(WorkPriority),
|
||||
WorkPriority == EVoxelTileWorkPriority::CollisionCritical ? 1 : 0,
|
||||
bMeasuredSupportCohort ? 1 : 0,
|
||||
NumVertices, NumTriangles, NumSkirtTriangles, PendingCooksAtSubmit,
|
||||
bUseAsyncCook ? 1 : 0, bUseComplexAsSimpleCollision ? 1 : 0,
|
||||
bShouldFastCookMeshes ? 1 : 0, bMergeAllMeshes ? 1 : 0,
|
||||
@@ -7810,7 +8109,7 @@ void AVoxelWorld::HandleTileCollisionCookComplete(
|
||||
RMCSubmitToBodyUpdateSeconds, FramesToBodyUpdate,
|
||||
bPhysicsStateReadyAtBodyUpdate ? 1 : 0, BodyEventToPhysicsReadySeconds,
|
||||
BodyUpdateToFutureCallbackSeconds, FutureCallbackToGameThreadSeconds,
|
||||
SuppressedToReadySeconds, MeshToCookSeconds,
|
||||
SuppressedToReadySeconds, MeshToCookSeconds, MeshGeneratedToReadySeconds,
|
||||
CollisionRequestToCookSeconds, BodyEventToFutureSeconds);
|
||||
}
|
||||
if (VoxelForgeStartupTrace::IsActive())
|
||||
@@ -7964,6 +8263,7 @@ void AVoxelWorld::ProcessPendingChunks()
|
||||
GVoxelForgeStep1PrequeueVisibleLOD0, Step1HeldVisibleLOD0Count,
|
||||
Step1HeldApplyResults.Num(), GetActiveGenerationTaskCount(), PendingTiles.Num(),
|
||||
static_cast<unsigned long long>(GFrameCounter));
|
||||
CSV_EVENT_GLOBAL(TEXT("VFStep1UploadCorpusReleased"));
|
||||
DrainStartCycles = FPlatformTime::Cycles64();
|
||||
}
|
||||
else
|
||||
@@ -8058,6 +8358,21 @@ bool AVoxelWorld::ApplyTileResult(FChunkResult& Result)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Retain one approximately 4k-triangle support stream only for the opt-in paused cook
|
||||
// replay. The replay is queued after the timed generation pause, so it measures the dedicated
|
||||
// cook path without concurrent tile generation and still uses this exact generated mesh.
|
||||
if (GVoxelForgeStep1CoreCookBenchmark != 0
|
||||
&& Result.bStep1SupportCohort
|
||||
&& !Step1PauseReplayStreams.IsValid()
|
||||
&& Result.Streams.IsValid()
|
||||
&& Result.NumTriangles >= 3500 && Result.NumTriangles <= 4300)
|
||||
{
|
||||
Step1PauseReplayStreams = MakeShared<RealtimeMesh::FRealtimeMeshStreamSet>(
|
||||
*Result.Streams);
|
||||
Step1PauseReplayTile = Result.Tile;
|
||||
Step1PauseReplayTriangles = Result.NumTriangles;
|
||||
}
|
||||
|
||||
// Store only lattices still within the local pawn's fixed 3×3×3 neighborhood. The cache is
|
||||
// independent of DiffLayer revisions because it contains only the procedural pre-edit field.
|
||||
if (Result.PreEditDensityGrid.Samples.Num() > 0
|
||||
@@ -8689,6 +9004,17 @@ void AVoxelWorld::BuildDesiredTiles(const FIntVector& Center, const FVector& Pla
|
||||
VoxelCollisionDesiredTiles::AddBox(
|
||||
CollisionDesiredTiles, Center, PawnCollisionRadius,
|
||||
PawnCollisionRadius, PawnCollisionRadius);
|
||||
if (GVoxelForgeStep1CoreCookBenchmark != 0 && Step1SupportCohortTiles.IsEmpty())
|
||||
{
|
||||
VoxelCollisionDesiredTiles::AddBox(
|
||||
Step1SupportCohortTiles, Center, PawnCollisionRadius,
|
||||
PawnCollisionRadius, PawnCollisionRadius);
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeStep1SupportCohort] stage=defined center=(%d,%d,%d) "
|
||||
"radius=%d tiles=%d"),
|
||||
Center.X, Center.Y, Center.Z, PawnCollisionRadius,
|
||||
Step1SupportCohortTiles.Num());
|
||||
}
|
||||
|
||||
// Streaming anchors (AI / remote players, §9.3): fold each one's small level-0 box into the SAME
|
||||
// desired set BEFORE the leaver sweep, so the delta cull releases an anchor's tiles automatically
|
||||
@@ -9617,6 +9943,28 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
|
||||
const uint32 TaskEpoch = GenerationEpoch;
|
||||
const uint32 TaskDesiredEpoch = DesiredEpoch;
|
||||
const uint32 TaskModificationEpoch = ModificationEpoch;
|
||||
if (GVoxelForgeStep1CoreCookBenchmark != 0
|
||||
&& Step1SupportCohortTiles.IsEmpty())
|
||||
{
|
||||
FVector PlayerPosition = FVector::ZeroVector;
|
||||
if (TryGetPlayerPosition(PlayerPosition))
|
||||
{
|
||||
const FIntVector CohortCenter = WorldToChunkCoord(
|
||||
WorldToLocalCm(PlayerPosition));
|
||||
const int32 PawnRadius = FMath::Clamp(
|
||||
Settings ? Settings->PawnCollisionRadiusChunks : 1, 1, 4);
|
||||
VoxelCollisionDesiredTiles::AddBox(
|
||||
Step1SupportCohortTiles, CohortCenter, PawnRadius,
|
||||
PawnRadius, PawnRadius);
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeStep1SupportCohort] stage=defined center=(%d,%d,%d) "
|
||||
"radius=%d tiles=%d source=first_load"),
|
||||
CohortCenter.X, CohortCenter.Y, CohortCenter.Z, PawnRadius,
|
||||
Step1SupportCohortTiles.Num());
|
||||
}
|
||||
}
|
||||
const bool bStep1SupportCohort = GVoxelForgeStep1CoreCookBenchmark != 0
|
||||
&& Step1SupportCohortTiles.Contains(Tile);
|
||||
|
||||
// CAPTURE-DURING-MESHING: only level-0 full-res tiles map 1:1 onto a density-clipmap level
|
||||
// (Step == 1<<Level, Cells == CHUNK_SIZE). When the density volume is active, ask the mesher to
|
||||
@@ -9662,6 +10010,7 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
|
||||
Empty.BandChunkLo = BandChunkLo;
|
||||
Empty.BandChunkHi = BandChunkHi;
|
||||
Empty.WorkPriority = WorkPriority;
|
||||
Empty.bStep1SupportCohort = bStep1SupportCohort;
|
||||
if (WorkPriority == EVoxelTileWorkPriority::CollisionCritical)
|
||||
CriticalProcessQueue.Enqueue(MoveTemp(Empty));
|
||||
else if (WorkPriority == EVoxelTileWorkPriority::Edited)
|
||||
@@ -9694,7 +10043,7 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
|
||||
bWantCapture, bWantPreEditDensityGrid, bWantSurfaceWitnesses,
|
||||
BandVoxLo, BandVoxHi, BandChunkLo, BandChunkHi,
|
||||
RequestStartCycles, TaskDesiredEpoch, TaskModificationEpoch, Cancellation,
|
||||
WorkPriority]()
|
||||
WorkPriority, bStep1SupportCohort]()
|
||||
{
|
||||
// RAII: decrement the counter on every exit path.
|
||||
struct FTaskGuard
|
||||
@@ -9717,6 +10066,7 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
|
||||
Canceled.ModificationEpoch = TaskModificationEpoch;
|
||||
Canceled.RequestStartCycles = RequestStartCycles;
|
||||
Canceled.WorkPriority = WorkPriority;
|
||||
Canceled.bStep1SupportCohort = bStep1SupportCohort;
|
||||
Canceled.bAborted = true;
|
||||
Canceled.bObsolete = true;
|
||||
if (WorkPriority == EVoxelTileWorkPriority::CollisionCritical)
|
||||
@@ -9741,6 +10091,7 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
|
||||
Result.DesiredEpoch = TaskDesiredEpoch;
|
||||
Result.ModificationEpoch = TaskModificationEpoch;
|
||||
Result.WorkPriority = WorkPriority;
|
||||
Result.bStep1SupportCohort = bStep1SupportCohort;
|
||||
Result.bCaptureSurfaceWitnesses = bWantSurfaceWitnesses;
|
||||
const uint64 GenerationStartCycles = FPlatformTime::Cycles64();
|
||||
Result.GenerationStartCycles = GenerationStartCycles;
|
||||
@@ -9753,6 +10104,17 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
|
||||
|
||||
const uint64 WorkerGenerationCycles = Result.GenerationEndCycles >= GenerationStartCycles
|
||||
? Result.GenerationEndCycles - GenerationStartCycles : 0;
|
||||
if (bStep1SupportCohort)
|
||||
{
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeStep1SupportCohort] stage=mesh_ready tile=(%d,%d,%d) "
|
||||
"empty=%d triangles=%d priority=%d request_to_worker_ms=%.3f "
|
||||
"generation_ms=%.3f"),
|
||||
Tile.Coord.X, Tile.Coord.Y, Tile.Coord.Z, Result.bEmpty ? 1 : 0,
|
||||
Result.NumTriangles, static_cast<int32>(WorkPriority),
|
||||
FPlatformTime::ToSeconds64(GenerationStartCycles - RequestStartCycles) * 1000.0,
|
||||
FPlatformTime::ToSeconds64(WorkerGenerationCycles) * 1000.0);
|
||||
}
|
||||
TotalWorkerGenerationTasks.fetch_add(1, std::memory_order_relaxed);
|
||||
TotalWorkerGenerationCycles.fetch_add(WorkerGenerationCycles, std::memory_order_relaxed);
|
||||
const bool bObsoleteWork = Cancellation->bObsolete.load(std::memory_order_relaxed)
|
||||
@@ -10235,14 +10597,15 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
|
||||
|
||||
if (GVoxelForgeStep1CoreCookBenchmark != 0
|
||||
&& Result.Tile.Level == 0
|
||||
&& Result.WorkPriority == EVoxelTileWorkPriority::CollisionCritical)
|
||||
&& (Result.WorkPriority == EVoxelTileWorkPriority::CollisionCritical
|
||||
|| Result.bStep1SupportCohort))
|
||||
{
|
||||
if (FQueuedThreadPool* CookPool = GVoxelForgeStep1CoreCookPool.GetOrCreate())
|
||||
{
|
||||
CookPool->AddQueuedWork(
|
||||
new FVoxelStep1CoreCookWork(
|
||||
MakeShared<RealtimeMesh::FRealtimeMeshStreamSet>(*Result.Streams),
|
||||
Result.Tile, Result.NumTriangles,
|
||||
Result.Tile, Result.NumTriangles, Result.bStep1SupportCohort, false,
|
||||
FPlatformTime::Cycles64()),
|
||||
EQueuedWorkPriority::High);
|
||||
}
|
||||
@@ -10656,6 +11019,7 @@ bool AVoxelWorld::ApplyMeshToTile(FChunkResult& Result)
|
||||
Pending.GenerationEndCycles = Result.GenerationEndCycles;
|
||||
Pending.ApplyStartCycles = Result.ApplyStartCycles;
|
||||
Pending.WorkPriority = Result.WorkPriority;
|
||||
Pending.bStep1SupportCohort = Result.bStep1SupportCohort;
|
||||
Pending.NumVertices = Result.NumVertices;
|
||||
Pending.NumTriangles = Result.NumTriangles;
|
||||
Pending.NumSkirtTriangles = Result.NumSkirtTriangles;
|
||||
|
||||
@@ -117,6 +117,8 @@ struct FChunkResult
|
||||
uint64 GenerationEndCycles = 0;
|
||||
uint64 ApplyStartCycles = 0;
|
||||
EVoxelTileWorkPriority WorkPriority = EVoxelTileWorkPriority::Horizon;
|
||||
// Step-1-only tag captured on the game thread for the pawn's bounded support cohort.
|
||||
bool bStep1SupportCohort = false;
|
||||
int32 ClassifyVerdict = -1; double ClassifySeconds = 0.0; double MeshSeconds = 0.0; double StreamSeconds = 0.0; int32 NumTriangles = 0; int32 NumCeilingTriangles = 0; int32 NumSkirtTriangles = 0;
|
||||
int32 NumVertices = 0;
|
||||
uint64 GeometryBytes = 0;
|
||||
@@ -304,6 +306,7 @@ public:
|
||||
uint64 GenerationEndCycles = 0;
|
||||
uint64 ApplyStartCycles = 0;
|
||||
EVoxelTileWorkPriority WorkPriority = EVoxelTileWorkPriority::Horizon;
|
||||
bool bStep1SupportCohort = false;
|
||||
int32 NumVertices = 0;
|
||||
int32 NumTriangles = 0;
|
||||
int32 NumSkirtTriangles = 0;
|
||||
@@ -1040,6 +1043,12 @@ public:
|
||||
// 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;
|
||||
// Exact pawn-radius box used only by the default-off Step-1 cook sidecar measurement.
|
||||
TSet<FVoxelTileKey> Step1SupportCohortTiles;
|
||||
TSharedPtr<RealtimeMesh::FRealtimeMeshStreamSet> Step1PauseReplayStreams;
|
||||
FVoxelTileKey Step1PauseReplayTile;
|
||||
int32 Step1PauseReplayTriangles = 0;
|
||||
bool bStep1PauseReplayQueued = false;
|
||||
// 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.
|
||||
TSet<FVoxelTileKey> CollisionGateDesiredTiles;
|
||||
|
||||
@@ -334,6 +334,10 @@ struct FExploreArguments
|
||||
float RenderMaxDistanceVoxels = DefaultRenderMaxDistanceVoxels;
|
||||
int32 ExportSize = DefaultExportSize;
|
||||
int32 ExportStep = DefaultExportStep;
|
||||
bool bExportTileOverride = false;
|
||||
int32 ExportTileX = 0;
|
||||
int32 ExportTileY = 0;
|
||||
int32 ExportTileZ = 0;
|
||||
// The canonical export owns one immutable lattice for the whole run. Batch cases can pass
|
||||
// density_grid_reuse=false to produce a matched wall-clock control.
|
||||
bool bReuseDensityGrid = true;
|
||||
@@ -463,6 +467,24 @@ bool ParseArguments(const FString& Params, FExploreArguments& OutArguments, FStr
|
||||
FParse::Value(*Params, TEXT("rendermaxdistance="), OutArguments.RenderMaxDistanceVoxels);
|
||||
FParse::Value(*Params, TEXT("exportsize="), OutArguments.ExportSize);
|
||||
FParse::Value(*Params, TEXT("exportstep="), OutArguments.ExportStep);
|
||||
const bool bExportTileXSpecified = Params.Contains(
|
||||
TEXT("exporttilex="), ESearchCase::IgnoreCase);
|
||||
const bool bExportTileYSpecified = Params.Contains(
|
||||
TEXT("exporttiley="), ESearchCase::IgnoreCase);
|
||||
const bool bExportTileZSpecified = Params.Contains(
|
||||
TEXT("exporttilez="), ESearchCase::IgnoreCase);
|
||||
if (bExportTileXSpecified || bExportTileYSpecified || bExportTileZSpecified)
|
||||
{
|
||||
if (!bExportTileXSpecified || !bExportTileYSpecified || !bExportTileZSpecified
|
||||
|| !FParse::Value(*Params, TEXT("exporttilex="), OutArguments.ExportTileX)
|
||||
|| !FParse::Value(*Params, TEXT("exporttiley="), OutArguments.ExportTileY)
|
||||
|| !FParse::Value(*Params, TEXT("exporttilez="), OutArguments.ExportTileZ))
|
||||
{
|
||||
OutError = TEXT("exporttilex, exporttiley, and exporttilez must be supplied together as level-0 tile coordinates.");
|
||||
return false;
|
||||
}
|
||||
OutArguments.bExportTileOverride = true;
|
||||
}
|
||||
int32 DensityGridReuse = OutArguments.bReuseDensityGrid ? 1 : 0;
|
||||
FParse::Value(*Params, TEXT("densitygridreuse="), DensityGridReuse);
|
||||
OutArguments.bReuseDensityGrid = DensityGridReuse != 0;
|
||||
@@ -2015,6 +2037,14 @@ FIntVector ChooseExploreMeshOrigin(
|
||||
int32 MeshSize,
|
||||
int32 TargetMiddleWorldZ)
|
||||
{
|
||||
if (Arguments.bExportTileOverride)
|
||||
{
|
||||
return FIntVector(
|
||||
Arguments.ExportTileX * CHUNK_SIZE,
|
||||
Arguments.ExportTileY * CHUNK_SIZE,
|
||||
Arguments.ExportTileZ * CHUNK_SIZE);
|
||||
}
|
||||
|
||||
FVector Centre(0.0f, 0.0f, static_cast<float>(TargetMiddleWorldZ));
|
||||
if (CameraSeedWalk != nullptr && CameraSeedWalk->CameraSeedPoses.Num() > 0)
|
||||
{
|
||||
|
||||
+30
-10
@@ -15,6 +15,9 @@ param(
|
||||
[int]$HorizonLevel = 5,
|
||||
[ValidateRange(1, 100000)]
|
||||
[int]$HorizonRenderDistanceChunks = 768,
|
||||
[ValidateSet(32, 64, 128)]
|
||||
[int]$ParityExportSize = 128,
|
||||
[string]$ParityExportTile = '',
|
||||
[string]$SharedDDC
|
||||
)
|
||||
|
||||
@@ -673,7 +676,7 @@ function New-CommonArguments([string]$Kind, [string]$LogPath, $CvarMap, [bool]$E
|
||||
|
||||
function New-CommandletArguments([string]$CommandletOut, [string]$Modes, [bool]$UseOwnerAssets,
|
||||
[bool]$GameRuntimeConfig, [int]$ExportSize = 128,
|
||||
[int]$Slot = 1) {
|
||||
[int]$Slot = 1, [string]$ExportTile = '') {
|
||||
$args = [System.Collections.Generic.List[string]]::new()
|
||||
[void]$args.Add('-seed=0')
|
||||
[void]$args.Add('-archetype=TunnelNetwork')
|
||||
@@ -682,6 +685,14 @@ function New-CommandletArguments([string]$CommandletOut, [string]$Modes, [bool]$
|
||||
[void]$args.Add('-blockearlyout=0')
|
||||
[void]$args.Add('-densitygridreuse=1')
|
||||
[void]$args.Add("-exportsize=$ExportSize")
|
||||
if (-not [string]::IsNullOrWhiteSpace($ExportTile)) {
|
||||
if ($ExportTile -notmatch '^\s*(-?\d+)\s*,\s*(-?\d+)\s*,\s*(-?\d+)\s*$') {
|
||||
throw "-ParityExportTile must be three comma-separated level-0 tile coordinates: $ExportTile"
|
||||
}
|
||||
[void]$args.Add("-exporttilex=$($Matches[1])")
|
||||
[void]$args.Add("-exporttiley=$($Matches[2])")
|
||||
[void]$args.Add("-exporttilez=$($Matches[3])")
|
||||
}
|
||||
if ($GameRuntimeConfig) { [void]$args.Add('-gameconfig=1') }
|
||||
if ($UseOwnerAssets) {
|
||||
[void]$args.Add('-strateref=/Game/VoxelForge/DA_Strate3.DA_Strate3')
|
||||
@@ -1108,11 +1119,17 @@ function Get-DigMotionMetrics([string]$LogPath) {
|
||||
if ($result.Success -and $result.Groups['v'].Value -eq 'PASS') { $metrics.status = 'passed' }
|
||||
foreach ($key in @('near_digs', 'near_frames', 'near_gate_frames', 'near_falling_frames',
|
||||
'near_vertical_motion_frames', 'under_saw_falling', 'under_gate_frames',
|
||||
'under_fall_cm', 'under_max_downward_velocity_cm_s')) {
|
||||
'under_fall_cm', 'under_max_downward_velocity_cm_s', 'under_digs',
|
||||
'first_frame_checks', 'support_observed_frames',
|
||||
'false_support_frames', 'false_air_frames',
|
||||
'invented_downward_velocity_events', 'field_override')) {
|
||||
$match = [regex]::Match($line, ('{0}=(?<v>[^ ]+)' -f $key))
|
||||
if (-not $match.Success) { continue }
|
||||
if ($key -in @('near_digs', 'near_frames', 'near_gate_frames', 'near_falling_frames',
|
||||
'near_vertical_motion_frames', 'under_saw_falling', 'under_gate_frames')) {
|
||||
'near_vertical_motion_frames', 'under_saw_falling', 'under_gate_frames',
|
||||
'under_digs', 'first_frame_checks', 'false_support_frames',
|
||||
'support_observed_frames',
|
||||
'false_air_frames', 'invented_downward_velocity_events', 'field_override')) {
|
||||
$metrics[$key] = [int]$match.Groups['v'].Value
|
||||
} else {
|
||||
$metrics[$key] = [double]$match.Groups['v'].Value
|
||||
@@ -1695,10 +1712,10 @@ try {
|
||||
}
|
||||
'parity' {
|
||||
$parityArgs = New-CommonArguments 'commandlet' (Join-Path $RunRoot 'Logs\parity-commandlet.log') (Merge-Cvars $baseCvars)
|
||||
# Use the same 128-voxel / 64-tile bounded region as the canonical exporter. The game
|
||||
# harness streams a larger clipmap around its center; comparison is restricted to these
|
||||
# exact level-0 tiles, including non-empty geometry rather than only an air tile.
|
||||
$parityArgs += New-CommandletArguments $commandletOut 'export' $true $true 128 0
|
||||
# Export one selected region size. The game harness streams a clipmap around its center;
|
||||
# comparison is restricted to these exact level-0 tiles, including non-empty geometry.
|
||||
$parityArgs += New-CommandletArguments $commandletOut 'export' $true $true `
|
||||
$ParityExportSize 0 $ParityExportTile
|
||||
$commandletLedger = Invoke-UnrealLaunch 'parity-commandlet' $parityArgs `
|
||||
(Join-Path $RunRoot 'Logs\parity-commandlet.log') (Join-Path $RunRoot 'Logs\parity-commandlet.stdout.log') (Join-Path $RunRoot 'Logs\parity-commandlet.stderr.log')
|
||||
$CommandletMetrics = Get-CommandletMetrics $commandletOut 'owner assets; game_runtime_config=1'
|
||||
@@ -1707,13 +1724,15 @@ try {
|
||||
$parityExport = Get-JsonValue $parityJson 'export'
|
||||
$region = Get-JsonValue $parityExport 'region_origin_voxels'
|
||||
if ($null -eq $region) { throw 'Parity commandlet did not report export.region_origin_voxels.' }
|
||||
$paritySize = [int](Get-JsonValue $parityExport 'region_size_cells_per_axis' 128)
|
||||
$paritySize = [int](Get-JsonValue $parityExport 'region_size_cells_per_axis' $ParityExportSize)
|
||||
$centerOffset = [int]($paritySize / 2)
|
||||
$centerX = [int]$region.x + $centerOffset
|
||||
$centerY = [int]$region.y + $centerOffset
|
||||
$centerZ = [int]$region.z + $centerOffset
|
||||
$gameDump = Join-Path $RunRoot 'game_tile_hashes.json'
|
||||
$baseCvars['voxel.TestExitSeconds'] = 15
|
||||
if ($null -eq $Cvars -or -not $Cvars.Contains('voxel.TestExitSeconds')) {
|
||||
$baseCvars['voxel.TestExitSeconds'] = 15
|
||||
}
|
||||
$baseCvars['voxel.StreamingTestCenterX'] = $centerX
|
||||
$baseCvars['voxel.StreamingTestCenterY'] = $centerY
|
||||
$baseCvars['voxel.StreamingTestCenterZ'] = $centerZ
|
||||
@@ -1721,7 +1740,8 @@ try {
|
||||
$baseCvars['voxel.UseBlockEarlyOut'] = 0
|
||||
$baseCvars['voxel.OuterClassifierMode'] = 0
|
||||
$baseCvars['voxel.SealedSolidProof'] = 0
|
||||
$gameArgs = New-CommonArguments 'game' (Join-Path $RunRoot 'Logs\parity-game.log') (Merge-Cvars $baseCvars)
|
||||
$gameArgs = New-CommonArguments 'game' (Join-Path $RunRoot 'Logs\parity-game.log') `
|
||||
(Merge-Cvars $baseCvars) $EnableRender.IsPresent
|
||||
$gameLedger = Invoke-UnrealLaunch 'parity-game' $gameArgs `
|
||||
(Join-Path $RunRoot 'Logs\parity-game.log') (Join-Path $RunRoot 'Logs\parity-game.stdout.log') (Join-Path $RunRoot 'Logs\parity-game.stderr.log')
|
||||
$GameMetrics = Get-StreamingMetrics (Join-Path $RunRoot 'Logs\parity-game.log')
|
||||
|
||||
Reference in new issue
Block a user