wip(step2f): coherent field -- flakes, speckle, grid, roof fixed; 3 crumbs + 636 ms left; last edits UNCOMPILED

Stopped by low memory (another app at 14 GB; processes failed with 0xC0000142).
r6 (compiled and measured, owner playing during the run), step 2e -> step 2f r6:
  crumbs < 1 m^3 (max per profile)   2,898 -> 3        FAIL (0,0,0,0,2,1,3,2)
  flakes (shard share / p95 angle)   40.7% / 84.2 deg -> 2.1% / 33.1 deg   pass
  lattice-scale detail energy        0.832 -> 0.016    pass
  grid signature (autocorrelation)   0.666 -> 0.349    pass
  min solid share / roof openings    2.7%, 26,657 -> 79.2%, 0   pass
  worst profile ring time            642.8 -> 636.3 ms FAIL (6 workers, 259^3)
Step 2e measured 642.8 ms here vs 570 ms idle earlier: the game was costing ~12% during this run.
Brick proofs + audit, seams + injected seam, 1 vs 6 worker determinism pass. After r6, feature
probabilities were lowered to remove the last crumbs: NOT compiled, NOT measured. TraversalSeeds,
in-game captures and the LOD-rejection figure for 2e are still to do. Reject screenshots included.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-09-30 19:50:42 +02:00
1 parent 0a7492ffab
commit d6555df5fa
9 files changed
+1655 -55

No files matched your search

+5 -5
View File
@@ -117,12 +117,12 @@ remains `0`, and lateral regions remain gated off.
|------|------| |------|------|
| `../../VoxelForge.uplugin` | Plugin manifest. Runtime module `VoxelForge` plus the Editor-only `VoxelForgeEditor` commandlet module. Beta. | | `../../VoxelForge.uplugin` | Plugin manifest. Runtime module `VoxelForge` plus the Editor-only `VoxelForgeEditor` commandlet module. Beta. |
| `VoxelForge.Build.cs` | Deps: Core, CoreUObject, Engine, **GameplayTags**, **RealtimeMeshComponent**. | | `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|region-eval|step2d|step2e`, optional `-Build`, `-Assets owner|default`, `-Cvars`, absolute `-Out`, `-Label`, `-HorizonLevel`, `-HorizonRenderDistanceChunks`, `-ParityExportSize=32|64|128`, `-ParityExportTile=x,y,z`, `-SharedDDC`, `-SampleDensityStacks`, `-StackSampleIntervalUs`, and `-RegionHashProbe -RegionWorkers N -RegionReferenceHash HASH`; stages the host/assets, isolates user/DDC/config state, serializes one Unreal launch at a time, enforces the 8 GiB/30-minute guards, waits for 6 GiB free RAM before launches, and emits compact `result.json`/`summary.txt` plus full logs. Parity can render its game leg with `-EnableRender` and compares the selected export region against the exact streamed tile hashes. `visual-gap-walk` records camera-frustum surface-point coverage at 6000 cm/s; `visual-gap-inject` proves an early component hide is detected; `dig-motion` logs per-frame vertical velocity, gate, support readiness, and movement mode. `-Cvars @{ 'voxel.TestGapAuditLegacyHandoff'=1 }` replays the old visibility handoff for A/B audit baselines. Horizon traces report desired/loaded tiles, resident stream geometry, worker seconds, steady wall time, and peak private memory; `horizon-render` captures the owner view; `determinism` compares per-tile hashes across the default CRT path and `-voxel.CrtFma3=0`; `region-eval` compares the live path with the default-off editor chunk program on the 259³ lattice and five-seed probe set; `step2d` retains the earlier independent field; `step2e` measures eight room-network profiles on the 259³ ring, emits variety metrics and live/new slices, and checks detail, boundaries, bounds, and 1-vs-6-worker hashes. `-RegionHashProbe -RegionWorkers N -RegionReferenceHash HASH` checks a full-ring hash at another partition count. `-SampleDensityStacks` captures profiles, and `-StackSampleIntervalUs` sets the sampling interval. | `Tools/VoxelForgeTest.ps1` | The single guarded test entry point. `-Scenario canonical|owner|probe|perf|tests|surface-fall|crossing|gate-stress|visual-edit|visual-lod|visual-gap-walk|visual-gap-inject|dig-motion|parity|horizon|horizon-render|determinism|region-eval|step2d|step2e|step2f|step2f-capture`, optional `-Build`, `-Assets owner|default`, `-Cvars`, absolute `-Out`, `-Label`, `-HorizonLevel`, `-HorizonRenderDistanceChunks`, `-Step2fProfile=0..7`, `-Step2fCaptureView=inside|wall-closeup`, `-ParityExportSize=32|64|128`, `-ParityExportTile=x,y,z`, `-SharedDDC`, `-SampleDensityStacks`, `-StackSampleIntervalUs`, and `-RegionHashProbe -RegionWorkers N -RegionReferenceHash HASH`; stages the host/assets, isolates user/DDC/config state, serializes one Unreal launch at a time, enforces the 8 GiB/30-minute guards, waits for 6 GiB free RAM before launches, and emits compact `result.json`/`summary.txt` plus full logs. Parity can render its game leg with `-EnableRender` and compares the selected export region against the exact streamed tile hashes. `visual-gap-walk` records camera-frustum surface-point coverage at 6000 cm/s; `visual-gap-inject` proves an early component hide is detected; `dig-motion` logs per-frame vertical velocity, gate, support readiness, and movement mode. `-Cvars @{ 'voxel.TestGapAuditLegacyHandoff'=1 }` replays the old visibility handoff for A/B audit baselines. Horizon traces report desired/loaded tiles, resident stream geometry, worker seconds, steady wall time, and peak private memory; `horizon-render` captures the owner view; `step2f-capture` selects one of eight parameter sets, captures an in-game owner view, and saves it under `Saved/Research/step2f/captures` (pass the persistent render DDC with `-SharedDDC`); `determinism` compares per-tile hashes across the default CRT path and `-voxel.CrtFma3=0`; `region-eval` compares the live path with the default-off editor chunk program on the 259³ lattice and five-seed probe set; `step2d` retains the earlier independent field; `step2e` retains its earlier benchmark; `step2f` compares frozen step 2e and the coherent field for all eight profiles on the same 259³ ring, applies geometry gates, and checks sound bounds, seams, and 1-vs-6-worker hashes. `-RegionHashProbe -RegionWorkers N -RegionReferenceHash HASH` checks a full-ring hash at another partition count. `-SampleDensityStacks` captures profiles, and `-StackSampleIntervalUs` sets the sampling interval.
| `Public/VoxelForgeModule.h` / `Private/VoxelForgeModule.cpp` | `FVoxelForgeModule` boilerplate (Startup/Shutdown just log). | | `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. Optional `-exporttilex/y/z` sets an exact L0 tile origin for controlled export/game parity; omitting all three preserves the existing center-derived origin. `-regioneval=1` retains the exact-field step-2c research harness. `-step2d` retains the earlier independent field benchmark; `-step2e` measures eight room-network profiles and writes timing, per-profile parameter ranges, nominal chamber-volume quantiles, vertical-run and tunnel metrics, and side-by-side live/new slice evidence under `Saved/Research/step2e`. The report records 12.5 cm scaling guidance and notes that basin water levels are parameters without water rendering or simulation. Its hardest-profile gates include full-detail parity, seam injection, sound and undersized-bound controls, and repeated / 1-vs-6-worker hash checks. | | `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. `-regioneval=1` retains the exact-field step-2c research harness. `-step2d` and `-step2e` retain their earlier benchmarks. `-step2f` compares frozen step 2e and the coherent field for all eight profiles on the same 259³ ring, counts sub-1 m³ connected components, measures mesh dihedrals, detail spectrum, LOD surface distance, chamber-pitch autocorrelation, roof/floor coverage, and six-worker timing. It also runs the sound/undersized-bound audit, seam injection, and 1-vs-6-worker hash checks and writes profile slices and JSON under `Saved/Research/step2f`. |
| `Source/VoxelForgeEditor/Private/VoxelForgeRegionEvalExperiment.*` | Six-worker lattice drivers for step 2c, step 2d, and step 2e. The original path still compares live `GetDensityAt` with the editor chunk program. Research modes assign contiguous batches dynamically so surface-heavy work balances across workers; step 2e uses 4³ proof bricks and stages each evaluated brick before copying contiguous X rows, while step 2d retains 8³. Each skip requires a macro Lipschitz bound plus the complete detail-amplitude supremum; uncertain bricks retain all 25 cm detail samples. Timings split classification, point evaluation, and uniform fill; step 2e hashes compare workers 1 and 6. | | `Source/VoxelForgeEditor/Private/VoxelForgeRegionEvalExperiment.*` | Six-worker lattice drivers for step 2c through step 2f. The original path still compares live `GetDensityAt` with the editor chunk program. Research modes assign contiguous batches dynamically so surface-heavy work balances across workers; steps 2e and 2f use 4³ proof bricks and stage each evaluated brick before copying contiguous X rows, while step 2d retains 8³. Each skip requires a macro Lipschitz bound plus the complete final-detail supremum; uncertain bricks retain all 25 cm detail samples. Timings split classification, point evaluation, and uniform fill; step 2f compares all profiles and hashes workers 1 and 6. |
| `Public/VoxelRedefinedDensityField.h` / `Private/VoxelRedefinedDensityField.cpp` | Two default-off `WITH_EDITOR` research fields, selected by `voxel.ResearchRedefinedDensityField` (step 2d) and `voxel.ResearchParameterizedCave` (step 2e). Step 2e deterministically assigns one of eight parameter profiles per strate, then builds jittered rooms and a root-connected tunnel graph with optional loops and shafts, pillars, ledges, floating masses, stalactites/stalagmites, and basin floors. The room evaluator checks the containing cell first, then cardinal and diagonal neighbors, with horizontal/vertical upper-bound rejection before feature work. A padded thread-local generated-cell cache is reused across adjacent proof bricks, and XY-dependent room/tunnel distances are reused through each vertical column while four bounded world-space value-noise bands, including a one-sample micro band, remain evaluated at full resolution. Step 2e proofs include the model Lipschitz interval, passage/refinement fallback, and full detail supremum; a local derivative bound permits sound proofs through landing-floor blends. The basin has a water-level parameter, but this field does not render or simulate water. The public API adds no reflected class layout. | | `Public/VoxelRedefinedDensityField.h` / `Private/VoxelRedefinedDensityField.cpp` | Two default-off `WITH_EDITOR` research fields, selected by `voxel.ResearchRedefinedDensityField` (step 2d) and `voxel.ResearchParameterizedCave` (steps 2e/2f). Step 2f retains frozen 2e parameters for its rejection audit, while the new profiles use strongly jittered, individually rotated elliptical rooms capped against nearest neighbors, leaving a solid web joined by narrow tree/loop tunnels and capped shafts. Room heights and feature rolls vary by cell; ledges, occasional pillars, stalactites/stalagmites, basin floors, and bounded hanging masses remain features. Point and brick detail sampling use the same 32/16/8-sample bands and smooth ridge response; the eight-sample shortest wavelength and 4.2-unit final-field supremum are preserved. The thread-local cell cache and per-column distance reuse remain. In an editor game with this field enabled, sealed-solid, out-of-layout, and outer-classifier verdicts are declined without changing their console switches. No reflected class layout or serialized assets change. |
| `Private/Tests/VoxelForgeParameterizedGenerationTest.cpp` | Separate three-seed functional sweep for the step 2e field; passage landing air/floor brackets and arrival-to-exit walking routes are checked without changing the live-world tests. | | `Private/Tests/VoxelForgeParameterizedGenerationTest.cpp` | Separate three-seed functional sweep for the step 2f parameterized field; passage landing air/floor brackets and arrival-to-exit walking routes are checked without changing the live-world tests. |
| `Private/Tests/VoxelForgeRedefinedDensityFieldTest.cpp` | Three-seed functional sweep for the research field: authored passage endpoints must bracket open air and solid floor, and each tested strate must have a walkable arrival-to-exit route. | | `Private/Tests/VoxelForgeRedefinedDensityFieldTest.cpp` | Three-seed functional sweep for the research field: authored passage endpoints must bracket open air and solid floor, and each tested strate must have a walkable arrival-to-exit route. |
| `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/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). | | `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). |
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

