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
@@ -54,10 +54,13 @@ namespace
|
||||
case ECaveGeneratorType::VerticalShafts:
|
||||
// A 0.6 shaft density does not guarantee an occupied immediate cell. The production
|
||||
// query therefore permits a bounded two-cell local search and still returns the
|
||||
// selected site's exact axis.
|
||||
// selected site's deterministic feature core/ledge-side pose.
|
||||
return FMath::Max(2.0f * Definition.VerticalShaftParams.ShaftSpacing, 1.0f);
|
||||
case ECaveGeneratorType::FloatingIslands:
|
||||
return FMath::Max(Definition.FloatingIslandParams.IslandSpacing, 1.0f);
|
||||
case ECaveGeneratorType::TunnelNetwork:
|
||||
case ECaveGeneratorType::Underwater:
|
||||
return FMath::Max(Definition.GenerationParams.RoomSpacing, 1.0f);
|
||||
default:
|
||||
return 0.0f;
|
||||
}
|
||||
@@ -85,6 +88,8 @@ bool FVoxelForgePassageLandsInOpenSpaceTest::RunTest(const FString& Parameters)
|
||||
|
||||
int32 NumInterStratePassages = 0;
|
||||
int32 NumChecked = 0;
|
||||
int32 NumUpperChecked = 0;
|
||||
int32 NumUpperQueryFalse = 0;
|
||||
int32 NumQueryFalse = 0;
|
||||
int32 NumUnsupported = 0;
|
||||
int32 NumSupportedWithoutPoint = 0;
|
||||
@@ -144,6 +149,74 @@ bool FVoxelForgePassageLandsInOpenSpaceTest::RunTest(const FString& Parameters)
|
||||
continue;
|
||||
}
|
||||
|
||||
// The upper mouth has its own source query too. This is deliberately evaluated from the
|
||||
// upper strate's definition, seed, bounds, and requested XY; it must never inherit the
|
||||
// lower mouth's answer or become an implicit passage chain.
|
||||
const float SourceTopZ = (float)(Source.TopChunkZ + 1) * CHUNK_SIZE;
|
||||
const float SourceBottomZ = (float)Source.BottomChunkZ * CHUNK_SIZE;
|
||||
const int32 SourceQuerySeed = OpenPointSeedFor(
|
||||
*SourceDefinition, Source, WorldSeed);
|
||||
const float SourceMaxLateralSnap = MaxLateralSnapFor(*SourceDefinition);
|
||||
FVector SuggestedUpperPoint = FVector::ZeroVector;
|
||||
const bool bCanAnswerUpper = VF_SuggestLandingPoint(
|
||||
SourceDefinition->GeneratorType,
|
||||
SourceDefinition->GenerationParams,
|
||||
SourceDefinition->SlabParams,
|
||||
SourceDefinition->MazeParams,
|
||||
SourceDefinition->VerticalShaftParams,
|
||||
SourceDefinition->FloatingIslandParams,
|
||||
SourceQuerySeed,
|
||||
SourceTopZ,
|
||||
SourceBottomZ,
|
||||
Passage.RequestedUpperPoint.X,
|
||||
Passage.RequestedUpperPoint.Y,
|
||||
SourceMaxLateralSnap,
|
||||
SuggestedUpperPoint);
|
||||
if (!bCanAnswerUpper)
|
||||
{
|
||||
++NumUpperQueryFalse;
|
||||
FalseArchetypes.Add(static_cast<uint8>(SourceDefinition->GeneratorType));
|
||||
}
|
||||
else
|
||||
{
|
||||
++NumUpperChecked;
|
||||
const float UpperSnapDX = Passage.UpperPoint.X
|
||||
- Passage.RequestedUpperPoint.X;
|
||||
const float UpperSnapDY = Passage.UpperPoint.Y
|
||||
- Passage.RequestedUpperPoint.Y;
|
||||
const float UpperSnapDistance = FMath::Sqrt(
|
||||
UpperSnapDX * UpperSnapDX + UpperSnapDY * UpperSnapDY);
|
||||
if (!FMath::IsNearlyEqual(Passage.UpperPoint.X, SuggestedUpperPoint.X, 0.01f)
|
||||
|| !FMath::IsNearlyEqual(Passage.UpperPoint.Y, SuggestedUpperPoint.Y, 0.01f)
|
||||
|| !FMath::IsNearlyEqual(Passage.UpperPoint.Z, SuggestedUpperPoint.Z, 0.01f))
|
||||
{
|
||||
bAllAnswerableEndpointsMatchQuery = false;
|
||||
AddError(FString::Printf(
|
||||
TEXT("Passage from strate %d (%s) missed its upper player-fit landing point: endpoint (%.9g, %.9g, %.9g), query (%.9g, %.9g, %.9g)."),
|
||||
Source.StrateIndex,
|
||||
*ArchetypeName(SourceDefinition->GeneratorType),
|
||||
Passage.UpperPoint.X,
|
||||
Passage.UpperPoint.Y,
|
||||
Passage.UpperPoint.Z,
|
||||
SuggestedUpperPoint.X,
|
||||
SuggestedUpperPoint.Y,
|
||||
SuggestedUpperPoint.Z));
|
||||
}
|
||||
if (!FMath::IsFinite(UpperSnapDistance)
|
||||
|| UpperSnapDistance > SourceMaxLateralSnap + 0.01f)
|
||||
{
|
||||
bAllAnswerableEndpointsMatchQuery = false;
|
||||
AddError(FString::Printf(
|
||||
TEXT("Passage from strate %d (%s) exceeded its upper lateral snap bound: %.6f > %.6f from requested (%.3f, %.3f)."),
|
||||
Source.StrateIndex,
|
||||
*ArchetypeName(SourceDefinition->GeneratorType),
|
||||
UpperSnapDistance,
|
||||
SourceMaxLateralSnap,
|
||||
Passage.RequestedUpperPoint.X,
|
||||
Passage.RequestedUpperPoint.Y));
|
||||
}
|
||||
}
|
||||
|
||||
const FStratePassageConfig& PassageConfig = SourceDefinition->PassageConfig;
|
||||
if (!FMath::IsFinite(PassageConfig.MouthRadius) || PassageConfig.MouthRadius <= 0.0f)
|
||||
{
|
||||
@@ -377,8 +450,10 @@ bool FVoxelForgePassageLandsInOpenSpaceTest::RunTest(const FString& Parameters)
|
||||
}
|
||||
|
||||
AddInfo(FString::Printf(
|
||||
TEXT("Passage open-space check: %d inter-strate passages, %d checked, %d footing checks, %d/%d room/slab ring samples air, %d query-false (%d unique archetypes; %d unsupported, %d supported-but-no-point)."),
|
||||
TEXT("Passage player-fit check: %d inter-strate passages, upper %d checked (%d false), lower %d checked (%d footing checks), %d/%d room/slab ring samples air, lower %d query-false (%d unique archetypes; %d unsupported, %d supported-but-no-point)."),
|
||||
NumInterStratePassages,
|
||||
NumUpperChecked,
|
||||
NumUpperQueryFalse,
|
||||
NumChecked,
|
||||
NumFootingChecked,
|
||||
NumRingAirSamples,
|
||||
@@ -398,6 +473,10 @@ bool FVoxelForgePassageLandsInOpenSpaceTest::RunTest(const FString& Parameters)
|
||||
{
|
||||
AddError(TEXT("VACUOUS: zero answerable passage mouths were checked; the passage invariant was not exercised."));
|
||||
}
|
||||
if (NumUpperChecked == 0)
|
||||
{
|
||||
AddError(TEXT("VACUOUS: zero answerable upper passage mouths were checked; independent upper-mouth aiming was not exercised."));
|
||||
}
|
||||
if (NumFootingChecked == 0)
|
||||
{
|
||||
AddError(TEXT("VACUOUS: no Maze/VerticalShafts/FloatingIslands footing was checked."));
|
||||
@@ -461,6 +540,7 @@ bool FVoxelForgePassageLandsInOpenSpaceTest::RunTest(const FString& Parameters)
|
||||
|
||||
return NumInterStratePassages > 0
|
||||
&& NumChecked > 0
|
||||
&& NumUpperChecked > 0
|
||||
&& NumFootingChecked > 0
|
||||
&& bAllNewFootingArchetypesAnswered
|
||||
// (fixture coverage of the room/slab archetypes is reported, not asserted — see above)
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "VoxelForgeTestFixture.h"
|
||||
#include "VoxelCaveMorphology.h"
|
||||
#include "VoxelStrateMeasure.h"
|
||||
#include "VoxelTypes.h"
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FVoxelForgeStrateConnectivityTest,
|
||||
@@ -327,6 +328,7 @@ namespace
|
||||
{
|
||||
float X = 0.0f;
|
||||
float Y = 0.0f;
|
||||
float Radius = 0.0f;
|
||||
int32 CellX = 0;
|
||||
int32 CellY = 0;
|
||||
};
|
||||
@@ -350,6 +352,10 @@ namespace
|
||||
+ VoxelHash::ToFloat01(VoxelHash::Mix(Hash ^ 0x12345678u)) * 0.7f) * Spacing;
|
||||
OutShaft.Y = (CellY + 0.15f
|
||||
+ VoxelHash::ToFloat01(VoxelHash::Mix(Hash ^ 0x9ABCDEF0u)) * 0.7f) * Spacing;
|
||||
OutShaft.Radius = FMath::Lerp(
|
||||
Params.ShaftMinRadius,
|
||||
Params.ShaftMaxRadius,
|
||||
VoxelHash::ToFloat01(VoxelHash::Mix(Hash ^ 0xBEEFu)));
|
||||
OutShaft.CellX = CellX;
|
||||
OutShaft.CellY = CellY;
|
||||
return true;
|
||||
@@ -788,14 +794,27 @@ namespace
|
||||
return Out;
|
||||
}
|
||||
|
||||
bool IsVerticalShaftAxis(
|
||||
bool FindVerticalShaftFeatureCore(
|
||||
const FVerticalShaftParams& Params,
|
||||
int32 Seed,
|
||||
const FVector& Point)
|
||||
const FVector& Point,
|
||||
FVector& OutAxisPoint)
|
||||
{
|
||||
const float Spacing = FMath::Max(Params.ShaftSpacing, 1.0f);
|
||||
const float InteriorMargin = FMath::Max(Params.SurfaceRoughness, 0.0f)
|
||||
* VOXEL_NOISE_SCALE + 0.25f;
|
||||
if (!FMath::IsFinite(InteriorMargin) || InteriorMargin < 0.0f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const int32 BaseCellX = FMath::FloorToInt(Point.X / Spacing);
|
||||
const int32 BaseCellY = FMath::FloorToInt(Point.Y / Spacing);
|
||||
float BestDistanceSq = FLT_MAX;
|
||||
int32 BestCellX = INT32_MAX;
|
||||
int32 BestCellY = INT32_MAX;
|
||||
FAuditShaft BestShaft;
|
||||
bool bFound = false;
|
||||
for (int32 DY = -1; DY <= 1; ++DY)
|
||||
{
|
||||
for (int32 DX = -1; DX <= 1; ++DX)
|
||||
@@ -806,14 +825,50 @@ namespace
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (FMath::IsNearlyEqual(Point.X, Shaft.X, 0.001f)
|
||||
&& FMath::IsNearlyEqual(Point.Y, Shaft.Y, 0.001f))
|
||||
const float SafeRadius = Shaft.Radius - InteriorMargin;
|
||||
if (!FMath::IsFinite(SafeRadius) || SafeRadius <= 0.0f)
|
||||
{
|
||||
return true;
|
||||
continue;
|
||||
}
|
||||
|
||||
const float DistanceSq = FMath::Square(Point.X - Shaft.X)
|
||||
+ FMath::Square(Point.Y - Shaft.Y);
|
||||
if (DistanceSq > FMath::Square(SafeRadius) + 1.0e-4f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const bool bCloser = DistanceSq < BestDistanceSq;
|
||||
const bool bTie = DistanceSq == BestDistanceSq
|
||||
&& (Shaft.CellX < BestCellX
|
||||
|| (Shaft.CellX == BestCellX && Shaft.CellY < BestCellY));
|
||||
if (bCloser || bTie)
|
||||
{
|
||||
BestDistanceSq = DistanceSq;
|
||||
BestCellX = Shaft.CellX;
|
||||
BestCellY = Shaft.CellY;
|
||||
BestShaft = Shaft;
|
||||
bFound = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
if (!bFound)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
OutAxisPoint = FVector(BestShaft.X, BestShaft.Y, Point.Z);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsVerticalShaftFeatureCore(
|
||||
const FVerticalShaftParams& Params,
|
||||
int32 Seed,
|
||||
const FVector& Point)
|
||||
{
|
||||
FVector AxisPoint = FVector::ZeroVector;
|
||||
return FindVerticalShaftFeatureCore(Params, Seed, Point, AxisPoint);
|
||||
}
|
||||
|
||||
bool FindChainMouths(
|
||||
@@ -3344,18 +3399,20 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
|
||||
|
||||
const FVerticalShaftParams& ShaftParams =
|
||||
Layout[VerticalShaftsIndex].Definition->VerticalShaftParams;
|
||||
if (!IsVerticalShaftAxis(
|
||||
ShaftParams, Seed, ResolutionArrivalPoint)
|
||||
|| !IsVerticalShaftAxis(ShaftParams, Seed, ResolutionDeparturePoint))
|
||||
const bool bArrivalFeatureCore = IsVerticalShaftFeatureCore(
|
||||
ShaftParams, Seed, ResolutionArrivalPoint);
|
||||
const bool bDepartureFeatureCore = IsVerticalShaftFeatureCore(
|
||||
ShaftParams, Seed, ResolutionDeparturePoint);
|
||||
if (!bArrivalFeatureCore || !bDepartureFeatureCore)
|
||||
{
|
||||
AddError(FString::Printf(
|
||||
TEXT("HARD FAILURE: fitted-window seed %d has a VerticalShafts mouth not on a "
|
||||
"shaft axis (arrival axis=%s, departure axis=%s; arrival=(%.3f,%.3f,%.3f); "
|
||||
TEXT("HARD FAILURE: fitted-window seed %d has a VerticalShafts mouth outside "
|
||||
"the shaft feature core (arrival core=%s, departure core=%s; arrival=(%.3f,%.3f,%.3f); "
|
||||
"departure=(%.3f,%.3f,%.3f); spacing=%.3f; density=%.3f)."),
|
||||
Seed,
|
||||
IsVerticalShaftAxis(ShaftParams, Seed, ResolutionArrivalPoint)
|
||||
bArrivalFeatureCore
|
||||
? TEXT("yes") : TEXT("no"),
|
||||
IsVerticalShaftAxis(ShaftParams, Seed, ResolutionDeparturePoint)
|
||||
bDepartureFeatureCore
|
||||
? TEXT("yes") : TEXT("no"),
|
||||
ResolutionArrivalPoint.X,
|
||||
ResolutionArrivalPoint.Y,
|
||||
@@ -3515,7 +3572,7 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
|
||||
"mean-radius shaft volume.\n");
|
||||
Summary += TEXT(
|
||||
" seed | fitted verdict | cells | components | small non-largest/total | strict minima/shafts "
|
||||
"| neighbour fallback | path orphans | axis mouths | doubled fitted-margin verdict/cells | "
|
||||
"| neighbour fallback | path orphans | shaft feature-core mouths | doubled fitted-margin verdict/cells | "
|
||||
"tree path XY bounds\n");
|
||||
|
||||
static constexpr int32 FittedSeedCases[] = {
|
||||
@@ -3577,8 +3634,12 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
|
||||
const FVerticalShaftParams RuntimeShaftParams =
|
||||
SeedWorld.StrateManager->GetVerticalShaftParamsForChunk(
|
||||
FIntVector(0, 0, ShaftMidChunkZ));
|
||||
const bool bAxisMouths = IsVerticalShaftAxis(ShaftParams, Seed, SeedArrivalPoint)
|
||||
&& IsVerticalShaftAxis(ShaftParams, Seed, SeedDeparturePoint);
|
||||
FVector ArrivalShaftAxis = SeedArrivalPoint;
|
||||
FVector DepartureShaftAxis = SeedDeparturePoint;
|
||||
const bool bShaftFeatureMouths = FindVerticalShaftFeatureCore(
|
||||
ShaftParams, Seed, SeedArrivalPoint, ArrivalShaftAxis)
|
||||
&& FindVerticalShaftFeatureCore(
|
||||
ShaftParams, Seed, SeedDeparturePoint, DepartureShaftAxis);
|
||||
const FVoxelStrateMeasureSettings FittedSettings = MakeFittedSettings(
|
||||
2,
|
||||
SeedArrivalPoint,
|
||||
@@ -3649,7 +3710,7 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
|
||||
&& Diagnostics.Result == EVoxelConnectivityResult::Connected
|
||||
&& !Diagnostics.bStartSnapped
|
||||
&& !Diagnostics.bGoalSnapped
|
||||
&& bAxisMouths;
|
||||
&& bShaftFeatureMouths;
|
||||
bool bCountsAsPass = bCountsAsDirectPass;
|
||||
FString DoubledSweepText = TEXT("not-run");
|
||||
FString TreePathText = TEXT("not-needed");
|
||||
@@ -3682,14 +3743,14 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
|
||||
FVector2D PathMax = FVector2D::ZeroVector;
|
||||
int32 PathHops = 0;
|
||||
const bool bArrivalPath = AuditVerticalShaftPathBounds(
|
||||
ShaftParams, Seed, SeedArrivalPoint, PathMin, PathMax, PathHops);
|
||||
ShaftParams, Seed, ArrivalShaftAxis, PathMin, PathMax, PathHops);
|
||||
FVector2D DeparturePathMin = FVector2D::ZeroVector;
|
||||
FVector2D DeparturePathMax = FVector2D::ZeroVector;
|
||||
int32 DeparturePathHops = 0;
|
||||
const bool bDeparturePath = AuditVerticalShaftPathBounds(
|
||||
ShaftParams,
|
||||
Seed,
|
||||
SeedDeparturePoint,
|
||||
DepartureShaftAxis,
|
||||
DeparturePathMin,
|
||||
DeparturePathMax,
|
||||
DeparturePathHops);
|
||||
@@ -3778,7 +3839,7 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
|
||||
Audit.NumShafts,
|
||||
Audit.NumNeighbourFallbacks,
|
||||
Audit.NumUnreachable,
|
||||
bAxisMouths ? TEXT("YES") : TEXT("NO"),
|
||||
bShaftFeatureMouths ? TEXT("YES") : TEXT("NO"),
|
||||
*DoubledSweepText,
|
||||
*TreePathText);
|
||||
|
||||
@@ -3786,10 +3847,10 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
|
||||
{
|
||||
const FVerticalShaftPhysicalAudit ArrivalPhysical =
|
||||
AuditVerticalShaftPathDensity(
|
||||
*SeedWorld.Generator, RuntimeShaftParams, Seed, SeedArrivalPoint);
|
||||
*SeedWorld.Generator, RuntimeShaftParams, Seed, ArrivalShaftAxis);
|
||||
const FVerticalShaftPhysicalAudit DeparturePhysical =
|
||||
AuditVerticalShaftPathDensity(
|
||||
*SeedWorld.Generator, RuntimeShaftParams, Seed, SeedDeparturePoint);
|
||||
*SeedWorld.Generator, RuntimeShaftParams, Seed, DepartureShaftAxis);
|
||||
const FVerticalShaftPhysicalAudit& FirstBadPhysical =
|
||||
ArrivalPhysical.NumNonAirSamples > 0 ? ArrivalPhysical : DeparturePhysical;
|
||||
Summary += FString::Printf(
|
||||
@@ -3827,15 +3888,15 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
|
||||
FirstBadPhysical.FirstBadFraction);
|
||||
}
|
||||
|
||||
if (Audit.NumUnreachable != 0 || !Audit.bOrderIndependent || !bAxisMouths)
|
||||
if (Audit.NumUnreachable != 0 || !Audit.bOrderIndependent || !bShaftFeatureMouths)
|
||||
{
|
||||
AddError(FString::Printf(
|
||||
TEXT("HARD FAILURE: fitted audit seed %d violated the shaft-tree/landing "
|
||||
"invariant (unreachable=%d, order-independent=%s, axis-mouths=%s)."),
|
||||
"invariant (unreachable=%d, order-independent=%s, feature-core-mouths=%s)."),
|
||||
Seed,
|
||||
Audit.NumUnreachable,
|
||||
Audit.bOrderIndependent ? TEXT("yes") : TEXT("no"),
|
||||
bAxisMouths ? TEXT("yes") : TEXT("no")));
|
||||
bShaftFeatureMouths ? TEXT("yes") : TEXT("no")));
|
||||
bAllChecksPassed = false;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -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);
|
||||
|
||||
|
||||
@@ -539,22 +539,27 @@ namespace VoxelCaveMorphology
|
||||
}
|
||||
|
||||
/**
|
||||
* Suggest a deterministic full landing point for an inter-strate passage mouth.
|
||||
* Suggest a deterministic player-fit landing point for an inter-strate passage mouth.
|
||||
*
|
||||
* This is deliberately a pure source query: it does not touch a generator, an operator stack,
|
||||
* a strate manager, a cache, or mutable state. DesiredX/DesiredY is the passage's deliberate
|
||||
* placement around the (0,0) spine. Sparse sources may move laterally, but never farther than
|
||||
* MaxLateralSnap; false means no confident landing site exists inside that bound.
|
||||
* MaxLateralSnap; false means no confident player-fit pose exists inside that bound. A successful
|
||||
* point has a locally supported floor within player step height, a walkable support patch, and a
|
||||
* clear capsule volume. The local source stencil deliberately does not claim flood-fill
|
||||
* connectivity or a measured component size; it selects a topology-backed feature core instead.
|
||||
* For TunnelNetwork/Underwater, Seed must be the strate-specific room seed returned by
|
||||
* VoxelCaveMorphology::MakeStrateSeed; for slab, maze, shaft, and island archetypes, Seed is the
|
||||
* generator world seed consumed by their source. SurfaceWorld deliberately has no answer here:
|
||||
* its production height can be selected by manager-owned biome context and per-chunk state,
|
||||
* which this re-entrant query cannot inspect.
|
||||
*
|
||||
* Propose un point d'atterrissage déterministe complet pour la bouche d'un passage inter-strates.
|
||||
* Cette requête reste pure et le déplacement latéral est strictement borné par MaxLateralSnap.
|
||||
* Pour VerticalShafts, la réponse XY est exactement l'axe d'un puits sélectionné; elle n'est pas
|
||||
* un point ouvert arbitraire à l'intérieur de son disque.
|
||||
* Propose un point d'atterrissage déterministe où la capsule du joueur tient réellement pour la
|
||||
* bouche d'un passage inter-strates. Cette requête reste pure et le déplacement latéral est
|
||||
* strictement borné par MaxLateralSnap; elle ne prétend pas prouver une composante connectée.
|
||||
* Pour VerticalShafts, la réponse s'appuie sur l'axe d'un puits sélectionné et peut se déplacer
|
||||
* vers une banquette déterministe (+X/+Y) pour obtenir un support; ce n'est pas un point ouvert
|
||||
* arbitraire à l'intérieur de son disque.
|
||||
*
|
||||
* @return true when the archetype can provide a confident point; false when it cannot.
|
||||
*/
|
||||
@@ -572,11 +577,13 @@ VOXELFORGE_API bool VF_SuggestLandingPoint(
|
||||
);
|
||||
|
||||
/**
|
||||
* Complete source-query overload for archetypes whose placement parameters are not part of
|
||||
* Complete player-fit source-query overload for archetypes whose placement parameters are not part of
|
||||
* FStrateGenerationParams/FSlabGenerationParams. The extra structs are read-only inputs only;
|
||||
* the function remains pure and SurfaceWorld still returns false for the reason above. The
|
||||
* returned point is either at the requested XY or within MaxLateralSnap of it. VerticalShafts
|
||||
* returns the selected shaft axis so the answer belongs to the connected shaft tree.
|
||||
* returned point is either at the requested XY or within MaxLateralSnap of it, and is accepted
|
||||
* only after the local player capsule/support stencil passes. VerticalShafts uses a selected
|
||||
* shaft's deterministic feature core and ledge-side pose; the axis identifies the topology but
|
||||
* is not itself claimed to be a standable point.
|
||||
*/
|
||||
VOXELFORGE_API bool VF_SuggestLandingPoint(
|
||||
ECaveGeneratorType Archetype,
|
||||
|
||||
Reference in new issue
Block a user