From d0ccdde06a7f1d1a14238f8a01a7da63ef735f6d Mon Sep 17 00:00:00 2001 From: axiomlogicnexus Date: Mon, 27 Apr 2026 15:56:22 +0200 Subject: [PATCH] Apply guidance outcome from dashboard --- .../HyperTwistCoachDashboardWidget.cpp | 343 +++++++++++++++++- .../HyperTwistCoachDashboardWidget.h | 31 ++ 2 files changed, 373 insertions(+), 1 deletion(-) diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp index 0e0ccda..27a9dae 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp @@ -407,6 +407,7 @@ FHyperTwistTrainingRunState UHyperTwistCoachDashboardWidget::StartActiveGuidance FHyperTwistTrainingRunState UHyperTwistCoachDashboardWidget::StartSelectedGuidancePreviewCaseRun() { const FHyperTwistTrainingDeck Deck = BuildSelectedGuidancePreviewCaseDeck(); + const FHyperTwistTrainingDeck* ActivePreviewDeck = GetActiveGuidancePreviewDeck(); if (!Deck.IsStructurallyValid()) { UpdateDashboardPresentation(); @@ -428,7 +429,7 @@ FHyperTwistTrainingRunState UHyperTwistCoachDashboardWidget::StartSelectedGuidan ); CaptureActiveGuidanceLaunchContext( RunState.Session.IsStructurallyValid() ? RunState.Session.TrainingSessionId : SessionId, - Deck, + ActivePreviewDeck != nullptr && ActivePreviewDeck->IsStructurallyValid() ? *ActivePreviewDeck : Deck, true, Deck.Cases.Num() > 0 ? Deck.Cases[0].CaseId : FString() ); @@ -552,6 +553,133 @@ bool UHyperTwistCoachDashboardWidget::ContinueCurrentRunFlow() return bSucceeded; } +bool UHyperTwistCoachDashboardWidget::ApplyGuidanceOutcomeAction() +{ + const EHyperTwistCoachDashboardOutcomeAction Action = DeriveGuidanceOutcomeAction(); + const bool bCanRestartAboveCompletedRun = + !CachedRunState.Session.IsStructurallyValid() + || CachedRunState.Session.SessionState == EHyperTwistTrainingSessionState::Completed + || CachedRunState.Session.SessionState == EHyperTwistTrainingSessionState::Aborted; + if (bHasLiveAttemptTimer || Action == EHyperTwistCoachDashboardOutcomeAction::None || !bCanRestartAboveCompletedRun) + { + UpdateDashboardPresentation(); + return false; + } + + const FHyperTwistTrainingSessionSummary* DisplayedRunRecap = nullptr; + const FHyperTwistCoachBrief* DisplayedGuidanceComparisonBrief = nullptr; + const FHyperTwistTrainingDeck* DisplayedGuidanceComparisonDeck = nullptr; + FString DisplayedGuidanceComparisonSelectedCaseId; + bool bDisplayedGuidanceComparisonSingleCase = false; + if (!ResolveDisplayedGuidanceComparisonContext( + DisplayedRunRecap, + DisplayedGuidanceComparisonBrief, + DisplayedGuidanceComparisonDeck, + DisplayedGuidanceComparisonSelectedCaseId, + bDisplayedGuidanceComparisonSingleCase)) + { + UpdateDashboardPresentation(); + return false; + } + + const FString ActionSessionIdBase = FString::Printf( + TEXT("%s_outcome_%s"), + *DefaultSessionId, + *FGuid::NewGuid().ToString(EGuidFormats::Digits) + ); + + switch (Action) + { + case EHyperTwistCoachDashboardOutcomeAction::RepeatSelectedCase: + { + const FHyperTwistTrainingDeck SelectedCaseDeck = BuildGuidanceOutcomeSelectedCaseDeck(); + if (!SelectedCaseDeck.IsStructurallyValid() || DisplayedGuidanceComparisonDeck == nullptr) + { + UpdateDashboardPresentation(); + return false; + } + + const FHyperTwistTrainingRunState RunState = StartTrainingRunFromDeck( + SelectedCaseDeck, + DefaultUserId, + ActionSessionIdBase + TEXT("_selected_case"), + DefaultMode + ); + CaptureActiveGuidanceLaunchContext( + RunState.Session.IsStructurallyValid() ? RunState.Session.TrainingSessionId : ActionSessionIdBase, + *DisplayedGuidanceComparisonDeck, + true, + DisplayedGuidanceComparisonSelectedCaseId + ); + SyncRetainedRunRecap(); + SyncSelectedQueueEntry(); + SyncSelectedAttempt(); + UpdateDashboardPresentation(); + return RunState.Session.IsStructurallyValid() && RunState.ActiveDeck.IsStructurallyValid(); + } + case EHyperTwistCoachDashboardOutcomeAction::RepeatPreviewDeck: + case EHyperTwistCoachDashboardOutcomeAction::BroadenPreviewDeck: + { + if (DisplayedGuidanceComparisonDeck == nullptr || !DisplayedGuidanceComparisonDeck->IsStructurallyValid()) + { + UpdateDashboardPresentation(); + return false; + } + + const FString SessionSuffix = Action == EHyperTwistCoachDashboardOutcomeAction::BroadenPreviewDeck + ? TEXT("_broaden_preview") + : TEXT("_repeat_preview"); + const FHyperTwistTrainingRunState RunState = StartTrainingRunFromDeck( + *DisplayedGuidanceComparisonDeck, + DefaultUserId, + ActionSessionIdBase + SessionSuffix, + DefaultMode + ); + CaptureActiveGuidanceLaunchContext( + RunState.Session.IsStructurallyValid() ? RunState.Session.TrainingSessionId : ActionSessionIdBase, + *DisplayedGuidanceComparisonDeck, + false, + DisplayedGuidanceComparisonSelectedCaseId + ); + SyncRetainedRunRecap(); + SyncSelectedQueueEntry(); + SyncSelectedAttempt(); + UpdateDashboardPresentation(); + return RunState.Session.IsStructurallyValid() && RunState.ActiveDeck.IsStructurallyValid(); + } + case EHyperTwistCoachDashboardOutcomeAction::RedirectCoachGuidance: + { + FHyperTwistTrainingRunState RunState; + if (CachedCoachPanelState.bCanStartCoachFollowUpRun && CachedCoachPanelState.bHasFollowUpGuidance) + { + RunState = StartCoachFollowUpRun(DefaultCoachMaxCases); + } + else if (CachedCoachPanelState.bCanStartQueuedRun) + { + RunState = StartNextQueuedCoachRun(DefaultCoachMaxCases); + } + else if (CachedCoachPanelState.bCanStartCoachRecommendedRun) + { + RunState = StartCoachRecommendedRun(DefaultCoachMaxCases); + } + else + { + UpdateDashboardPresentation(); + return false; + } + + SyncRetainedRunRecap(); + SyncSelectedQueueEntry(); + SyncSelectedAttempt(); + UpdateDashboardPresentation(); + return RunState.Session.IsStructurallyValid() && RunState.ActiveDeck.IsStructurallyValid(); + } + default: + UpdateDashboardPresentation(); + return false; + } +} + bool UHyperTwistCoachDashboardWidget::StartLiveAttemptTimer() { if (!CachedRunState.Session.IsStructurallyValid() || !CachedRunState.CurrentSelection.TrainingCase.IsStructurallyValid()) @@ -817,6 +945,11 @@ void UHyperTwistCoachDashboardWidget::HandleContinueRunClicked() ContinueCurrentRunFlow(); } +void UHyperTwistCoachDashboardWidget::HandleGuidanceOutcomeActionClicked() +{ + ApplyGuidanceOutcomeAction(); +} + void UHyperTwistCoachDashboardWidget::HandleClearRunClicked() { ClearActiveTrainingRun(); @@ -1152,6 +1285,17 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt() ); RepeatRunButtonLabel = RepeatRunLabelRaw; + UTextBlock* GuidanceOutcomeActionLabelRaw = nullptr; + GuidanceOutcomeActionButton = HyperTwistCoachDashboardWidgetInternal::AddButton( + WidgetTree, + RunRecapActionRow, + TEXT("GuidanceOutcomeActionButton"), + TEXT("GuidanceOutcomeActionButtonLabel"), + TEXT("Apply Outcome"), + GuidanceOutcomeActionLabelRaw + ); + GuidanceOutcomeActionButtonLabel = GuidanceOutcomeActionLabelRaw; + UTextBlock* DismissRecapLabelRaw = nullptr; DismissRecapButton = HyperTwistCoachDashboardWidgetInternal::AddButton( WidgetTree, @@ -1797,6 +1941,10 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt() { RepeatRunButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleRepeatRunClicked); } + if (GuidanceOutcomeActionButton != nullptr) + { + GuidanceOutcomeActionButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleGuidanceOutcomeActionClicked); + } if (DismissRecapButton != nullptr) { DismissRecapButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleDismissRecapClicked); @@ -2108,6 +2256,177 @@ FHyperTwistTrainingDeck UHyperTwistCoachDashboardWidget::BuildSelectedGuidancePr return SingleCaseDeck; } +bool UHyperTwistCoachDashboardWidget::ResolveDisplayedGuidanceComparisonContext( + const FHyperTwistTrainingSessionSummary*& OutDisplayedRunRecap, + const FHyperTwistCoachBrief*& OutDisplayedGuidanceComparisonBrief, + const FHyperTwistTrainingDeck*& OutDisplayedGuidanceComparisonDeck, + FString& OutDisplayedGuidanceComparisonSelectedCaseId, + bool& bOutDisplayedGuidanceComparisonSingleCase +) const +{ + OutDisplayedRunRecap = nullptr; + OutDisplayedGuidanceComparisonBrief = nullptr; + OutDisplayedGuidanceComparisonDeck = nullptr; + OutDisplayedGuidanceComparisonSelectedCaseId.Reset(); + bOutDisplayedGuidanceComparisonSingleCase = false; + + const bool bHasCurrentRunRecap = CachedRunState.Session.IsStructurallyValid() + && (CachedRunState.Session.SessionState == EHyperTwistTrainingSessionState::Completed + || CachedRunState.Session.SessionState == EHyperTwistTrainingSessionState::Aborted); + OutDisplayedRunRecap = bHasCurrentRunRecap + ? &CachedRunSummary + : (bHasRetainedRunRecap ? &RetainedRunRecapSummary : nullptr); + if (OutDisplayedRunRecap == nullptr || OutDisplayedRunRecap->TrainingSessionId.IsEmpty()) + { + return false; + } + + const bool bHasActiveGuidanceComparison = + bHasCurrentRunRecap + && !ActiveGuidanceLaunchSessionId.IsEmpty() + && OutDisplayedRunRecap->TrainingSessionId == ActiveGuidanceLaunchSessionId + && ActiveGuidanceLaunchLane != EHyperTwistCoachDashboardGuidancePreviewLane::None; + const bool bHasDisplayedRetainedGuidanceComparison = + !bHasCurrentRunRecap + && bHasRetainedGuidanceComparison + && !RetainedGuidanceComparisonSessionId.IsEmpty() + && OutDisplayedRunRecap->TrainingSessionId == RetainedGuidanceComparisonSessionId + && RetainedGuidanceComparisonLane != EHyperTwistCoachDashboardGuidancePreviewLane::None; + + if (bHasActiveGuidanceComparison) + { + OutDisplayedGuidanceComparisonBrief = &ActiveGuidanceLaunchBrief; + OutDisplayedGuidanceComparisonDeck = &ActiveGuidanceLaunchDeck; + OutDisplayedGuidanceComparisonSelectedCaseId = ActiveGuidanceLaunchSelectedCaseId; + bOutDisplayedGuidanceComparisonSingleCase = bActiveGuidanceLaunchSingleCase; + } + else if (bHasDisplayedRetainedGuidanceComparison) + { + OutDisplayedGuidanceComparisonBrief = &RetainedGuidanceComparisonBrief; + OutDisplayedGuidanceComparisonDeck = &RetainedGuidanceComparisonDeck; + OutDisplayedGuidanceComparisonSelectedCaseId = RetainedGuidanceComparisonSelectedCaseId; + bOutDisplayedGuidanceComparisonSingleCase = bRetainedGuidanceComparisonSingleCase; + } + + return OutDisplayedGuidanceComparisonBrief != nullptr + && OutDisplayedGuidanceComparisonDeck != nullptr + && OutDisplayedGuidanceComparisonDeck->IsStructurallyValid(); +} + +EHyperTwistCoachDashboardOutcomeAction UHyperTwistCoachDashboardWidget::DeriveGuidanceOutcomeAction() const +{ + const FHyperTwistTrainingSessionSummary* DisplayedRunRecap = nullptr; + const FHyperTwistCoachBrief* DisplayedGuidanceComparisonBrief = nullptr; + const FHyperTwistTrainingDeck* DisplayedGuidanceComparisonDeck = nullptr; + FString DisplayedGuidanceComparisonSelectedCaseId; + bool bDisplayedGuidanceComparisonSingleCase = false; + if (!ResolveDisplayedGuidanceComparisonContext( + DisplayedRunRecap, + DisplayedGuidanceComparisonBrief, + DisplayedGuidanceComparisonDeck, + DisplayedGuidanceComparisonSelectedCaseId, + bDisplayedGuidanceComparisonSingleCase) + || DisplayedRunRecap == nullptr + || DisplayedRunRecap->CompletedAttemptCount <= 0) + { + return EHyperTwistCoachDashboardOutcomeAction::None; + } + + const int32 NonSuccessCount = + DisplayedRunRecap->FailureCount + DisplayedRunRecap->TimeoutCount + DisplayedRunRecap->DNFCount; + if (bDisplayedGuidanceComparisonSingleCase) + { + if (DisplayedRunRecap->SuccessRate >= 0.85f + && DisplayedRunRecap->TimeoutCount == 0 + && DisplayedRunRecap->DNFCount == 0 + && DisplayedRunRecap->TotalMistakes <= 1) + { + return EHyperTwistCoachDashboardOutcomeAction::BroadenPreviewDeck; + } + + return DisplayedGuidanceComparisonSelectedCaseId.IsEmpty() + ? EHyperTwistCoachDashboardOutcomeAction::None + : EHyperTwistCoachDashboardOutcomeAction::RepeatSelectedCase; + } + + if (DisplayedRunRecap->SuccessRate >= 0.8f + && NonSuccessCount <= FMath::Max(1, DisplayedRunRecap->CompletedAttemptCount / 4) + && DisplayedRunRecap->TotalMistakes <= FMath::Max(1, DisplayedRunRecap->CompletedAttemptCount / 2)) + { + return EHyperTwistCoachDashboardOutcomeAction::RedirectCoachGuidance; + } + + if (DisplayedRunRecap->SuccessRate >= 0.55f) + { + return EHyperTwistCoachDashboardOutcomeAction::RepeatPreviewDeck; + } + + return !DisplayedGuidanceComparisonSelectedCaseId.IsEmpty() + ? EHyperTwistCoachDashboardOutcomeAction::RepeatSelectedCase + : EHyperTwistCoachDashboardOutcomeAction::RedirectCoachGuidance; +} + +FString UHyperTwistCoachDashboardWidget::BuildGuidanceOutcomeActionLabel( + const EHyperTwistCoachDashboardOutcomeAction Action) const +{ + switch (Action) + { + case EHyperTwistCoachDashboardOutcomeAction::RepeatSelectedCase: + return TEXT("Apply: Repeat Selected Case"); + case EHyperTwistCoachDashboardOutcomeAction::RepeatPreviewDeck: + return TEXT("Apply: Repeat Preview Deck"); + case EHyperTwistCoachDashboardOutcomeAction::BroadenPreviewDeck: + return TEXT("Apply: Broaden to Preview Deck"); + case EHyperTwistCoachDashboardOutcomeAction::RedirectCoachGuidance: + return TEXT("Apply: Next Coach Guidance"); + default: + return TEXT("Apply Outcome"); + } +} + +FHyperTwistTrainingDeck UHyperTwistCoachDashboardWidget::BuildGuidanceOutcomeSelectedCaseDeck() const +{ + const FHyperTwistTrainingSessionSummary* DisplayedRunRecap = nullptr; + const FHyperTwistCoachBrief* DisplayedGuidanceComparisonBrief = nullptr; + const FHyperTwistTrainingDeck* DisplayedGuidanceComparisonDeck = nullptr; + FString DisplayedGuidanceComparisonSelectedCaseId; + bool bDisplayedGuidanceComparisonSingleCase = false; + if (!ResolveDisplayedGuidanceComparisonContext( + DisplayedRunRecap, + DisplayedGuidanceComparisonBrief, + DisplayedGuidanceComparisonDeck, + DisplayedGuidanceComparisonSelectedCaseId, + bDisplayedGuidanceComparisonSingleCase) + || DisplayedGuidanceComparisonDeck == nullptr + || DisplayedGuidanceComparisonSelectedCaseId.IsEmpty()) + { + return FHyperTwistTrainingDeck(); + } + + const FHyperTwistTrainingCase* SelectedCase = DisplayedGuidanceComparisonDeck->Cases.FindByPredicate( + [&DisplayedGuidanceComparisonSelectedCaseId](const FHyperTwistTrainingCase& Candidate) + { + return Candidate.CaseId == DisplayedGuidanceComparisonSelectedCaseId; + }); + if (SelectedCase == nullptr) + { + return FHyperTwistTrainingDeck(); + } + + FHyperTwistTrainingDeck SingleCaseDeck = *DisplayedGuidanceComparisonDeck; + SingleCaseDeck.Cases = { *SelectedCase }; + SingleCaseDeck.DeckId = FString::Printf( + TEXT("%s_case_%s"), + DisplayedGuidanceComparisonDeck->DeckId.IsEmpty() ? TEXT("guidance") : *DisplayedGuidanceComparisonDeck->DeckId, + SelectedCase->CaseId.IsEmpty() ? TEXT("selected") : *SelectedCase->CaseId + ); + SingleCaseDeck.Title = FString::Printf( + TEXT("%s (Selected Case)"), + DisplayedGuidanceComparisonDeck->Title.IsEmpty() ? TEXT("Guidance Comparison") : *DisplayedGuidanceComparisonDeck->Title + ); + return SingleCaseDeck; +} + void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() { const bool bHasActiveRun = CachedRunState.Session.IsStructurallyValid() @@ -2722,6 +3041,20 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() ); } } + const EHyperTwistCoachDashboardOutcomeAction GuidanceOutcomeAction = DeriveGuidanceOutcomeAction(); + const bool bCanRedirectGuidanceOutcome = + CachedCoachPanelState.bCanStartCoachFollowUpRun + || CachedCoachPanelState.bCanStartQueuedRun + || CachedCoachPanelState.bCanStartCoachRecommendedRun; + const bool bCanApplyGuidanceOutcomeAction = + GuidanceOutcomeAction != EHyperTwistCoachDashboardOutcomeAction::None + && !bHasLiveAttemptTimer + && (!bHasActiveRun + || CachedRunState.Session.SessionState == EHyperTwistTrainingSessionState::Completed + || CachedRunState.Session.SessionState == EHyperTwistTrainingSessionState::Aborted) + && (GuidanceOutcomeAction != EHyperTwistCoachDashboardOutcomeAction::RedirectCoachGuidance + || bCanRedirectGuidanceOutcome); + const FString GuidanceOutcomeActionLabel = BuildGuidanceOutcomeActionLabel(GuidanceOutcomeAction); const bool bCanStartPreviewDeck = !bHasLiveAttemptTimer && !bHasActiveRun && ActivePreviewDeck != nullptr && ActivePreviewDeck->IsStructurallyValid(); const bool bCanStartPreviewCase = bCanStartPreviewDeck && SelectedGuidancePreviewCase != nullptr; @@ -2862,6 +3195,14 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() { GuidanceOutcomeDetailTextBlock->SetText(FText::FromString(GuidanceOutcomeDetailLine)); } + if (GuidanceOutcomeActionButtonLabel != nullptr) + { + GuidanceOutcomeActionButtonLabel->SetText(FText::FromString(GuidanceOutcomeActionLabel)); + } + if (GuidanceOutcomeActionButton != nullptr) + { + GuidanceOutcomeActionButton->SetIsEnabled(bCanApplyGuidanceOutcomeAction); + } if (RepeatRunButtonLabel != nullptr) { RepeatRunButtonLabel->SetText(FText::FromString(TEXT("Repeat Run"))); diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h index 2d2433a..1d26eca 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h @@ -23,6 +23,15 @@ enum class EHyperTwistCoachDashboardGuidancePreviewLane : uint8 FollowUp }; +enum class EHyperTwistCoachDashboardOutcomeAction : uint8 +{ + None, + RepeatSelectedCase, + RepeatPreviewDeck, + BroadenPreviewDeck, + RedirectCoachGuidance +}; + UCLASS(BlueprintType, Blueprintable) class UNREALHYPERTWIST_API UHyperTwistCoachDashboardWidget : public UHyperTwistTrainingPanelWidget { @@ -137,6 +146,9 @@ public: UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard") bool ContinueCurrentRunFlow(); + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard") + bool ApplyGuidanceOutcomeAction(); + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard") bool StartLiveAttemptTimer(); @@ -206,6 +218,12 @@ protected: UPROPERTY(Transient) TObjectPtr GuidanceOutcomeDetailTextBlock = nullptr; + UPROPERTY(Transient) + TObjectPtr GuidanceOutcomeActionButton = nullptr; + + UPROPERTY(Transient) + TObjectPtr GuidanceOutcomeActionButtonLabel = nullptr; + UPROPERTY(Transient) TObjectPtr CoachHandoffHeaderTextBlock = nullptr; @@ -566,6 +584,9 @@ protected: UFUNCTION() void HandleContinueRunClicked(); + UFUNCTION() + void HandleGuidanceOutcomeActionClicked(); + UFUNCTION() void HandleClearRunClicked(); @@ -632,6 +653,16 @@ private: const FHyperTwistTrainingDeck* GetActiveGuidancePreviewDeck() const; const FHyperTwistCoachBrief* GetActiveGuidancePreviewBrief() const; FHyperTwistTrainingDeck BuildSelectedGuidancePreviewCaseDeck() const; + bool ResolveDisplayedGuidanceComparisonContext( + const FHyperTwistTrainingSessionSummary*& OutDisplayedRunRecap, + const FHyperTwistCoachBrief*& OutDisplayedGuidanceComparisonBrief, + const FHyperTwistTrainingDeck*& OutDisplayedGuidanceComparisonDeck, + FString& OutDisplayedGuidanceComparisonSelectedCaseId, + bool& bOutDisplayedGuidanceComparisonSingleCase + ) const; + EHyperTwistCoachDashboardOutcomeAction DeriveGuidanceOutcomeAction() const; + FString BuildGuidanceOutcomeActionLabel(EHyperTwistCoachDashboardOutcomeAction Action) const; + FHyperTwistTrainingDeck BuildGuidanceOutcomeSelectedCaseDeck() const; void CaptureActiveGuidanceLaunchContext( const FString& SessionId, const FHyperTwistTrainingDeck& Deck,