diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp index 27a9dae..9996bb7 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp @@ -143,6 +143,7 @@ void UHyperTwistCoachDashboardWidget::RefreshCoachDashboardView() SyncRetainedRunRecap(); SyncSelectedQueueEntry(); SyncSelectedAttempt(); + SyncSelectedGuidanceDecisionHistoryEntry(); UpdateDashboardPresentation(); } @@ -611,6 +612,18 @@ bool UHyperTwistCoachDashboardWidget::ApplyGuidanceOutcomeAction() true, DisplayedGuidanceComparisonSelectedCaseId ); + if (RunState.Session.IsStructurallyValid() && RunState.ActiveDeck.IsStructurallyValid()) + { + RecordGuidanceOutcomeDecision( + Action, + EHyperTwistCoachDashboardOutcomeAction::RepeatSelectedCase, + *DisplayedRunRecap, + *DisplayedGuidanceComparisonBrief, + *DisplayedGuidanceComparisonDeck, + DisplayedGuidanceComparisonSelectedCaseId, + bDisplayedGuidanceComparisonSingleCase + ); + } SyncRetainedRunRecap(); SyncSelectedQueueEntry(); SyncSelectedAttempt(); @@ -641,6 +654,18 @@ bool UHyperTwistCoachDashboardWidget::ApplyGuidanceOutcomeAction() false, DisplayedGuidanceComparisonSelectedCaseId ); + if (RunState.Session.IsStructurallyValid() && RunState.ActiveDeck.IsStructurallyValid()) + { + RecordGuidanceOutcomeDecision( + Action, + Action, + *DisplayedRunRecap, + *DisplayedGuidanceComparisonBrief, + *DisplayedGuidanceComparisonDeck, + DisplayedGuidanceComparisonSelectedCaseId, + bDisplayedGuidanceComparisonSingleCase + ); + } SyncRetainedRunRecap(); SyncSelectedQueueEntry(); SyncSelectedAttempt(); @@ -668,6 +693,18 @@ bool UHyperTwistCoachDashboardWidget::ApplyGuidanceOutcomeAction() return false; } + if (RunState.Session.IsStructurallyValid() && RunState.ActiveDeck.IsStructurallyValid()) + { + RecordGuidanceOutcomeDecision( + Action, + EHyperTwistCoachDashboardOutcomeAction::RedirectCoachGuidance, + *DisplayedRunRecap, + *DisplayedGuidanceComparisonBrief, + *DisplayedGuidanceComparisonDeck, + DisplayedGuidanceComparisonSelectedCaseId, + bDisplayedGuidanceComparisonSingleCase + ); + } SyncRetainedRunRecap(); SyncSelectedQueueEntry(); SyncSelectedAttempt(); @@ -680,6 +717,46 @@ bool UHyperTwistCoachDashboardWidget::ApplyGuidanceOutcomeAction() } } +bool UHyperTwistCoachDashboardWidget::SelectPreviousGuidanceDecisionHistoryEntry() +{ + if (GuidanceDecisionHistoryEntries.Num() <= 0) + { + SelectedGuidanceDecisionIndex = INDEX_NONE; + return false; + } + + if (SelectedGuidanceDecisionIndex == INDEX_NONE) + { + SyncSelectedGuidanceDecisionHistoryEntry(); + return SelectedGuidanceDecisionIndex != INDEX_NONE; + } + + SelectedGuidanceDecisionIndex = + (SelectedGuidanceDecisionIndex - 1 + GuidanceDecisionHistoryEntries.Num()) % GuidanceDecisionHistoryEntries.Num(); + UpdateDashboardPresentation(); + return true; +} + +bool UHyperTwistCoachDashboardWidget::SelectNextGuidanceDecisionHistoryEntry() +{ + if (GuidanceDecisionHistoryEntries.Num() <= 0) + { + SelectedGuidanceDecisionIndex = INDEX_NONE; + return false; + } + + if (SelectedGuidanceDecisionIndex == INDEX_NONE) + { + SyncSelectedGuidanceDecisionHistoryEntry(); + return SelectedGuidanceDecisionIndex != INDEX_NONE; + } + + SelectedGuidanceDecisionIndex = + (SelectedGuidanceDecisionIndex + 1) % GuidanceDecisionHistoryEntries.Num(); + UpdateDashboardPresentation(); + return true; +} + bool UHyperTwistCoachDashboardWidget::StartLiveAttemptTimer() { if (!CachedRunState.Session.IsStructurallyValid() || !CachedRunState.CurrentSelection.TrainingCase.IsStructurallyValid()) @@ -950,6 +1027,16 @@ void UHyperTwistCoachDashboardWidget::HandleGuidanceOutcomeActionClicked() ApplyGuidanceOutcomeAction(); } +void UHyperTwistCoachDashboardWidget::HandlePreviousGuidanceDecisionClicked() +{ + SelectPreviousGuidanceDecisionHistoryEntry(); +} + +void UHyperTwistCoachDashboardWidget::HandleNextGuidanceDecisionClicked() +{ + SelectNextGuidanceDecisionHistoryEntry(); +} + void UHyperTwistCoachDashboardWidget::HandleClearRunClicked() { ClearActiveTrainingRun(); @@ -1203,6 +1290,24 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt() TEXT("CoachGuidanceOutcomeDetail"), 8 ); + GuidanceDecisionHistoryHeaderTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow( + WidgetTree, + RootLayout, + TEXT("CoachGuidanceDecisionHistoryHeader"), + 2 + ); + GuidanceDecisionHistoryStatusTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow( + WidgetTree, + RootLayout, + TEXT("CoachGuidanceDecisionHistoryStatus"), + 2 + ); + GuidanceDecisionHistoryDetailTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow( + WidgetTree, + RootLayout, + TEXT("CoachGuidanceDecisionHistoryDetail"), + 8 + ); CoachHandoffHeaderTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow( WidgetTree, RootLayout, @@ -1296,6 +1401,28 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt() ); GuidanceOutcomeActionButtonLabel = GuidanceOutcomeActionLabelRaw; + UTextBlock* PreviousGuidanceDecisionLabelRaw = nullptr; + PreviousGuidanceDecisionButton = HyperTwistCoachDashboardWidgetInternal::AddButton( + WidgetTree, + RunRecapActionRow, + TEXT("PreviousGuidanceDecisionButton"), + TEXT("PreviousGuidanceDecisionButtonLabel"), + TEXT("Prev Decision"), + PreviousGuidanceDecisionLabelRaw + ); + PreviousGuidanceDecisionButtonLabel = PreviousGuidanceDecisionLabelRaw; + + UTextBlock* NextGuidanceDecisionLabelRaw = nullptr; + NextGuidanceDecisionButton = HyperTwistCoachDashboardWidgetInternal::AddButton( + WidgetTree, + RunRecapActionRow, + TEXT("NextGuidanceDecisionButton"), + TEXT("NextGuidanceDecisionButtonLabel"), + TEXT("Next Decision"), + NextGuidanceDecisionLabelRaw + ); + NextGuidanceDecisionButtonLabel = NextGuidanceDecisionLabelRaw; + UTextBlock* DismissRecapLabelRaw = nullptr; DismissRecapButton = HyperTwistCoachDashboardWidgetInternal::AddButton( WidgetTree, @@ -1945,6 +2072,14 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt() { GuidanceOutcomeActionButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleGuidanceOutcomeActionClicked); } + if (PreviousGuidanceDecisionButton != nullptr) + { + PreviousGuidanceDecisionButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandlePreviousGuidanceDecisionClicked); + } + if (NextGuidanceDecisionButton != nullptr) + { + NextGuidanceDecisionButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleNextGuidanceDecisionClicked); + } if (DismissRecapButton != nullptr) { DismissRecapButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleDismissRecapClicked); @@ -2181,6 +2316,21 @@ void UHyperTwistCoachDashboardWidget::SyncSelectedGuidancePreviewCase() } } +void UHyperTwistCoachDashboardWidget::SyncSelectedGuidanceDecisionHistoryEntry() +{ + if (GuidanceDecisionHistoryEntries.Num() <= 0) + { + SelectedGuidanceDecisionIndex = INDEX_NONE; + return; + } + + if (SelectedGuidanceDecisionIndex < 0 + || SelectedGuidanceDecisionIndex >= GuidanceDecisionHistoryEntries.Num()) + { + SelectedGuidanceDecisionIndex = GuidanceDecisionHistoryEntries.Num() - 1; + } +} + const FHyperTwistTrainingDeck* UHyperTwistCoachDashboardWidget::GetActiveGuidancePreviewDeck() const { switch (ActiveGuidancePreviewLane) @@ -2427,6 +2577,54 @@ FHyperTwistTrainingDeck UHyperTwistCoachDashboardWidget::BuildGuidanceOutcomeSel return SingleCaseDeck; } +void UHyperTwistCoachDashboardWidget::RecordGuidanceOutcomeDecision( + const EHyperTwistCoachDashboardOutcomeAction RecommendedAction, + const EHyperTwistCoachDashboardOutcomeAction AppliedAction, + const FHyperTwistTrainingSessionSummary& DisplayedRunRecap, + const FHyperTwistCoachBrief& DisplayedGuidanceComparisonBrief, + const FHyperTwistTrainingDeck& DisplayedGuidanceComparisonDeck, + const FString& DisplayedGuidanceComparisonSelectedCaseId, + const bool bDisplayedGuidanceComparisonSingleCase +) +{ + FHyperTwistCoachDashboardDecisionHistoryEntry Entry; + Entry.RecordedAtUtc = FDateTime::UtcNow().ToIso8601(); + Entry.SourceSessionId = DisplayedRunRecap.TrainingSessionId; + Entry.GuidanceLane = !ActiveGuidanceLaunchSessionId.IsEmpty() + && DisplayedRunRecap.TrainingSessionId == ActiveGuidanceLaunchSessionId + ? ActiveGuidanceLaunchLane + : (!RetainedGuidanceComparisonSessionId.IsEmpty() + && DisplayedRunRecap.TrainingSessionId == RetainedGuidanceComparisonSessionId + ? RetainedGuidanceComparisonLane + : EHyperTwistCoachDashboardGuidancePreviewLane::None); + Entry.bSingleCaseScope = bDisplayedGuidanceComparisonSingleCase; + Entry.SelectedCaseId = DisplayedGuidanceComparisonSelectedCaseId; + Entry.BriefHeadline = DisplayedGuidanceComparisonBrief.Headline.IsEmpty() + ? (DisplayedGuidanceComparisonDeck.Title.IsEmpty() + ? DisplayedGuidanceComparisonDeck.DeckId + : DisplayedGuidanceComparisonDeck.Title) + : DisplayedGuidanceComparisonBrief.Headline; + Entry.RecommendedAction = RecommendedAction; + Entry.RecommendedActionLabel = BuildGuidanceOutcomeActionLabel(RecommendedAction); + Entry.AppliedAction = AppliedAction; + Entry.AppliedActionLabel = BuildGuidanceOutcomeActionLabel(AppliedAction); + Entry.SuccessRate = DisplayedRunRecap.SuccessRate; + Entry.CompletedAttemptCount = DisplayedRunRecap.CompletedAttemptCount; + Entry.TotalMistakes = DisplayedRunRecap.TotalMistakes; + + if (Entry.IsStructurallyValid()) + { + GuidanceDecisionHistoryEntries.Add(Entry); + constexpr int32 MaxRetainedDecisionHistoryEntries = 16; + if (GuidanceDecisionHistoryEntries.Num() > MaxRetainedDecisionHistoryEntries) + { + const int32 ExcessCount = GuidanceDecisionHistoryEntries.Num() - MaxRetainedDecisionHistoryEntries; + GuidanceDecisionHistoryEntries.RemoveAt(0, ExcessCount, EAllowShrinking::No); + } + SelectedGuidanceDecisionIndex = GuidanceDecisionHistoryEntries.Num() - 1; + } +} + void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() { const bool bHasActiveRun = CachedRunState.Session.IsStructurallyValid() @@ -3055,6 +3253,45 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() && (GuidanceOutcomeAction != EHyperTwistCoachDashboardOutcomeAction::RedirectCoachGuidance || bCanRedirectGuidanceOutcome); const FString GuidanceOutcomeActionLabel = BuildGuidanceOutcomeActionLabel(GuidanceOutcomeAction); + const FHyperTwistCoachDashboardDecisionHistoryEntry* SelectedGuidanceDecisionEntry = + SelectedGuidanceDecisionIndex >= 0 + && SelectedGuidanceDecisionIndex < GuidanceDecisionHistoryEntries.Num() + ? &GuidanceDecisionHistoryEntries[SelectedGuidanceDecisionIndex] + : nullptr; + const FString GuidanceDecisionLaneLabel = + SelectedGuidanceDecisionEntry != nullptr + ? (SelectedGuidanceDecisionEntry->GuidanceLane == EHyperTwistCoachDashboardGuidancePreviewLane::Recommended + ? TEXT("recommended") + : (SelectedGuidanceDecisionEntry->GuidanceLane == EHyperTwistCoachDashboardGuidancePreviewLane::FollowUp + ? TEXT("follow-up") + : TEXT("none"))) + : TEXT("none"); + const FString GuidanceDecisionHistoryStatusLine = + SelectedGuidanceDecisionEntry != nullptr + ? FString::Printf( + TEXT("Decision history: %d entries | selected %d/%d | Lane: %s | Scope: %s | Recorded: %s"), + GuidanceDecisionHistoryEntries.Num(), + SelectedGuidanceDecisionIndex + 1, + GuidanceDecisionHistoryEntries.Num(), + *GuidanceDecisionLaneLabel, + SelectedGuidanceDecisionEntry->bSingleCaseScope ? TEXT("selected case") : TEXT("preview deck"), + *SelectedGuidanceDecisionEntry->RecordedAtUtc + ) + : TEXT("Decision history: no applied guidance-outcome decisions recorded yet."); + const FString GuidanceDecisionHistoryDetailLine = + SelectedGuidanceDecisionEntry != nullptr + ? FString::Printf( + TEXT("Session: %s | Brief: %s | Recommended: %s | Applied: %s | Selected case: %s | Success rate: %.2f | Completed attempts: %d | Mistakes: %d"), + *SelectedGuidanceDecisionEntry->SourceSessionId, + SelectedGuidanceDecisionEntry->BriefHeadline.IsEmpty() ? TEXT("n/a") : *SelectedGuidanceDecisionEntry->BriefHeadline, + *SelectedGuidanceDecisionEntry->RecommendedActionLabel, + *SelectedGuidanceDecisionEntry->AppliedActionLabel, + SelectedGuidanceDecisionEntry->SelectedCaseId.IsEmpty() ? TEXT("n/a") : *SelectedGuidanceDecisionEntry->SelectedCaseId, + SelectedGuidanceDecisionEntry->SuccessRate, + SelectedGuidanceDecisionEntry->CompletedAttemptCount, + SelectedGuidanceDecisionEntry->TotalMistakes + ) + : TEXT("Decision detail: apply a guidance outcome from the dashboard to retain the authored choice history."); const bool bCanStartPreviewDeck = !bHasLiveAttemptTimer && !bHasActiveRun && ActivePreviewDeck != nullptr && ActivePreviewDeck->IsStructurallyValid(); const bool bCanStartPreviewCase = bCanStartPreviewDeck && SelectedGuidancePreviewCase != nullptr; @@ -3203,6 +3440,34 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() { GuidanceOutcomeActionButton->SetIsEnabled(bCanApplyGuidanceOutcomeAction); } + if (GuidanceDecisionHistoryHeaderTextBlock != nullptr) + { + GuidanceDecisionHistoryHeaderTextBlock->SetText(FText::FromString(TEXT("[Decision History]"))); + } + if (GuidanceDecisionHistoryStatusTextBlock != nullptr) + { + GuidanceDecisionHistoryStatusTextBlock->SetText(FText::FromString(GuidanceDecisionHistoryStatusLine)); + } + if (GuidanceDecisionHistoryDetailTextBlock != nullptr) + { + GuidanceDecisionHistoryDetailTextBlock->SetText(FText::FromString(GuidanceDecisionHistoryDetailLine)); + } + if (PreviousGuidanceDecisionButtonLabel != nullptr) + { + PreviousGuidanceDecisionButtonLabel->SetText(FText::FromString(TEXT("Prev Decision"))); + } + if (PreviousGuidanceDecisionButton != nullptr) + { + PreviousGuidanceDecisionButton->SetIsEnabled(GuidanceDecisionHistoryEntries.Num() > 1); + } + if (NextGuidanceDecisionButtonLabel != nullptr) + { + NextGuidanceDecisionButtonLabel->SetText(FText::FromString(TEXT("Next Decision"))); + } + if (NextGuidanceDecisionButton != nullptr) + { + NextGuidanceDecisionButton->SetIsEnabled(GuidanceDecisionHistoryEntries.Num() > 1); + } 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 1d26eca..6890908 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h @@ -32,6 +32,34 @@ enum class EHyperTwistCoachDashboardOutcomeAction : uint8 RedirectCoachGuidance }; +struct FHyperTwistCoachDashboardDecisionHistoryEntry +{ + FString RecordedAtUtc; + FString SourceSessionId; + EHyperTwistCoachDashboardGuidancePreviewLane GuidanceLane = + EHyperTwistCoachDashboardGuidancePreviewLane::None; + bool bSingleCaseScope = false; + FString SelectedCaseId; + FString BriefHeadline; + FString RecommendedActionLabel; + EHyperTwistCoachDashboardOutcomeAction RecommendedAction = + EHyperTwistCoachDashboardOutcomeAction::None; + FString AppliedActionLabel; + EHyperTwistCoachDashboardOutcomeAction AppliedAction = + EHyperTwistCoachDashboardOutcomeAction::None; + float SuccessRate = 0.0f; + int32 CompletedAttemptCount = 0; + int32 TotalMistakes = 0; + + bool IsStructurallyValid() const + { + return !RecordedAtUtc.IsEmpty() + && !SourceSessionId.IsEmpty() + && RecommendedAction != EHyperTwistCoachDashboardOutcomeAction::None + && AppliedAction != EHyperTwistCoachDashboardOutcomeAction::None; + } +}; + UCLASS(BlueprintType, Blueprintable) class UNREALHYPERTWIST_API UHyperTwistCoachDashboardWidget : public UHyperTwistTrainingPanelWidget { @@ -71,6 +99,9 @@ public: UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training|Coach|Dashboard") int32 SelectedAttemptIndex = INDEX_NONE; + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training|Coach|Dashboard") + int32 SelectedGuidanceDecisionIndex = INDEX_NONE; + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training|Coach|Dashboard") bool bHasLiveAttemptTimer = false; @@ -149,6 +180,12 @@ public: UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard") bool ApplyGuidanceOutcomeAction(); + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard") + bool SelectPreviousGuidanceDecisionHistoryEntry(); + + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard") + bool SelectNextGuidanceDecisionHistoryEntry(); + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard") bool StartLiveAttemptTimer(); @@ -224,6 +261,27 @@ protected: UPROPERTY(Transient) TObjectPtr GuidanceOutcomeActionButtonLabel = nullptr; + UPROPERTY(Transient) + TObjectPtr GuidanceDecisionHistoryHeaderTextBlock = nullptr; + + UPROPERTY(Transient) + TObjectPtr GuidanceDecisionHistoryStatusTextBlock = nullptr; + + UPROPERTY(Transient) + TObjectPtr GuidanceDecisionHistoryDetailTextBlock = nullptr; + + UPROPERTY(Transient) + TObjectPtr PreviousGuidanceDecisionButton = nullptr; + + UPROPERTY(Transient) + TObjectPtr PreviousGuidanceDecisionButtonLabel = nullptr; + + UPROPERTY(Transient) + TObjectPtr NextGuidanceDecisionButton = nullptr; + + UPROPERTY(Transient) + TObjectPtr NextGuidanceDecisionButtonLabel = nullptr; + UPROPERTY(Transient) TObjectPtr CoachHandoffHeaderTextBlock = nullptr; @@ -587,6 +645,12 @@ protected: UFUNCTION() void HandleGuidanceOutcomeActionClicked(); + UFUNCTION() + void HandlePreviousGuidanceDecisionClicked(); + + UFUNCTION() + void HandleNextGuidanceDecisionClicked(); + UFUNCTION() void HandleClearRunClicked(); @@ -641,6 +705,7 @@ private: void SyncSelectedAttempt(); void SyncRetainedRunRecap(); void SyncSelectedGuidancePreviewCase(); + void SyncSelectedGuidanceDecisionHistoryEntry(); void UpdateDashboardPresentation(); void RefreshLiveAttemptClock(); FString BuildDefaultDeferredUntilUtc() const; @@ -663,6 +728,15 @@ private: EHyperTwistCoachDashboardOutcomeAction DeriveGuidanceOutcomeAction() const; FString BuildGuidanceOutcomeActionLabel(EHyperTwistCoachDashboardOutcomeAction Action) const; FHyperTwistTrainingDeck BuildGuidanceOutcomeSelectedCaseDeck() const; + void RecordGuidanceOutcomeDecision( + EHyperTwistCoachDashboardOutcomeAction RecommendedAction, + EHyperTwistCoachDashboardOutcomeAction AppliedAction, + const FHyperTwistTrainingSessionSummary& DisplayedRunRecap, + const FHyperTwistCoachBrief& DisplayedGuidanceComparisonBrief, + const FHyperTwistTrainingDeck& DisplayedGuidanceComparisonDeck, + const FString& DisplayedGuidanceComparisonSelectedCaseId, + bool bDisplayedGuidanceComparisonSingleCase + ); void CaptureActiveGuidanceLaunchContext( const FString& SessionId, const FHyperTwistTrainingDeck& Deck, @@ -699,6 +773,7 @@ private: FHyperTwistCoachBrief RetainedGuidanceComparisonBrief; FHyperTwistTrainingDeck RetainedGuidanceComparisonDeck; FString RetainedGuidanceComparisonSelectedCaseId; + TArray GuidanceDecisionHistoryEntries; EHyperTwistCoachDashboardGuidancePreviewLane ActiveGuidancePreviewLane = EHyperTwistCoachDashboardGuidancePreviewLane::None; int32 SelectedGuidancePreviewCaseIndex = INDEX_NONE;