fix(passages): land mouths where a BODY fits -- and the cause is now cornered
VF_SuggestLandingPoint aimed at open AIR. Air is not where a body fits: a mouth could sit in a perfectly open pocket no capsule can stand in. It now requires a legal capsule pose -- clearance (r 1.36, half-height 3.52), support within a 1.8-voxel / 45 cm step height, a walkable support PATCH (4 of 5 columns, centre required, slope <= 44.8 deg), positive density throughout the capsule, strict 2x seal interior, bounded lateral snap. It stays a PURE free function, which was the hard constraint -- GeneratePassages runs inside Initialize while the layout is half-built. So it cannot flood-fill, and Codex correctly did not pretend otherwise: it aims at deterministic guaranteed features instead (room centre/floor, slab floor, horizontal maze edge, shaft feature-core ledge pose, island top). Both mouths query their own strate independently; no passage chaining, so LayoutOrderIndependence stays green. Placement measurably improved: Maze comps 309 -> 82, largest 176 -> 1217, gap 206 -> 122 VerticalShafts comps 112 -> 36, largest 96 -> 619 CrystalChamber gap 47.5 -> 19.1, departure mouth now in a 717-cell comp Underwater comps 86 -> 13 ⚠️ AND THE LAW IS STILL 1/8. Reported honestly rather than tuned. Mouths now land on legal body-sized poses; the poses just are not connected to each other. ⇒ BY ELIMINATION, THE CAUSE IS GENERATION. Four candidates, three dead: size DEAD -- Jahni walked it; bore measures 3.62 m vs 3.677 m spec the stencil FIXED -- fragmentation was 3x overstated by an exact-Z-1 floor placement FIXED -- mouths now sit on legal poses, several in large comps GENERATION STANDS -- body-sized space exists in big components that do not reach each other ⭐ AND THE MAZE TIES TWO OF JAHNI'S COMPLAINTS TOGETHER. Its arrival mouth lands in a 30-cell pocket while the main network holds 1217. Edge selection is INDEPENDENT HASH GATING per +X/+Y/+Z edge (VoxelDensityOpStack.cpp:582/584/586) -- so the corridor graph is a random subgraph with no connectivity guarantee, exactly the percolation failure VerticalShafts had before its drainage tree. That is the same root cause as "mostly a cube full of straight pathway along the edges": a spanning tree would make it BOTH feel like a maze (dead ends, uneven runs) AND be connected by construction. One fix, two complaints. Codex found a real error in my spec: an infinite shaft cylinder has no support surface AT its axis, so the old exact-axis assertion was unsatisfiable. It now requires a deterministic feature core and audits tree connectivity from the resolved axis. Build clean, 29 succeeded / 0 failed / 2 known deliberate warnings. Box audits 0 violations. Shaft refinement 16/16. Order-independence green across 10 slots, 31 passages, 3 pool orders. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K8V9my1PL68pmQb2gutZZV
This commit is contained in:
1 parent
110ae9d0ed
commit
4378385b37
7 files changed
+1365
-165
No files matched your search
@@ -603,8 +603,8 @@ void UVoxelStrateManager::GeneratePassages()
|
||||
constexpr uint32 PassageSaltBendFreq = 0xA110000Au;
|
||||
|
||||
int32 TotalPassages = 0;
|
||||
int32 NumAimedAtUpperOpenPoint = 0;
|
||||
int32 NumAimedAtLowerOpenPoint = 0;
|
||||
int32 NumAimedAtUpperPlayerFit = 0;
|
||||
int32 NumAimedAtLowerPlayerFit = 0;
|
||||
TSet<FString> NoQueryArchetypes;
|
||||
|
||||
const auto ArchetypeName = [](ECaveGeneratorType Archetype)
|
||||
@@ -629,8 +629,9 @@ void UVoxelStrateManager::GeneratePassages()
|
||||
case ECaveGeneratorType::VerticalShafts:
|
||||
// A density of 0.6 does not guarantee an occupied cell in the immediate grid
|
||||
// neighbourhood. Allow the nearest occupied shaft to be two grid cells away while
|
||||
// remaining a bounded local query; the landing query still returns that shaft's
|
||||
// exact axis, so this cannot create a non-topological mouth.
|
||||
// remaining a bounded local query; the landing query still returns a pose inside
|
||||
// that shaft's deterministic feature core, so this cannot create a non-topological
|
||||
// mouth.
|
||||
return FMath::Max(2.0f * Definition.VerticalShaftParams.ShaftSpacing, 1.0f);
|
||||
|
||||
case ECaveGeneratorType::FloatingIslands:
|
||||
@@ -749,20 +750,18 @@ void UVoxelStrateManager::GeneratePassages()
|
||||
// their source. The two queries are deliberately independent: a passage never reads
|
||||
// another passage's endpoint or any live layout state.
|
||||
FVector SuggestedUpperPoint = FVector::ZeroVector;
|
||||
bool bAimedUpperAtOpenPoint = false;
|
||||
bool bAimedUpperAtPlayerFitPoint = false;
|
||||
if (UpperDef)
|
||||
{
|
||||
const bool bUsesRoomSeed =
|
||||
UpperDef->GeneratorType == ECaveGeneratorType::TunnelNetwork
|
||||
|| UpperDef->GeneratorType == ECaveGeneratorType::Underwater;
|
||||
const int32 UpperQuerySeed = SeasonStrates.IsValidIndex(i)
|
||||
? SeasonStrates[i].Seed
|
||||
: (bUsesRoomSeed
|
||||
? static_cast<int32>(VoxelCaveMorphology::MakeStrateSeed(
|
||||
static_cast<uint32>(CachedSeed), Upper.StrateIndex))
|
||||
: CachedSeed);
|
||||
const int32 UpperQuerySeed = bUsesRoomSeed
|
||||
? static_cast<int32>(VoxelCaveMorphology::MakeStrateSeed(
|
||||
static_cast<uint32>(CachedSeed), Upper.StrateIndex))
|
||||
: CachedSeed;
|
||||
|
||||
bAimedUpperAtOpenPoint = VF_SuggestLandingPoint(
|
||||
bAimedUpperAtPlayerFitPoint = VF_SuggestLandingPoint(
|
||||
UpperDef->GeneratorType,
|
||||
UpperDef->GenerationParams,
|
||||
UpperDef->SlabParams,
|
||||
@@ -777,9 +776,9 @@ void UVoxelStrateManager::GeneratePassages()
|
||||
MaxLateralSnapFor(*UpperDef),
|
||||
SuggestedUpperPoint);
|
||||
|
||||
if (bAimedUpperAtOpenPoint)
|
||||
if (bAimedUpperAtPlayerFitPoint)
|
||||
{
|
||||
++NumAimedAtUpperOpenPoint;
|
||||
++NumAimedAtUpperPlayerFit;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -788,22 +787,20 @@ void UVoxelStrateManager::GeneratePassages()
|
||||
}
|
||||
|
||||
FVector SuggestedLowerPoint = FVector::ZeroVector;
|
||||
bool bAimedLowerAtOpenPoint = false;
|
||||
bool bAimedLowerAtPlayerFitPoint = false;
|
||||
const UVoxelStrateDefinition* LowerDef = Lower.Definition;
|
||||
if (LowerDef)
|
||||
{
|
||||
const bool bUsesRoomSeed =
|
||||
LowerDef->GeneratorType == ECaveGeneratorType::TunnelNetwork
|
||||
|| LowerDef->GeneratorType == ECaveGeneratorType::Underwater;
|
||||
const int32 LowerQuerySeed = SeasonStrates.IsValidIndex(i + 1)
|
||||
? SeasonStrates[i + 1].Seed
|
||||
: (bUsesRoomSeed
|
||||
? static_cast<int32>(VoxelCaveMorphology::MakeStrateSeed(
|
||||
static_cast<uint32>(CachedSeed), Lower.StrateIndex))
|
||||
: CachedSeed);
|
||||
const int32 LowerQuerySeed = bUsesRoomSeed
|
||||
? static_cast<int32>(VoxelCaveMorphology::MakeStrateSeed(
|
||||
static_cast<uint32>(CachedSeed), Lower.StrateIndex))
|
||||
: CachedSeed;
|
||||
|
||||
const float MaxLateralSnap = MaxLateralSnapFor(*LowerDef);
|
||||
bAimedLowerAtOpenPoint = VF_SuggestLandingPoint(
|
||||
bAimedLowerAtPlayerFitPoint = VF_SuggestLandingPoint(
|
||||
LowerDef->GeneratorType,
|
||||
LowerDef->GenerationParams,
|
||||
LowerDef->SlabParams,
|
||||
@@ -818,9 +815,9 @@ void UVoxelStrateManager::GeneratePassages()
|
||||
MaxLateralSnap,
|
||||
SuggestedLowerPoint);
|
||||
|
||||
if (bAimedLowerAtOpenPoint)
|
||||
if (bAimedLowerAtPlayerFitPoint)
|
||||
{
|
||||
++NumAimedAtLowerOpenPoint;
|
||||
++NumAimedAtLowerPlayerFit;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -842,7 +839,7 @@ void UVoxelStrateManager::GeneratePassages()
|
||||
Passage.RequestedUpperPoint = FVector(PX, PY, TopZ);
|
||||
Passage.RequestedLowerPoint = FVector(PX, PY, BottomZ);
|
||||
|
||||
if (bAimedUpperAtOpenPoint)
|
||||
if (bAimedUpperAtPlayerFitPoint)
|
||||
{
|
||||
const float UpperSealThickness = BoundarySealThicknessFor(*UpperDef);
|
||||
const float InnerBottomZ = UpperBottomZ + UpperSealThickness;
|
||||
@@ -868,7 +865,7 @@ void UVoxelStrateManager::GeneratePassages()
|
||||
}
|
||||
}
|
||||
|
||||
if (bAimedLowerAtOpenPoint)
|
||||
if (bAimedLowerAtPlayerFitPoint)
|
||||
{
|
||||
const float LowerSealThickness = BoundarySealThicknessFor(*LowerDef);
|
||||
const float LowerBottomZ = (float)(Lower.BottomChunkZ) * CHUNK_SIZE;
|
||||
@@ -1031,12 +1028,12 @@ void UVoxelStrateManager::GeneratePassages()
|
||||
}
|
||||
|
||||
UE_LOG(LogTemp, Log,
|
||||
TEXT("[StrateManager] Passage landing sites: upper %d/%d and lower %d/%d aimed at generator-confirmed open space, %d/%d mouth queries fell back to random reach (archetypes with no query: %s)."),
|
||||
NumAimedAtUpperOpenPoint,
|
||||
TEXT("[StrateManager] Passage landing sites: upper %d/%d and lower %d/%d aimed at player-fit source space, %d/%d mouth queries fell back to random reach (archetypes with no query: %s)."),
|
||||
NumAimedAtUpperPlayerFit,
|
||||
TotalPassages,
|
||||
NumAimedAtLowerOpenPoint,
|
||||
NumAimedAtLowerPlayerFit,
|
||||
TotalPassages,
|
||||
(TotalPassages * 2) - NumAimedAtUpperOpenPoint - NumAimedAtLowerOpenPoint,
|
||||
(TotalPassages * 2) - NumAimedAtUpperPlayerFit - NumAimedAtLowerPlayerFit,
|
||||
TotalPassages * 2,
|
||||
*NoQueryList);
|
||||
|
||||
|
||||
Reference in new issue
Block a user