#include "Misc/AutomationTest.h"

#include "Blueprint/WidgetTree.h"
#include "Engine/GameInstance.h"
#include "Engine/Level.h"
#include "Engine/World.h"
#include "GameFramework/PlayerStart.h"
#include "GameFramework/WorldSettings.h"
#include "HyperTwistSimulation/HyperTwistMelindaProjectionGameMode.h"
#include "HyperTwistSimulation/HyperTwistClassicCubeHUDWidget.h"
#include "HyperTwistSimulation/HyperTwistMelindaProjectionPlayerController.h"
#include "HyperTwistSimulation/HyperTwistVirtual3333ProjectionGameMode.h"
#include "HyperTwistSimulation/HyperTwistVirtual3333ProjectionPlayerController.h"
#include "HyperTwistTraining/HyperTwistHigherDimensionalTrainingGameMode.h"
#include "HyperTwistTraining/HyperTwistTrainingSubsystem.h"
#include "HyperTwistUX/HyperTwistCoachAssistantWidget.h"
#include "HyperTwistUX/HyperTwistDictationCaptureComponent.h"
#include "HyperTwistUX/HyperTwistFourDimensionalHUDWidget.h"
#include "HyperTwistUX/HyperTwistHigherDimensionalHUDWidget.h"
#include "HyperTwistUX/HyperTwistPlayerControllerBase.h"
#include "HyperTwistUX/HyperTwistPlayerSettings.h"
#include "HyperTwistUX/HyperTwistSettingsPanelWidget.h"
#include "Materials/MaterialInterface.h"
#include "Misc/PackageName.h"