@@ -12,12 +12,14 @@
#include "VoxelTypes.h" #include "VoxelTypes.h"
#include <algorithm> #include <algorithm>
#include <atomic>
namespace VoxelForgeRedefinedDensityField namespace VoxelForgeRedefinedDensityField
{ {
namespace namespace
{ {
constexpr float DetailAmplitude = 1.4f + 0.9f + 0.45f + 2.5f; constexpr float LegacyDetailAmplitude = 1.4f + 0.9f + 0.45f + 2.5f;
constexpr float CoherentDetailAmplitude = 2.0f + 1.2f + 0.6f + 0.4f;
constexpr float DetailBoundNumericalMargin = 1.0e-3f; constexpr float DetailBoundNumericalMargin = 1.0e-3f;
constexpr float BaseFieldLipschitzBound = 1.25f; constexpr float BaseFieldLipschitzBound = 1.25f;
constexpr float ParameterizedFieldLipschitzBound = 1.60f; constexpr float ParameterizedFieldLipschitzBound = 1.60f;
@@ -54,7 +56,8 @@ namespace
FGenerationParameters Parameters; FGenerationParameters Parameters;
}; };
const FParameterProfile GParameterProfiles[] = { // Frozen step 2e profiles are retained only for the step 2f before/after audit.
const FParameterProfile GLegacyStep2eProfiles[] = {
{ TEXT("OpenVaults"), { 176.0f, 0.62f, 0.76f, 340.0f, 12.0f, 40.0f, 36.0f, { TEXT("OpenVaults"), { 176.0f, 0.62f, 0.76f, 340.0f, 12.0f, 40.0f, 36.0f,
0.35f, 0.14f, 22.0f, 0.04f, 0.12f, 0.08f, 0.15f, 0.00f, 0.0f, 0.50f } }, 0.35f, 0.14f, 22.0f, 0.04f, 0.12f, 0.08f, 0.15f, 0.00f, 0.0f, 0.50f } },
{ TEXT("TightNetwork"), { 104.0f, 0.52f, 0.64f, 256.0f, 8.0f, 20.0f, 22.0f, { TEXT("TightNetwork"), { 104.0f, 0.52f, 0.64f, 256.0f, 8.0f, 20.0f, 22.0f,
@@ -73,6 +76,38 @@ namespace
0.40f, 0.55f, 20.0f, 0.45f, 0.68f, 0.50f, 0.75f, 0.42f, 28.0f, 0.40f } }, 0.40f, 0.55f, 20.0f, 0.45f, 0.68f, 0.50f, 0.75f, 0.42f, 28.0f, 0.40f } },
}; };
const FParameterProfile GParameterProfiles[] = {
{ TEXT("OpenVaults"), { 176.0f, 0.31f, 0.40f, 132.0f, 8.0f, 22.0f, 18.0f,
0.12f, 0.08f, 16.0f, 0.02f, 0.06f, 0.04f, 0.22f, 0.00f, 0.0f, 0.50f } },
{ TEXT("TightNetwork"), { 104.0f, 0.27f, 0.35f, 104.0f, 5.0f, 18.0f, 14.0f,
0.28f, 0.12f, 15.0f, 0.02f, 0.06f, 0.02f, 0.14f, 0.00f, 0.0f, 0.50f } },
{ TEXT("ChamberHalls"), { 144.0f, 0.32f, 0.41f, 136.0f, 10.0f, 21.0f, 17.0f,
0.18f, 0.10f, 16.0f, 0.03f, 0.06f, 0.06f, 0.20f, 0.05f, 16.0f, 0.40f } },
{ TEXT("ShaftForest"), { 128.0f, 0.28f, 0.36f, 120.0f, 7.0f, 19.0f, 15.0f,
0.16f, 0.42f, 20.0f, 0.03f, 0.06f, 0.03f, 0.22f, 0.00f, 0.0f, 0.50f } },
{ TEXT("FloatingArchipelago"), { 136.0f, 0.29f, 0.37f, 116.0f, 6.0f, 19.0f, 15.0f,
0.12f, 0.08f, 15.0f, 0.02f, 0.06f, 0.08f, 0.20f, 0.00f, 0.0f, 0.50f } },
{ TEXT("PillaredCathedral"), { 160.0f, 0.31f, 0.40f, 132.0f, 8.0f, 22.0f, 17.0f,
0.20f, 0.08f, 16.0f, 0.16f, 0.06f, 0.04f, 0.22f, 0.00f, 0.0f, 0.50f } },
{ TEXT("TerracedBasin"), { 136.0f, 0.30f, 0.38f, 124.0f, 8.0f, 20.0f, 16.0f,
0.14f, 0.10f, 16.0f, 0.04f, 0.06f, 0.04f, 0.18f, 0.46f, 24.0f, 0.30f } },
{ TEXT("HyperComplex"), { 96.0f, 0.28f, 0.37f, 104.0f, 9.0f, 18.0f, 14.0f,
0.26f, 0.32f, 17.0f, 0.08f, 0.06f, 0.05f, 0.18f, 0.12f, 12.0f, 0.40f } },
};
std::atomic<bool> GUseLegacyStep2eAuditMode{false};
FORCEINLINE bool IsLegacyStep2eAuditMode()
{
return GUseLegacyStep2eAuditMode.load(std::memory_order_relaxed);
}
FORCEINLINE float CurrentDetailAmplitude(bool bParameterized)
{
return bParameterized && !IsLegacyStep2eAuditMode()
? CoherentDetailAmplitude : LegacyDetailAmplitude;
}
struct FLandingSite struct FLandingSite
{ {
FVector Position = FVector::ZeroVector; FVector Position = FVector::ZeroVector;
@@ -161,6 +196,7 @@ namespace
uint32 LayoutVersion = 0xFFFFFFFFu; uint32 LayoutVersion = 0xFFFFFFFFu;
int32 Seed = INT32_MIN; int32 Seed = INT32_MIN;
int32 ParameterSet = INT32_MIN; int32 ParameterSet = INT32_MIN;
bool bLegacyStep2eAuditMode = false;
TArray<FStratePlan> Strates; TArray<FStratePlan> Strates;
TArray<FPassageBound> PassageBounds; TArray<FPassageBound> PassageBounds;
}; };
@@ -223,7 +259,10 @@ namespace
return FMath::Lerp(Z0Y0, Z0Y1, AZ); return FMath::Lerp(Z0Y0, Z0Y1, AZ);
} }
FORCEINLINE float DetailAt(float X, float Y, float Z, uint32 Seed) FORCEINLINE float DetailAt(
float X, float Y, float Z, uint32 Seed, bool bCoherentStep2f)
{
if (!bCoherentStep2f || IsLegacyStep2eAuditMode())
{ {
const float Broad = Noise3D(X, Y, Z, 8.0f, Seed ^ 0x68BC21EBu); const float Broad = Noise3D(X, Y, Z, 8.0f, Seed ^ 0x68BC21EBu);
const float Medium = Noise3D(X, Y, Z, 4.0f, Seed ^ 0x02E5BE93u); const float Medium = Noise3D(X, Y, Z, 4.0f, Seed ^ 0x02E5BE93u);
@@ -232,6 +271,16 @@ namespace
return Broad * 1.4f + Medium * 0.9f + Fine * 0.45f + Micro * 2.5f; return Broad * 1.4f + Medium * 0.9f + Fine * 0.45f + Micro * 2.5f;
} }
// The shortest wavelength is eight lattice samples (two meters). Three
// interpolated scales and the smooth ridge response keep broad folds
// while excluding the lattice-scale noise used by the frozen 2e field.
const float Broad = Noise3D(X, Y, Z, 32.0f, Seed ^ 0x68BC21EBu);
const float Medium = Noise3D(X, Y, Z, 16.0f, Seed ^ 0x02E5BE93u);
const float Fine = Noise3D(X, Y, Z, 8.0f, Seed ^ 0x967A889Bu);
const float SmoothRidge = FMath::Sqrt(Fine * Fine + 0.0625f) - 0.25f;
return Broad * 2.0f + Medium * 1.2f + Fine * 0.6f + SmoothRidge * 0.4f;
}
FORCEINLINE float DistanceToPeriodicLine(float Position, float Spacing, float Offset) FORCEINLINE float DistanceToPeriodicLine(float Position, float Spacing, float Offset)
{ {
const float Cell = (Position - Offset) / Spacing; const float Cell = (Position - Offset) / Spacing;
@@ -398,7 +447,7 @@ namespace
// A 36-voxel bore (9 m at 25 cm) keeps the deterministic route tree visible to the // A 36-voxel bore (9 m at 25 cm) keeps the deterministic route tree visible to the
// 2-voxel navigation/refinement probes while still allowing full-strength wall noise. // 2-voxel navigation/refinement probes while still allowing full-strength wall noise.
const float ConnectorRadius = FMath::Max3( const float ConnectorRadius = FMath::Max3(
Params.ConnectorRadius, DetailAmplitude + 1.0f, 18.0f); Params.ConnectorRadius, LegacyDetailAmplitude + 1.0f, 18.0f);
const float CellMinX = CellX * Spacing; const float CellMinX = CellX * Spacing;
const float CellMaxX = CellMinX + Spacing; const float CellMaxX = CellMinX + Spacing;
const float CellMinY = CellY * Spacing; const float CellMinY = CellY * Spacing;
@@ -447,7 +496,7 @@ namespace
const float Spacing = FMath::Max(Params.ShaftSpacing, 1.0f); const float Spacing = FMath::Max(Params.ShaftSpacing, 1.0f);
const int32 BaseCellX = FMath::FloorToInt(X / Spacing); const int32 BaseCellX = FMath::FloorToInt(X / Spacing);
const int32 BaseCellY = FMath::FloorToInt(Y / Spacing); const int32 BaseCellY = FMath::FloorToInt(Y / Spacing);
const float VerticalPad = DetailAmplitude + 1.0f; const float VerticalPad = LegacyDetailAmplitude + 1.0f;
const float BottomZ = Params.StrateBottomWorldZ + Params.BoundarySealThickness; const float BottomZ = Params.StrateBottomWorldZ + Params.BoundarySealThickness;
const float TopZ = Params.StrateTopWorldZ - Params.BoundarySealThickness; const float TopZ = Params.StrateTopWorldZ - Params.BoundarySealThickness;
const float ShaftVertical = FMath::Min(Z - BottomZ, TopZ - Z); const float ShaftVertical = FMath::Min(Z - BottomZ, TopZ - Z);
@@ -491,11 +540,13 @@ namespace
const uint64 Lifetime = Manager->GetCacheLifetimeId(); const uint64 Lifetime = Manager->GetCacheLifetimeId();
const uint32 Version = Manager->GetLayoutVersion(); const uint32 Version = Manager->GetLayoutVersion();
const int32 ParameterSet = CVarResearchParameterizedCaveSet.GetValueOnAnyThread(); const int32 ParameterSet = CVarResearchParameterizedCaveSet.GetValueOnAnyThread();
const bool bLegacyStep2eAuditMode = IsLegacyStep2eAuditMode();
if (GPlanCache.Manager == Manager if (GPlanCache.Manager == Manager
&& GPlanCache.ManagerLifetimeId == Lifetime && GPlanCache.ManagerLifetimeId == Lifetime
&& GPlanCache.LayoutVersion == Version && GPlanCache.LayoutVersion == Version
&& GPlanCache.Seed == Generator.Seed && GPlanCache.Seed == Generator.Seed
&& GPlanCache.ParameterSet == ParameterSet) && GPlanCache.ParameterSet == ParameterSet
&& GPlanCache.bLegacyStep2eAuditMode == bLegacyStep2eAuditMode)
{ {
return; return;
} }
@@ -507,6 +558,7 @@ namespace
GPlanCache.LayoutVersion = Version; GPlanCache.LayoutVersion = Version;
GPlanCache.Seed = Generator.Seed; GPlanCache.Seed = Generator.Seed;
GPlanCache.ParameterSet = ParameterSet; GPlanCache.ParameterSet = ParameterSet;
GPlanCache.bLegacyStep2eAuditMode = bLegacyStep2eAuditMode;
const TArray<FStrateSlot>& Layout = Manager->GetLayout(); const TArray<FStrateSlot>& Layout = Manager->GetLayout();
GPlanCache.Strates.Reserve(Layout.Num()); GPlanCache.Strates.Reserve(Layout.Num());
for (const FStrateSlot& Slot : Layout) for (const FStrateSlot& Slot : Layout)
@@ -568,9 +620,12 @@ namespace
: static_cast<int32>(Hash3( : static_cast<int32>(Hash3(
Plan.StrateIndex, Plan.MinChunkZ, 0x2E, StrateSeed) Plan.StrateIndex, Plan.MinChunkZ, 0x2E, StrateSeed)
% UE_ARRAY_COUNT(GParameterProfiles)); % UE_ARRAY_COUNT(GParameterProfiles));
Plan.Generation = GParameterProfiles[Plan.GenerationProfile].Parameters; Plan.Generation = (bLegacyStep2eAuditMode
const float StrateHeight = FMath::Max( ? GLegacyStep2eProfiles[Plan.GenerationProfile]
48.0f, Top - Bottom - 8.0f); : GParameterProfiles[Plan.GenerationProfile]).Parameters;
const float StrateHeight = bLegacyStep2eAuditMode
? FMath::Max(48.0f, Top - Bottom - 8.0f)
: FMath::Max(48.0f, Top - Bottom - 64.0f);
Plan.Generation.RoomHeightVoxels = FMath::Clamp( Plan.Generation.RoomHeightVoxels = FMath::Clamp(
Plan.Generation.RoomHeightVoxels, 48.0f, StrateHeight); Plan.Generation.RoomHeightVoxels, 48.0f, StrateHeight);
Plan.Generation.RoomPitchVoxels = FMath::Max( Plan.Generation.RoomPitchVoxels = FMath::Max(
@@ -792,6 +847,8 @@ namespace
float CeilingZ = 0.0f; float CeilingZ = 0.0f;
float RadiusX = 0.0f; float RadiusX = 0.0f;
float RadiusY = 0.0f; float RadiusY = 0.0f;
float RoomCos = 1.0f;
float RoomSin = 0.0f;
float PillarX = 0.0f; float PillarX = 0.0f;
float PillarY = 0.0f; float PillarY = 0.0f;
float PillarRadius = 0.0f; float PillarRadius = 0.0f;
@@ -800,6 +857,8 @@ namespace
float FloatingY[2] = {}; float FloatingY[2] = {};
float FloatingZ[2] = {}; float FloatingZ[2] = {};
float FloatingRadius[2] = {}; float FloatingRadius[2] = {};
float FloatingRadiusY[2] = {};
float FloatingRadiusZ[2] = {};
float SpireLength = 0.0f; float SpireLength = 0.0f;
float SpireRadius = 0.0f; float SpireRadius = 0.0f;
uint8 FeatureFlags = 0; uint8 FeatureFlags = 0;
@@ -861,6 +920,19 @@ namespace
OutY = -Plan.GridSin * X + Plan.GridCos * Y; OutY = -Plan.GridSin * X + Plan.GridCos * Y;
} }
FORCEINLINE void GeneratedCellGridCenter(
const FStratePlan& Plan, int32 CellX, int32 CellY,
float& OutX, float& OutY, bool bLegacyStep2eAuditMode)
{
const uint32 Hash = Hash3(CellX, CellY, Plan.StrateIndex, Plan.GenerationSeed);
const float JitterScale = bLegacyStep2eAuditMode ? 0.08f : 1.00f;
const float JitterX = (ParameterRoll(Hash, 0x91A2u) - 0.5f) * JitterScale;
const float JitterY = (ParameterRoll(Hash, 0xB4C3u) - 0.5f) * JitterScale;
const float Pitch = Plan.Generation.RoomPitchVoxels;
OutX = Plan.GridOffsetX + (CellX + 0.5f + JitterX) * Pitch;
OutY = Plan.GridOffsetY + (CellY + 0.5f + JitterY) * Pitch;
}
FGeneratedCaveCell BuildGeneratedCell( FGeneratedCaveCell BuildGeneratedCell(
const FStratePlan& Plan, int32 CellX, int32 CellY) const FStratePlan& Plan, int32 CellX, int32 CellY)
{ {
@@ -870,18 +942,48 @@ namespace
const FGenerationParameters& P = Plan.Generation; const FGenerationParameters& P = Plan.Generation;
const float Pitch = P.RoomPitchVoxels; const float Pitch = P.RoomPitchVoxels;
const uint32 Hash = Hash3(CellX, CellY, Plan.StrateIndex, Plan.GenerationSeed); const uint32 Hash = Hash3(CellX, CellY, Plan.StrateIndex, Plan.GenerationSeed);
const float JitterX = (ParameterRoll(Hash, 0x91A2u) - 0.5f) * 0.08f; const bool bLegacyStep2eAuditMode = IsLegacyStep2eAuditMode();
const float JitterY = (ParameterRoll(Hash, 0xB4C3u) - 0.5f) * 0.08f; float LocalX = 0.0f;
const float LocalX = Plan.GridOffsetX + (CellX + 0.5f + JitterX) * Pitch; float LocalY = 0.0f;
const float LocalY = Plan.GridOffsetY + (CellY + 0.5f + JitterY) * Pitch; GeneratedCellGridCenter(
Plan, CellX, CellY, LocalX, LocalY, bLegacyStep2eAuditMode);
Cell.CenterX = Plan.GridCos * LocalX - Plan.GridSin * LocalY; Cell.CenterX = Plan.GridCos * LocalX - Plan.GridSin * LocalY;
Cell.CenterY = Plan.GridSin * LocalX + Plan.GridCos * LocalY; Cell.CenterY = Plan.GridSin * LocalX + Plan.GridCos * LocalY;
const float RadiusFraction = FMath::Lerp( const float RadiusFraction = FMath::Lerp(
P.MinimumRoomRadiusFraction, P.MaximumRoomRadiusFraction, P.MinimumRoomRadiusFraction, P.MaximumRoomRadiusFraction,
ParameterRoll(Hash, 0xA61Bu)); ParameterRoll(Hash, 0xA61Bu));
Cell.RadiusX = Pitch * RadiusFraction; Cell.RadiusX = Pitch * RadiusFraction;
if (!bLegacyStep2eAuditMode)
{
float NearestCenterSpacing = FLT_MAX;
for (int32 NeighborY = -1; NeighborY <= 1; ++NeighborY)
for (int32 NeighborX = -1; NeighborX <= 1; ++NeighborX)
{
if (NeighborX == 0 && NeighborY == 0)
{
continue;
}
float NeighborLocalX = 0.0f;
float NeighborLocalY = 0.0f;
GeneratedCellGridCenter(Plan, CellX + NeighborX, CellY + NeighborY,
NeighborLocalX, NeighborLocalY, false);
NearestCenterSpacing = FMath::Min(NearestCenterSpacing,
FMath::Sqrt(FMath::Square(NeighborLocalX - LocalX)
+ FMath::Square(NeighborLocalY - LocalY)));
}
// Keep a measurable rock web between chambers. Their graph edges open only narrow
// connecting tunnels, so the room lattice no longer merges into a forest of pillars.
Cell.RadiusX = FMath::Min(Cell.RadiusX, NearestCenterSpacing * 0.43f);
const float Angle = ParameterRoll(Hash, 0xE17Du) * 6.283185307179586f;
VoxelMath::DetSinCos(Cell.RoomSin, Cell.RoomCos, Angle);
Cell.RadiusY = Cell.RadiusX * FMath::Lerp(
0.64f, 0.94f, ParameterRoll(Hash, 0x27C5u));
}
else
{
Cell.RadiusY = Cell.RadiusX * FMath::Lerp( Cell.RadiusY = Cell.RadiusX * FMath::Lerp(
0.82f, 0.98f, ParameterRoll(Hash, 0x27C5u)); 0.82f, 0.98f, ParameterRoll(Hash, 0x27C5u));
}
if (VoxelHash::ToFloat01(Hash3(CellX, CellY, 0x53, Plan.GenerationSeed)) if (VoxelHash::ToFloat01(Hash3(CellX, CellY, 0x53, Plan.GenerationSeed))
< P.VerticalShaftProbability) < P.VerticalShaftProbability)
{ {
@@ -905,7 +1007,11 @@ namespace
} }
Cell.FloorZ = Plan.GenerationFloorZ Cell.FloorZ = Plan.GenerationFloorZ
+ (ParameterRoll(Hash, 0xF103u) * 2.0f - 1.0f) * P.FloorReliefVoxels; + (ParameterRoll(Hash, 0xF103u) * 2.0f - 1.0f) * P.FloorReliefVoxels;
Cell.CeilingZ = Cell.FloorZ + P.RoomHeightVoxels; const float RoomHeight = bLegacyStep2eAuditMode
? P.RoomHeightVoxels
: P.RoomHeightVoxels * FMath::Lerp(
0.82f, 1.12f, ParameterRoll(Hash, 0x55A9u));
Cell.CeilingZ = Cell.FloorZ + RoomHeight;
if (ParameterRoll(Hash, 0x8811u) < P.PillarProbability) if (ParameterRoll(Hash, 0x8811u) < P.PillarProbability)
{ {
@@ -926,7 +1032,8 @@ namespace
if (ParameterRoll(Hash, 0xE25Fu) < P.FloatingMassProbability) if (ParameterRoll(Hash, 0xE25Fu) < P.FloatingMassProbability)
{ {
Cell.FeatureFlags |= HasFloatingFlag; Cell.FeatureFlags |= HasFloatingFlag;
for (int32 Blob = 0; Blob < 2; ++Blob) const int32 BlobCount = bLegacyStep2eAuditMode ? 2 : 1;
for (int32 Blob = 0; Blob < BlobCount; ++Blob)
{ {
const uint32 BlobSalt = 0x1567u + static_cast<uint32>(Blob) * 0x101u; const uint32 BlobSalt = 0x1567u + static_cast<uint32>(Blob) * 0x101u;
const float OffsetX = (ParameterRoll(Hash, BlobSalt) - 0.5f) * Cell.RadiusX * 0.60f; const float OffsetX = (ParameterRoll(Hash, BlobSalt) - 0.5f) * Cell.RadiusX * 0.60f;
@@ -937,8 +1044,19 @@ namespace
Cell.FloorZ + P.RoomHeightVoxels * 0.28f, Cell.FloorZ + P.RoomHeightVoxels * 0.28f,
Cell.CeilingZ - P.RoomHeightVoxels * 0.28f, Cell.CeilingZ - P.RoomHeightVoxels * 0.28f,
ParameterRoll(Hash, BlobSalt + 2u)); ParameterRoll(Hash, BlobSalt + 2u));
Cell.FloatingRadius[Blob] = Cell.RadiusX * FMath::Lerp( Cell.FloatingRadius[Blob] = bLegacyStep2eAuditMode
0.07f, 0.15f, ParameterRoll(Hash, BlobSalt + 3u)); ? Cell.RadiusX * FMath::Lerp(
0.07f, 0.15f, ParameterRoll(Hash, BlobSalt + 3u))
: FMath::Clamp(Cell.RadiusX * FMath::Lerp(
0.16f, 0.28f, ParameterRoll(Hash, BlobSalt + 3u)), 8.0f, 18.0f);
Cell.FloatingRadiusY[Blob] = bLegacyStep2eAuditMode
? Cell.FloatingRadius[Blob]
: Cell.FloatingRadius[Blob] * FMath::Lerp(
0.62f, 1.16f, ParameterRoll(Hash, BlobSalt + 4u));
Cell.FloatingRadiusZ[Blob] = bLegacyStep2eAuditMode
? Cell.FloatingRadius[Blob]
: Cell.FloatingRadius[Blob] * FMath::Lerp(
0.58f, 1.62f, ParameterRoll(Hash, BlobSalt + 5u));
} }
} }
if (ParameterRoll(Hash, 0xC6A9u) < P.SpireProbability) if (ParameterRoll(Hash, 0xC6A9u) < P.SpireProbability)
@@ -1014,7 +1132,7 @@ namespace
FORCEINLINE float GeneratedRoomCell( FORCEINLINE float GeneratedRoomCell(
const FGeneratedCaveCell& Cell, const FGeneratedCaveCell& Cell,
float Z, float Radius, float Horizontal, float X, float Y, float Z, float Radius, float Horizontal,
const FParameterizedColumnContext& Column, int32 CellIndex) const FParameterizedColumnContext& Column, int32 CellIndex)
{ {
const float FloorZ = Column.RoomFloors[CellIndex]; const float FloorZ = Column.RoomFloors[CellIndex];
@@ -1043,16 +1161,44 @@ namespace
} }
if ((Cell.FeatureFlags & HasFloatingFlag) != 0) if ((Cell.FeatureFlags & HasFloatingFlag) != 0)
{ {
for (int32 Blob = 0; Blob < 2; ++Blob) const int32 BlobCount = IsLegacyStep2eAuditMode() ? 2 : 1;
for (int32 Blob = 0; Blob < BlobCount; ++Blob)
{ {
if (Room <= -Cell.FloatingRadius[Blob]) const float FloatingSupportRadius = IsLegacyStep2eAuditMode()
? Cell.FloatingRadius[Blob]
: FMath::Max3(Cell.FloatingRadius[Blob],
Cell.FloatingRadiusY[Blob], Cell.FloatingRadiusZ[Blob]);
if (Room <= -FloatingSupportRadius)
{ {
continue; continue;
} }
const float BZ = Z - Cell.FloatingZ[Blob]; const float BZ = Z - Cell.FloatingZ[Blob];
const float BlobSolid = FMath::Sqrt( float BlobSolid = 0.0f;
if (IsLegacyStep2eAuditMode())
{
BlobSolid = FMath::Sqrt(
Column.FloatingXYDistanceSquared[CellIndex][Blob] + BZ * BZ) Column.FloatingXYDistanceSquared[CellIndex][Blob] + BZ * BZ)
- Cell.FloatingRadius[Blob]; - Cell.FloatingRadius[Blob];
}
else
{
const float BX = X - Cell.FloatingX[Blob];
const float BY = Y - Cell.FloatingY[Blob];
const float RadiusX = FMath::Max(Cell.FloatingRadius[Blob], 1.0f);
const float RadiusY = FMath::Max(Cell.FloatingRadiusY[Blob], 1.0f);
const float RadiusZ = FMath::Max(Cell.FloatingRadiusZ[Blob], 1.0f);
const float ScaledLength = FMath::Sqrt(
FMath::Square(BX / RadiusX)
+ FMath::Square(BY / RadiusY)
+ FMath::Square(BZ / RadiusZ));
const float ScaledGradient = FMath::Sqrt(
FMath::Square(BX / FMath::Square(RadiusX))
+ FMath::Square(BY / FMath::Square(RadiusY))
+ FMath::Square(BZ / FMath::Square(RadiusZ)));
BlobSolid = ScaledGradient > 1.0e-3f
? ScaledLength * (ScaledLength - 1.0f) / ScaledGradient
: -FMath::Min3(RadiusX, RadiusY, RadiusZ);
}
if (BlobSolid < Room) if (BlobSolid < Room)
{ {
Room = BlobSolid; Room = BlobSolid;
@@ -1212,7 +1358,20 @@ namespace
const float DY = Y - OutColumn.Cells[Index]->CenterY; const float DY = Y - OutColumn.Cells[Index]->CenterY;
const float DistanceSquared = DX * DX + DY * DY; const float DistanceSquared = DX * DX + DY * DY;
OutColumn.RoomDistanceSquared[Index] = DistanceSquared; OutColumn.RoomDistanceSquared[Index] = DistanceSquared;
if (IsLegacyStep2eAuditMode())
{
OutColumn.RoomRadii[Index] = FMath::Sqrt(DistanceSquared); OutColumn.RoomRadii[Index] = FMath::Sqrt(DistanceSquared);
}
else
{
const FGeneratedCaveCell& Cell = *OutColumn.Cells[Index];
const float LocalX = Cell.RoomCos * DX + Cell.RoomSin * DY;
const float LocalY = -Cell.RoomSin * DX + Cell.RoomCos * DY;
const float ScaledY = LocalY * Cell.RadiusX
/ FMath::Max(Cell.RadiusY, 1.0f);
OutColumn.RoomRadii[Index] = FMath::Sqrt(
LocalX * LocalX + ScaledY * ScaledY);
}
OutColumn.RoomHorizontals[Index] = OutColumn.Cells[Index]->RadiusX OutColumn.RoomHorizontals[Index] = OutColumn.Cells[Index]->RadiusX
- OutColumn.RoomRadii[Index]; - OutColumn.RoomRadii[Index];
@@ -1347,7 +1506,7 @@ namespace
} }
} }
Macro = SmoothMaximum(Macro, Macro = SmoothMaximum(Macro,
GeneratedRoomCell(Cell, Z, GeneratedRoomCell(Cell, X, Y, Z,
ColumnContext->RoomRadii[Index], ColumnContext->RoomRadii[Index],
ColumnContext->RoomHorizontals[Index], ColumnContext->RoomHorizontals[Index],
*ColumnContext, Index), 6.0f); *ColumnContext, Index), 6.0f);
@@ -1384,7 +1543,10 @@ namespace
} }
const float Horizontal = Plan.Generation.ShaftRadiusVoxels const float Horizontal = Plan.Generation.ShaftRadiusVoxels
- ColumnContext->RoomRadii[Index]; - ColumnContext->RoomRadii[Index];
const float Vertical = FMath::Min(Z - Bottom, Top - Z); const float ShaftInset = IsLegacyStep2eAuditMode()
? 0.0f : FMath::Min(48.0f, (Top - Bottom) * 0.20f);
const float Vertical = FMath::Min(
Z - (Bottom + ShaftInset), (Top - ShaftInset) - Z);
Macro = SmoothMaximum(Macro, Macro = SmoothMaximum(Macro,
SmoothMinimum(Horizontal, Vertical, 5.0f), 5.0f); SmoothMinimum(Horizontal, Vertical, 5.0f), 5.0f);
} }
@@ -1458,6 +1620,19 @@ namespace
} }
} }
if (!IsLegacyStep2eAuditMode())
{
// Keep a four-meter roof and floor through rooms, shafts, and authored passage
// modifiers. The same cap is applied again after passage edits in EvaluateInternal.
constexpr float StrateSealThickness = 16.0f;
const float StrateBottom = static_cast<float>(Plan.MinChunkZ * CHUNK_SIZE);
const float StrateTop = static_cast<float>(Plan.MaxChunkZExclusive * CHUNK_SIZE);
const float BoundaryLimit = FMath::Min(
Z - StrateBottom - StrateSealThickness,
StrateTop - StrateSealThickness - Z);
Macro = FMath::Min(Macro, BoundaryLimit);
}
if (OutDetailScale != nullptr) if (OutDetailScale != nullptr)
{ {
*OutDetailScale = DetailFade; *OutDetailScale = DetailFade;
@@ -1524,9 +1699,6 @@ namespace
FMath::Min(VerticalAir, GridAir), LandingMacro); FMath::Min(VerticalAir, GridAir), LandingMacro);
if (Plan.Archetype == ECaveGeneratorType::VerticalShafts) if (Plan.Archetype == ECaveGeneratorType::VerticalShafts)
{ {
// Keep one continuous, walkable cavern horizon between the full-height shaft wells.
// The wells and their tree links add vertical routes; this band makes every authored
// passage landing part of the same deterministic navigation level.
Macro = FMath::Max(Macro, VerticalAir); Macro = FMath::Max(Macro, VerticalAir);
Macro = FMath::Max(Macro, EvaluateVerticalShaftMacro(Plan, X, Y, Z)); Macro = FMath::Max(Macro, EvaluateVerticalShaftMacro(Plan, X, Y, Z));
} }
@@ -1717,6 +1889,11 @@ void SetParameterizedEnabled(bool bEnabled)
CVarResearchParameterizedCave->Set(bEnabled ? 1 : 0, ECVF_SetByCode); CVarResearchParameterizedCave->Set(bEnabled ? 1 : 0, ECVF_SetByCode);
} }
void SetLegacyStep2eAuditMode(bool bEnabled)
{
GUseLegacyStep2eAuditMode.store(bEnabled, std::memory_order_relaxed);
}
void SetParameterSet(int32 ParameterSet) void SetParameterSet(int32 ParameterSet)
{ {
CVarResearchParameterizedCaveSet->Set( CVarResearchParameterizedCaveSet->Set(
@@ -1741,8 +1918,10 @@ bool GetParameterSetInfo(int32 ParameterSet, const TCHAR*& OutName,
{ {
return false; return false;
} }
OutName = GParameterProfiles[ParameterSet].Name; const FParameterProfile& Profile = IsLegacyStep2eAuditMode()
OutParameters = GParameterProfiles[ParameterSet].Parameters; ? GLegacyStep2eProfiles[ParameterSet] : GParameterProfiles[ParameterSet];
OutName = Profile.Name;
OutParameters = Profile.Parameters;
return true; return true;
} }
@@ -1817,7 +1996,7 @@ static float EvaluateInternal(const UVoxelGenerator& Generator,
const uint32 DetailSeed = static_cast<uint32>(Generator.Seed) const uint32 DetailSeed = static_cast<uint32>(Generator.Seed)
^ static_cast<uint32>(Plan->StrateIndex * 7919 + 104729); ^ static_cast<uint32>(Plan->StrateIndex * 7919 + 104729);
const float FinalDetail = bHasDetailValue const float FinalDetail = bHasDetailValue
? DetailValue : DetailAt(WorldX, WorldY, WorldZ, DetailSeed); ? DetailValue : DetailAt(WorldX, WorldY, WorldZ, DetailSeed, bParameterized);
Result = Macro + FinalDetail * DetailFade * DetailScale; Result = Macro + FinalDetail * DetailFade * DetailScale;
const bool bMayContainPassage = bParameterized const bool bMayContainPassage = bParameterized
@@ -1888,6 +2067,17 @@ static float EvaluateInternal(const UVoxelGenerator& Generator,
} }
} }
if (bParameterized && Plan != nullptr && !IsLegacyStep2eAuditMode())
{
constexpr float StrateSealThickness = 16.0f;
const float StrateBottom = static_cast<float>(Plan->MinChunkZ * CHUNK_SIZE);
const float StrateTop = static_cast<float>(Plan->MaxChunkZExclusive * CHUNK_SIZE);
const float BoundaryLimit = FMath::Min(
WorldZ - StrateBottom - StrateSealThickness,
StrateTop - StrateSealThickness - WorldZ);
Result = FMath::Min(Result, BoundaryLimit);
}
if (OutPreEditDensity != nullptr) if (OutPreEditDensity != nullptr)
{ {
*OutPreEditDensity = Result; *OutPreEditDensity = Result;
@@ -1927,6 +2117,24 @@ float EvaluateParameterized(const UVoxelGenerator& Generator,
OutPreEditDensity, DetailScale, nullptr, true, nullptr); OutPreEditDensity, DetailScale, nullptr, true, nullptr);
} }
float EvaluateParameterizedDetail(
const UVoxelGenerator& Generator, float WorldX, float WorldY, float WorldZ)
{
EnsurePlan(Generator);
const FIntVector Chunk(
FMath::FloorToInt(WorldX / CHUNK_SIZE),
FMath::FloorToInt(WorldY / CHUNK_SIZE),
FMath::FloorToInt(WorldZ / CHUNK_SIZE));
const FStratePlan* Plan = FindStrate(Chunk);
if (Plan == nullptr)
{
return 0.0f;
}
const uint32 DetailSeed = static_cast<uint32>(Generator.Seed)
^ static_cast<uint32>(Plan->StrateIndex * 7919 + 104729);
return DetailAt(WorldX, WorldY, WorldZ, DetailSeed, true);
}
float EvaluateParameterizedWithDetailValue(const UVoxelGenerator& Generator, float EvaluateParameterizedWithDetailValue(const UVoxelGenerator& Generator,
float WorldX, float WorldY, float WorldZ, float DetailValue, float WorldX, float WorldY, float WorldZ, float DetailValue,
float* OutPreEditDensity, float DetailScale, float* OutPreEditDensity, float DetailScale,
@@ -2076,7 +2284,7 @@ void BuildParameterizedBrickContext(
void BuildDetailBrick(const UVoxelGenerator& Generator, FIntVector OriginVoxels, void BuildDetailBrick(const UVoxelGenerator& Generator, FIntVector OriginVoxels,
FIntVector Size, float DetailScale, TArray<float>& OutDetail, FIntVector Size, float DetailScale, TArray<float>& OutDetail,
FBrickEvaluationContext* OutContext) FBrickEvaluationContext* OutContext, bool bParameterized)
{ {
(void)DetailScale; (void)DetailScale;
const int32 SampleCount = Size.X * Size.Y * Size.Z; const int32 SampleCount = Size.X * Size.Y * Size.Z;
@@ -2160,7 +2368,7 @@ void BuildDetailBrick(const UVoxelGenerator& Generator, FIntVector OriginVoxels,
OutDetail[(Z * Size.Y + Y) * Size.X + X] = DetailAt( OutDetail[(Z * Size.Y + Y) * Size.X + X] = DetailAt(
static_cast<float>(OriginVoxels.X + X), static_cast<float>(OriginVoxels.X + X),
static_cast<float>(OriginVoxels.Y + Y), static_cast<float>(OriginVoxels.Y + Y),
static_cast<float>(WorldZ), SampleSeed); static_cast<float>(WorldZ), SampleSeed, bParameterized);
} }
} }
return; return;
@@ -2250,8 +2458,13 @@ void BuildDetailBrick(const UVoxelGenerator& Generator, FIntVector OriginVoxels,
{ {
return (Z * CountY + Y) * CountX + X; return (Z * CountY + Y) * CountX + X;
}; };
const auto AddNoiseBand = [&](int32 CellSize, uint32 BandSeed, float Amplitude) const auto AddNoiseBand = [&](int32 CellSize, uint32 BandSeed, float Amplitude,
TArray<float>* OutBand)
{ {
if (OutBand != nullptr)
{
OutBand->SetNumUninitialized(SampleCount);
}
const int32 MinCellX = FMath::FloorToInt( const int32 MinCellX = FMath::FloorToInt(
static_cast<float>(OriginVoxels.X) / static_cast<float>(CellSize)); static_cast<float>(OriginVoxels.X) / static_cast<float>(CellSize));
const int32 MinCellY = FMath::FloorToInt( const int32 MinCellY = FMath::FloorToInt(
@@ -2330,14 +2543,39 @@ void BuildDetailBrick(const UVoxelGenerator& Generator, FIntVector OriginVoxels,
Grid[Index + GridX * GridY + 1], AX), Grid[Index + GridX * GridY + 1], AX),
FMath::Lerp(Grid[Index + GridX * GridY + GridX], FMath::Lerp(Grid[Index + GridX * GridY + GridX],
Grid[Index + GridX * GridY + GridX + 1], AX), AY), AZ); Grid[Index + GridX * GridY + GridX + 1], AX), AY), AZ);
OutDetail[SampleIndex(X, Y, Z)] += Value * Amplitude; const int32 OutputIndex = SampleIndex(X, Y, Z);
if (OutBand != nullptr)
{
(*OutBand)[OutputIndex] = Value;
}
else
{
OutDetail[OutputIndex] += Value * Amplitude;
}
} }
}; };
AddNoiseBand(8, Seed ^ 0x68BC21EBu, 1.4f); if (bParameterized && !IsLegacyStep2eAuditMode())
AddNoiseBand(4, Seed ^ 0x02E5BE93u, 0.9f); {
AddNoiseBand(2, Seed ^ 0x967A889Bu, 0.45f); TArray<float> Broad;
AddNoiseBand(1, Seed ^ 0xA511E9B3u, 2.5f); TArray<float> Medium;
TArray<float> Fine;
AddNoiseBand(32, Seed ^ 0x68BC21EBu, 1.0f, &Broad);
AddNoiseBand(16, Seed ^ 0x02E5BE93u, 1.0f, &Medium);
AddNoiseBand(8, Seed ^ 0x967A889Bu, 1.0f, &Fine);
for (int32 Index = 0; Index < SampleCount; ++Index)
{
const float Ridge = FMath::Sqrt(Fine[Index] * Fine[Index] + 0.0625f) - 0.25f;
OutDetail[Index] = Broad[Index] * 2.0f + Medium[Index] * 1.2f
+ Fine[Index] * 0.6f + Ridge * 0.4f;
}
return;
}
AddNoiseBand(8, Seed ^ 0x68BC21EBu, 1.4f, nullptr);
AddNoiseBand(4, Seed ^ 0x02E5BE93u, 0.9f, nullptr);
AddNoiseBand(2, Seed ^ 0x967A889Bu, 0.45f, nullptr);
AddNoiseBand(1, Seed ^ 0xA511E9B3u, 2.5f, nullptr);
} }
static bool TryClassifyBrickInternal( static bool TryClassifyBrickInternal(
@@ -2511,7 +2749,7 @@ static bool TryClassifyBrickInternal(
? EvaluateParameterizedMacro(*Plan, Centre.X, Centre.Y, Centre.Z, nullptr) ? EvaluateParameterizedMacro(*Plan, Centre.X, Centre.Y, Centre.Z, nullptr)
: EvaluateMacro(*Plan, Centre.X, Centre.Y, Centre.Z, nullptr); : EvaluateMacro(*Plan, Centre.X, Centre.Y, Centre.Z, nullptr);
const float FinalBound = MacroLipschitzBound * Radius const float FinalBound = MacroLipschitzBound * Radius
+ DetailAmplitude * FMath::Abs(DetailScale) + CurrentDetailAmplitude(bParameterized) * FMath::Abs(DetailScale)
+ DetailBoundNumericalMargin * FMath::Max(1.0f, FMath::Abs(DetailScale)); + DetailBoundNumericalMargin * FMath::Max(1.0f, FMath::Abs(DetailScale));
if (Macro > FinalBound) if (Macro > FinalBound)
{ {
+16 -3
View File
@@ -74,10 +74,20 @@ static bool VF_TileFootprintsOverlap(const FVoxelTileKey& A, const FVoxelTileKey
#if WITH_EDITOR #if WITH_EDITOR
#include "VoxelStrateComposer.h" #include "VoxelStrateComposer.h"
#include "VoxelStrateMeasure.h" #include "VoxelStrateMeasure.h"
#include "VoxelRedefinedDensityField.h"
#endif #endif
namespace namespace
{ {
bool IsParameterizedResearchFieldActive()
{
#if WITH_EDITOR
return VoxelForgeRedefinedDensityField::IsParameterizedEnabled();
#else
return false;
#endif
}
int32 GVoxelForgeCollisionGate = 1; int32 GVoxelForgeCollisionGate = 1;
FAutoConsoleVariableRef CVarVoxelForgeCollisionGate( FAutoConsoleVariableRef CVarVoxelForgeCollisionGate(
TEXT("voxel.CollisionGate"), TEXT("voxel.CollisionGate"),
@@ -2197,7 +2207,8 @@ void AVoxelWorld::BeginPlay()
Mesher->bGenerateSkirts = Settings->bGenerateSkirts; Mesher->bGenerateSkirts = Settings->bGenerateSkirts;
Mesher->SkirtCells = Settings->SkirtCells; Mesher->SkirtCells = Settings->SkirtCells;
Mesher->LODOctaveDrop = Settings->LODOctaveDrop; // T2.b — 0 = off Mesher->LODOctaveDrop = Settings->LODOctaveDrop; // T2.b — 0 = off
Mesher->bUseBlockEarlyOut = GVoxelForgeUseBlockEarlyOut != 0; Mesher->bUseBlockEarlyOut = GVoxelForgeUseBlockEarlyOut != 0
&& !IsParameterizedResearchFieldActive();
UE_LOG(LogTemp, Display, UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeClassifierConfig] outer_mode=%d sealed_solid_proof=%d nested_block_early_out=%d"), TEXT("[VoxelForgeClassifierConfig] outer_mode=%d sealed_solid_proof=%d nested_block_early_out=%d"),
@@ -10368,7 +10379,8 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
// même pour les cellules uniformes, et ces tuiles sont rares (fenêtre d'ombre). // même pour les cellules uniformes, et ces tuiles sont rares (fenêtre d'ombre).
// (Gate IsoLevel == 0 : les verdicts du classifieur supposent l'iso MC à zéro exactement.) // (Gate IsoLevel == 0 : les verdicts du classifieur supposent l'iso MC à zéro exactement.)
bool bTrivialEmpty = false; bool bTrivialEmpty = false;
if (GVoxelForgeSealedSolidProof != 0 if (!IsParameterizedResearchFieldActive()
&& GVoxelForgeSealedSolidProof != 0
&& !bWantCapture && SharedDensityOverride == nullptr && !bWantCapture && SharedDensityOverride == nullptr
&& Generator && Mesher && Generator && Mesher
&& Mesher->IsoLevel == 0.0f) && Mesher->IsoLevel == 0.0f)
@@ -10411,7 +10423,8 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
return; return;
} }
const int32 OuterClassifierMode = FMath::Clamp(GVoxelForgeOuterClassifierMode, 0, 1); const int32 OuterClassifierMode = FMath::Clamp(GVoxelForgeOuterClassifierMode, 0, 1);
if (!bTrivialEmpty && OuterClassifierMode != 0 if (!IsParameterizedResearchFieldActive()
&& !bTrivialEmpty && OuterClassifierMode != 0
&& !bWantCapture && SharedDensityOverride == nullptr && !bWantCapture && SharedDensityOverride == nullptr
&& Generator && Mesher && Mesher->IsoLevel == 0.0f) && Generator && Mesher && Mesher->IsoLevel == 0.0f)
{ {
@@ -40,7 +40,7 @@ namespace VoxelForgeRedefinedDensityField
TArray<int32> PassageCandidates; TArray<int32> PassageCandidates;
}; };
/** Cell-level controls for the separate step 2e procedural room-network field. */ /** Cell-level controls for the separate editor-only parameterized cave field. */
struct FGenerationParameters struct FGenerationParameters
{ {
float RoomPitchVoxels = 144.0f; float RoomPitchVoxels = 144.0f;
@@ -101,9 +101,11 @@ namespace VoxelForgeRedefinedDensityField
VOXELFORGE_API float GetDetailScale(); VOXELFORGE_API float GetDetailScale();
VOXELFORGE_API void SetDetailScale(float Scale); VOXELFORGE_API void SetDetailScale(float Scale);
/** Step 2e is editor-only and default-off. -1 rolls one of eight profiles per strate. */ /** The parameterized cave field is editor-only and default-off. -1 rolls per strate. */
VOXELFORGE_API bool IsParameterizedEnabled(); VOXELFORGE_API bool IsParameterizedEnabled();
VOXELFORGE_API void SetParameterizedEnabled(bool bEnabled); VOXELFORGE_API void SetParameterizedEnabled(bool bEnabled);
/** Select the frozen step 2e implementation while the step 2f audit measures its gates. */
VOXELFORGE_API void SetLegacyStep2eAuditMode(bool bEnabled);
VOXELFORGE_API void SetParameterSet(int32 ParameterSet); VOXELFORGE_API void SetParameterSet(int32 ParameterSet);
VOXELFORGE_API int32 GetParameterSet(); VOXELFORGE_API int32 GetParameterSet();
VOXELFORGE_API int32 GetParameterSetCount(); VOXELFORGE_API int32 GetParameterSetCount();
@@ -115,6 +117,10 @@ namespace VoxelForgeRedefinedDensityField
const UVoxelGenerator& Generator, float WorldX, float WorldY, float WorldZ, const UVoxelGenerator& Generator, float WorldX, float WorldY, float WorldZ,
float* OutPreEditDensity = nullptr, float DetailScale = 1.0f); float* OutPreEditDensity = nullptr, float DetailScale = 1.0f);
/** Return the active parameterized detail signal for the band-limit research gate. */
VOXELFORGE_API float EvaluateParameterizedDetail(
const UVoxelGenerator& Generator, float WorldX, float WorldY, float WorldZ);
VOXELFORGE_API float EvaluateParameterizedWithDetailValue( VOXELFORGE_API float EvaluateParameterizedWithDetailValue(
const UVoxelGenerator& Generator, float WorldX, float WorldY, float WorldZ, const UVoxelGenerator& Generator, float WorldX, float WorldY, float WorldZ,
float DetailValue, float* OutPreEditDensity = nullptr, float DetailScale = 1.0f, float DetailValue, float* OutPreEditDensity = nullptr, float DetailScale = 1.0f,
@@ -144,7 +150,8 @@ namespace VoxelForgeRedefinedDensityField
/** Build mathematically identical multi-scale noise for one integer-lattice brick. */ /** Build mathematically identical multi-scale noise for one integer-lattice brick. */
VOXELFORGE_API void BuildDetailBrick(const UVoxelGenerator& Generator, VOXELFORGE_API void BuildDetailBrick(const UVoxelGenerator& Generator,
FIntVector OriginVoxels, FIntVector Size, float DetailScale, FIntVector OriginVoxels, FIntVector Size, float DetailScale,
TArray<float>& OutDetail, FBrickEvaluationContext* OutContext = nullptr); TArray<float>& OutDetail, FBrickEvaluationContext* OutContext = nullptr,
bool bParameterized = false);
/** Evaluate with a detail value produced by BuildDetailBrick. */ /** Evaluate with a detail value produced by BuildDetailBrick. */
VOXELFORGE_API float EvaluateWithDetailValue(const UVoxelGenerator& Generator, VOXELFORGE_API float EvaluateWithDetailValue(const UVoxelGenerator& Generator,
File diff suppressed because it is too large. Load diff
@@ -312,7 +312,7 @@ namespace VoxelForgeRegionEval
const double EvaluationStart = FPlatformTime::Seconds(); const double EvaluationStart = FPlatformTime::Seconds();
VoxelForgeRedefinedDensityField::BuildDetailBrick( VoxelForgeRedefinedDensityField::BuildDetailBrick(
Generator, Brick.Origin, Brick.Size, DetailScale, BrickDetail, Generator, Brick.Origin, Brick.Size, DetailScale, BrickDetail,
bParameterized ? nullptr : &BrickContext); bParameterized ? nullptr : &BrickContext, bParameterized);
if (bParameterized) if (bParameterized)
{ {
VoxelForgeRedefinedDensityField::BuildParameterizedBrickContext( VoxelForgeRedefinedDensityField::BuildParameterizedBrickContext(
+78 -5
View File
@@ -1,6 +1,6 @@
[CmdletBinding()] [CmdletBinding()]
param( param(
[ValidateSet('canonical', 'owner', 'probe', 'perf', 'visual-edit', 'visual-lod', 'visual-gap-walk', 'visual-gap-inject', 'dig-motion', 'tests', 'surface-fall', 'crossing', 'gate-stress', 'parity', 'horizon', 'horizon-render', 'determinism', 'symbolize-stacks', 'region-eval', 'step2d', 'step2e')] [ValidateSet('canonical', 'owner', 'probe', 'perf', 'visual-edit', 'visual-lod', 'visual-gap-walk', 'visual-gap-inject', 'dig-motion', 'tests', 'surface-fall', 'crossing', 'gate-stress', 'parity', 'horizon', 'horizon-render', 'determinism', 'symbolize-stacks', 'region-eval', 'step2d', 'step2e', 'step2f', 'step2f-capture')]
[string]$Scenario = 'canonical', [string]$Scenario = 'canonical',
[switch]$Build, [switch]$Build,
[switch]$EnableRender, [switch]$EnableRender,
@@ -22,6 +22,10 @@ param(
[int]$HorizonLevel = 5, [int]$HorizonLevel = 5,
[ValidateRange(1, 100000)] [ValidateRange(1, 100000)]
[int]$HorizonRenderDistanceChunks = 768, [int]$HorizonRenderDistanceChunks = 768,
[ValidateRange(0, 7)]
[int]$Step2fProfile = 0,
[ValidateSet('inside', 'wall-closeup')]
[string]$Step2fCaptureView = 'inside',
[ValidateSet(32, 64, 128)] [ValidateSet(32, 64, 128)]
[int]$ParityExportSize = 128, [int]$ParityExportSize = 128,
[string]$ParityExportTile = '', [string]$ParityExportTile = '',
@@ -62,12 +66,12 @@ $AssetEvidence = @()
$CommandletMetrics = [ordered]@{} $CommandletMetrics = [ordered]@{}
$GameMetrics = [ordered]@{} $GameMetrics = [ordered]@{}
$HorizonMetrics = [ordered]@{ $HorizonMetrics = [ordered]@{
requested = $Scenario -in @('horizon', 'horizon-render') requested = $Scenario -in @('horizon', 'horizon-render', 'step2f-capture')
status = 'not_run' status = 'not_run'
level = $HorizonLevel level = $HorizonLevel
render_distance_chunks = $HorizonRenderDistanceChunks render_distance_chunks = $HorizonRenderDistanceChunks
} }
$HorizonRenderMetrics = [ordered]@{ requested = $Scenario -eq 'horizon-render'; status = 'not_run' } $HorizonRenderMetrics = [ordered]@{ requested = $Scenario -in @('horizon-render', 'step2f-capture'); status = 'not_run' }
$DeterminismEvidence = [ordered]@{ requested = $Scenario -eq 'determinism'; status = 'not_run' } $DeterminismEvidence = [ordered]@{ requested = $Scenario -eq 'determinism'; status = 'not_run' }
$SurfaceFallMetrics = [ordered]@{ $SurfaceFallMetrics = [ordered]@{
requested = $Scenario -eq 'surface-fall' -or requested = $Scenario -eq 'surface-fall' -or
@@ -575,7 +579,7 @@ function Sync-OwnerAssets {
$null = Copy-Item -LiteralPath $source -Destination $target -Force -PassThru $null = Copy-Item -LiteralPath $source -Destination $target -Force -PassThru
[void]$rows.Add([ordered]@{ [void]$rows.Add([ordered]@{
name = $assetName name = $assetName
used_by_scenario = $Scenario -in @('owner', 'probe', 'parity', 'tests', 'surface-fall', 'crossing', 'gate-stress', 'visual-edit', 'visual-lod', 'visual-gap-walk', 'visual-gap-inject', 'dig-motion', 'horizon', 'horizon-render', 'determinism', 'region-eval', 'step2d', 'step2e') used_by_scenario = $Scenario -in @('owner', 'probe', 'parity', 'tests', 'surface-fall', 'crossing', 'gate-stress', 'visual-edit', 'visual-lod', 'visual-gap-walk', 'visual-gap-inject', 'dig-motion', 'horizon', 'horizon-render', 'determinism', 'region-eval', 'step2d', 'step2e', 'step2f', 'step2f-capture')
source_path = $source source_path = $source
source_sha256 = Get-Sha256 $source source_sha256 = Get-Sha256 $source
staged_path = $target staged_path = $target
@@ -623,7 +627,7 @@ function Get-AssetEvidence {
$target = Join-Path $HostRoot "Content\VoxelForge\$assetName.uasset" $target = Join-Path $HostRoot "Content\VoxelForge\$assetName.uasset"
[void]$rows.Add([ordered]@{ [void]$rows.Add([ordered]@{
name = $assetName name = $assetName
used_by_scenario = $Scenario -in @('owner', 'probe', 'parity', 'tests', 'surface-fall', 'crossing', 'gate-stress', 'visual-edit', 'visual-lod', 'visual-gap-walk', 'visual-gap-inject', 'dig-motion', 'horizon', 'horizon-render', 'determinism', 'region-eval', 'step2d', 'step2e') used_by_scenario = $Scenario -in @('owner', 'probe', 'parity', 'tests', 'surface-fall', 'crossing', 'gate-stress', 'visual-edit', 'visual-lod', 'visual-gap-walk', 'visual-gap-inject', 'dig-motion', 'horizon', 'horizon-render', 'determinism', 'region-eval', 'step2d', 'step2e', 'step2f', 'step2f-capture')
source_path = $source source_path = $source
source_sha256 = Get-Sha256 $source source_sha256 = Get-Sha256 $source
staged_path = $target staged_path = $target
@@ -1526,6 +1530,33 @@ try {
} }
} }
} }
'step2f' {
$step2fLog = Join-Path $RunRoot 'Logs\step2f.log'
$args = New-CommonArguments 'commandlet' $step2fLog (Merge-Cvars $baseCvars)
$args += New-CommandletArguments $commandletOut 'walk,export' ($Assets -eq 'owner') $false 128
$researchOut = Join-Path $PluginRoot "Saved\Research\step2f\$Label"
$args += @('-step2f', '-regionworkers=6', ('-researchout="{0}"' -f $researchOut))
$commandletLedger = Invoke-UnrealLaunch 'step2f-commandlet' $args `
$step2fLog (Join-Path $RunRoot 'Logs\step2f.stdout.log') `
(Join-Path $RunRoot 'Logs\step2f.stderr.log')
$step2fReportPath = Join-Path $researchOut 'step2f_report.json'
$step2fReport = Read-JsonFile $step2fReportPath
if ($null -eq $step2fReport) {
$CommandletMetrics = [ordered]@{ status = 'missing'; output = $researchOut; report = $step2fReportPath }
Add-Failure "step2f did not produce $step2fReportPath"
} else {
$CommandletMetrics = [ordered]@{
status = if ((Get-JsonValue $step2fReport 'pass' $false) -and $commandletLedger.status -eq 'passed') { 'passed' } else { 'failed' }
output = $researchOut
report = $step2fReportPath
worst_profile_meets_600ms = Get-JsonValue $step2fReport 'worst_profile_meets_600ms' $false
result = $step2fReport
}
if (-not (Get-JsonValue $step2fReport 'pass' $false)) {
Add-Failure 'step2f geometry gates, 2e rejection audit, speed, sound proofs, seams, or determinism failed'
}
}
}
'symbolize-stacks' { 'symbolize-stacks' {
if (([string]::IsNullOrWhiteSpace($StackSamplesPath)) -or -not (Test-Path -LiteralPath $StackSamplesPath -PathType Leaf)) { if (([string]::IsNullOrWhiteSpace($StackSamplesPath)) -or -not (Test-Path -LiteralPath $StackSamplesPath -PathType Leaf)) {
throw "-StackSamplesPath must name an existing raw sampler CSV." throw "-StackSamplesPath must name an existing raw sampler CSV."
@@ -1591,6 +1622,47 @@ try {
Add-Failure "horizon render did not produce a capture: $($HorizonRenderMetrics.status)" Add-Failure "horizon render did not produce a capture: $($HorizonRenderMetrics.status)"
} }
} }
'step2f-capture' {
$tracePath = Join-Path $RunRoot 'step2f_capture_trace.jsonl'
$baseCvars['voxel.TestRenderDistanceChunks'] = $HorizonRenderDistanceChunks
$baseCvars['voxel.TestMaxClipLevel'] = $HorizonLevel
$baseCvars['voxel.StartupTraceFile'] = $tracePath
$baseCvars['voxel.StartupTraceMaxTiles'] = 16384
$baseCvars['voxel.TestCeilingView'] = 1
$baseCvars['voxel.TestCeilingCaptureDelaySeconds'] = 60
$baseCvars['voxel.TestExitSeconds'] = 120
$baseCvars['voxel.OuterClassifierMode'] = 0
$baseCvars['voxel.ResearchParameterizedCave'] = 1
$baseCvars['voxel.ResearchParameterizedCaveSet'] = $Step2fProfile
$renderLog = Join-Path $RunRoot 'Logs\step2f-capture.log'
$args = New-CommonArguments 'game' $renderLog (Merge-Cvars $baseCvars) $true
$gameLedger = Invoke-UnrealLaunch "step2f-profile-$Step2fProfile-$Step2fCaptureView" $args `
$renderLog (Join-Path $RunRoot 'Logs\step2f-capture.stdout.log') `
(Join-Path $RunRoot 'Logs\step2f-capture.stderr.log')
$GameMetrics = Get-StreamingMetrics $renderLog
$HorizonMetrics = Get-HorizonMetrics $tracePath $gameLedger $GameMetrics
$HorizonRenderMetrics = Get-HorizonRenderMetrics $gameLedger $renderLog
if ($HorizonMetrics.status -ne 'ok') {
Add-Failure "step2f capture render trace did not settle: $($HorizonMetrics.status)"
}
if ($HorizonRenderMetrics.status -ne 'passed') {
Add-Failure "step2f profile $Step2fProfile in-game capture failed: $($HorizonRenderMetrics.status)"
} else {
$profileNames = @('OpenVaults', 'TightNetwork', 'ChamberHalls', 'ShaftForest', 'FloatingArchipelago', 'PillaredCathedral', 'TerracedBasin', 'HyperComplex')
$profileName = $profileNames[$Step2fProfile]
$captureDirectory = Join-Path $PluginRoot 'Saved\Research\step2f\captures'
Ensure-Directory $captureDirectory
$captureName = '{0}_{1}.png' -f $profileName, $Step2fCaptureView
$capturePath = Join-Path $captureDirectory $captureName
$image = @($HorizonRenderMetrics.images | Sort-Object modified_utc | Select-Object -Last 1)
if ($image.Count -eq 0 -or -not (Test-Path -LiteralPath $image[0].path -PathType Leaf)) {
Add-Failure "step2f profile $Step2fProfile image path was missing after capture"
} else {
Copy-Item -LiteralPath $image[0].path -Destination $capturePath -Force
$HorizonRenderMetrics.saved_capture = $capturePath
}
}
}
'determinism' { 'determinism' {
$defaultDump = Join-Path $RunRoot 'tile_hashes_default.json' $defaultDump = Join-Path $RunRoot 'tile_hashes_default.json'
$crtFma3OffDump = Join-Path $RunRoot 'tile_hashes_crt_fma3_off.json' $crtFma3OffDump = Join-Path $RunRoot 'tile_hashes_crt_fma3_off.json'
@@ -1932,6 +2004,7 @@ $result = [ordered]@{
commandlet = $CommandletMetrics commandlet = $CommandletMetrics
region_eval = if ($Scenario -eq 'region-eval') { $CommandletMetrics } else { $null } region_eval = if ($Scenario -eq 'region-eval') { $CommandletMetrics } else { $null }
step2e = if ($Scenario -eq 'step2e') { $CommandletMetrics } else { $null } step2e = if ($Scenario -eq 'step2e') { $CommandletMetrics } else { $null }
step2f = if ($Scenario -eq 'step2f') { $CommandletMetrics } else { $null }
game = $GameMetrics game = $GameMetrics
horizon = $HorizonMetrics horizon = $HorizonMetrics
horizon_render = $HorizonRenderMetrics horizon_render = $HorizonRenderMetrics