research(step2f): coherent caves pass every gate, worst 572 ms idle -- look still to be judged in game
Editor-only, default off (voxel.ResearchParameterizedCave 1; the live skip proofs now decline automatically). Jittered elliptical chambers of varied size and height with rock between them, narrow tunnels, capped shafts, occasional pillars/ledges/stalactites/stalagmites/hanging masses; detail bands at 48/24/12 samples (shortest 3 m), bound 3.822. Gates, step 2e -> step 2f R29 (worst of 8 profiles), each rejecting 2e: crumbs < 1 m^3 2,290 -> 0; dihedral >45 deg 40.7% -> 2.2%; lattice detail energy 0.83 -> 0.007 (RMS floor kept: 0.484 >= 0.40); LOD1 distance p95 0.709 -> 0.377 samples; grid autocorrelation 0.667 -> 0.348; solid share 2.7% -> 79-82%; roof/floor openings 26,657 -> 0. Speed, 259^3 ring, six workers, idle machine (20 GB free): 527-572 ms, worst TerracedBasin 572.3 ms (R27 591 ms, R28 603 ms: run-to-run spread is ~5%). Proof audit 0 violations, undersized control caught; seams pass; 1 vs 6 worker hash 876069a813c83cee; TraversalSeeds pass. Captures are UNLIT (lit runs showed only sky; three D3D12 device-removed crashes): they show the material's tiling, not the cave forms, so the look is not yet judged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
No files matched your search
@@ -117,11 +117,11 @@ 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|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.
|
||||
| `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, finds a positive-density camera point and aims at a sampled rock wall for an inside or wall-closeup view, then saves it under `Saved/Research/step2f/captures` (pass the persistent render DDC with `-SharedDDC`); `determinism` compares per-tile hashes across the default CRT path and `-voxel.CrtFma3=0`; `region-eval` compares the live path with the default-off editor chunk program on the 259³ lattice and five-seed probe set; `step2d` retains the earlier independent field; `step2e` retains its earlier benchmark; `step2f` compares frozen step 2e and the coherent field for all eight profiles on the same 259³ ring, applies geometry gates, and checks sound bounds, seams, and 1-vs-6-worker hashes. `-RegionHashProbe -RegionWorkers N -RegionReferenceHash HASH` checks a full-ring hash at another partition count. `-SampleDensityStacks` captures profiles, and `-StackSampleIntervalUs` sets the sampling interval.
|
||||
| `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` 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/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 (p95 threshold 0.5 lattice samples), 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 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` (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. |
|
||||
| `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 independently rotated 48/24/12-sample bands and smooth ridge response; the twelve-sample shortest wavelength and 3.822-unit final-field detail bound 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 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. |
|
||||
| `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. |
|
||||
|
||||
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
@@ -19,7 +19,8 @@ namespace VoxelForgeRedefinedDensityField
|
||||
namespace
|
||||
{
|
||||
constexpr float LegacyDetailAmplitude = 1.4f + 0.9f + 0.45f + 2.5f;
|
||||
constexpr float CoherentDetailAmplitude = 2.0f + 1.2f + 0.6f + 0.4f;
|
||||
constexpr float CoherentDetailAmplitude = 1.82f + 1.092f + 0.546f + 0.364f;
|
||||
constexpr float CoherentRockDensityBias = 0.10f;
|
||||
constexpr float DetailBoundNumericalMargin = 1.0e-3f;
|
||||
constexpr float BaseFieldLipschitzBound = 1.25f;
|
||||
constexpr float ParameterizedFieldLipschitzBound = 1.60f;
|
||||
@@ -271,14 +272,25 @@ namespace
|
||||
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
|
||||
// The shortest wavelength is twelve lattice samples (three meters). Three
|
||||
// independently rotated, 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);
|
||||
// Independent orthonormal frames keep the value-noise bands smooth and bounded without
|
||||
// exposing their axis-aligned interpolation cells as repeated square relief on the rock.
|
||||
const float BroadX = 0.838671f * X - 0.493587f * Y + 0.230129f * Z;
|
||||
const float BroadY = 0.544639f * X + 0.760095f * Y - 0.354003f * Z;
|
||||
const float BroadZ = 0.422618f * Y + 0.906308f * Z;
|
||||
const float MediumX = 0.956305f * X + 0.220661f * Y + 0.191825f * Z;
|
||||
const float MediumY = -0.292372f * X + 0.721484f * Y + 0.627548f * Z;
|
||||
const float MediumZ = 0.656059f * Y + 0.754710f * Z;
|
||||
const float FineX = 0.325568f * X - 0.834725f * Y - 0.443914f * Z;
|
||||
const float FineY = 0.945519f * X + 0.287381f * Y + 0.152824f * Z;
|
||||
const float FineZ = -0.469472f * Y + 0.882948f * Z;
|
||||
const float Broad = Noise3D(BroadX, BroadY, BroadZ, 48.0f, Seed ^ 0x68BC21EBu);
|
||||
const float Medium = Noise3D(MediumX, MediumY, MediumZ, 24.0f, Seed ^ 0x02E5BE93u);
|
||||
const float Fine = Noise3D(FineX, FineY, FineZ, 12.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;
|
||||
return Broad * 1.82f + Medium * 1.092f + Fine * 0.546f + SmoothRidge * 0.364f;
|
||||
}
|
||||
|
||||
FORCEINLINE float DistanceToPeriodicLine(float Position, float Spacing, float Offset)
|
||||
@@ -869,6 +881,10 @@ namespace
|
||||
{
|
||||
FGeneratedCaveCell GeneratedCells[9];
|
||||
const FGeneratedCaveCell* Cells[9] = {};
|
||||
const FGeneratedCaveCell* TunnelCells[25] = {};
|
||||
uint8 TunnelEdgeCellA[24] = {};
|
||||
uint8 TunnelEdgeCellB[24] = {};
|
||||
int32 TunnelEdgeCount = 0;
|
||||
float RoomDistanceSquared[9] = {};
|
||||
float RoomRadii[9] = {};
|
||||
float RoomHorizontals[9] = {};
|
||||
@@ -879,8 +895,8 @@ namespace
|
||||
float SpireTipZ[9] = {};
|
||||
float SpireBaseTopZ[9] = {};
|
||||
float SpireTipBottomZ[9] = {};
|
||||
float TunnelT[4] = {};
|
||||
float TunnelHorizontal[4] = {};
|
||||
float TunnelT[24] = {};
|
||||
float TunnelHorizontal[24] = {};
|
||||
float LandingDistanceSquared[8] = {};
|
||||
float LandingDistance[8] = {};
|
||||
float LandingBridgeT[8] = {};
|
||||
@@ -1401,25 +1417,36 @@ namespace
|
||||
}
|
||||
if ((Cell.FeatureFlags & HasSpireFlag) != 0)
|
||||
{
|
||||
OutColumn.SpireBaseZ[Index] = Cell.FloorZ + 2.0f;
|
||||
OutColumn.SpireBaseZ[Index] = Cell.FloorZ - 2.0f;
|
||||
OutColumn.SpireTipZ[Index] = FMath::Min(
|
||||
Cell.CeilingZ - 2.0f,
|
||||
OutColumn.SpireBaseZ[Index] + Cell.SpireLength);
|
||||
OutColumn.SpireBaseTopZ[Index] = Cell.CeilingZ - 2.0f;
|
||||
OutColumn.SpireBaseTopZ[Index] = Cell.CeilingZ + 2.0f;
|
||||
OutColumn.SpireTipBottomZ[Index] = FMath::Max(
|
||||
Cell.FloorZ + 2.0f,
|
||||
Cell.FloorZ - 2.0f,
|
||||
OutColumn.SpireBaseTopZ[Index] - Cell.SpireLength);
|
||||
}
|
||||
}
|
||||
|
||||
for (int32 CellY = -1; CellY <= 1; ++CellY)
|
||||
for (int32 CellX = -1; CellX <= 1; ++CellX)
|
||||
{
|
||||
const int32 TunnelIndex = (CellY + 2) * 5 + CellX + 2;
|
||||
const int32 RoomIndex = (CellY + 1) * 3 + CellX + 1;
|
||||
OutColumn.TunnelCells[TunnelIndex] = OutColumn.Cells[RoomIndex];
|
||||
}
|
||||
|
||||
if (IsLegacyStep2eAuditMode())
|
||||
{
|
||||
// Keep the frozen 2e baseline byte-for-byte in its original four-edge evaluation.
|
||||
const FGeneratedCaveCell& Centre = *OutColumn.Cells[4];
|
||||
const int32 EdgeIndices[] = {1, 7, 3, 5};
|
||||
const int32 TunnelCellIndices[] = {7, 17, 11, 13};
|
||||
const uint8 EdgeFlags[] = {
|
||||
TunnelToNegativeY, TunnelToPositiveY,
|
||||
TunnelToNegativeX, TunnelToPositiveX};
|
||||
for (int32 EdgeIndex = 0; EdgeIndex < UE_ARRAY_COUNT(EdgeIndices); ++EdgeIndex)
|
||||
{
|
||||
OutColumn.TunnelHorizontal[EdgeIndex] = -FLT_MAX;
|
||||
if ((Centre.OpenTunnelEdges & EdgeFlags[EdgeIndex]) == 0)
|
||||
{
|
||||
continue;
|
||||
@@ -1434,9 +1461,89 @@ namespace
|
||||
: 0.0f;
|
||||
const float DX = X - (Centre.CenterX + T * ABX);
|
||||
const float DY = Y - (Centre.CenterY + T * ABY);
|
||||
OutColumn.TunnelT[EdgeIndex] = T;
|
||||
OutColumn.TunnelHorizontal[EdgeIndex] = Plan.Generation.TunnelRadiusVoxels
|
||||
OutColumn.TunnelEdgeCellA[OutColumn.TunnelEdgeCount] = 12;
|
||||
OutColumn.TunnelEdgeCellB[OutColumn.TunnelEdgeCount] =
|
||||
static_cast<uint8>(TunnelCellIndices[EdgeIndex]);
|
||||
OutColumn.TunnelT[OutColumn.TunnelEdgeCount] = T;
|
||||
OutColumn.TunnelHorizontal[OutColumn.TunnelEdgeCount] =
|
||||
Plan.Generation.TunnelRadiusVoxels - FMath::Sqrt(DX * DX + DY * DY);
|
||||
++OutColumn.TunnelEdgeCount;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto CacheTunnelEdge = [&](int32 AX, int32 AY, int32 BX, int32 BY)
|
||||
{
|
||||
const uint8 CellAIndex = static_cast<uint8>((AY + 2) * 5 + AX + 2);
|
||||
const uint8 CellBIndex = static_cast<uint8>((BY + 2) * 5 + BX + 2);
|
||||
const FGeneratedCaveCell& A = *OutColumn.TunnelCells[CellAIndex];
|
||||
const FGeneratedCaveCell& B = *OutColumn.TunnelCells[CellBIndex];
|
||||
const uint8 OpenEdgeFlag = AX != BX
|
||||
? TunnelToPositiveX : TunnelToPositiveY;
|
||||
if ((A.OpenTunnelEdges & OpenEdgeFlag) == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Beyond this horizontal reach, even the largest supported detail and tunnel
|
||||
// smooth-union rise cannot turn rock into air. Reject before calculating the
|
||||
// point-to-segment distance.
|
||||
constexpr float MaximumTunnelSignReach =
|
||||
CoherentDetailAmplitude * 4.0f + 1.25f + 2.0f;
|
||||
const float Reach = Plan.Generation.TunnelRadiusVoxels
|
||||
+ MaximumTunnelSignReach;
|
||||
const float MinX = FMath::Min(A.CenterX, B.CenterX);
|
||||
const float MaxX = FMath::Max(A.CenterX, B.CenterX);
|
||||
const float MinY = FMath::Min(A.CenterY, B.CenterY);
|
||||
const float MaxY = FMath::Max(A.CenterY, B.CenterY);
|
||||
const float GapX = X < MinX ? MinX - X : (X > MaxX ? X - MaxX : 0.0f);
|
||||
const float GapY = Y < MinY ? MinY - Y : (Y > MaxY ? Y - MaxY : 0.0f);
|
||||
if (GapX * GapX + GapY * GapY >= Reach * Reach)
|
||||
{
|
||||
return;
|
||||
}
|
||||
check(OutColumn.TunnelEdgeCount < UE_ARRAY_COUNT(OutColumn.TunnelT));
|
||||
const float ABX = B.CenterX - A.CenterX;
|
||||
const float ABY = B.CenterY - A.CenterY;
|
||||
const float LengthSquared = ABX * ABX + ABY * ABY;
|
||||
const float T = LengthSquared > 1.0e-8f
|
||||
? FMath::Clamp(((X - A.CenterX) * ABX
|
||||
+ (Y - A.CenterY) * ABY) / LengthSquared, 0.0f, 1.0f)
|
||||
: 0.0f;
|
||||
const float DX = X - (A.CenterX + T * ABX);
|
||||
const float DY = Y - (A.CenterY + T * ABY);
|
||||
const float Horizontal = Plan.Generation.TunnelRadiusVoxels
|
||||
- FMath::Sqrt(DX * DX + DY * DY);
|
||||
int32 InsertAt = OutColumn.TunnelEdgeCount;
|
||||
while (InsertAt > 0
|
||||
&& OutColumn.TunnelHorizontal[InsertAt - 1] < Horizontal)
|
||||
{
|
||||
OutColumn.TunnelEdgeCellA[InsertAt] =
|
||||
OutColumn.TunnelEdgeCellA[InsertAt - 1];
|
||||
OutColumn.TunnelEdgeCellB[InsertAt] =
|
||||
OutColumn.TunnelEdgeCellB[InsertAt - 1];
|
||||
OutColumn.TunnelT[InsertAt] = OutColumn.TunnelT[InsertAt - 1];
|
||||
OutColumn.TunnelHorizontal[InsertAt] =
|
||||
OutColumn.TunnelHorizontal[InsertAt - 1];
|
||||
--InsertAt;
|
||||
}
|
||||
OutColumn.TunnelEdgeCellA[InsertAt] = CellAIndex;
|
||||
OutColumn.TunnelEdgeCellB[InsertAt] = CellBIndex;
|
||||
OutColumn.TunnelT[InsertAt] = T;
|
||||
OutColumn.TunnelHorizontal[InsertAt] = Horizontal;
|
||||
++OutColumn.TunnelEdgeCount;
|
||||
};
|
||||
// Every undirected edge whose endpoints lie in the room-query 3x3 neighborhood is
|
||||
// considered once. This retains the old base cell's edges when the query base shifts.
|
||||
for (int32 CellY = -1; CellY <= 1; ++CellY)
|
||||
for (int32 CellX = -1; CellX < 1; ++CellX)
|
||||
{
|
||||
CacheTunnelEdge(CellX, CellY, CellX + 1, CellY);
|
||||
}
|
||||
for (int32 CellX = -1; CellX <= 1; ++CellX)
|
||||
for (int32 CellY = -1; CellY < 1; ++CellY)
|
||||
{
|
||||
CacheTunnelEdge(CellX, CellY, CellX, CellY + 1);
|
||||
}
|
||||
}
|
||||
|
||||
OutColumn.bHasLandingXYCache = Plan.Landings.Num() <= UE_ARRAY_COUNT(
|
||||
@@ -1511,21 +1618,66 @@ namespace
|
||||
ColumnContext->RoomHorizontals[Index],
|
||||
*ColumnContext, Index), 6.0f);
|
||||
}
|
||||
const FGeneratedCaveCell& Centre = *ColumnContext->Cells[4];
|
||||
const int32 EdgeIndices[] = {1, 7, 3, 5};
|
||||
const uint8 EdgeFlags[] = {
|
||||
TunnelToNegativeY, TunnelToPositiveY,
|
||||
TunnelToNegativeX, TunnelToPositiveX};
|
||||
for (int32 EdgeIndex = 0; EdgeIndex < UE_ARRAY_COUNT(EdgeIndices); ++EdgeIndex)
|
||||
const bool bLegacyStep2eAuditMode = IsLegacyStep2eAuditMode();
|
||||
if (bLegacyStep2eAuditMode)
|
||||
{
|
||||
if ((Centre.OpenTunnelEdges & EdgeFlags[EdgeIndex]) != 0)
|
||||
for (int32 EdgeIndex = 0; EdgeIndex < ColumnContext->TunnelEdgeCount; ++EdgeIndex)
|
||||
{
|
||||
const FGeneratedCaveCell& A = *ColumnContext->TunnelCells[
|
||||
ColumnContext->TunnelEdgeCellA[EdgeIndex]];
|
||||
const FGeneratedCaveCell& B = *ColumnContext->TunnelCells[
|
||||
ColumnContext->TunnelEdgeCellB[EdgeIndex]];
|
||||
Macro = SmoothMaximum(Macro, GeneratedTunnelEdge(
|
||||
Plan, Centre, *ColumnContext->Cells[EdgeIndices[EdgeIndex]], Z,
|
||||
ColumnContext->TunnelT[EdgeIndex],
|
||||
Plan, A, B, Z, ColumnContext->TunnelT[EdgeIndex],
|
||||
ColumnContext->TunnelHorizontal[EdgeIndex]), 5.0f);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Far inside solid rock, density magnitude is only a proof margin. Capping it at
|
||||
// -64 preserves every sign even at the supported 4x detail scale, and makes tunnels
|
||||
// at least 5 units below that floor mathematically irrelevant to the smooth union.
|
||||
Macro = FMath::Max(Macro, -64.0f);
|
||||
const float MinimumTunnelFloor =
|
||||
Plan.GenerationFloorZ - Plan.Generation.FloorReliefVoxels;
|
||||
const float MaximumTunnelFloor =
|
||||
Plan.GenerationFloorZ + Plan.Generation.FloorReliefVoxels;
|
||||
const float VerticalTunnelUpperBound = FMath::Min(
|
||||
Z - MinimumTunnelFloor,
|
||||
MaximumTunnelFloor + 2.0f * Plan.Generation.TunnelHalfHeightVoxels - Z);
|
||||
constexpr float MaximumDetailAtSupportedScale = CoherentDetailAmplitude * 4.0f;
|
||||
constexpr float MaximumSmoothUnionRise = 1.25f;
|
||||
constexpr float SolidTunnelMargin =
|
||||
MaximumDetailAtSupportedScale + MaximumSmoothUnionRise + 2.0f;
|
||||
const bool bNoTunnelCanChangeMacro = VerticalTunnelUpperBound <= Macro - 5.0f;
|
||||
const bool bNoTunnelCanChangeSign = Macro <= -SolidTunnelMargin
|
||||
&& VerticalTunnelUpperBound <= -SolidTunnelMargin;
|
||||
float TunnelMacro = -FLT_MAX;
|
||||
if (!bNoTunnelCanChangeMacro && !bNoTunnelCanChangeSign)
|
||||
{
|
||||
for (int32 EdgeIndex = 0;
|
||||
EdgeIndex < ColumnContext->TunnelEdgeCount; ++EdgeIndex)
|
||||
{
|
||||
const float Horizontal = ColumnContext->TunnelHorizontal[EdgeIndex];
|
||||
if (Horizontal <= Macro - 5.0f || Horizontal <= TunnelMacro)
|
||||
{
|
||||
// Remaining tunnel SDFs cannot change either the selected maximum or the
|
||||
// room/tunnel smooth union; edges are ordered by descending horizontal bound.
|
||||
break;
|
||||
}
|
||||
const FGeneratedCaveCell& A = *ColumnContext->TunnelCells[
|
||||
ColumnContext->TunnelEdgeCellA[EdgeIndex]];
|
||||
const FGeneratedCaveCell& B = *ColumnContext->TunnelCells[
|
||||
ColumnContext->TunnelEdgeCellB[EdgeIndex]];
|
||||
TunnelMacro = FMath::Max(TunnelMacro, GeneratedTunnelEdge(
|
||||
Plan, A, B, Z, ColumnContext->TunnelT[EdgeIndex], Horizontal));
|
||||
}
|
||||
}
|
||||
if (TunnelMacro > -FLT_MAX * 0.5f)
|
||||
{
|
||||
Macro = SmoothMaximum(Macro, TunnelMacro, 5.0f);
|
||||
}
|
||||
}
|
||||
|
||||
for (int32 Index = 0; Index < UE_ARRAY_COUNT(ColumnContext->Cells); ++Index)
|
||||
{
|
||||
@@ -1631,6 +1783,9 @@ namespace
|
||||
Z - StrateBottom - StrateSealThickness,
|
||||
StrateTop - StrateSealThickness - Z);
|
||||
Macro = FMath::Min(Macro, BoundaryLimit);
|
||||
// A 2.5 cm solidward offset closes isolated near-zero air samples and helps
|
||||
// sub-cubic-meter rock contacts meet the surrounding wall without changing detail.
|
||||
Macro -= CoherentRockDensityBias;
|
||||
}
|
||||
|
||||
if (OutDetailScale != nullptr)
|
||||
@@ -2117,6 +2272,60 @@ float EvaluateParameterized(const UVoxelGenerator& Generator,
|
||||
OutPreEditDensity, DetailScale, nullptr, true, nullptr);
|
||||
}
|
||||
|
||||
float EvaluateParameterizedMacroOnly(
|
||||
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);
|
||||
return Plan != nullptr
|
||||
? EvaluateParameterizedMacro(*Plan, WorldX, WorldY, WorldZ, nullptr)
|
||||
: 0.0f;
|
||||
}
|
||||
|
||||
FString DescribeParameterizedMacroAt(
|
||||
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 TEXT("no-plan");
|
||||
}
|
||||
|
||||
FParameterizedColumnContext Column;
|
||||
BuildParameterizedColumnContext(*Plan, WorldX, WorldY, nullptr, Column);
|
||||
const FIntPoint BaseCell = GenerationCellAt(*Plan, WorldX, WorldY);
|
||||
FString Description = FString::Printf(
|
||||
TEXT("base=(%d,%d); cells="), BaseCell.X, BaseCell.Y);
|
||||
for (int32 Index = 0; Index < UE_ARRAY_COUNT(Column.Cells); ++Index)
|
||||
{
|
||||
const FGeneratedCaveCell& Cell = *Column.Cells[Index];
|
||||
const float Room = GeneratedRoomCell(Cell, WorldX, WorldY, WorldZ,
|
||||
Column.RoomRadii[Index], Column.RoomHorizontals[Index], Column, Index);
|
||||
Description += FString::Printf(
|
||||
TEXT("(%d,%d,r=%.2f,h=%.2f,v=%.2f,room=%.2f,flags=%u,edges=%u)"),
|
||||
Cell.CellX, Cell.CellY, Cell.RadiusX, Column.RoomHorizontals[Index],
|
||||
FMath::Min(WorldZ - Column.RoomFloors[Index], Cell.CeilingZ - WorldZ),
|
||||
Room, static_cast<uint32>(Cell.FeatureFlags),
|
||||
static_cast<uint32>(Cell.OpenTunnelEdges));
|
||||
if (Index + 1 < UE_ARRAY_COUNT(Column.Cells))
|
||||
{
|
||||
Description += TEXT(",");
|
||||
}
|
||||
}
|
||||
Description += FString::Printf(TEXT(";macro=%.3f"),
|
||||
EvaluateParameterizedMacro(*Plan, WorldX, WorldY, WorldZ, nullptr));
|
||||
return Description;
|
||||
}
|
||||
|
||||
float EvaluateParameterizedDetail(
|
||||
const UVoxelGenerator& Generator, float WorldX, float WorldY, float WorldZ)
|
||||
{
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
#include "Engine/GameViewportClient.h"
|
||||
#include "Slate/SceneViewport.h"
|
||||
#include "Camera/CameraActor.h"
|
||||
#include "Components/PointLightComponent.h"
|
||||
#include "VoxelAtmosphereManager.h"
|
||||
#include "DrawDebugHelpers.h"
|
||||
#include "IImageWrapper.h"
|
||||
@@ -1760,6 +1761,21 @@ void AVoxelWorld::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||
|
||||
void AVoxelWorld::BeginPlay()
|
||||
{
|
||||
#if WITH_EDITOR
|
||||
int32 ResearchParameterizedCave = 0;
|
||||
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.ResearchParameterizedCave="),
|
||||
ResearchParameterizedCave))
|
||||
{
|
||||
VoxelForgeRedefinedDensityField::SetParameterizedEnabled(
|
||||
ResearchParameterizedCave != 0);
|
||||
}
|
||||
int32 ResearchParameterizedCaveSet = -1;
|
||||
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.ResearchParameterizedCaveSet="),
|
||||
ResearchParameterizedCaveSet))
|
||||
{
|
||||
VoxelForgeRedefinedDensityField::SetParameterSet(ResearchParameterizedCaveSet);
|
||||
}
|
||||
#endif
|
||||
VoxelForgeStartupTrace::BeginFromCommandLine();
|
||||
VoxelForgeStartupTrace::FStageScope StartupTraceStage(TEXT("BeginPlay"));
|
||||
Super::BeginPlay();
|
||||
@@ -2059,6 +2075,7 @@ void AVoxelWorld::BeginPlay()
|
||||
}
|
||||
|
||||
int32 TestCeilingView = 0;
|
||||
int32 TestCeilingWallCloseup = 0;
|
||||
FParse::Value(FCommandLine::Get(), TEXT("voxel.TestCeilingView="), TestCeilingView);
|
||||
bTestCeilingViewEnabled = TestCeilingView != 0;
|
||||
if (bTestCeilingViewEnabled)
|
||||
@@ -2078,6 +2095,8 @@ void AVoxelWorld::BeginPlay()
|
||||
FParse::Value(FCommandLine::Get(), TEXT("voxel.TestCeilingCaptureDelaySeconds="),
|
||||
TestCeilingCaptureDelaySeconds);
|
||||
TestCeilingCaptureDelaySeconds = FMath::Max(TestCeilingCaptureDelaySeconds, 1.0f);
|
||||
FParse::Value(FCommandLine::Get(), TEXT("voxel.TestCeilingWallCloseup="),
|
||||
TestCeilingWallCloseup);
|
||||
TestCeilingViewBeginSeconds = FPlatformTime::Seconds();
|
||||
bHeadlessStreamingTestCenterOverride = true;
|
||||
HeadlessStreamingTestCenterVoxel = WorldToLocalVoxel(TestCeilingCameraWorldPosition);
|
||||
@@ -2255,6 +2274,138 @@ void AVoxelWorld::BeginPlay()
|
||||
DiffLayer->SetBudget(Settings->MaxModifications, Settings->MaxBrushRadius, Settings->MaxTotalVolume);
|
||||
Generator->SetDiffLayer(DiffLayer);
|
||||
|
||||
if (bTestCeilingViewEnabled && IsParameterizedResearchFieldActive())
|
||||
{
|
||||
const FVector RequestedCenter = HeadlessStreamingTestCenterVoxel;
|
||||
FVector BestAirPoint = RequestedCenter;
|
||||
float BestAirDensity = Generator->GetDensityAt(
|
||||
RequestedCenter.X, RequestedCenter.Y, RequestedCenter.Z);
|
||||
TArray<int32> SearchZValues;
|
||||
constexpr int32 SearchZMargin = 8;
|
||||
constexpr int32 SearchZStep = 16;
|
||||
if (StrateManager != nullptr)
|
||||
{
|
||||
for (const FStrateSlot& Slot : StrateManager->GetLayout())
|
||||
{
|
||||
const int32 MinChunkZ = FMath::Min(Slot.TopChunkZ, Slot.BottomChunkZ);
|
||||
const int32 MaxChunkZ = FMath::Max(Slot.TopChunkZ, Slot.BottomChunkZ);
|
||||
const int32 MinZ = MinChunkZ * CHUNK_SIZE + SearchZMargin;
|
||||
const int32 MaxZ = (MaxChunkZ + 1) * CHUNK_SIZE - SearchZMargin;
|
||||
for (int32 Z = MinZ; Z <= MaxZ; Z += SearchZStep)
|
||||
{
|
||||
SearchZValues.Add(Z);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (SearchZValues.IsEmpty())
|
||||
{
|
||||
constexpr int32 SearchRadiusZ = 256;
|
||||
for (int32 Z = FMath::FloorToInt(RequestedCenter.Z) - SearchRadiusZ;
|
||||
Z <= FMath::FloorToInt(RequestedCenter.Z) + SearchRadiusZ;
|
||||
Z += SearchZStep)
|
||||
{
|
||||
SearchZValues.Add(Z);
|
||||
}
|
||||
}
|
||||
constexpr int32 SearchRadiusXY = 256;
|
||||
constexpr int32 SearchStepXY = 16;
|
||||
for (const int32 Z : SearchZValues)
|
||||
for (int32 DY = -SearchRadiusXY; DY <= SearchRadiusXY; DY += SearchStepXY)
|
||||
for (int32 DX = -SearchRadiusXY; DX <= SearchRadiusXY; DX += SearchStepXY)
|
||||
{
|
||||
const FVector Candidate(RequestedCenter.X + DX,
|
||||
RequestedCenter.Y + DY, static_cast<float>(Z));
|
||||
const float Density = Generator->GetDensityAt(
|
||||
Candidate.X, Candidate.Y, Candidate.Z);
|
||||
if (Density > BestAirDensity)
|
||||
{
|
||||
BestAirDensity = Density;
|
||||
BestAirPoint = Candidate;
|
||||
}
|
||||
}
|
||||
|
||||
if (BestAirDensity > 0.0f)
|
||||
{
|
||||
// Frame the capture from a plausible player-height point in the same chamber, rather
|
||||
// than directly at its maximum-clearance center. This brings the floor into view while
|
||||
// retaining a generous air margin and does not affect normal gameplay or generation.
|
||||
constexpr float CaptureEyeDropVoxels = 32.0f;
|
||||
const FVector LowerCapturePoint = BestAirPoint
|
||||
+ FVector(0.0f, 0.0f, -CaptureEyeDropVoxels);
|
||||
const float LowerCaptureDensity = Generator->GetDensityAt(
|
||||
LowerCapturePoint.X, LowerCapturePoint.Y, LowerCapturePoint.Z);
|
||||
if (LowerCaptureDensity > 12.0f)
|
||||
{
|
||||
BestAirPoint = LowerCapturePoint;
|
||||
BestAirDensity = LowerCaptureDensity;
|
||||
}
|
||||
|
||||
FVector BestViewDirection = FVector(1.0f, 0.0f, 0.0f);
|
||||
float BestViewWallDistance = 0.0f;
|
||||
float BestViewScore = -FLT_MAX;
|
||||
for (int32 DZ = -1; DZ <= 1; ++DZ)
|
||||
for (int32 DY = -1; DY <= 1; ++DY)
|
||||
for (int32 DX = -1; DX <= 1; ++DX)
|
||||
{
|
||||
if (TestCeilingWallCloseup == 0 && DZ != 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const FVector Direction(static_cast<float>(DX),
|
||||
static_cast<float>(DY), static_cast<float>(DZ));
|
||||
if (Direction.IsNearlyZero())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const FVector NormalizedDirection = Direction.GetSafeNormal();
|
||||
for (float Distance = 4.0f; Distance <= 256.0f; Distance += 4.0f)
|
||||
{
|
||||
const FVector Probe = BestAirPoint + NormalizedDirection * Distance;
|
||||
if (Generator->GetDensityAt(Probe.X, Probe.Y, Probe.Z) <= 0.0f)
|
||||
{
|
||||
const float TargetDistance = TestCeilingWallCloseup != 0
|
||||
? 24.0f : 64.0f;
|
||||
const float Score = -FMath::Abs(Distance - TargetDistance);
|
||||
if (Score > BestViewScore)
|
||||
{
|
||||
BestViewScore = Score;
|
||||
BestViewDirection = NormalizedDirection;
|
||||
BestViewWallDistance = Distance;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (BestViewWallDistance > 0.0f)
|
||||
{
|
||||
TestCeilingCameraRotation = BestViewDirection.Rotation();
|
||||
if (TestCeilingWallCloseup != 0)
|
||||
{
|
||||
constexpr float WallInset = 8.0f;
|
||||
BestAirPoint += BestViewDirection * FMath::Max(
|
||||
0.0f, BestViewWallDistance - WallInset);
|
||||
}
|
||||
}
|
||||
TestCeilingCameraWorldPosition = LocalVoxelToWorld(BestAirPoint);
|
||||
HeadlessStreamingTestCenterVoxel = BestAirPoint;
|
||||
const FVector WallProbe = BestAirPoint + BestViewDirection
|
||||
* (TestCeilingWallCloseup != 0 ? 8.0f : BestViewWallDistance);
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeTestCeiling] view_ray=(%.3f,%.3f,%.3f) wall_distance_vox=%.1f "
|
||||
"wall_probe_density=%.3f"),
|
||||
BestViewDirection.X, BestViewDirection.Y, BestViewDirection.Z,
|
||||
BestViewWallDistance,
|
||||
Generator->GetDensityAt(WallProbe.X, WallProbe.Y, WallProbe.Z));
|
||||
}
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeTestCeiling] research_air_search=1 requested_local=(%.1f,%.1f,%.1f) "
|
||||
"selected_local=(%.1f,%.1f,%.1f) selected_density=%.3f wall_closeup=%d"),
|
||||
RequestedCenter.X, RequestedCenter.Y, RequestedCenter.Z,
|
||||
BestAirPoint.X, BestAirPoint.Y, BestAirPoint.Z, BestAirDensity,
|
||||
TestCeilingWallCloseup != 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
// Content manager — distance-based decoration grid (no LOD pop) + level-0 water planes.
|
||||
ContentManager = NewObject<UVoxelContentManager>(this);
|
||||
ContentManager->Initialize(
|
||||
@@ -2266,6 +2417,11 @@ void AVoxelWorld::BeginPlay()
|
||||
AtmosphereManager = NewObject<UVoxelAtmosphereManager>(this);
|
||||
AtmosphereManager->Initialize(this, StrateManager, Generator);
|
||||
}
|
||||
else if (bTestCeilingViewEnabled)
|
||||
{
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeTestCeiling] atmosphere=disabled_by_world_setting"));
|
||||
}
|
||||
|
||||
// Density volume — player-centred clipmap streamed to the GPU for mini-sun raymarched shadows.
|
||||
if (Settings->bEnableDensityVolume)
|
||||
@@ -6547,6 +6703,23 @@ void AVoxelWorld::Tick(float DeltaTime)
|
||||
{
|
||||
TestCeilingCamera = Camera;
|
||||
PC->SetViewTarget(Camera);
|
||||
UPointLightComponent* CaptureHeadlamp = NewObject<UPointLightComponent>(Camera);
|
||||
if (IsValid(CaptureHeadlamp))
|
||||
{
|
||||
Camera->AddInstanceComponent(CaptureHeadlamp);
|
||||
CaptureHeadlamp->SetupAttachment(Camera->GetRootComponent());
|
||||
CaptureHeadlamp->SetMobility(EComponentMobility::Movable);
|
||||
CaptureHeadlamp->SetIntensity(80000.0f);
|
||||
CaptureHeadlamp->SetAttenuationRadius(10000.0f);
|
||||
CaptureHeadlamp->SetUseInverseSquaredFalloff(false);
|
||||
CaptureHeadlamp->SetLightFalloffExponent(0.0f);
|
||||
CaptureHeadlamp->SetLightColor(FLinearColor(1.0f, 0.88f, 0.72f));
|
||||
CaptureHeadlamp->SetCastShadows(false);
|
||||
CaptureHeadlamp->RegisterComponent();
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeTestCeiling] camera_headlamp=1 intensity=80000 radius_cm=10000 "
|
||||
"inverse_square=0 falloff_exponent=0"));
|
||||
}
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeTestCeiling] camera_installed=1 applied_visible_tiles=%llu"),
|
||||
static_cast<unsigned long long>(AppliedVisibleTileCount));
|
||||
@@ -6559,6 +6732,27 @@ void AVoxelWorld::Tick(float DeltaTime)
|
||||
>= static_cast<double>(TestCeilingCaptureDelaySeconds))
|
||||
{
|
||||
bTestCeilingCaptureRequested = true;
|
||||
FVector ActiveViewWorldPosition = FVector::ZeroVector;
|
||||
FRotator ActiveViewRotation = FRotator::ZeroRotator;
|
||||
PC->GetPlayerViewPoint(ActiveViewWorldPosition, ActiveViewRotation);
|
||||
const FVector ActiveViewVoxel = WorldToLocalVoxel(ActiveViewWorldPosition);
|
||||
const float ActiveViewDensity = Generator != nullptr
|
||||
? Generator->GetDensityAt(ActiveViewVoxel.X, ActiveViewVoxel.Y, ActiveViewVoxel.Z)
|
||||
: 0.0f;
|
||||
int32 ActiveResearchEnabled = 0;
|
||||
int32 ActiveResearchProfile = -1;
|
||||
#if WITH_EDITOR
|
||||
ActiveResearchEnabled = VoxelForgeRedefinedDensityField::IsParameterizedEnabled() ? 1 : 0;
|
||||
ActiveResearchProfile = VoxelForgeRedefinedDensityField::GetParameterSet();
|
||||
#endif
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeTestCeiling] active_view_target=%s active_view_world_cm=(%.3f,%.3f,%.3f) "
|
||||
"rotation=(%.3f,%.3f,%.3f) local_vox=(%.3f,%.3f,%.3f) density=%.3f research=%d profile=%d"),
|
||||
*GetNameSafe(PC->GetViewTarget()),
|
||||
ActiveViewWorldPosition.X, ActiveViewWorldPosition.Y, ActiveViewWorldPosition.Z,
|
||||
ActiveViewRotation.Pitch, ActiveViewRotation.Yaw, ActiveViewRotation.Roll,
|
||||
ActiveViewVoxel.X, ActiveViewVoxel.Y, ActiveViewVoxel.Z, ActiveViewDensity,
|
||||
ActiveResearchEnabled, ActiveResearchProfile);
|
||||
// UEngine::Exec is not routed through the game viewport in the command-line game
|
||||
// target, so HighResShot returns false there even with a live render viewport. Call
|
||||
// the viewport API directly; it schedules the same one-frame high-res capture.
|
||||
|
||||
@@ -117,6 +117,14 @@ namespace VoxelForgeRedefinedDensityField
|
||||
const UVoxelGenerator& Generator, float WorldX, float WorldY, float WorldZ,
|
||||
float* OutPreEditDensity = nullptr, float DetailScale = 1.0f);
|
||||
|
||||
/** Evaluate only the parameterized room/tunnel macro, before passage overlays and seals. */
|
||||
VOXELFORGE_API float EvaluateParameterizedMacroOnly(
|
||||
const UVoxelGenerator& Generator, float WorldX, float WorldY, float WorldZ);
|
||||
|
||||
/** Describe the candidate room SDF values at a point for step 2f proof diagnosis. */
|
||||
VOXELFORGE_API FString DescribeParameterizedMacroAt(
|
||||
const UVoxelGenerator& Generator, float WorldX, float WorldY, float WorldZ);
|
||||
|
||||
/** 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);
|
||||
|
||||
@@ -2551,10 +2551,20 @@ struct FStep2dBrickProofAudit
|
||||
float FirstViolationDensity = 0.0f;
|
||||
float FirstViolationCenterMacro = 0.0f;
|
||||
float FirstViolationSampleMacro = 0.0f;
|
||||
float FirstViolationCenterRoomMacro = 0.0f;
|
||||
float FirstViolationSampleRoomMacro = 0.0f;
|
||||
float FirstViolationSampleDetail = 0.0f;
|
||||
float FirstViolationMacroDeltaPerVoxel = 0.0f;
|
||||
float FirstViolationPathMaxGradient = 0.0f;
|
||||
float FirstViolationTinyStepMaxGradient = 0.0f;
|
||||
FVector FirstViolationPathMaxFrom = FVector::ZeroVector;
|
||||
FVector FirstViolationPathMaxTo = FVector::ZeroVector;
|
||||
float FirstViolationPathMaxFromMacro = 0.0f;
|
||||
float FirstViolationPathMaxToMacro = 0.0f;
|
||||
float FirstViolationPathMaxFromRoomMacro = 0.0f;
|
||||
float FirstViolationPathMaxToRoomMacro = 0.0f;
|
||||
FString FirstViolationPathMaxFromRoomDiagnostic;
|
||||
FString FirstViolationPathMaxToRoomDiagnostic;
|
||||
bool bFirstViolationWasInsideBrick = false;
|
||||
bool bFirstViolationClaimedAir = false;
|
||||
uint64 DeliberatelyUndersizedBoundClaimsChecked = 0;
|
||||
@@ -2653,6 +2663,15 @@ FStep2dBrickProofAudit AuditStep2dBrickProofs(
|
||||
: VoxelForgeRedefinedDensityField::Evaluate(
|
||||
Generator, SamplePoint.X, SamplePoint.Y, SamplePoint.Z,
|
||||
nullptr, 0.0f);
|
||||
if (bParameterized)
|
||||
{
|
||||
Result.FirstViolationCenterRoomMacro =
|
||||
VoxelForgeRedefinedDensityField::EvaluateParameterizedMacroOnly(
|
||||
Generator, ProofCentre.X, ProofCentre.Y, ProofCentre.Z);
|
||||
Result.FirstViolationSampleRoomMacro =
|
||||
VoxelForgeRedefinedDensityField::EvaluateParameterizedMacroOnly(
|
||||
Generator, SamplePoint.X, SamplePoint.Y, SamplePoint.Z);
|
||||
}
|
||||
Result.FirstViolationSampleDetail = Density
|
||||
- Result.FirstViolationSampleMacro;
|
||||
const float CentreDistance = (SamplePoint - ProofCentre).Size();
|
||||
@@ -2664,6 +2683,7 @@ FStep2dBrickProofAudit AuditStep2dBrickProofs(
|
||||
{
|
||||
constexpr int32 PathSteps = 32;
|
||||
float PreviousMacro = Result.FirstViolationSampleMacro;
|
||||
FVector PreviousPoint = SamplePoint;
|
||||
for (int32 PathStep = 1; PathStep <= PathSteps; ++PathStep)
|
||||
{
|
||||
const float Alpha = static_cast<float>(PathStep) / PathSteps;
|
||||
@@ -2674,11 +2694,35 @@ FStep2dBrickProofAudit AuditStep2dBrickProofs(
|
||||
Generator, Point.X, Point.Y, Point.Z, nullptr, 0.0f)
|
||||
: VoxelForgeRedefinedDensityField::Evaluate(
|
||||
Generator, Point.X, Point.Y, Point.Z, nullptr, 0.0f);
|
||||
Result.FirstViolationPathMaxGradient = FMath::Max(
|
||||
Result.FirstViolationPathMaxGradient,
|
||||
FMath::Abs(PathMacro - PreviousMacro)
|
||||
* PathSteps / CentreDistance);
|
||||
const float PathGradient = FMath::Abs(
|
||||
PathMacro - PreviousMacro) * PathSteps / CentreDistance;
|
||||
if (PathGradient > Result.FirstViolationPathMaxGradient)
|
||||
{
|
||||
Result.FirstViolationPathMaxGradient = PathGradient;
|
||||
Result.FirstViolationPathMaxFrom = PreviousPoint;
|
||||
Result.FirstViolationPathMaxTo = Point;
|
||||
Result.FirstViolationPathMaxFromMacro = PreviousMacro;
|
||||
Result.FirstViolationPathMaxToMacro = PathMacro;
|
||||
if (bParameterized)
|
||||
{
|
||||
Result.FirstViolationPathMaxFromRoomMacro =
|
||||
VoxelForgeRedefinedDensityField::EvaluateParameterizedMacroOnly(
|
||||
Generator, PreviousPoint.X, PreviousPoint.Y,
|
||||
PreviousPoint.Z);
|
||||
Result.FirstViolationPathMaxToRoomMacro =
|
||||
VoxelForgeRedefinedDensityField::EvaluateParameterizedMacroOnly(
|
||||
Generator, Point.X, Point.Y, Point.Z);
|
||||
Result.FirstViolationPathMaxFromRoomDiagnostic =
|
||||
VoxelForgeRedefinedDensityField::DescribeParameterizedMacroAt(
|
||||
Generator, PreviousPoint.X, PreviousPoint.Y,
|
||||
PreviousPoint.Z);
|
||||
Result.FirstViolationPathMaxToRoomDiagnostic =
|
||||
VoxelForgeRedefinedDensityField::DescribeParameterizedMacroAt(
|
||||
Generator, Point.X, Point.Y, Point.Z);
|
||||
}
|
||||
}
|
||||
PreviousMacro = PathMacro;
|
||||
PreviousPoint = Point;
|
||||
}
|
||||
}
|
||||
constexpr float TinyStep = 0.01f;
|
||||
@@ -3173,12 +3217,27 @@ struct FStep2fFragmentMetrics
|
||||
int64 SolidCrumbsBelowOneCubicMeter = 0;
|
||||
int64 AirCrumbVoxelSamples = 0;
|
||||
int64 SolidCrumbVoxelSamples = 0;
|
||||
int64 AirCrumbsAfterBoundaryPadding = -1;
|
||||
int64 SolidCrumbsAfterBoundaryPadding = -1;
|
||||
int32 LargestAirComponentVoxels = 0;
|
||||
int32 LargestSolidComponentVoxels = 0;
|
||||
struct FCrumbComponent
|
||||
{
|
||||
bool bAir = false;
|
||||
int32 Samples = 0;
|
||||
FIntVector Min = FIntVector::ZeroValue;
|
||||
FIntVector Max = FIntVector::ZeroValue;
|
||||
FIntVector Root = FIntVector::ZeroValue;
|
||||
int32 SignMismatchSamples = 0;
|
||||
float DensityAtRoot = 0.0f;
|
||||
float DensityAtMin = 0.0f;
|
||||
};
|
||||
TArray<FCrumbComponent> CrumbComponents;
|
||||
};
|
||||
|
||||
FStep2fFragmentMetrics ComputeStep2fFragmentMetrics(
|
||||
const TArray<float>& Density, int32 Dimension)
|
||||
const TArray<float>& Density, int32 Dimension,
|
||||
int32 CoreInset = 0, int32 CoreDimension = 0)
|
||||
{
|
||||
FStep2fFragmentMetrics Result;
|
||||
const int32 Count = Density.Num();
|
||||
@@ -3252,7 +3311,20 @@ FStep2fFragmentMetrics ComputeStep2fFragmentMetrics(
|
||||
}
|
||||
}
|
||||
}
|
||||
TSet<int32> ComponentsIntersectingCore;
|
||||
if (CoreDimension > 0 && CoreInset >= 0
|
||||
&& CoreInset + CoreDimension <= Dimension)
|
||||
{
|
||||
for (int32 Z = CoreInset; Z < CoreInset + CoreDimension; ++Z)
|
||||
for (int32 Y = CoreInset; Y < CoreInset + CoreDimension; ++Y)
|
||||
for (int32 X = CoreInset; X < CoreInset + CoreDimension; ++X)
|
||||
{
|
||||
const int32 Index = (Z * Dimension + Y) * Dimension + X;
|
||||
ComponentsIntersectingCore.Add(FindRoot(Index));
|
||||
}
|
||||
}
|
||||
constexpr int32 VoxelsPerCubicMeter = 4 * 4 * 4;
|
||||
TMap<int32, FStep2fFragmentMetrics::FCrumbComponent> CrumbByRoot;
|
||||
for (int32 Index = 0; Index < Count; ++Index)
|
||||
{
|
||||
if (Parent[Index] >= 0)
|
||||
@@ -3261,15 +3333,29 @@ FStep2fFragmentMetrics ComputeStep2fFragmentMetrics(
|
||||
}
|
||||
const int32 ComponentVoxels = -Parent[Index];
|
||||
const bool bAir = Density[Index] > 0.0f;
|
||||
const bool bIntersectsCore = ComponentsIntersectingCore.IsEmpty()
|
||||
|| ComponentsIntersectingCore.Contains(Index);
|
||||
if (bAir)
|
||||
{
|
||||
++Result.AirComponents;
|
||||
Result.LargestAirComponentVoxels = FMath::Max(
|
||||
Result.LargestAirComponentVoxels, ComponentVoxels);
|
||||
if (ComponentVoxels < VoxelsPerCubicMeter)
|
||||
if (ComponentVoxels < VoxelsPerCubicMeter && bIntersectsCore)
|
||||
{
|
||||
++Result.AirCrumbsBelowOneCubicMeter;
|
||||
Result.AirCrumbVoxelSamples += ComponentVoxels;
|
||||
if (Result.CrumbComponents.Num() + CrumbByRoot.Num() < 32)
|
||||
{
|
||||
FStep2fFragmentMetrics::FCrumbComponent& Crumb =
|
||||
CrumbByRoot.Add(Index);
|
||||
Crumb.bAir = true;
|
||||
Crumb.Samples = ComponentVoxels;
|
||||
Crumb.Min = FIntVector(Dimension, Dimension, Dimension);
|
||||
Crumb.Max = FIntVector::ZeroValue;
|
||||
Crumb.Root = FIntVector(Index % Dimension,
|
||||
(Index / Dimension) % Dimension, Index / (Dimension * Dimension));
|
||||
Crumb.DensityAtRoot = Density[Index];
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -3277,13 +3363,59 @@ FStep2fFragmentMetrics ComputeStep2fFragmentMetrics(
|
||||
++Result.SolidComponents;
|
||||
Result.LargestSolidComponentVoxels = FMath::Max(
|
||||
Result.LargestSolidComponentVoxels, ComponentVoxels);
|
||||
if (ComponentVoxels < VoxelsPerCubicMeter)
|
||||
if (ComponentVoxels < VoxelsPerCubicMeter && bIntersectsCore)
|
||||
{
|
||||
++Result.SolidCrumbsBelowOneCubicMeter;
|
||||
Result.SolidCrumbVoxelSamples += ComponentVoxels;
|
||||
if (Result.CrumbComponents.Num() + CrumbByRoot.Num() < 32)
|
||||
{
|
||||
FStep2fFragmentMetrics::FCrumbComponent& Crumb =
|
||||
CrumbByRoot.Add(Index);
|
||||
Crumb.bAir = false;
|
||||
Crumb.Samples = ComponentVoxels;
|
||||
Crumb.Min = FIntVector(Dimension, Dimension, Dimension);
|
||||
Crumb.Max = FIntVector::ZeroValue;
|
||||
Crumb.Root = FIntVector(Index % Dimension,
|
||||
(Index / Dimension) % Dimension, Index / (Dimension * Dimension));
|
||||
Crumb.DensityAtRoot = Density[Index];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!CrumbByRoot.IsEmpty())
|
||||
{
|
||||
for (int32 Index = 0; Index < Count; ++Index)
|
||||
{
|
||||
const int32 Root = FindRoot(Index);
|
||||
FStep2fFragmentMetrics::FCrumbComponent* Crumb = CrumbByRoot.Find(Root);
|
||||
if (Crumb == nullptr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const int32 X = Index % Dimension;
|
||||
const int32 Y = (Index / Dimension) % Dimension;
|
||||
const int32 Z = Index / (Dimension * Dimension);
|
||||
Crumb->SignMismatchSamples +=
|
||||
(Density[Index] > 0.0f) != Crumb->bAir ? 1 : 0;
|
||||
Crumb->Min.X = FMath::Min(Crumb->Min.X, X);
|
||||
Crumb->Min.Y = FMath::Min(Crumb->Min.Y, Y);
|
||||
Crumb->Min.Z = FMath::Min(Crumb->Min.Z, Z);
|
||||
Crumb->Max.X = FMath::Max(Crumb->Max.X, X);
|
||||
Crumb->Max.Y = FMath::Max(Crumb->Max.Y, Y);
|
||||
Crumb->Max.Z = FMath::Max(Crumb->Max.Z, Z);
|
||||
}
|
||||
TArray<int32> Roots;
|
||||
CrumbByRoot.GetKeys(Roots);
|
||||
Roots.Sort();
|
||||
for (const int32 Root : Roots)
|
||||
{
|
||||
FStep2fFragmentMetrics::FCrumbComponent& Crumb = CrumbByRoot[Root];
|
||||
const int32 MinIndex = (Crumb.Min.Z * Dimension + Crumb.Min.Y)
|
||||
* Dimension + Crumb.Min.X;
|
||||
Crumb.DensityAtMin = Density[MinIndex];
|
||||
Result.CrumbComponents.Add(Crumb);
|
||||
}
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
@@ -5006,10 +5138,44 @@ int32 RunStep2f(const FString& Params)
|
||||
static_cast<double>(Shape.Fragments.AirCrumbsBelowOneCubicMeter));
|
||||
Json->SetNumberField(TEXT("solid_crumbs_below_1m3"),
|
||||
static_cast<double>(Shape.Fragments.SolidCrumbsBelowOneCubicMeter));
|
||||
Json->SetNumberField(TEXT("air_crumbs_below_1m3_after_boundary_padding"),
|
||||
static_cast<double>(Shape.Fragments.AirCrumbsAfterBoundaryPadding >= 0
|
||||
? Shape.Fragments.AirCrumbsAfterBoundaryPadding
|
||||
: Shape.Fragments.AirCrumbsBelowOneCubicMeter));
|
||||
Json->SetNumberField(TEXT("solid_crumbs_below_1m3_after_boundary_padding"),
|
||||
static_cast<double>(Shape.Fragments.SolidCrumbsAfterBoundaryPadding >= 0
|
||||
? Shape.Fragments.SolidCrumbsAfterBoundaryPadding
|
||||
: Shape.Fragments.SolidCrumbsBelowOneCubicMeter));
|
||||
Json->SetNumberField(TEXT("air_crumb_voxel_samples"),
|
||||
static_cast<double>(Shape.Fragments.AirCrumbVoxelSamples));
|
||||
Json->SetNumberField(TEXT("solid_crumb_voxel_samples"),
|
||||
static_cast<double>(Shape.Fragments.SolidCrumbVoxelSamples));
|
||||
TArray<TSharedPtr<FJsonValue>> CrumbComponents;
|
||||
for (const FStep2fFragmentMetrics::FCrumbComponent& Crumb
|
||||
: Shape.Fragments.CrumbComponents)
|
||||
{
|
||||
TSharedRef<FJsonObject> CrumbJson = MakeShared<FJsonObject>();
|
||||
CrumbJson->SetStringField(TEXT("sign"), Crumb.bAir ? TEXT("air") : TEXT("solid"));
|
||||
CrumbJson->SetNumberField(TEXT("samples"), Crumb.Samples);
|
||||
CrumbJson->SetNumberField(TEXT("density_at_min"), Crumb.DensityAtMin);
|
||||
CrumbJson->SetArrayField(TEXT("root_ring_voxel"), {
|
||||
MakeShared<FJsonValueNumber>(Crumb.Root.X),
|
||||
MakeShared<FJsonValueNumber>(Crumb.Root.Y),
|
||||
MakeShared<FJsonValueNumber>(Crumb.Root.Z)});
|
||||
CrumbJson->SetNumberField(TEXT("density_at_root"), Crumb.DensityAtRoot);
|
||||
CrumbJson->SetNumberField(TEXT("sign_mismatch_samples"),
|
||||
Crumb.SignMismatchSamples);
|
||||
CrumbJson->SetArrayField(TEXT("min_ring_voxel"), {
|
||||
MakeShared<FJsonValueNumber>(Crumb.Min.X),
|
||||
MakeShared<FJsonValueNumber>(Crumb.Min.Y),
|
||||
MakeShared<FJsonValueNumber>(Crumb.Min.Z)});
|
||||
CrumbJson->SetArrayField(TEXT("max_ring_voxel"), {
|
||||
MakeShared<FJsonValueNumber>(Crumb.Max.X),
|
||||
MakeShared<FJsonValueNumber>(Crumb.Max.Y),
|
||||
MakeShared<FJsonValueNumber>(Crumb.Max.Z)});
|
||||
CrumbComponents.Add(MakeShared<FJsonValueObject>(CrumbJson));
|
||||
}
|
||||
Json->SetArrayField(TEXT("crumb_components_first_32"), CrumbComponents);
|
||||
Json->SetNumberField(TEXT("largest_air_component_voxels"),
|
||||
Shape.Fragments.LargestAirComponentVoxels);
|
||||
Json->SetNumberField(TEXT("largest_solid_component_voxels"),
|
||||
@@ -5041,6 +5207,35 @@ int32 RunStep2f(const FString& Params)
|
||||
return Json;
|
||||
};
|
||||
|
||||
constexpr int32 CrumbBoundaryPadding = 64;
|
||||
const int32 PaddedCrumbDimension = Dimension + 2 * CrumbBoundaryPadding;
|
||||
const FIntVector PaddedCrumbOrigin = Origin
|
||||
- FIntVector(CrumbBoundaryPadding, CrumbBoundaryPadding, CrumbBoundaryPadding);
|
||||
const auto CountBoundaryPaddedCrumbs = [&](bool bLegacyField,
|
||||
int64& OutAirCrumbs, int64& OutSolidCrumbs,
|
||||
TArray<FStep2fFragmentMetrics::FCrumbComponent>* OutCrumbComponents = nullptr)
|
||||
{
|
||||
VoxelForgeRedefinedDensityField::SetLegacyStep2eAuditMode(bLegacyField);
|
||||
TArray<float> PaddedDensity;
|
||||
VoxelForgeRegionEval::FTiming PaddedTiming;
|
||||
if (!VoxelForgeRegionEval::EvaluateRedefined(
|
||||
*World.Generator, PaddedCrumbOrigin, PaddedCrumbDimension,
|
||||
Workers, 1.0f, PaddedDensity, PaddedTiming, false, true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const FStep2fFragmentMetrics PaddedFragments = ComputeStep2fFragmentMetrics(
|
||||
PaddedDensity, PaddedCrumbDimension,
|
||||
CrumbBoundaryPadding, Dimension);
|
||||
OutAirCrumbs = PaddedFragments.AirCrumbsBelowOneCubicMeter;
|
||||
OutSolidCrumbs = PaddedFragments.SolidCrumbsBelowOneCubicMeter;
|
||||
if (OutCrumbComponents != nullptr)
|
||||
{
|
||||
*OutCrumbComponents = PaddedFragments.CrumbComponents;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
for (int32 Profile = 0;
|
||||
Profile < VoxelForgeRedefinedDensityField::GetParameterSetCount(); ++Profile)
|
||||
{
|
||||
@@ -5072,12 +5267,20 @@ int32 RunStep2f(const FString& Params)
|
||||
}
|
||||
LegacyTimes.Sort();
|
||||
const double LegacyMedianMs = LegacyTimes[LegacyTimes.Num() / 2];
|
||||
const FStep2fShapeSnapshot LegacyShape = MeasureShape(
|
||||
FStep2fShapeSnapshot LegacyShape = MeasureShape(
|
||||
LegacyDensity, LegacyParameters);
|
||||
const FStep2eVarietyMetrics LegacyVariety = LegacyShape.Variety;
|
||||
if (!CountBoundaryPaddedCrumbs(true,
|
||||
LegacyShape.Fragments.AirCrumbsAfterBoundaryPadding,
|
||||
LegacyShape.Fragments.SolidCrumbsAfterBoundaryPadding))
|
||||
{
|
||||
UE_LOG(LogTemp, Error,
|
||||
TEXT("[VoxelForgeStep2f] profile %d padded step 2e crumb pass failed."), Profile);
|
||||
return 2;
|
||||
}
|
||||
WorstStep2eMs = FMath::Max(WorstStep2eMs, LegacyMedianMs);
|
||||
const int64 LegacyCrumbs = LegacyShape.Fragments.AirCrumbsBelowOneCubicMeter
|
||||
+ LegacyShape.Fragments.SolidCrumbsBelowOneCubicMeter;
|
||||
const int64 LegacyCrumbs = LegacyShape.Fragments.AirCrumbsAfterBoundaryPadding
|
||||
+ LegacyShape.Fragments.SolidCrumbsAfterBoundaryPadding;
|
||||
WorstStep2eCrumbs = FMath::Max(WorstStep2eCrumbs, LegacyCrumbs);
|
||||
bStep2eCrumbsRejected &= LegacyCrumbs > 0;
|
||||
WorstStep2eDihedralShare = FMath::Max(
|
||||
@@ -5092,7 +5295,7 @@ int32 RunStep2f(const FString& Params)
|
||||
bStep2eBandLimitRejected &= LegacyShape.Detail.LatticeScaleEnergyRatio > 0.20;
|
||||
WorstStep2eLodDistance = FMath::Max(
|
||||
WorstStep2eLodDistance, LegacyShape.Lod1P95SurfaceErrorVoxels);
|
||||
bStep2eLodRejected |= LegacyShape.Lod1P95SurfaceErrorVoxels > 3.5;
|
||||
bStep2eLodRejected |= LegacyShape.Lod1P95SurfaceErrorVoxels > 0.5;
|
||||
WorstStep2ePitchCorrelation = FMath::Max(
|
||||
WorstStep2ePitchCorrelation, LegacyShape.Grid.PitchCorrelationPeak);
|
||||
WorstStep2ePitchExcess = FMath::Max(
|
||||
@@ -5136,12 +5339,22 @@ int32 RunStep2f(const FString& Params)
|
||||
}
|
||||
NewTimes.Sort();
|
||||
const double NewMedianMs = NewTimes[NewTimes.Num() / 2];
|
||||
const FStep2fShapeSnapshot NewShape = MeasureShape(NewDensity, NewParameters);
|
||||
FStep2fShapeSnapshot NewShape = MeasureShape(NewDensity, NewParameters);
|
||||
const FStep2eVarietyMetrics NewVariety = NewShape.Variety;
|
||||
TArray<FStep2fFragmentMetrics::FCrumbComponent> NewPaddedCrumbComponents;
|
||||
if (!CountBoundaryPaddedCrumbs(false,
|
||||
NewShape.Fragments.AirCrumbsAfterBoundaryPadding,
|
||||
NewShape.Fragments.SolidCrumbsAfterBoundaryPadding,
|
||||
&NewPaddedCrumbComponents))
|
||||
{
|
||||
UE_LOG(LogTemp, Error,
|
||||
TEXT("[VoxelForgeStep2f] profile %d padded step 2f crumb pass failed."), Profile);
|
||||
return 2;
|
||||
}
|
||||
WorstStep2fMs = FMath::Max(WorstStep2fMs, NewMedianMs);
|
||||
const double NewSolidFraction = 1.0 - NewVariety.OpenFraction;
|
||||
const int64 NewCrumbs = NewShape.Fragments.AirCrumbsBelowOneCubicMeter
|
||||
+ NewShape.Fragments.SolidCrumbsBelowOneCubicMeter;
|
||||
const int64 NewCrumbs = NewShape.Fragments.AirCrumbsAfterBoundaryPadding
|
||||
+ NewShape.Fragments.SolidCrumbsAfterBoundaryPadding;
|
||||
WorstStep2fCrumbs = FMath::Max(WorstStep2fCrumbs, NewCrumbs);
|
||||
WorstStep2fDihedralShare = FMath::Max(
|
||||
WorstStep2fDihedralShare, NewShape.Mesh.ShareDihedralsOver45Degrees);
|
||||
@@ -5163,8 +5376,8 @@ int32 RunStep2f(const FString& Params)
|
||||
WorstStep2fFloorOpenings, NewShape.Structure.FloorOpenings);
|
||||
WorstStep2fSolidFraction = FMath::Max(WorstStep2fSolidFraction, NewSolidFraction);
|
||||
BestStep2fSolidFraction = FMath::Min(BestStep2fSolidFraction, NewSolidFraction);
|
||||
bStep2fCrumbGatePassed &= NewShape.Fragments.AirCrumbsBelowOneCubicMeter == 0
|
||||
&& NewShape.Fragments.SolidCrumbsBelowOneCubicMeter == 0;
|
||||
bStep2fCrumbGatePassed &= NewShape.Fragments.AirCrumbsAfterBoundaryPadding == 0
|
||||
&& NewShape.Fragments.SolidCrumbsAfterBoundaryPadding == 0;
|
||||
bStep2fFlakeGatePassed &= NewShape.Mesh.TriangleCount > 0
|
||||
&& NewShape.Mesh.ManifoldEdgeCount > 0
|
||||
&& NewShape.Mesh.ShareDihedralsOver45Degrees <= 0.25
|
||||
@@ -5172,7 +5385,7 @@ int32 RunStep2f(const FString& Params)
|
||||
bStep2fBandLimitPassed &= NewShape.Detail.LatticeScaleEnergyRatio <= 0.20
|
||||
&& NewShape.Detail.Rms >= 0.40;
|
||||
bStep2fLodPassed &= NewShape.Lod1VertexCount > 0
|
||||
&& NewShape.Lod1P95SurfaceErrorVoxels <= 3.5;
|
||||
&& NewShape.Lod1P95SurfaceErrorVoxels <= 0.5;
|
||||
bStep2fGridPassed &= NewShape.Grid.PitchCorrelationPeak <= 0.40
|
||||
&& NewShape.Grid.PitchPeakExcess <= 0.08;
|
||||
bStep2fStructurePassed &= NewSolidFraction >= 0.25 && NewSolidFraction <= 0.88
|
||||
@@ -5189,6 +5402,51 @@ int32 RunStep2f(const FString& Params)
|
||||
LegacyShape, LegacyMedianMs, LegacyVariety));
|
||||
Row->SetObjectField(TEXT("step2f"), ShapeJson(
|
||||
NewShape, NewMedianMs, NewVariety));
|
||||
TArray<TSharedPtr<FJsonValue>> PaddedCrumbDiagnostics;
|
||||
for (const FStep2fFragmentMetrics::FCrumbComponent& Crumb
|
||||
: NewPaddedCrumbComponents)
|
||||
{
|
||||
// Root is an actual sample in the connected component. The min/max coordinates are
|
||||
// an axis-aligned bound and their corner may not belong to the component.
|
||||
const FIntVector SampleVoxel = PaddedCrumbOrigin + Crumb.Root;
|
||||
const float SampleX = static_cast<float>(SampleVoxel.X);
|
||||
const float SampleY = static_cast<float>(SampleVoxel.Y);
|
||||
const float SampleZ = static_cast<float>(SampleVoxel.Z);
|
||||
TSharedRef<FJsonObject> CrumbJson = MakeShared<FJsonObject>();
|
||||
CrumbJson->SetStringField(TEXT("sign"), Crumb.bAir ? TEXT("air") : TEXT("solid"));
|
||||
CrumbJson->SetNumberField(TEXT("samples"), Crumb.Samples);
|
||||
CrumbJson->SetNumberField(TEXT("density_from_padded_array_at_min"),
|
||||
Crumb.DensityAtMin);
|
||||
CrumbJson->SetNumberField(TEXT("density_at_root"), Crumb.DensityAtRoot);
|
||||
CrumbJson->SetNumberField(TEXT("sign_mismatch_samples"),
|
||||
Crumb.SignMismatchSamples);
|
||||
CrumbJson->SetArrayField(TEXT("root_world_voxel"), {
|
||||
MakeShared<FJsonValueNumber>(PaddedCrumbOrigin.X + Crumb.Root.X),
|
||||
MakeShared<FJsonValueNumber>(PaddedCrumbOrigin.Y + Crumb.Root.Y),
|
||||
MakeShared<FJsonValueNumber>(PaddedCrumbOrigin.Z + Crumb.Root.Z)});
|
||||
CrumbJson->SetArrayField(TEXT("min_world_voxel"), {
|
||||
MakeShared<FJsonValueNumber>(SampleVoxel.X),
|
||||
MakeShared<FJsonValueNumber>(SampleVoxel.Y),
|
||||
MakeShared<FJsonValueNumber>(SampleVoxel.Z)});
|
||||
CrumbJson->SetArrayField(TEXT("max_world_voxel"), {
|
||||
MakeShared<FJsonValueNumber>(PaddedCrumbOrigin.X + Crumb.Max.X),
|
||||
MakeShared<FJsonValueNumber>(PaddedCrumbOrigin.Y + Crumb.Max.Y),
|
||||
MakeShared<FJsonValueNumber>(PaddedCrumbOrigin.Z + Crumb.Max.Z)});
|
||||
CrumbJson->SetNumberField(TEXT("final_density_at_min"),
|
||||
VoxelForgeRedefinedDensityField::EvaluateParameterized(
|
||||
*World.Generator, SampleX, SampleY, SampleZ));
|
||||
CrumbJson->SetNumberField(TEXT("macro_at_min"),
|
||||
VoxelForgeRedefinedDensityField::EvaluateParameterizedMacroOnly(
|
||||
*World.Generator, SampleX, SampleY, SampleZ));
|
||||
CrumbJson->SetNumberField(TEXT("detail_at_min"),
|
||||
VoxelForgeRedefinedDensityField::EvaluateParameterizedDetail(
|
||||
*World.Generator, SampleX, SampleY, SampleZ));
|
||||
CrumbJson->SetStringField(TEXT("macro_description_at_min"),
|
||||
VoxelForgeRedefinedDensityField::DescribeParameterizedMacroAt(
|
||||
*World.Generator, SampleX, SampleY, SampleZ));
|
||||
PaddedCrumbDiagnostics.Add(MakeShared<FJsonValueObject>(CrumbJson));
|
||||
}
|
||||
Row->SetArrayField(TEXT("step2f_padded_crumb_diagnostics"), PaddedCrumbDiagnostics);
|
||||
Row->SetObjectField(TEXT("step2e_parameters"), Step2eParametersJson(LegacyParameters));
|
||||
Row->SetObjectField(TEXT("step2f_parameters"), Step2eParametersJson(NewParameters));
|
||||
TArray<TSharedPtr<FJsonValue>> LegacyTrialValues;
|
||||
@@ -5269,8 +5527,8 @@ int32 RunStep2f(const FString& Params)
|
||||
"solid=%.3f crumbs=%lld/%lld flakes=%.3f/%.3f grid=%.3f/%.3f "
|
||||
"detail=%.3f lod95=%.2f roof=%lld floor=%lld"),
|
||||
Profile, ProfileName, LegacyMedianMs, NewMedianMs, NewSolidFraction,
|
||||
static_cast<long long>(NewShape.Fragments.AirCrumbsBelowOneCubicMeter),
|
||||
static_cast<long long>(NewShape.Fragments.SolidCrumbsBelowOneCubicMeter),
|
||||
static_cast<long long>(NewShape.Fragments.AirCrumbsAfterBoundaryPadding),
|
||||
static_cast<long long>(NewShape.Fragments.SolidCrumbsAfterBoundaryPadding),
|
||||
NewShape.Mesh.ShareDihedralsOver45Degrees, NewShape.Detail.LatticeScaleEnergyRatio,
|
||||
NewShape.Grid.PitchCorrelationPeak, NewShape.Grid.PitchPeakExcess,
|
||||
NewShape.Detail.Rms, NewShape.Lod1P95SurfaceErrorVoxels,
|
||||
@@ -5363,7 +5621,7 @@ int32 RunStep2f(const FString& Params)
|
||||
Step2eCrumbNumbers->SetNumberField(TEXT("crumbs_below_1m3_max"), WorstStep2eCrumbs);
|
||||
TSharedRef<FJsonObject> Step2fCrumbNumbers = MakeShared<FJsonObject>();
|
||||
Step2fCrumbNumbers->SetNumberField(TEXT("crumbs_below_1m3_max"), WorstStep2fCrumbs);
|
||||
AddGate(TEXT("crumbs"), TEXT("zero 25 cm-grid connected components smaller than 64 voxels (about 1 m3)"),
|
||||
AddGate(TEXT("crumbs"), TEXT("zero 25 cm-grid components smaller than 64 voxels, measured in a 64-sample-padded volume and restricted to components intersecting the 259-cube"),
|
||||
bStep2eCrumbsRejected, bStep2fCrumbGatePassed, Step2eCrumbNumbers, Step2fCrumbNumbers);
|
||||
TSharedRef<FJsonObject> Step2eFlakeNumbers = MakeShared<FJsonObject>();
|
||||
Step2eFlakeNumbers->SetNumberField(TEXT("max_dihedral_share_over_45deg"), WorstStep2eDihedralShare);
|
||||
@@ -5385,7 +5643,7 @@ int32 RunStep2f(const FString& Params)
|
||||
Step2eLodNumbers->SetNumberField(TEXT("max_l1_p95_surface_distance_voxels"), WorstStep2eLodDistance);
|
||||
TSharedRef<FJsonObject> Step2fLodNumbers = MakeShared<FJsonObject>();
|
||||
Step2fLodNumbers->SetNumberField(TEXT("max_l1_p95_surface_distance_voxels"), WorstStep2fLodDistance);
|
||||
AddGate(TEXT("lod_stability"), TEXT("L1 mesh p95 estimated distance from the L0 isosurface <= 3.5 lattice samples"),
|
||||
AddGate(TEXT("lod_stability"), TEXT("L1 mesh p95 estimated distance from the L0 isosurface <= 0.5 lattice samples"),
|
||||
bStep2eLodRejected, bStep2fLodPassed, Step2eLodNumbers, Step2fLodNumbers);
|
||||
TSharedRef<FJsonObject> Step2eGridNumbers = MakeShared<FJsonObject>();
|
||||
Step2eGridNumbers->SetNumberField(TEXT("max_pitch_autocorrelation"), WorstStep2ePitchCorrelation);
|
||||
@@ -5419,6 +5677,10 @@ int32 RunStep2f(const FString& Params)
|
||||
Report->SetNumberField(TEXT("step2f_solid_fraction_max"), WorstStep2fSolidFraction);
|
||||
Report->SetNumberField(TEXT("seed"), Seed);
|
||||
Report->SetNumberField(TEXT("ring_dimension"), Dimension);
|
||||
Report->SetArrayField(TEXT("ring_origin_voxels"), {
|
||||
MakeShared<FJsonValueNumber>(Origin.X),
|
||||
MakeShared<FJsonValueNumber>(Origin.Y),
|
||||
MakeShared<FJsonValueNumber>(Origin.Z)});
|
||||
Report->SetNumberField(TEXT("ring_samples"), static_cast<double>(TotalSamples));
|
||||
Report->SetNumberField(TEXT("workers_requested"), Workers);
|
||||
Report->SetNumberField(TEXT("world_prepare_ms_excluded"), WorldPrepareSeconds * 1000.0);
|
||||
@@ -5427,12 +5689,15 @@ int32 RunStep2f(const FString& Params)
|
||||
"A conservative nearest-neighbor cap leaves a solid web between chambers; a rooted "
|
||||
"tree of narrow tunnels and capped shafts joins them. Sparse ledges, pillars, "
|
||||
"stalactites and large hanging masses remain per-cell rolls. The point and "
|
||||
"brick detail samplers share three 32/16/8-sample bands with an eight-sample "
|
||||
"shortest period and a smooth ridge response."));
|
||||
"brick detail samplers share independently rotated 48/24/12-sample bands with a "
|
||||
"twelve-sample shortest period and a smooth ridge response; their final-field "
|
||||
"detail supremum is 3.822 units."));
|
||||
Report->SetStringField(TEXT("component_method"),
|
||||
TEXT("26-neighbor connected components are counted on the exact 25 cm sign lattice; "
|
||||
"components smaller than 64 samples (< 1 m3) are reported separately for air and solid. "
|
||||
"The existing one-meter downsampled air component count is also reported."));
|
||||
TEXT("26-neighbor connected components are counted on the exact 25 cm sign lattice. "
|
||||
"Raw components below 64 samples (< 1 m3) are reported, then a 64-sample pad is "
|
||||
"evaluated around each 259-cube and only components intersecting the original cube "
|
||||
"are counted again; this prevents a clipped large component at the ring edge from "
|
||||
"being called a crumb. The one-meter downsampled air component count is also reported."));
|
||||
Report->SetStringField(TEXT("mesh_method"),
|
||||
TEXT("Eight level-0 tiles and one level-1 tile cover the same 64-sample cube selected "
|
||||
"for maximum coarse-sampled surface crossings. "
|
||||
@@ -5452,6 +5717,63 @@ int32 RunStep2f(const FString& Params)
|
||||
static_cast<double>(ProofAudit.CorrectProofSamplesChecked));
|
||||
Existing->SetNumberField(TEXT("correct_proof_sign_violations"),
|
||||
static_cast<double>(ProofAudit.CorrectProofViolations));
|
||||
TSharedRef<FJsonObject> FirstProofViolation = MakeShared<FJsonObject>();
|
||||
FirstProofViolation->SetBoolField(TEXT("present"), ProofAudit.CorrectProofViolations > 0);
|
||||
FirstProofViolation->SetArrayField(TEXT("brick_origin_voxels"), {
|
||||
MakeShared<FJsonValueNumber>(ProofAudit.FirstViolationBrickOrigin.X),
|
||||
MakeShared<FJsonValueNumber>(ProofAudit.FirstViolationBrickOrigin.Y),
|
||||
MakeShared<FJsonValueNumber>(ProofAudit.FirstViolationBrickOrigin.Z)});
|
||||
FirstProofViolation->SetArrayField(TEXT("brick_size"), {
|
||||
MakeShared<FJsonValueNumber>(ProofAudit.FirstViolationBrickSize.X),
|
||||
MakeShared<FJsonValueNumber>(ProofAudit.FirstViolationBrickSize.Y),
|
||||
MakeShared<FJsonValueNumber>(ProofAudit.FirstViolationBrickSize.Z)});
|
||||
FirstProofViolation->SetArrayField(TEXT("sample_voxels"), {
|
||||
MakeShared<FJsonValueNumber>(ProofAudit.FirstViolationSample.X),
|
||||
MakeShared<FJsonValueNumber>(ProofAudit.FirstViolationSample.Y),
|
||||
MakeShared<FJsonValueNumber>(ProofAudit.FirstViolationSample.Z)});
|
||||
FirstProofViolation->SetBoolField(TEXT("inside_brick"),
|
||||
ProofAudit.bFirstViolationWasInsideBrick);
|
||||
FirstProofViolation->SetBoolField(TEXT("claimed_air"),
|
||||
ProofAudit.bFirstViolationClaimedAir);
|
||||
FirstProofViolation->SetNumberField(TEXT("final_density"),
|
||||
ProofAudit.FirstViolationDensity);
|
||||
FirstProofViolation->SetNumberField(TEXT("center_macro"),
|
||||
ProofAudit.FirstViolationCenterMacro);
|
||||
FirstProofViolation->SetNumberField(TEXT("sample_macro"),
|
||||
ProofAudit.FirstViolationSampleMacro);
|
||||
FirstProofViolation->SetNumberField(TEXT("center_room_macro"),
|
||||
ProofAudit.FirstViolationCenterRoomMacro);
|
||||
FirstProofViolation->SetNumberField(TEXT("sample_room_macro"),
|
||||
ProofAudit.FirstViolationSampleRoomMacro);
|
||||
FirstProofViolation->SetNumberField(TEXT("sample_detail"),
|
||||
ProofAudit.FirstViolationSampleDetail);
|
||||
FirstProofViolation->SetNumberField(TEXT("center_to_sample_macro_delta_per_voxel"),
|
||||
ProofAudit.FirstViolationMacroDeltaPerVoxel);
|
||||
FirstProofViolation->SetNumberField(TEXT("path_max_macro_gradient"),
|
||||
ProofAudit.FirstViolationPathMaxGradient);
|
||||
FirstProofViolation->SetArrayField(TEXT("path_max_from_voxels"), {
|
||||
MakeShared<FJsonValueNumber>(ProofAudit.FirstViolationPathMaxFrom.X),
|
||||
MakeShared<FJsonValueNumber>(ProofAudit.FirstViolationPathMaxFrom.Y),
|
||||
MakeShared<FJsonValueNumber>(ProofAudit.FirstViolationPathMaxFrom.Z)});
|
||||
FirstProofViolation->SetArrayField(TEXT("path_max_to_voxels"), {
|
||||
MakeShared<FJsonValueNumber>(ProofAudit.FirstViolationPathMaxTo.X),
|
||||
MakeShared<FJsonValueNumber>(ProofAudit.FirstViolationPathMaxTo.Y),
|
||||
MakeShared<FJsonValueNumber>(ProofAudit.FirstViolationPathMaxTo.Z)});
|
||||
FirstProofViolation->SetNumberField(TEXT("path_max_from_macro"),
|
||||
ProofAudit.FirstViolationPathMaxFromMacro);
|
||||
FirstProofViolation->SetNumberField(TEXT("path_max_to_macro"),
|
||||
ProofAudit.FirstViolationPathMaxToMacro);
|
||||
FirstProofViolation->SetNumberField(TEXT("path_max_from_room_macro"),
|
||||
ProofAudit.FirstViolationPathMaxFromRoomMacro);
|
||||
FirstProofViolation->SetNumberField(TEXT("path_max_to_room_macro"),
|
||||
ProofAudit.FirstViolationPathMaxToRoomMacro);
|
||||
FirstProofViolation->SetStringField(TEXT("path_max_from_room_diagnostic"),
|
||||
ProofAudit.FirstViolationPathMaxFromRoomDiagnostic);
|
||||
FirstProofViolation->SetStringField(TEXT("path_max_to_room_diagnostic"),
|
||||
ProofAudit.FirstViolationPathMaxToRoomDiagnostic);
|
||||
FirstProofViolation->SetNumberField(TEXT("tiny_step_max_macro_gradient"),
|
||||
ProofAudit.FirstViolationTinyStepMaxGradient);
|
||||
Existing->SetObjectField(TEXT("first_proof_violation"), FirstProofViolation);
|
||||
Existing->SetNumberField(TEXT("undersized_bound_claims_checked"),
|
||||
static_cast<double>(ProofAudit.DeliberatelyUndersizedBoundClaimsChecked));
|
||||
Existing->SetNumberField(TEXT("wrong_sign_samples_caught_by_undersized_bound"),
|
||||
|
||||
@@ -589,6 +589,53 @@ function Sync-OwnerAssets {
|
||||
return @($rows)
|
||||
}
|
||||
|
||||
function Sync-Step2fCaptureMaterialDependencies {
|
||||
# The authored terrain materials depend on packages outside the plugin. Copy their exact
|
||||
# source packages into the isolated host so the in-game capture uses the owner's material
|
||||
# graph instead of Unreal's fallback material. Source assets remain untouched.
|
||||
$rows = [System.Collections.Generic.List[object]]::new()
|
||||
$relativePackages = @(
|
||||
'Magic_Forest\Landscape_Material\Rock_Cliffs\T_Rock_C',
|
||||
'Magic_Forest\Mesh\Rock\Textures\T_Rocks_N',
|
||||
'VoxelForge\MF_LayerWeight',
|
||||
'VoxelForge\MF_Triplanar',
|
||||
'VoxelForge\MF_VoxelOrbLighting',
|
||||
'LUSH_StylizedEnvironmentSet\Foliage\Grass\LG_Grass',
|
||||
'LUSH_StylizedEnvironmentSet\Landscape\Textures\T_BeachSand_N',
|
||||
'UltraDynamicSky\Textures\3D_Clouds\3DCells_128'
|
||||
)
|
||||
foreach ($relativePackage in $relativePackages) {
|
||||
$sourceBase = Join-Path (Join-Path $ProjectRoot 'Content') $relativePackage
|
||||
$targetBase = Join-Path (Join-Path $HostRoot 'Content') $relativePackage
|
||||
$sourceAsset = "$sourceBase.uasset"
|
||||
if (-not (Test-Path -LiteralPath $sourceAsset -PathType Leaf)) {
|
||||
throw "Step2f capture material dependency is missing: $sourceAsset"
|
||||
}
|
||||
Ensure-Directory (Split-Path -Parent $targetBase)
|
||||
$copied = [System.Collections.Generic.List[string]]::new()
|
||||
foreach ($extension in @('.uasset', '.uexp', '.ubulk', '.uptnl')) {
|
||||
$source = "$sourceBase$extension"
|
||||
if (Test-Path -LiteralPath $source -PathType Leaf) {
|
||||
$target = "$targetBase$extension"
|
||||
$null = Copy-Item -LiteralPath $source -Destination $target -Force -PassThru
|
||||
[void]$copied.Add($target)
|
||||
}
|
||||
}
|
||||
foreach ($target in $copied) {
|
||||
$relativeTarget = $target.Substring((Join-Path $HostRoot 'Content').Length).TrimStart('\')
|
||||
[void]$rows.Add([ordered]@{
|
||||
name = "Step2fCapture/$relativeTarget"
|
||||
used_by_scenario = $true
|
||||
source_path = (Join-Path (Join-Path $ProjectRoot 'Content') $relativeTarget)
|
||||
source_sha256 = Get-Sha256 (Join-Path (Join-Path $ProjectRoot 'Content') $relativeTarget)
|
||||
staged_path = $target
|
||||
staged_sha256 = Get-Sha256 $target
|
||||
})
|
||||
}
|
||||
}
|
||||
return @($rows)
|
||||
}
|
||||
|
||||
function Sync-TestCharacterFixture {
|
||||
$rows = [System.Collections.Generic.List[object]]::new()
|
||||
|
||||
@@ -652,7 +699,8 @@ function Merge-Cvars([System.Collections.IDictionary]$Base) {
|
||||
return $merged
|
||||
}
|
||||
|
||||
function New-CommonArguments([string]$Kind, [string]$LogPath, $CvarMap, [bool]$EnableRender = $false) {
|
||||
function New-CommonArguments([string]$Kind, [string]$LogPath, $CvarMap,
|
||||
[bool]$EnableRender = $false) {
|
||||
$safeLabel = ($Label -replace '[^A-Za-z0-9_.-]', '_')
|
||||
$args = [System.Collections.Generic.List[string]]::new()
|
||||
# Quoting the whole -project= token is intentional: this is the failure mode that used to
|
||||
@@ -676,6 +724,10 @@ function New-CommonArguments([string]$Kind, [string]$LogPath, $CvarMap, [bool]$E
|
||||
[void]$args.Add('-NoZenAutoLaunch')
|
||||
[void]$args.Add('-notraceserver')
|
||||
[void]$args.Add('-nocrashreports')
|
||||
# The editor's default usage-data setting creates a process summary under the
|
||||
# machine-wide Epic analytics directory, outside this run's isolated output.
|
||||
# Keep harness launches self-contained and let the external-state guard verify it.
|
||||
[void]$args.Add('-ini:EditorSettings:/Script/UnrealEd.AnalyticsPrivacySettings:bSendUsageData=false')
|
||||
[void]$args.Add('-WindowsPlatformCrashContext.ForceCrashReportDialogOff=1')
|
||||
[void]$args.Add("-enginesaveddirsuffix=VoxelForgeTest_$safeLabel")
|
||||
[void]$args.Add(('-userdir="{0}"' -f (Join-Path $RunRoot 'User')))
|
||||
@@ -1388,6 +1440,9 @@ try {
|
||||
} else {
|
||||
$AssetEvidence = @(Get-AssetEvidence)
|
||||
}
|
||||
if ($Scenario -in @('step2f-capture', 'horizon-render')) {
|
||||
$AssetEvidence += @(Sync-Step2fCaptureMaterialDependencies)
|
||||
}
|
||||
if ($Scenario -eq 'tests') {
|
||||
$AssetEvidence += @(Sync-TestCharacterFixture)
|
||||
}
|
||||
@@ -1623,11 +1678,14 @@ try {
|
||||
}
|
||||
}
|
||||
'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.TestCeilingCameraX'] = 0
|
||||
$baseCvars['voxel.TestCeilingCameraY'] = 0
|
||||
$baseCvars['voxel.TestCeilingCameraZ'] = -8800
|
||||
$baseCvars['voxel.TestCeilingCameraPitch'] = 0
|
||||
$baseCvars['voxel.TestCeilingCameraYaw'] = 0
|
||||
$baseCvars['voxel.TestCeilingWallCloseup'] = if ($Step2fCaptureView -eq 'wall-closeup') { 1 } else { 0 }
|
||||
$baseCvars['voxel.TestCeilingView'] = 1
|
||||
$baseCvars['voxel.TestCeilingCaptureDelaySeconds'] = 60
|
||||
$baseCvars['voxel.TestExitSeconds'] = 120
|
||||
@@ -1640,11 +1698,8 @@ try {
|
||||
$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
|
||||
$HorizonMetrics = [ordered]@{ requested = $false; status = 'not_requested_for_capture' }
|
||||
$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 {
|
||||
|
||||