// Scenarios that test the harness itself. One Scenarios_.cpp per topic; each file registers its own with // DESCENT_SCENARIO (macro and step API: DescentScenario.h). Run one with Tools/run_scenario.ps1 -Name . #include "Debug/DescentScenario.h" #include "Debug/DescentDebugSubsystem.h" #include "Debug/DescentShowcaseRig.h" #include "Debug/DescentStateRegistry.h" #include "TheDescent.h" #include "Engine/World.h" #include "GameFramework/Pawn.h" #include "GameFramework/PlayerController.h" #include "HAL/FileManager.h" #include "Misc/Paths.h" DESCENT_SCENARIO(smoke, "Boots the default map and exercises every Descent.* console command plus a screenshot and a state dump. Proves the harness itself.") { S.TimeLimit(120.f); S.WaitFrames(5); S.DumpState(TEXT("boot")); // The map holds only a PlayerStart, so a picture needs something lit in it: the showcase rig (seabed, fill // lights, on the Throat's water) is the cheapest thing that renders. S.Do(TEXT("spawn something to look at"), [](FDescentScenarioContext& C) { if (!ADescentShowcaseRig::FindOrSpawn(C.World)) { C.Fail(TEXT("could not spawn the showcase rig")); } }); // Console commands, exactly as typed into the ~ console. S.Exec(TEXT("Descent.Teleport 10 -4 2")); S.WaitFrames(3); S.Exec(TEXT("Descent.DumpState smoke_console_dump")); S.Do(TEXT("check teleport + console dump"), [](FDescentScenarioContext& C) { const APlayerController* PC = C.World->GetFirstPlayerController(); const APawn* Pawn = PC ? PC->GetPawn() : nullptr; if (!Pawn || !Pawn->GetActorLocation().Equals(FVector(1000.0, -400.0, 200.0), 1.0)) { C.Fail(FString::Printf(TEXT("Descent.Teleport 10 -4 2 left the pawn at %s (cm)"), Pawn ? *Pawn->GetActorLocation().ToString() : TEXT("(no pawn)"))); return; } const FString Dump = FDescentStateRegistry::GetStateDir() / TEXT("smoke_console_dump.json"); if (!IFileManager::Get().FileExists(*Dump)) { C.Fail(TEXT("Descent.DumpState did not write ") + Dump); } }); S.Exec(TEXT("Descent.Camera 0 0 5 -90 0 0 70")); // straight down from 5 m S.Shot(TEXT("view")); // The shot sidecar and the region command (DESIGN_UE.md section 9.2). S.Do(TEXT("check the sidecar of the shot"), [](FDescentScenarioContext& C) { const FString Side = UDescentDebugSubsystem::GetShotSidecarPath(C.ScenarioName + TEXT("_view")); if (!IFileManager::Get().FileExists(*Side)) { C.Fail(TEXT("the shot has no sidecar JSON: ") + Side); } }); S.Exec(TEXT("Descent.ShotRegion smoke_view 0.25 0.25 0.75 0.75")); S.Exec(TEXT("Descent.ShotRing smoke_view 640 360 100 300")); S.Exec(TEXT("Descent.Shot smoke_console_shot")); // the console route to the same screenshot machinery S.Until(TEXT("wait for console shot"), [](FDescentScenarioContext& C) { if (C.Debug->IsShotPending()) { return false; } const FString Png = FDescentStateRegistry::GetShotDir() / TEXT("smoke_console_shot.png"); if (!IFileManager::Get().FileExists(*Png)) { C.Fail(TEXT("Descent.Shot did not write ") + Png); } return true; }); S.Quit(); } DESCENT_SCENARIO(selftest_timeout, "DELIBERATELY FAILS: a scenario whose step outlasts its 6 s time limit. Proves the runner logs an error and exits non-zero.") { S.TimeLimit(6.f); S.WaitSeconds(60.f); S.Quit(); } DESCENT_SCENARIO(selftest_expect, "DELIBERATELY FAILS: an ExpectNear with numbers that do not match. Proves the failure names the label and the numbers.") { S.TimeLimit(60.f); S.ExpectNear(TEXT("two plus two"), 4.0, 4.0, 0.001); S.ExpectNear(TEXT("two plus two is five"), 4.0, 5.0, 0.001); S.Quit(); }