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:
1 parent
55bf807f60
commit
d97373c5a8
18 files changed
+2433
-1021
No files matched your search
@@ -24,6 +24,11 @@ namespace VoxelPassageGeometry
|
||||
constexpr float SealSafetyMarginVoxels = 4.0f;
|
||||
constexpr float RoomRoundingVoxels = 1.5f;
|
||||
constexpr float LandingCarveBlendVoxels = 4.0f;
|
||||
// The origin landing is reserved for the same straight spine that the vertical-shaft
|
||||
// generator uses: OriginSpineRadius on each side, with a three-voxel / 0.75 m floor ledge
|
||||
// around it after the support patch's one-voxel inset. At the default radius (14 voxels) this
|
||||
// is a 28-voxel / 7 m future shaft diameter.
|
||||
constexpr float OriginShaftFloorClearanceVoxels = 4.0f;
|
||||
// Level tunnel apron outside each room-floor edge. It covers the room/tunnel SDF blend and
|
||||
// keeps the ramp from starting while the route is still over the room's square support patch.
|
||||
constexpr float WalkableTunnelLandingApronVoxels = 6.0f; // 1.5 m
|
||||
@@ -33,18 +38,16 @@ namespace VoxelPassageGeometry
|
||||
// ownership band: the floor writer owns the plane, while the air writer owns the capsule
|
||||
// volume above it even when two overlapping passage projections differ by a fraction.
|
||||
constexpr float WalkableTunnelFloorAirClearanceVoxels = 0.5f;
|
||||
// The graph-tunnel post owns the interior above its floor after terrain and passage-floor
|
||||
// writers have run. The support-floor predicate is also an exclusion mask for the air post,
|
||||
// so overlapping graph tubes cannot erase one another's walkable support band.
|
||||
constexpr float CaveTunnelAirCoreInsetVoxels = 0.5f;
|
||||
|
||||
// A 15 degree ramp is comfortably below the 44 degree engine walkability ceiling. The
|
||||
// latter is a scramble limit, not a sensible default for a tunnel the player walks through.
|
||||
constexpr float WalkableTunnelMaxGradientDegrees = 15.0f;
|
||||
constexpr float WalkableTunnelMaxGradient = 0.2679491924311227f; // tan(15 degrees)
|
||||
|
||||
// The network connector retains the §6.5 single-file dimensions: 2.5 m clear width and 3 m
|
||||
// clear height. These values are in voxels because all passage descriptors are voxel-space.
|
||||
constexpr float ConnectorRadiusVoxels = 5.0f;
|
||||
constexpr float ConnectorHeightVoxels = 12.0f;
|
||||
constexpr float RootOverlapVoxels = 2.25f;
|
||||
|
||||
// The walkability contract is about the floor carried by the tube, not only its centreline.
|
||||
// A tapered tube changes that floor by the radius delta, so the construction and the audit
|
||||
// use this same conservative floor delta.
|
||||
@@ -53,6 +56,204 @@ namespace VoxelPassageGeometry
|
||||
return Centreline.Z - FMath::Abs(Radius);
|
||||
}
|
||||
|
||||
// Vertical-shaft tree links are round air capsules, but the player-fit graph needs a
|
||||
// deterministic support plane to walk between shafts. Keep that plane on the first
|
||||
// seal-safe ledge shared by the whole strate, so every tree edge has the same walk level.
|
||||
// This is shaft-network support, not an origin landing connector.
|
||||
FORCEINLINE float VerticalShaftConnectorFloorThickness(float Radius)
|
||||
{
|
||||
return FMath::Min(
|
||||
LandingFloorThicknessVoxels,
|
||||
FMath::Max(0.5f, FMath::Abs(Radius) * 0.5f));
|
||||
}
|
||||
|
||||
FORCEINLINE float VerticalShaftConnectorFloorZ(
|
||||
float StrateBottomZ,
|
||||
float InteriorBottomZ,
|
||||
float InteriorTopZ,
|
||||
float LedgeSpacing,
|
||||
float LedgeDepth,
|
||||
float ConnectorRadius)
|
||||
{
|
||||
const float FloorThickness = VerticalShaftConnectorFloorThickness(ConnectorRadius);
|
||||
const float MinimumFloor = InteriorBottomZ
|
||||
+ FloorThickness + SealSafetyMarginVoxels;
|
||||
const float MaximumFloor = InteriorTopZ
|
||||
- PlayerHeightVoxels - SealSafetyMarginVoxels;
|
||||
if (!FMath::IsFinite(StrateBottomZ)
|
||||
|| !FMath::IsFinite(InteriorBottomZ)
|
||||
|| !FMath::IsFinite(InteriorTopZ)
|
||||
|| !FMath::IsFinite(LedgeSpacing)
|
||||
|| !FMath::IsFinite(LedgeDepth)
|
||||
|| !FMath::IsFinite(MinimumFloor)
|
||||
|| !FMath::IsFinite(MaximumFloor)
|
||||
|| !(MaximumFloor > MinimumFloor))
|
||||
{
|
||||
return 0.5f * (InteriorBottomZ + InteriorTopZ);
|
||||
}
|
||||
|
||||
if (LedgeSpacing > 0.0f && LedgeDepth > 0.0f)
|
||||
{
|
||||
const float RelativeBottom = InteriorBottomZ - StrateBottomZ;
|
||||
const int32 FirstPeriod = FMath::FloorToInt(RelativeBottom / LedgeSpacing);
|
||||
// The small fixed bound keeps malformed authored values from turning generation into
|
||||
// an unbounded search. Normal shaft strates find the first or second period.
|
||||
constexpr int32 MaxPeriods = 64;
|
||||
for (int32 PeriodOffset = 0; PeriodOffset < MaxPeriods; ++PeriodOffset)
|
||||
{
|
||||
const float PeriodStart = static_cast<float>(FirstPeriod + PeriodOffset)
|
||||
* LedgeSpacing;
|
||||
const float Candidate = StrateBottomZ + PeriodStart + LedgeDepth;
|
||||
if (Candidate > MinimumFloor && Candidate < MaximumFloor)
|
||||
{
|
||||
return Candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return FMath::Clamp(
|
||||
0.5f * (InteriorBottomZ + InteriorTopZ), MinimumFloor, MaximumFloor);
|
||||
}
|
||||
|
||||
FORCEINLINE float VerticalShaftConnectorCenterZ(
|
||||
float StrateBottomZ,
|
||||
float InteriorBottomZ,
|
||||
float InteriorTopZ,
|
||||
float LedgeSpacing,
|
||||
float LedgeDepth,
|
||||
float ConnectorRadius)
|
||||
{
|
||||
const float SafeRadius = FMath::Abs(ConnectorRadius);
|
||||
const float FloorThickness = VerticalShaftConnectorFloorThickness(SafeRadius);
|
||||
return VerticalShaftConnectorFloorZ(
|
||||
StrateBottomZ, InteriorBottomZ, InteriorTopZ,
|
||||
LedgeSpacing, LedgeDepth, SafeRadius)
|
||||
+ SafeRadius - FloorThickness;
|
||||
}
|
||||
|
||||
/** Add a flat, inset support band below one horizontal shaft-tree capsule. */
|
||||
FORCEINLINE void VF_ApplyVerticalShaftConnectorFloor(
|
||||
float& Density,
|
||||
float WorldX,
|
||||
float WorldY,
|
||||
float WorldZ,
|
||||
const FVector& Start,
|
||||
const FVector& End,
|
||||
float Radius,
|
||||
float FloorZ,
|
||||
float BaseDensity)
|
||||
{
|
||||
const float SafeRadius = FMath::Abs(Radius);
|
||||
const float FloorThickness = VerticalShaftConnectorFloorThickness(SafeRadius);
|
||||
const float FloorRadius = FMath::Max(0.5f, SafeRadius - 0.5f);
|
||||
if (!(SafeRadius > 0.0f)
|
||||
|| !(BaseDensity > 0.0f)
|
||||
|| !FMath::IsFinite(WorldX)
|
||||
|| !FMath::IsFinite(WorldY)
|
||||
|| !FMath::IsFinite(WorldZ)
|
||||
|| !FMath::IsFinite(FloorZ)
|
||||
|| !FMath::IsFinite(Start.X) || !FMath::IsFinite(Start.Y)
|
||||
|| !FMath::IsFinite(End.X) || !FMath::IsFinite(End.Y))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const FVector2D A(Start.X, Start.Y);
|
||||
const FVector2D Delta(End.X - Start.X, End.Y - Start.Y);
|
||||
const float LengthSquared = Delta.SizeSquared();
|
||||
const float T = LengthSquared > KINDA_SMALL_NUMBER
|
||||
? FMath::Clamp(
|
||||
FVector2D::DotProduct(FVector2D(WorldX, WorldY) - A, Delta)
|
||||
/ LengthSquared,
|
||||
0.0f, 1.0f)
|
||||
: 0.0f;
|
||||
const FVector2D Closest = A + Delta * T;
|
||||
if ((FVector2D(WorldX, WorldY) - Closest).SizeSquared()
|
||||
> FMath::Square(FloorRadius))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (WorldZ <= FloorZ + KINDA_SMALL_NUMBER
|
||||
&& WorldZ > FloorZ - FloorThickness)
|
||||
{
|
||||
Density = FMath::Max(Density, BaseDensity);
|
||||
}
|
||||
}
|
||||
|
||||
// ApplyVerticalShaftConnectorAir runs before the generator's shared disturbance pass. Keep
|
||||
// this one-bit, call-local hand-off so a bridge/ridge cannot refill the core it just opened.
|
||||
// It is thread-local state, never a per-voxel cache, and is reset around every density query.
|
||||
FORCEINLINE bool& VerticalShaftConnectorAirMarker()
|
||||
{
|
||||
static thread_local bool bMarked = false;
|
||||
return bMarked;
|
||||
}
|
||||
|
||||
FORCEINLINE void ResetVerticalShaftConnectorAirMarker()
|
||||
{
|
||||
VerticalShaftConnectorAirMarker() = false;
|
||||
}
|
||||
|
||||
/** Reassert the walkable air core above one horizontal shaft-tree floor band. */
|
||||
FORCEINLINE void VF_ApplyVerticalShaftConnectorAir(
|
||||
float& Density,
|
||||
float WorldX,
|
||||
float WorldY,
|
||||
float WorldZ,
|
||||
const FVector& Start,
|
||||
const FVector& End,
|
||||
float Radius,
|
||||
float FloorZ,
|
||||
float BaseDensity)
|
||||
{
|
||||
const float SafeRadius = FMath::Abs(Radius);
|
||||
// Leave a one-voxel wall band for the roughness/solid shell, while retaining more than
|
||||
// enough width for the 1.36-voxel player capsule and its support stencil.
|
||||
const float CoreRadius = FMath::Max(0.5f, SafeRadius - 1.0f);
|
||||
if (!(SafeRadius > 0.0f)
|
||||
|| !(BaseDensity > 0.0f)
|
||||
|| !FMath::IsFinite(WorldX)
|
||||
|| !FMath::IsFinite(WorldY)
|
||||
|| !FMath::IsFinite(WorldZ)
|
||||
|| !FMath::IsFinite(FloorZ)
|
||||
|| !FMath::IsFinite(Start.X) || !FMath::IsFinite(Start.Y)
|
||||
|| !FMath::IsFinite(Start.Z)
|
||||
|| !FMath::IsFinite(End.X) || !FMath::IsFinite(End.Y)
|
||||
|| !FMath::IsFinite(End.Z))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// The exact floor sample belongs to the air core's lower ownership edge. The floor
|
||||
// writer only owns the three voxels below FloorZ, so clearing from the half-voxel band
|
||||
// above it cannot remove support.
|
||||
if (WorldZ + KINDA_SMALL_NUMBER
|
||||
< FloorZ + WalkableTunnelFloorAirClearanceVoxels)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const FVector Delta = End - Start;
|
||||
const float LengthSquared = Delta.SizeSquared();
|
||||
const float T = LengthSquared > KINDA_SMALL_NUMBER
|
||||
? FMath::Clamp(FVector::DotProduct(FVector(WorldX, WorldY, WorldZ) - Start, Delta)
|
||||
/ LengthSquared, 0.0f, 1.0f)
|
||||
: 0.0f;
|
||||
const FVector Closest = Start + Delta * T;
|
||||
if ((FVector(WorldX, WorldY, WorldZ) - Closest).SizeSquared()
|
||||
> FMath::Square(CoreRadius))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Internal convention: negative is air, positive is solid. This is intentionally a
|
||||
// post-ledge write; a partial shaft shelf must not cap the tree route's player-height
|
||||
// envelope after the capsule has already carved it.
|
||||
VerticalShaftConnectorAirMarker() = true;
|
||||
Density = FMath::Min(Density, -BaseDensity);
|
||||
}
|
||||
|
||||
FORCEINLINE float TunnelFloorGradient(
|
||||
const FVector& Start, float StartRadius,
|
||||
const FVector& End, float EndRadius)
|
||||
@@ -194,7 +395,14 @@ namespace VoxelPassageGeometry
|
||||
return Result;
|
||||
}
|
||||
|
||||
Result.HalfWidth = LandingHalfWidthForRadius(OriginRadius);
|
||||
// Keep a solid walking ledge around the future shaft, not merely a room whose wall is
|
||||
// tangent to it. The shaft diameter is 2 * OriginRadius; the extra four voxels leave a
|
||||
// three-voxel / 0.75 m floor margin after the support patch's one-voxel inset at the
|
||||
// authored 25 cm scale.
|
||||
Result.HalfWidth = FMath::Max(
|
||||
LandingHalfWidthForRadius(OriginRadius),
|
||||
FMath::Max(FMath::Abs(OriginRadius), 1.0f)
|
||||
+ OriginShaftFloorClearanceVoxels);
|
||||
const float DesiredHeight = LandingHeightForRadius(OriginRadius);
|
||||
const float SafeSeal = FMath::Max(SealThickness, 0.0f);
|
||||
const float InnerBottom = StrateBottomZ + SafeSeal;
|
||||
|
||||
Reference in new issue
Block a user