// Headless tests of the environment query seam (Core/DescentEnvQuery.h): U5 (26 degrees inside) and U6 (dry interior) // as the rest of the game will see them, with no carrier in sight. #include "Tests/DescentTestCommon.h" #include "Core/DescentEnvQuery.h" #include "Core/DescentMath.h" #if WITH_DEV_AUTOMATION_TESTS IMPLEMENT_SIMPLE_AUTOMATION_TEST(FDescentEnvWaterTemperatureTest, "Descent.Env.WaterTemperature", DESCENT_TEST_FLAGS) bool FDescentEnvWaterTemperatureTest::RunTest(const FString& Parameters) { // Section 2.7: 4.2 + 9 clamp((Depth - 1740) / 1400, 0, 1) + (Depth > 2300 ? 6 : 0) using Q = UDescentEnvQuerySubsystem; TestEqual(TEXT("at the rim (depth 1,740)"), Q::WaterTemperatureC(1740.0), 4.2, 1e-9); TestEqual(TEXT("above the rim is clamped"), Q::WaterTemperatureC(0.0), 4.2, 1e-9); TestEqual(TEXT("at the carrier start (depth 1,810)"), Q::WaterTemperatureC(1810.0), 4.2 + 9.0 * (70.0 / 1400.0), 1e-9); TestEqual(TEXT("at depth 2,300 the step has not happened yet"), Q::WaterTemperatureC(2300.0), 4.2 + 9.0 * (560.0 / 1400.0), 1e-9); TestEqual(TEXT("just below 2,300 the step adds 6"), Q::WaterTemperatureC(2300.001), 4.2 + 9.0 * (560.001 / 1400.0) + 6.0, 1e-9); TestEqual(TEXT("at the goal anchor (depth 3,105)"), Q::WaterTemperatureC(3105.0), 4.2 + 9.0 * (1365.0 / 1400.0) + 6.0, 1e-9); TestEqual(TEXT("below 3,140 the rise is capped: 4.2 + 9 + 6"), Q::WaterTemperatureC(5000.0), 19.2, 1e-9); return true; } IMPLEMENT_SIMPLE_AUTOMATION_TEST(FDescentEnvQueryDefaultsTest, "Descent.Env.QueryDefaults", DESCENT_TEST_FLAGS) bool FDescentEnvQueryDefaultsTest::RunTest(const FString& Parameters) { FDescentTestWorld TW; UDescentEnvQuerySubsystem* Q = TW.Sub(); if (!TestNotNull(TEXT("the env query subsystem exists in a game world"), Q)) { return false; } // By default everything is water. const FVector Start = Descent::MetresToUU(FVector(0.0, 0.0, -30.0)); TestFalse(TEXT("the carrier start is not dry by default"), Q->IsDry(Start)); TestFalse(TEXT("nor is any other point"), Q->IsDry(Descent::MetresToUU(FVector(1.0e5, -2.0e5, -900.0)))); TestEqual(TEXT("default temperature at the start is the water formula"), Q->AmbientTemperatureC(Start), 4.2 + 9.0 * (70.0 / 1400.0), 1e-9); TestEqual(TEXT("... through the metres variant"), Q->AmbientTemperatureAtMetres(FVector(0.0, 0.0, -30.0)), 4.2 + 9.0 * (70.0 / 1400.0), 1e-9); TestEqual(TEXT("depth decides: Z -1,325 is depth 3,105"), Q->AmbientTemperatureAtMetres(FVector(0.0, 0.0, -1325.0)), 4.2 + 9.0 * (1365.0 / 1400.0) + 6.0, 1e-9); TestEqual(TEXT("no owners"), Q->GetDryVolumeOwners().Num(), 0); // The free functions the other systems call. TestFalse(TEXT("Descent::IsDry with no provider"), Descent::IsDry(TW.Get(), Start)); TestEqual(TEXT("Descent::AmbientTemperatureAt with no provider"), Descent::AmbientTemperatureAt(TW.Get(), Start), Q->AmbientTemperatureC(Start), 1e-12); TestFalse(TEXT("a null world is water"), Descent::IsDry(nullptr, Start)); TestEqual(TEXT("a null world still has the water temperature"), Descent::AmbientTemperatureAt(nullptr, Start), Q->AmbientTemperatureC(Start), 1e-12); return true; } IMPLEMENT_SIMPLE_AUTOMATION_TEST(FDescentEnvQueryProviderTest, "Descent.Env.DryVolumeProvider", DESCENT_TEST_FLAGS) bool FDescentEnvQueryProviderTest::RunTest(const FString& Parameters) { FDescentTestWorld TW; UDescentEnvQuerySubsystem* Q = TW.Sub(); if (!TestNotNull(TEXT("the env query subsystem"), Q)) { return false; } // A stand-in "carrier": a box 100 m x 20 m x 20 m centred on the start, in world metres. const FBox HullM(FVector(-50.0, -10.0, -40.0), FVector(50.0, 10.0, -20.0)); const FDescentHandle H = Q->RegisterDryVolume(TEXT("TestHull"), [HullM](const FVector& WorldUU) { return HullM.IsInside(Descent::UUToMetres(WorldUU)); }); TestTrue(TEXT("handle valid"), H.IsValid()); const FVector Inside = Descent::MetresToUU(FVector(0.0, 0.0, -30.0)); const FVector Outside = Descent::MetresToUU(FVector(0.0, 0.0, -45.0)); // 5 m below the hull TestTrue(TEXT("inside the hull is dry"), Q->IsDry(Inside)); TestFalse(TEXT("outside is water"), Q->IsDry(Outside)); TestEqual(TEXT("U5: 26 degrees inside"), Q->AmbientTemperatureC(Inside), 26.0, 0.0); TestEqual(TEXT("U5: the water formula outside"), Q->AmbientTemperatureC(Outside), 4.2 + 9.0 * (85.0 / 1400.0), 1e-9); TestTrue(TEXT("the metres variant agrees"), Q->IsDryAtMetres(FVector(0.0, 0.0, -30.0)) && !Q->IsDryAtMetres(FVector(0.0, 0.0, -45.0))); TestTrue(TEXT("the free functions agree"), Descent::IsDry(TW.Get(), Inside) && Descent::AmbientTemperatureAt(TW.Get(), Inside) == 26.0); TestEqual(TEXT("owner listed"), Q->GetDryVolumeOwners().Num(), 1); // The temperature inside is the provider's, not a constant of the query. const FDescentHandle Warm = Q->RegisterDryVolume(TEXT("AAWarm"), [](const FVector& WorldUU) { return Descent::UUToMetres(WorldUU.X) > 200.0; }, 31.5); const FVector InWarm = Descent::MetresToUU(FVector(250.0, 0.0, -30.0)); TestEqual(TEXT("a second volume has its own temperature"), Q->AmbientTemperatureC(InWarm), 31.5, 0.0); TestTrue(TEXT("both are dry"), Q->IsDry(InWarm) && Q->IsDry(Inside)); // Where two volumes overlap, the answer is the first owner by name, whatever the registration order. const FDescentHandle Overlap = Q->RegisterDryVolume(TEXT("ZZCold"), [](const FVector&) { return true; }, -5.0); TestEqual(TEXT("overlap: first owner by name wins"), Q->AmbientTemperatureC(InWarm), 31.5, 0.0); TestEqual(TEXT("overlap: AAWarm sorts before TestHull and ZZCold"), Q->AmbientTemperatureC(Inside), 26.0, 0.0); TestTrue(TEXT("unregister"), Q->UnregisterDryVolume(Overlap)); TestFalse(TEXT("unregister twice"), Q->UnregisterDryVolume(Overlap)); // Removing the hull makes its space water again. TestTrue(TEXT("unregister the hull"), Q->UnregisterDryVolume(H)); TestFalse(TEXT("the hull is water again"), Q->IsDry(Inside)); TestEqual(TEXT("and cold again"), Q->AmbientTemperatureC(Inside), 4.2 + 9.0 * (70.0 / 1400.0), 1e-9); Q->UnregisterDryVolume(Warm); TestEqual(TEXT("no owners left"), Q->GetDryVolumeOwners().Num(), 0); AddExpectedError(TEXT("has an empty function"), EAutomationExpectedErrorFlags::Contains, 1); TestFalse(TEXT("an empty provider is refused"), Q->RegisterDryVolume(TEXT("Empty"), UDescentEnvQuerySubsystem::FDryVolumeFn()).IsValid()); return true; } #endif // WITH_DEV_AUTOMATION_TESTS