feat(gen): Jahni's playtest corrections -- rooms flattened, roads deleted

From walking a TunnelNetwork preset at (0,0). Four corrections, and two of
them were the same mistake.

2a. The (0,0) landing room is sized to host a FUTURE straight vertical
    shaft from the surface, with flooring around the origin itself. The
    room is the landing for a shaft the player digs later.
2b. The inter-strate descent no longer starts at the spine -- placed by
    deterministic hash within a radius around (0,0). The spine room is for
    the surface shaft; the descent is elsewhere.
2c. ⛔ DELETED the landing->hub connector roads from 413c15d. Jahni:
    "its straight up making other tunnels straightly through everything...
    they have nothing to do here." Zero references remain.
2d. ⭐ FLATTEN THE ROOM JOINS instead. Where two spherical rooms intersect
    and leave a lip above head height, the join is brought down to a
    walkable level.

⭐ 2c AND 2d ARE ONE CORRECTION. I solved "rooms do not connect" by
TUNNELLING BETWEEN THEM when the answer was MAKING THEIR FLOORS MEET. The
81-run sweep had already said so -- zero narrow-gap events, every failure a
vertical severance, renders showing "a sharp diagonal ledge descending into
black open space". A radial corridor to the origin could never have fixed
that; flattening the joins is aimed at exactly it.

2e. Descent tunnel 1.5x wider, per "1.5x bigger wouldn't be too much".
2f. Corridors WANDER. Jahni, from the renders: "its not a maze, its still a
    latice made straight paths." The spanning tree changed WHICH edges
    exist, not what an edge looks like -- every corridor was a straight
    axis-aligned capsule, every junction 90 degrees. Now hash-jittered
    chains, seam-safe like the tree.

RENDERER: 192x128 -> 512x288, and cameras are now seeded from the
player-fit set -- "a density-checked pose from the exact player-fit walk
mask". Previously they sat INSIDE ROCK looking at back-faces, which Jahni
spotted instantly and no metric could. First genuinely legible pictures of
this world.

ParallelFor landed at 6 sites in VoxelStrateMeasure before I stopped the
run; the rest of that work continues next.

⚠️ AND THE RENDERER IS ARCHITECTURALLY WRONG, per Jahni: "why, in engine,
does it take a minute to have clear view of thousands of angles, but in our
renderer it takes ages?" Because the engine MESHES ONCE and rasterises --
~32k density calls per chunk, then every angle is free. I RAYMARCH: tens of
millions of density calls PER IMAGE, nothing reused between frames. We
already have the mesher and an export mode. Next task renders from the
mesh.

Also from him: runs must stay under 30 minutes. This one hit 33 and
produced 2 images.

Build clean. Committed as a checkpoint -- the task was stopped mid-Part-3,
not finished.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aheo2crrZmHuYtPpdY2Cb
This commit is contained in:
Fr0zkaandClaude Opus 5 committed 2026-09-08 14:14:29 +02:00
1 parent 55bf807f60
commit d97373c5a8
18 files changed
+2433 -1021

No files matched your search