#if WITH_AUTOMATION_TESTS

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistPlayerPreferenceDefaultsContractTest,
	"HyperTwist.PlayerExperience.PreferenceDefaults",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistPlayerPreferenceDefaultsContractTest::RunTest(
	const FString& Parameters
)
{
	static_cast<void>(Parameters);
	const FHyperTwistPlayerPreferences Preferences =
		UHyperTwistPlayerSettingsLibrary::GetDefaultPreferences();

	TestTrue(
		TEXT("Default player preferences must be structurally valid."),
		Preferences.IsStructurallyValid());
	TestEqual(TEXT("The current settings schema must remain explicit."), Preferences.SchemaVersion, 4);
	TestEqual(
		TEXT("Professional Classic controls must remain the default profile."),
		Preferences.KeyboardProfileId,
		FString(TEXT("classic-wca-keyboard/v1")));
	TestFalse(
		TEXT("Cloud providers must remain explicit opt-in."),
		Preferences.bAllowCloudProviders);
	TestFalse(
		TEXT("Generated coach responses must remain explicit opt-in."),
		Preferences.bCoachAiEnabled);
	TestTrue(
		TEXT("The private built-in assistant must remain available without AI."),
		Preferences.bAssistantPanelEnabled);
	TestEqual(
		TEXT("Speech must default to the local Whisper lane."),
		Preferences.SpeechProviderId,
		FString(TEXT("local-whisper-cpp")));
	TestEqual(
		TEXT("Dictation must default to a reviewable coach draft."),
		Preferences.SpeechDictationDestination,
		FString(TEXT("coach-draft")));
	TestEqual(
		TEXT("Narration must default to the local Piper lane."),
		Preferences.VoiceProviderId,
		FString(TEXT("local-piper")));
	TestEqual(
		TEXT("The fixed assistant shortcut must default to F3."),
		UHyperTwistPlayerSettingsLibrary::ResolveKeyBinding(
			Preferences,
			TEXT("assistant.toggle")),
		EKeys::F3);

	const TArray<FHyperTwistKeyBindingDescriptor> Bindings =
		UHyperTwistPlayerSettingsLibrary::BuildKeyBindingDescriptors(Preferences);
	TestTrue(
		TEXT("The settings surface must expose a substantial configurable keyboard map."),
		Bindings.Num() >= 24);
	for (const FHyperTwistKeyBindingDescriptor& Binding : Bindings)
	{
		TestTrue(
			*FString::Printf(
				TEXT("Binding %s must remain structurally valid."),
				*Binding.ActionId.ToString()),
			Binding.IsStructurallyValid());
	}

	for (const FString& ProfileId :
		 UHyperTwistPlayerSettingsLibrary::GetSupportedKeyboardProfileIds())
	{
		if (ProfileId == TEXT("custom/v1"))
		{
			continue;
		}

		FHyperTwistPlayerPreferences ProfilePreferences = Preferences;
		TestTrue(
			*FString::Printf(TEXT("Profile %s must apply."), *ProfileId),
			UHyperTwistPlayerSettingsLibrary::ApplyKeyboardProfile(
				ProfilePreferences,
				ProfileId));
		TSet<FName> UsedKeys;
		for (const FHyperTwistKeyBindingDescriptor& Binding :
			 UHyperTwistPlayerSettingsLibrary::BuildKeyBindingDescriptors(
				 ProfilePreferences))
		{
			const FName KeyName = Binding.Key.GetFName();
			TestFalse(
				*FString::Printf(
					TEXT("Profile %s must not assign %s more than once."),
					*ProfileId,
					*KeyName.ToString()),
				UsedKeys.Contains(KeyName));
			UsedKeys.Add(KeyName);
		}
	}
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistProviderEndpointPolicyContractTest,
	"HyperTwist.PlayerExperience.ProviderEndpointPolicy",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistProviderEndpointPolicyContractTest::RunTest(
	const FString& Parameters
)
{
	static_cast<void>(Parameters);
	FString FailureReason;
	TestTrue(
		TEXT("Loopback HTTP must work without cloud consent."),
		UHyperTwistPlayerSettingsLibrary::IsProviderEndpointAllowed(
			TEXT("http://127.0.0.1:8766"),
			false,
			FailureReason));
	TestTrue(TEXT("A valid loopback endpoint must not retain an error."), FailureReason.IsEmpty());

	TestTrue(
		TEXT("IPv6 loopback HTTPS must remain local."),
		UHyperTwistPlayerSettingsLibrary::IsProviderEndpointAllowed(
			TEXT("https://[::1]:8766/v1"),
			false,
			FailureReason));

	TestFalse(
		TEXT("Remote HTTPS must require explicit cloud consent."),
		UHyperTwistPlayerSettingsLibrary::IsProviderEndpointAllowed(
			TEXT("https://api.openai.com/v1/responses"),
			false,
			FailureReason));
	TestTrue(
		TEXT("Remote HTTPS may proceed after explicit cloud consent."),
		UHyperTwistPlayerSettingsLibrary::IsProviderEndpointAllowed(
			TEXT("https://api.openai.com/v1/responses"),
			true,
			FailureReason));
	TestFalse(
		TEXT("Remote plaintext HTTP must never be accepted."),
		UHyperTwistPlayerSettingsLibrary::IsProviderEndpointAllowed(
			TEXT("http://example.com/v1"),
			true,
			FailureReason));
	TestFalse(
		TEXT("A deceptive localhost suffix must not pass the loopback boundary."),
		UHyperTwistPlayerSettingsLibrary::IsProviderEndpointAllowed(
			TEXT("http://localhost.example.com/v1"),
			false,
			FailureReason));
	TestFalse(
		TEXT("Provider URLs must reject embedded credentials."),
		UHyperTwistPlayerSettingsLibrary::IsProviderEndpointAllowed(
			TEXT("https://user:secret@example.com/v1"),
			true,
			FailureReason));
	TestFalse(
		TEXT("Provider URLs must reject whitespace."),
		UHyperTwistPlayerSettingsLibrary::IsProviderEndpointAllowed(
			TEXT("https://example.com/v1 responses"),
			true,
			FailureReason));
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistNativeControlCenterAndAssistantContractTest,
	"HyperTwist.PlayerExperience.NativeControlCenterAndAssistant",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistNativeControlCenterAndAssistantContractTest::RunTest(
	const FString& Parameters
)
{
	static_cast<void>(Parameters);
	UHyperTwistSettingsPanelWidget* Settings =
		NewObject<UHyperTwistSettingsPanelWidget>();
	TestNotNull(TEXT("The native settings surface must be constructible."), Settings);
	if (Settings != nullptr)
	{
		Settings->TakeWidget();
		TestTrue(
			TEXT("The complete native settings surface must materialize."),
			Settings->IsSettingsSurfaceReady());
			const FName RequiredSettingsControls[] = {
				TEXT("MasterVolumeSlider"),
				TEXT("MusicVolumeSlider"),
				TEXT("EffectsVolumeSlider"),
				TEXT("VoiceVolumeSlider"),
				TEXT("MutedCheck"),
				TEXT("SpeechInputCheck"),
				TEXT("SpeechProviderCombo"),
				TEXT("SpeechActivationModeCombo"),
				TEXT("SpeechDictationDestinationCombo"),
				TEXT("SpeechLanguageCombo"),
				TEXT("SpeechModelInput"),
				TEXT("SpeechMicrophoneCombo"),
				TEXT("SpeechContextInput"),
			TEXT("SpeechEndpointInput"),
			TEXT("SpeechCredentialInput"),
			TEXT("VoiceProviderCombo"),
			TEXT("VoiceModelInput"),
			TEXT("VoiceIdInput"),
			TEXT("VoiceSpeedSlider"),
			TEXT("VoiceEndpointInput"),
			TEXT("VoiceCredentialInput"),
			TEXT("CoachProviderCombo"),
			TEXT("CoachEndpointInput"),
			TEXT("CoachModelInput"),
			TEXT("CoachInstructionsInput"),
			TEXT("CoachMaxResponseTokensSlider"),
			TEXT("CoachRequestTimeoutSlider"),
			TEXT("CoachCredentialInput"),
			TEXT("AssistantPanelEnabledCheck"),
				TEXT("CoachAiEnabledCheck"),
				TEXT("CloudProvidersCheck")
				};
		for (const FName ControlName : RequiredSettingsControls)
		{
			TestNotNull(
				*FString::Printf(
					TEXT("Settings must expose %s."),
					*ControlName.ToString()),
				Settings->WidgetTree != nullptr
					? Settings->WidgetTree->FindWidget(ControlName)
					: nullptr);
		}

		const TArray<FHyperTwistMicrophoneDescriptor> Microphones =
			UHyperTwistPlayerSettingsLibrary::GetAvailableMicrophones();
		TestTrue(
			TEXT("The control center must always expose a system-default microphone route."),
			Microphones.ContainsByPredicate(
				[](const FHyperTwistMicrophoneDescriptor& Microphone)
				{
					return Microphone.DeviceId == TEXT("system-default")
						&& Microphone.bIsDefault
						&& Microphone.IsStructurallyValid();
				}));
	}

	UHyperTwistCoachAssistantWidget* Assistant =
		NewObject<UHyperTwistCoachAssistantWidget>();
	TestNotNull(TEXT("The fixed in-game assistant must be constructible."), Assistant);
	if (Assistant != nullptr)
	{
		Assistant->TakeWidget();
		TestTrue(
			TEXT("The fixed in-game assistant must materialize its conversation surface."),
			Assistant->IsAssistantSurfaceReady());
			TestNotNull(
				TEXT("The assistant must expose a session-only prompt composer."),
				Assistant->WidgetTree != nullptr
					? Assistant->WidgetTree->FindWidget(
						FName(TEXT("CoachAssistantPrompt")))
					: nullptr);
			TestNotNull(
				TEXT("The assistant must expose global dictation without polluting puzzle HUDs."),
				Assistant->WidgetTree != nullptr
					? Assistant->WidgetTree->FindWidget(
						FName(TEXT("CoachAssistantDictationButton")))
					: nullptr);
		}

	const AHyperTwistPlayerControllerBase* PlayerControllerDefaults =
		GetDefault<AHyperTwistPlayerControllerBase>();
		TestNotNull(
			TEXT("Every native puzzle controller must own the common dictation capture route."),
			PlayerControllerDefaults != nullptr
				? PlayerControllerDefaults->GlobalDictationCapture.Get()
				: nullptr);
		TestNotNull(
			TEXT("Every native puzzle controller must expose non-blocking coach narration."),
			AHyperTwistPlayerControllerBase::StaticClass()->FindFunctionByName(
				TEXT("NarrateCoachResponse")));
		TestNotNull(
			TEXT("Every native puzzle controller must expose narration cancellation."),
			AHyperTwistPlayerControllerBase::StaticClass()->FindFunctionByName(
				TEXT("StopCoachNarration")));

	UHyperTwistFourDimensionalHUDWidget* FourDimensionalHud =
		NewObject<UHyperTwistFourDimensionalHUDWidget>();
				TestNotNull(
		TEXT("The shared 4D HUD must be constructible."),
		FourDimensionalHud);
	if (FourDimensionalHud != nullptr)
	{
		FourDimensionalHud->TakeWidget();
		TestTrue(
			TEXT("The shared 4D HUD must materialize its player controls."),
			FourDimensionalHud->IsHudSurfaceReady());
		const FName RequiredFourDimensionalControls[] = {
			TEXT("FourDimensionalHudAxisButton"),
			TEXT("FourDimensionalHudPreviousLayerButton"),
			TEXT("FourDimensionalHudNextLayerButton"),
			TEXT("FourDimensionalHudCounterClockwiseButton"),
			TEXT("FourDimensionalHudClockwiseButton"),
			TEXT("FourDimensionalHudScrambleButton"),
			TEXT("FourDimensionalHudResetButton"),
			TEXT("FourDimensionalHudSaveButton"),
			TEXT("FourDimensionalHudLoadButton")
		};
		for (const FName ControlName : RequiredFourDimensionalControls)
		{
			TestNotNull(
				*FString::Printf(
					TEXT("The shared 4D HUD must expose %s."),
					*ControlName.ToString()),
					FourDimensionalHud->WidgetTree != nullptr
						? FourDimensionalHud->WidgetTree->FindWidget(ControlName)
						: nullptr);
			}
		}

	UHyperTwistHigherDimensionalHUDWidget* HigherDimensionalHud =
		NewObject<UHyperTwistHigherDimensionalHUDWidget>();
	TestNotNull(
		TEXT("The exact 120-cell and 5D HUD must be constructible."),
		HigherDimensionalHud);
	if (HigherDimensionalHud != nullptr)
	{
		HigherDimensionalHud->TakeWidget();
		TestTrue(
			TEXT("The exact 120-cell and 5D HUD must materialize its player controls."),
			HigherDimensionalHud->IsHudSurfaceReady());
		const FName RequiredHigherDimensionalControls[] = {
			TEXT("HigherDimensionalHudPreviousPrimary"),
			TEXT("HigherDimensionalHudNextPrimary"),
			TEXT("HigherDimensionalHudPreviousSecondary"),
			TEXT("HigherDimensionalHudNextSecondary"),
			TEXT("HigherDimensionalHudPreviousTertiary"),
			TEXT("HigherDimensionalHudNextTertiary"),
			TEXT("HigherDimensionalHudFaceSide"),
			TEXT("HigherDimensionalHudNegativeTurn"),
			TEXT("HigherDimensionalHudPositiveTurn"),
			TEXT("HigherDimensionalHudPreviousProjectionLayer"),
			TEXT("HigherDimensionalHudNextProjectionLayer"),
			TEXT("HigherDimensionalHudAutoRotate"),
			TEXT("HigherDimensionalHudScramble"),
			TEXT("HigherDimensionalHudReset"),
			TEXT("HigherDimensionalHudSave"),
			TEXT("HigherDimensionalHudLoad"),
			TEXT("HigherDimensionalHudCoach"),
			TEXT("HigherDimensionalHudMenu")
		};
		for (const FName ControlName : RequiredHigherDimensionalControls)
		{
			TestNotNull(
				*FString::Printf(
					TEXT("The exact 120-cell and 5D HUD must expose %s."),
					*ControlName.ToString()),
				HigherDimensionalHud->WidgetTree != nullptr
					? HigherDimensionalHud->WidgetTree->FindWidget(ControlName)
					: nullptr);
		}
	}

	UHyperTwistClassicCubeHUDWidget* ClassicHud =
		NewObject<UHyperTwistClassicCubeHUDWidget>();
	TestNotNull(TEXT("The Classic HUD must remain constructible."), ClassicHud);
	if (ClassicHud != nullptr)
	{
		ClassicHud->TakeWidget();
		TestNull(
			TEXT("Speech activation must not reappear as a Classic puzzle HUD button."),
			ClassicHud->WidgetTree != nullptr
				? ClassicHud->WidgetTree->FindWidget(FName(TEXT("VoiceHoldButton")))
				: nullptr);
		TestNull(
			TEXT("Voice selection must remain a global setting, not a Classic HUD button."),
			ClassicHud->WidgetTree != nullptr
				? ClassicHud->WidgetTree->FindWidget(FName(TEXT("VoiceCycleButton")))
				: nullptr);
	}

	const AHyperTwistPlayerControllerBase* ControllerDefaults =
		GetDefault<AHyperTwistPlayerControllerBase>();
	TestTrue(
		TEXT("Gameplay controllers must keep the fixed assistant capability enabled."),
		ControllerDefaults != nullptr
			&& ControllerDefaults->bEnableGlobalAssistantPanel);
	const AHyperTwistMelindaProjectionPlayerController* CellFirstController =
		GetDefault<AHyperTwistMelindaProjectionPlayerController>();
	const AHyperTwistVirtual3333ProjectionPlayerController* VisibleSliceController =
		GetDefault<AHyperTwistVirtual3333ProjectionPlayerController>();
	const AHyperTwistHigherDimensionalTrainingPlayerController*
		HigherDimensionalController =
			GetDefault<AHyperTwistHigherDimensionalTrainingPlayerController>();
	TestTrue(
		TEXT("All native higher-dimensional routes must show actionable player HUDs by default."),
		CellFirstController != nullptr
			&& CellFirstController->bShowPuzzleHud
			&& VisibleSliceController != nullptr
			&& VisibleSliceController->bShowPuzzleHud
			&& HigherDimensionalController != nullptr
			&& HigherDimensionalController->bShowPuzzleHud);
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistGlobalAssistantSpeechSessionContractTest,
	"HyperTwist.PlayerExperience.GlobalAssistantSpeechSession",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistGlobalAssistantSpeechSessionContractTest::RunTest(
	const FString& Parameters
)
{
	static_cast<void>(Parameters);
	UGameInstance* GameInstance = NewObject<UGameInstance>();
	UHyperTwistTrainingSubsystem* TrainingSubsystem =
		GameInstance != nullptr
			? NewObject<UHyperTwistTrainingSubsystem>(GameInstance)
			: nullptr;
	TestNotNull(
		TEXT("The global assistant must be able to create its speech subsystem."),
		TrainingSubsystem);
	if (TrainingSubsystem == nullptr)
	{
		return false;
	}

	TestFalse(
		TEXT("This proof must not depend on a Classic training run."),
		TrainingSubsystem->HasActiveRun());
	TrainingSubsystem->SetCompanionSpeechClientKindForAutomation(TEXT("mock"));

	FString OpenError;
	TestTrue(
		TEXT("The fixed assistant must open dictation in 4D, 5D, and 120-cell routes without an active training run."),
		TrainingSubsystem->OpenActiveCompanionSpeechSession(OpenError));
	TestTrue(
		TEXT("Standalone assistant dictation must not report an open error."),
		OpenError.IsEmpty());

	const FHyperTwistTrainingCompanionSpeechSessionState SessionState =
		TrainingSubsystem->GetActiveCompanionSpeechSessionState();
	TestTrue(
		TEXT("The standalone assistant speech session must remain open."),
		SessionState.bSessionOpen);
	TestTrue(
		TEXT("The standalone assistant speech session must own an explicit global session id."),
		SessionState.ActiveSessionId.StartsWith(TEXT("speech-global-")));
	TestTrue(
		TEXT("The standalone assistant speech configuration must remain structurally valid."),
		SessionState.SessionConfig.IsStructurallyValid());

	FString CloseError;
	TestTrue(
		TEXT("The standalone assistant speech session must close cleanly."),
		TrainingSubsystem->CloseActiveCompanionSpeechSession(CloseError));
	TestTrue(
		TEXT("Closing standalone assistant dictation must not report an error."),
		CloseError.IsEmpty());
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistDedicatedVirtual4DMapContractTest,
	"HyperTwist.PlayerExperience.DedicatedVirtual4DMaps",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistDedicatedVirtual4DMapContractTest::RunTest(
	const FString& Parameters
)
{
	static_cast<void>(Parameters);
	struct FMapExpectation
	{
		int32 Order;
		const TCHAR* MapAssetPath;
		UClass* GameModeClass;
		const TCHAR* ProjectionActorClassPath;
	};

	const FMapExpectation Expectations[] = {
		{
			2,
			TEXT("/Game/HyperTwistTraining/Maps/L_HyperTwist_MagicCube4D_2x2x2x2Training"),
			AHyperTwistMelindaProjectionGameMode::StaticClass(),
			TEXT("/Script/UnrealHyperTwist.HyperTwistMelindaProjectionActor")
		},
		{
			3,
			TEXT("/Game/HyperTwistTraining/Maps/L_HyperTwist_MagicCube4D_3x3x3x3Training"),
			AHyperTwistVirtual3333ProjectionGameMode::StaticClass(),
			TEXT("/Script/UnrealHyperTwist.HyperTwistVirtual3333ProjectionActor")
		},
		{
			4,
			TEXT("/Game/HyperTwistTraining/Maps/L_HyperTwist_MagicCube4D_4x4x4x4Training"),
			AHyperTwistVirtual4444ProjectionGameMode::StaticClass(),
			TEXT("/Script/UnrealHyperTwist.HyperTwistVirtual3333ProjectionActor")
		},
		{
			5,
			TEXT("/Game/HyperTwistTraining/Maps/L_HyperTwist_MagicCube4D_5x5x5x5Training"),
			AHyperTwistVirtual5555ProjectionGameMode::StaticClass(),
			TEXT("/Script/UnrealHyperTwist.HyperTwistVirtual3333ProjectionActor")
		},
		{
			6,
			TEXT("/Game/HyperTwistTraining/Maps/L_HyperTwist_MagicCube4D_6x6x6x6Training"),
			AHyperTwistVirtual6666ProjectionGameMode::StaticClass(),
			TEXT("/Script/UnrealHyperTwist.HyperTwistVirtual3333ProjectionActor")
		}
	};

	TestNotNull(
		TEXT("The vertex-color projection material must be a real content asset."),
		LoadObject<UMaterialInterface>(
			nullptr,
			TEXT("/Game/HyperTwistTraining/Materials/M_HT_ProjectionVertexColor.M_HT_ProjectionVertexColor")));

	for (const FMapExpectation& Expectation : Expectations)
	{
		const FString MapAssetPath(Expectation.MapAssetPath);
		const FString MapObjectPath = FString::Printf(
			TEXT("%s.%s"),
			*MapAssetPath,
			*FPackageName::GetShortName(MapAssetPath));
		UWorld* World = LoadObject<UWorld>(nullptr, *MapObjectPath);
		TestNotNull(
			*FString::Printf(
				TEXT("The exact %dx%dx%dx%d route must load its dedicated map."),
				Expectation.Order,
				Expectation.Order,
				Expectation.Order,
				Expectation.Order),
			World);
		if (World == nullptr || World->PersistentLevel == nullptr)
		{
			continue;
		}

		const AWorldSettings* WorldSettings = World->GetWorldSettings();
		TestEqual(
			*FString::Printf(
				TEXT("The %dD-order map must own its exact runtime game mode."),
				Expectation.Order),
			WorldSettings != nullptr && WorldSettings->DefaultGameMode != nullptr
				? WorldSettings->DefaultGameMode->GetPathName()
				: FString(),
			Expectation.GameModeClass->GetPathName());

		APlayerStart* DedicatedAnchor = nullptr;
		int32 PlayerStartCount = 0;
		for (AActor* Actor : World->PersistentLevel->Actors)
		{
			if (APlayerStart* PlayerStart = Cast<APlayerStart>(Actor))
			{
				++PlayerStartCount;
				DedicatedAnchor = PlayerStart;
			}
		}
		TestEqual(
			*FString::Printf(
				TEXT("The %dx route must own exactly one launch anchor."),
				Expectation.Order),
			PlayerStartCount,
			1);
		if (DedicatedAnchor != nullptr)
		{
			const FName ExpectedOrderTag(*FString::Printf(
				TEXT("puzzle-order:%d"),
				Expectation.Order));
			const FName ExpectedProjectionClassTag(*FString::Printf(
				TEXT("projection-class:%s"),
				Expectation.ProjectionActorClassPath));
			TestTrue(
				TEXT("The dedicated launch anchor must retain first-party ownership."),
				DedicatedAnchor->ActorHasTag(
					FName(TEXT("ownership:first-party-dedicated-map"))));
			TestTrue(
				TEXT("The dedicated launch anchor must identify its puzzle order."),
				DedicatedAnchor->ActorHasTag(ExpectedOrderTag));
			TestTrue(
				TEXT("The projection must remain runtime-owned by the game mode."),
				DedicatedAnchor->ActorHasTag(
					FName(TEXT("projection-spawn:runtime-game-mode"))));
			TestTrue(
				TEXT("The launch anchor must bind its exact projection actor class."),
				DedicatedAnchor->ActorHasTag(ExpectedProjectionClassTag));
			TestTrue(
				TEXT("The dedicated route must retain its keyboard-and-mouse lane."),
				DedicatedAnchor->ActorHasTag(FName(TEXT("input:keyboard-mouse"))));
		}

		if (Expectation.Order == 2)
		{
			const AHyperTwistMelindaProjectionGameMode* Defaults =
				Cast<AHyperTwistMelindaProjectionGameMode>(
					Expectation.GameModeClass->GetDefaultObject());
			TestTrue(
				TEXT("The exact 2x runtime must auto-spawn its projection actor."),
				Defaults != nullptr && Defaults->bAutoSpawnProjectionActor);
		}
		else
		{
			const AHyperTwistVirtual3333ProjectionGameMode* Defaults =
				Cast<AHyperTwistVirtual3333ProjectionGameMode>(
					Expectation.GameModeClass->GetDefaultObject());
			TestTrue(
				*FString::Printf(
					TEXT("The exact %dx runtime must auto-spawn its projection actor."),
					Expectation.Order),
				Defaults != nullptr && Defaults->bAutoSpawnProjectionActor);
			TestEqual(
				*FString::Printf(
					TEXT("The exact %dx runtime must preserve its puzzle order."),
					Expectation.Order),
				Defaults != nullptr ? Defaults->PuzzleOrder : 0,
				Expectation.Order);
		}
	}
	return true;
}

#endif
