// Headless tests of the world definition and its density function (DESIGN_UE.md section 10, M1): // Descent.World.Determinism, .Counts, .Places, .NoBypass, .CarrierFit, .Shear #include "Tests/DescentTestCommon.h" #include "Core/DescentMath.h" #include "World/DescentRoutes.h" #include "World/DescentShear.h" #include "World/DescentTerrainSubsystem.h" #include "World/DescentWorldDef.h" #if WITH_DEV_AUTOMATION_TESTS using namespace Descent::World; namespace { /** FNV of GenRaw (to 0.01 m) at 2,000 seeded points inside the claim box. Recorded from the first run that matched the design's numbers. */ constexpr uint64 WorldTestExpectedGenHash = 0xfca131e9fc960d2dull; // recorded 2026-10-05 from the first build whose CarrierFit passed uint64 WorldTestGenHash(const FDescentWorldDef& Def) { Descent::FRng R(7u); uint64 H = 0xcbf29ce484222325ull; for (int32 I = 0; I < 2000; ++I) { const FVector P(R.Range(-330.0, 1560.0), R.Range(-980.0, 480.0), R.Range(-1700.0, 60.0)); const int64 Q = static_cast(FMath::RoundToDouble(Def.Sdf().GenRawSlow(P) * 100.0)); for (int32 B = 0; B < 8; ++B) { H ^= static_cast((Q >> (8 * B)) & 0xff); H *= 0x100000001b3ull; } } return H; } } IMPLEMENT_SIMPLE_AUTOMATION_TEST(FDescentWorldDeterminismTest, "Descent.World.Determinism", DESCENT_TEST_FLAGS) bool FDescentWorldDeterminismTest::RunTest(const FString& Parameters) { const FDescentWorldDef& A = FDescentWorldDef::Get(); double Ms = 0.0; const TSharedRef B = FDescentWorldDef::BuildNew(&Ms); TestEqual(TEXT("two builds have the same world_def.hash"), A.HashHex(), B->HashHex()); TestEqual(TEXT("two builds have the same number of segments"), A.Segs.Num(), B->Segs.Num()); const uint64 HA = WorldTestGenHash(A), HB = WorldTestGenHash(*B); TestEqual(TEXT("GenRaw at 2,000 seeded points is the same in both builds"), HA, HB); AddInfo(FString::Printf(TEXT("world hash %s, GenRaw hash %016llx, build %.0f ms"), *A.HashHex(), HA, Ms)); if (WorldTestExpectedGenHash != 0) { TestEqual(TEXT("GenRaw at 2,000 seeded points matches the stored hash"), HA, WorldTestExpectedGenHash); } return true; } IMPLEMENT_SIMPLE_AUTOMATION_TEST(FDescentWorldCountsTest, "Descent.World.Counts", DESCENT_TEST_FLAGS) bool FDescentWorldCountsTest::RunTest(const FString& Parameters) { const FDescentWorldDef& D = FDescentWorldDef::Get(); TestEqual(TEXT("600 ore deposits (section 2.12)"), D.Deposits.Num(), 600); TestEqual(TEXT("900 pores (section 2.9)"), D.Pores.Num(), 900); TestEqual(TEXT("36 worm tunnels"), D.NumWorms, 36); TestEqual(TEXT("4 wells"), D.Wells.Num(), 4); TestEqual(TEXT("2 shelves, 2 pinches, 2 rings"), D.Shelves.Num() * 100 + D.Pinches.Num() * 10 + D.Rings.Num(), 222); TestEqual(TEXT("8 designed chambers"), D.NumDesignedChambers, 8); TestEqual(TEXT("8 beacons"), D.Beacons.Num(), 8); int32 Counts[3] = { 0, 0, 0 }; for (const FDeposit& Dep : D.Deposits) { ++Counts[static_cast(Dep.Type)]; TestTrue(TEXT("a deposit yields at least 2 chunks"), Dep.Yield >= 2); } AddInfo(FString::Printf(TEXT("deposits: ferrite %d, cobalt %d, pearl %d; segments %d, chambers %d (pockets %d)"), Counts[0], Counts[1], Counts[2], D.Segs.Num(), D.Chambers.Num(), D.NumPockets)); const int32 Gallery = D.FindTube(TEXT("B")); AddInfo(FString::Printf(TEXT("gallery B %.0f m, gallery S %.0f m"), D.Tubes[Gallery].Len, D.Tubes[D.FindTube(TEXT("S"))].Len)); TestTrue(TEXT("gallery B is about 550 m"), FMath::Abs(D.Tubes[Gallery].Len - 550.0) < 15.0); TestTrue(TEXT("gallery S is about 1,152 m"), FMath::Abs(D.Tubes[D.FindTube(TEXT("S"))].Len - 1152.0) < 25.0); return true; } IMPLEMENT_SIMPLE_AUTOMATION_TEST(FDescentWorldPlacesTest, "Descent.World.Places", DESCENT_TEST_FLAGS) bool FDescentWorldPlacesTest::RunTest(const FString& Parameters) { const FDescentWorldDef& D = FDescentWorldDef::Get(); for (const FPlace& P : D.Places) { const double V = D.Sdf().GenRawSlow(P.PosM); TestTrue(FString::Printf(TEXT("place %s (%.0f %.0f %.0f) is in open water (density %.2f)"), *P.Name.ToString(), P.PosM.X, P.PosM.Y, P.PosM.Z, V), V < -1.0); } for (const FBeacon& B : D.Beacons) { const double V = D.Sdf().GenRawSlow(B.P); TestTrue(FString::Printf(TEXT("beacon %s is in open water (density %.2f)"), *B.Id.ToString(), V), V < 0.0); } return true; } IMPLEMENT_SIMPLE_AUTOMATION_TEST(FDescentWorldNoBypassTest, "Descent.World.NoBypass", DESCENT_TEST_FLAGS) bool FDescentWorldNoBypassTest::RunTest(const FString& Parameters) { const FDescentWorldDef& D = FDescentWorldDef::Get(); constexpr double M = 30.0; for (const FPore& P : D.Pores) { if (D.CarrierRouteDistance(P.C) < M + P.R - 1e-6) { AddError(FString::Printf(TEXT("pore at %.0f %.0f %.0f r %.1f is within 30 m + r of a carrier route"), P.C.X, P.C.Y, P.C.Z, P.R)); break; } } for (const FSegment& S : D.Segs) { if (S.bBig) { continue; } // Tender tubes are designed content and may touch a well; worm tunnels (Tube == -1) are procedural and may not. if (S.Tube >= 0) { continue; } const int32 N = 4; for (int32 K = 0; K <= N; ++K) { const FVector P = FMath::Lerp(S.A, S.B, static_cast(K) / N); if (D.CarrierRouteDistance(P) < M + S.Ra - 1e-6) { AddError(FString::Printf(TEXT("worm tunnel point %.0f %.0f %.0f r %.1f is within 30 m + r of a carrier route"), P.X, P.Y, P.Z, S.Ra)); K = N; } } } for (int32 I = D.NumDesignedChambers; I < D.Chambers.Num(); ++I) { const FChamber& C = D.Chambers[I]; TestTrue(TEXT("a worm pocket keeps 30 m + radius from every carrier route"), D.CarrierRouteDistance(C.C) >= M + C.R.GetMax() - 1e-6); } // The sponge: every sample of the Sieve that lies within 30 m of a carrier route is closed (masked). int32 Inside = 0, Open = 0; const FSpongeZone& Z = D.Sponges[0]; for (double X = Z.Box.Min.X - 40; X <= Z.Box.Max.X + 40; X += 8) { for (double Y = Z.Box.Min.Y - 40; Y <= Z.Box.Max.Y + 40; Y += 8) { for (double Zc = Z.Box.Min.Z - 40; Zc <= Z.Box.Max.Z + 40; Zc += 8) { const FVector P(X, Y, Zc); if (D.CarrierRouteDistance(P) < M) { ++Inside; if (D.Sdf().SpongeSDF(Z, X, Y, Zc) < 0.0) { ++Open; } } } } } AddInfo(FString::Printf(TEXT("sponge: %d samples within 30 m of a carrier route, %d open"), Inside, Open)); TestEqual(TEXT("no sponge sample within 30 m of a carrier route is open"), Open, 0); return true; } IMPLEMENT_SIMPLE_AUTOMATION_TEST(FDescentWorldCarrierFitTest, "Descent.World.CarrierFit", DESCENT_TEST_FLAGS) bool FDescentWorldCarrierFitTest::RunTest(const FString& Parameters) { for (int32 Route = 1; Route <= FDescentRoutes::NumRoutes; ++Route) { FDescentTestWorld TW; UDescentTerrainSubsystem* T = TW.Sub(); if (!TestNotNull(TEXT("the terrain subsystem exists"), T)) { return false; } const TArray Poses = FDescentRoutes::Poses(Route, 5.0); const TArray Chokes = FDescentRoutes::Chokes(Route); constexpr double ZoneM = 120.0; // hull half length + the choke's own reach auto Check = [&](bool bAfter) { double WorstFree = 1e9; FVector WorstAt = FVector::ZeroVector; FRoutePose WorstPose; TArray ChokeMin; ChokeMin.Init(1e9, Chokes.Num()); for (const FRoutePose& Pose : Poses) { int32 Zone = -1; for (int32 C = 0; C < Chokes.Num(); ++C) { if (FVector::Dist(Pose.P, Chokes[C].P) < ZoneM) { Zone = C; break; } } FVector At; const double Cl = FDescentRoutes::MinClearance(*T, MakeArrayView(&Pose, 1), &At); if (Zone >= 0) { ChokeMin[Zone] = FMath::Min(ChokeMin[Zone], Cl); } else if (Cl < WorstFree) { WorstFree = Cl; WorstAt = At; WorstPose = Pose; } } if (!bAfter) { TestTrue(FString::Printf(TEXT("route %d: clearance outside the chokes is at least 1.0 m before ClearRoute (worst %.2f at %.0f %.0f %.0f; pose %.0f %.0f %.0f yaw %.0f pitch %.0f)"), Route, WorstFree, WorstAt.X, WorstAt.Y, WorstAt.Z, WorstPose.P.X, WorstPose.P.Y, WorstPose.P.Z, FMath::RadiansToDegrees(WorstPose.YawRad), FMath::RadiansToDegrees(WorstPose.PitchRad)), WorstFree >= 1.0); for (int32 C = 0; C < Chokes.Num(); ++C) { TestTrue(FString::Printf(TEXT("route %d: choke %s blocks the carrier before ClearRoute (min clearance %.2f)"), Route, *Chokes[C].Id.ToString(), ChokeMin[C]), ChokeMin[C] < 0.0); } } else { double All = WorstFree; for (const double M : ChokeMin) { All = FMath::Min(All, M); } TestTrue(FString::Printf(TEXT("route %d: clearance is at least 1.0 m everywhere after ClearRoute (worst %.2f)"), Route, All), All >= 1.0); } }; Check(false); const int32 Calls = FDescentRoutes::ClearRoute(*T, Route); AddInfo(FString::Printf(TEXT("route %d (%s): %d poses, ClearRoute made %d carve calls, %d chunks modified"), Route, *FDescentRoutes::RouteName(Route), Poses.Num(), Calls, T->GetStats().ChunksModified)); Check(true); } return true; } IMPLEMENT_SIMPLE_AUTOMATION_TEST(FDescentWorldShearTest, "Descent.World.Shear", DESCENT_TEST_FLAGS) bool FDescentWorldShearTest::RunTest(const FString& Parameters) { TestEqual(TEXT("no push at the Throat's start depth"), FDescentShear::VelocityAt(FVector(0, 0, -30)).Size(), 0.0); TestEqual(TEXT("no push at Z +25"), FDescentShear::VelocityAt(FVector(300, 0, 25)).Size(), 0.0); const double S65 = FDescentShear::VelocityAt(FVector(300, 0, 65)).Size(); const double S125 = FDescentShear::VelocityAt(FVector(300, 0, 125)).Size(); TestTrue(FString::Printf(TEXT("about 10 m/s at Z +65 (%.1f)"), S65), FMath::Abs(S65 - 10.0) < 1.5); TestTrue(FString::Printf(TEXT("about 25 m/s at Z +125 (%.1f)"), S125), FMath::Abs(S125 - 25.0) < 1.5); TestTrue(TEXT("over the canyon the push points down"), FDescentShear::VelocityAt(FVector(300, 0, 100)).Z < 0.0); TestFalse(TEXT("no warning at Z 0"), FDescentShear::IsWarning(FVector(0, 0, 0))); TestTrue(TEXT("warning from Z +10"), FDescentShear::IsWarning(FVector(0, 0, 10))); return true; } #endif // WITH_DEV_AUTOMATION_TESTS