+145 -58
View File
@@ -7,6 +7,7 @@
#include "VoxelGenerator.h"
#include "VoxelStrateManager.h"
#include "VoxelTypes.h"
#include "Async/ParallelFor.h"
namespace VoxelStrateMeasurePrivate
{
@@ -388,21 +389,26 @@ namespace VoxelStrateMeasurePrivate
OutGrid.Density.SetNumUninitialized(OutGrid.CellCount);
}
bool bSawNonFiniteDensity = false;
int32 CellIndex = 0;
for (int32 Z = 0; Z < OutGrid.NumZ; ++Z)
// Each Z slab writes a disjoint contiguous region. GetDensityAt is a const, read-only
// query used concurrently by the mesher, and custom samplers are required to provide
// the same const/thread-safe contract. The non-finite flag is also per-cell so the
// diagnostic reduction below never depends on task completion order.
TArray<uint8> NonFiniteFlags;
NonFiniteFlags.Init(0u, OutGrid.CellCount);
ParallelFor(OutGrid.NumZ, [&](int32 Z)
{
for (int32 Y = 0; Y < OutGrid.NumY; ++Y)
{
for (int32 X = 0; X < OutGrid.NumX; ++X)
{
const int32 CellIndex = OutGrid.Index(X, Y, Z);
const FVector Sample = OutGrid.CellCenter(X, Y, Z);
const float Density = Sampler != nullptr
? Sampler->SampleDensity(Sample.X, Sample.Y, Sample.Z)
: Generator->GetDensityAt(Sample.X, Sample.Y, Sample.Z);
if (!FMath::IsFinite(Density))
{
bSawNonFiniteDensity = true;
NonFiniteFlags[CellIndex] = 1u;
}
// MC convention in this codebase: negative is solid, positive is air.
@@ -410,11 +416,16 @@ namespace VoxelStrateMeasurePrivate
{
OutGrid.Density[CellIndex] = Density;
}
OutGrid.Air[CellIndex++] = Density > 0.0f ? 1u : 0u;
OutGrid.Air[CellIndex] = Density > 0.0f ? 1u : 0u;
}
}
}
});
bool bSawNonFiniteDensity = false;
for (int32 CellIndex = 0; CellIndex < OutGrid.CellCount; ++CellIndex)
{
bSawNonFiniteDensity |= NonFiniteFlags[CellIndex] != 0u;
}
if (bSawNonFiniteDensity)
{
return Refuse(OutReason, TEXT("At least one density sample was non-finite."));
@@ -552,54 +563,104 @@ namespace VoxelStrateMeasurePrivate
void BuildManhattanDistanceToSolid(const FSampleGrid& Grid, TArray<int32>& OutDistances)
{
// Two raster passes with 6-neighbour Manhattan relaxation. The result is an approximation
// of distance-to-solid in the original field: each coarse grid unit is SampleStep VOXELS.
// Two wavefront passes with 6-neighbour Manhattan relaxation. A cell depends only on the
// immediately preceding diagonal in the forward pass (or following diagonal in the
// reverse pass), so each diagonal is safe to process in parallel while the diagonal
// barriers preserve the exact raster result. Each coarse grid unit is SampleStep VOXELS.
constexpr int32 Infinity = INT32_MAX;
OutDistances.SetNumUninitialized(Grid.CellCount);
for (int32 Index = 0; Index < Grid.CellCount; ++Index)
ParallelFor(Grid.CellCount, [&](int32 Index)
{
OutDistances[Index] = Grid.Air[Index] == 0u ? 0 : Infinity;
}
});
auto Relax = [&](int32 Current, int32 Neighbor)
const int32 LastDiagonal = Grid.NumX + Grid.NumY + Grid.NumZ - 3;
const auto ProcessDiagonal = [&](int32 Diagonal, bool bForward)
{
if (OutDistances[Neighbor] == Infinity)
const int32 MinZ = FMath::Max(
0, Diagonal - (Grid.NumX - 1) - (Grid.NumY - 1));
const int32 MaxZ = FMath::Min(Grid.NumZ - 1, Diagonal);
if (MinZ > MaxZ)
{
return;
}
const int32 Candidate = OutDistances[Neighbor] == INT32_MAX - 1
? Infinity : OutDistances[Neighbor] + 1;
OutDistances[Current] = FMath::Min(OutDistances[Current], Candidate);
int32 DiagonalCellCount = 0;
for (int32 Z = MinZ; Z <= MaxZ; ++Z)
{
const int32 MinY = FMath::Max(
0, Diagonal - Z - (Grid.NumX - 1));
const int32 MaxY = FMath::Min(Grid.NumY - 1, Diagonal - Z);
DiagonalCellCount += FMath::Max(0, MaxY - MinY + 1);
}
const auto ProcessZ = [&](int32 Z)
{
const int32 MinY = FMath::Max(
0, Diagonal - Z - (Grid.NumX - 1));
const int32 MaxY = FMath::Min(Grid.NumY - 1, Diagonal - Z);
for (int32 Y = MinY; Y <= MaxY; ++Y)
{
const int32 X = Diagonal - Z - Y;
const int32 Current = Grid.Index(X, Y, Z);
if (Grid.Air[Current] == 0u)
{
continue;
}
const auto Relax = [&](int32 Neighbor)
{
if (OutDistances[Neighbor] == Infinity)
{
return;
}
const int32 Candidate = OutDistances[Neighbor] == INT32_MAX - 1
? Infinity : OutDistances[Neighbor] + 1;
OutDistances[Current] = FMath::Min(
OutDistances[Current], Candidate);
};
if (bForward)
{
if (X > 0) Relax(Grid.Index(X - 1, Y, Z));
if (Y > 0) Relax(Grid.Index(X, Y - 1, Z));
if (Z > 0) Relax(Grid.Index(X, Y, Z - 1));
}
else
{
if (X + 1 < Grid.NumX) Relax(Grid.Index(X + 1, Y, Z));
if (Y + 1 < Grid.NumY) Relax(Grid.Index(X, Y + 1, Z));
if (Z + 1 < Grid.NumZ) Relax(Grid.Index(X, Y, Z + 1));
}
}
};
// Tiny edge diagonals are cheaper to execute directly; substantial diagonals get
// one task per Z row, with every row writing a disjoint set of cells.
const int32 ZCount = MaxZ - MinZ + 1;
if (ZCount > 1 && DiagonalCellCount >= 256)
{
ParallelFor(ZCount, [&](int32 ZOffset)
{
ProcessZ(MinZ + ZOffset);
});
}
else
{
for (int32 Z = MinZ; Z <= MaxZ; ++Z)
{
ProcessZ(Z);
}
}
};
for (int32 Z = 0; Z < Grid.NumZ; ++Z)
for (int32 Diagonal = 0; Diagonal <= LastDiagonal; ++Diagonal)
{
for (int32 Y = 0; Y < Grid.NumY; ++Y)
{
for (int32 X = 0; X < Grid.NumX; ++X)
{
const int32 Current = Grid.Index(X, Y, Z);
if (Grid.Air[Current] == 0u) continue;
if (X > 0) Relax(Current, Grid.Index(X - 1, Y, Z));
if (Y > 0) Relax(Current, Grid.Index(X, Y - 1, Z));
if (Z > 0) Relax(Current, Grid.Index(X, Y, Z - 1));
}
}
ProcessDiagonal(Diagonal, true);
}
for (int32 Z = Grid.NumZ - 1; Z >= 0; --Z)
for (int32 Diagonal = LastDiagonal; Diagonal >= 0; --Diagonal)
{
for (int32 Y = Grid.NumY - 1; Y >= 0; --Y)
{
for (int32 X = Grid.NumX - 1; X >= 0; --X)
{
const int32 Current = Grid.Index(X, Y, Z);
if (Grid.Air[Current] == 0u) continue;
if (X + 1 < Grid.NumX) Relax(Current, Grid.Index(X + 1, Y, Z));
if (Y + 1 < Grid.NumY) Relax(Current, Grid.Index(X, Y + 1, Z));
if (Z + 1 < Grid.NumZ) Relax(Current, Grid.Index(X, Y, Z + 1));
}
}
ProcessDiagonal(Diagonal, false);
}
}
@@ -1092,14 +1153,23 @@ namespace VoxelStrateMeasurePrivate
}
OutPlayerFit.Init(0u, Grid.CellCount);
TArray<float> SupportHeights;
SupportHeights.SetNumUninitialized(SupportOffsets.Num());
TArray<int32> SupportAnchorZs;
SupportAnchorZs.SetNumUninitialized(SupportOffsets.Num());
TArray<uint8> bSupported;
bSupported.Init(0u, SupportOffsets.Num());
for (int32 Z = 1; Z < Grid.NumZ; ++Z)
TArray<int64> PlayerFitCounts;
PlayerFitCounts.Init(0, Grid.NumZ);
// A candidate pose only reads the immutable sampled grid and writes its own mask cell.
// Give each Z slab private stencil scratch, then reduce the per-slab counts in Z order
// below. This removes the old shared scratch/count race without changing any predicate.
ParallelFor(FMath::Max(Grid.NumZ - 1, 0), [&](int32 SliceIndex)
{
const int32 Z = SliceIndex + 1;
TArray<float> SupportHeights;
SupportHeights.SetNumUninitialized(SupportOffsets.Num());
TArray<int32> SupportAnchorZs;
SupportAnchorZs.SetNumUninitialized(SupportOffsets.Num());
TArray<uint8> bSupported;
bSupported.Init(0u, SupportOffsets.Num());
int64 NumPlayerFitInSlice = 0;
for (int32 Y = 0; Y < Grid.NumY; ++Y)
{
for (int32 X = 0; X < Grid.NumX; ++X)
@@ -1188,14 +1258,18 @@ namespace VoxelStrateMeasurePrivate
{
continue;
}
if (OutPlayerFit[Current] != 0u)
{
continue;
}
OutPlayerFit[Current] = 1u;
++OutNumPlayerFitCells;
++NumPlayerFitInSlice;
}
}
PlayerFitCounts[Z] = NumPlayerFitInSlice;
});
// Fixed-index reduction is intentional: even though the terms are integral, keeping the
// reduction serial documents the determinism rule shared by the floating metrics below.
for (int32 Z = 1; Z < Grid.NumZ; ++Z)
{
OutNumPlayerFitCells += PlayerFitCounts[Z];
}
return true;
}
@@ -1211,11 +1285,16 @@ namespace VoxelStrateMeasurePrivate
const int32 PlaneCells = Grid.NumX * Grid.NumY;
constexpr int32 Infinity = INT32_MAX / 4;
TArray<int32> Distances;
Distances.SetNumUninitialized(PlaneCells);
int32 MinimumDistance = Infinity;
for (int32 Z = 0; Z < Grid.NumZ; ++Z)
TArray<int32> PerSliceMinimum;
PerSliceMinimum.Init(Infinity, Grid.NumZ);
// Each horizontal distance transform is independent. Keep its mutable raster in the
// worker and publish one integer result per Z slice; the final minimum is reduced in
// ascending Z order for an explicit deterministic reduction.
ParallelFor(Grid.NumZ, [&](int32 Z)
{
TArray<int32> Distances;
Distances.SetNumUninitialized(PlaneCells);
for (int32 Y = 0; Y < Grid.NumY; ++Y)
{
for (int32 X = 0; X < Grid.NumX; ++X)
@@ -1269,6 +1348,7 @@ namespace VoxelStrateMeasurePrivate
}
}
int32 SliceMinimum = Infinity;
for (int32 Y = 0; Y < Grid.NumY; ++Y)
{
for (int32 X = 0; X < Grid.NumX; ++X)
@@ -1276,11 +1356,18 @@ namespace VoxelStrateMeasurePrivate
const int32 Cell = Grid.Index(X, Y, Z);
if (PlayerFit[Cell] != 0u)
{
MinimumDistance = FMath::Min(
MinimumDistance, Distances[X + Grid.NumX * Y]);
SliceMinimum = FMath::Min(
SliceMinimum, Distances[X + Grid.NumX * Y]);
}
}
}
PerSliceMinimum[Z] = SliceMinimum;
});
int32 MinimumDistance = Infinity;
for (int32 Z = 0; Z < Grid.NumZ; ++Z)
{
MinimumDistance = FMath::Min(MinimumDistance, PerSliceMinimum[Z]);
}
return MinimumDistance == Infinity