From 1e104702f3a0ac97ed562c5b8557d16ea7f80d2f Mon Sep 17 00:00:00 2001 From: axiomlogicnexus Date: Mon, 27 Apr 2026 18:22:03 +0200 Subject: [PATCH] Apply queue recovery from dashboard --- .../HyperTwistCoachDashboardWidget.cpp | 133 +++++++++++++++++- .../HyperTwistCoachDashboardWidget.h | 12 ++ 2 files changed, 143 insertions(+), 2 deletions(-) diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp index 2ef09f7..d2b692f 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp @@ -252,6 +252,76 @@ bool UHyperTwistCoachDashboardWidget::PromoteFirstDeferredQueueEntry() return bSucceeded; } +bool UHyperTwistCoachDashboardWidget::ApplyQueueRecoveryPosture() +{ + if (bHasLiveAttemptTimer) + { + return false; + } + + const FString DeferredEntryId = FindFirstDeferredQueueEntryId(); + if (!DeferredEntryId.IsEmpty()) + { + return PromoteFirstDeferredQueueEntry(); + } + + const bool bQueueRecoveryMemorySource = + CachedCoachPanelState.PreferredGuidanceSourceLabel.StartsWith(TEXT("coach-memory-queue-")); + const bool bPrefersFollowUp = + CachedCoachPanelState.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp; + const bool bPrefersRecommended = + CachedCoachPanelState.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::Recommended; + + auto StartRunAndRefresh = [this](const FHyperTwistTrainingRunState& RunState) + { + SyncRetainedRunRecap(); + SyncSelectedQueueEntry(); + SyncSelectedAttempt(); + UpdateDashboardPresentation(); + return RunState.Session.IsStructurallyValid(); + }; + + if (bQueueRecoveryMemorySource && CachedCoachPanelState.bCanStartQueuedRun) + { + const bool bQueueMatchesPreference = + (!bPrefersFollowUp && !bPrefersRecommended) + || (bPrefersFollowUp + && CachedCoachPanelState.NextQueueSourceLabel == TEXT("follow-up-brief")) + || (bPrefersRecommended + && CachedCoachPanelState.NextQueueSourceLabel == TEXT("primary-brief")); + if (bQueueMatchesPreference) + { + return StartRunAndRefresh(StartNextQueuedCoachRun(DefaultCoachMaxCases)); + } + } + + if (bPrefersFollowUp + && CachedCoachPanelState.bCanStartCoachFollowUpRun + && CachedCoachPanelState.bHasFollowUpGuidance) + { + return StartRunAndRefresh(StartCoachFollowUpRun(DefaultCoachMaxCases)); + } + if (bPrefersRecommended && CachedCoachPanelState.bCanStartCoachRecommendedRun) + { + return StartRunAndRefresh(StartCoachRecommendedRun(DefaultCoachMaxCases)); + } + if (CachedCoachPanelState.bCanStartQueuedRun) + { + return StartRunAndRefresh(StartNextQueuedCoachRun(DefaultCoachMaxCases)); + } + if (CachedCoachPanelState.bCanStartCoachFollowUpRun && CachedCoachPanelState.bHasFollowUpGuidance) + { + return StartRunAndRefresh(StartCoachFollowUpRun(DefaultCoachMaxCases)); + } + if (CachedCoachPanelState.bCanStartCoachRecommendedRun) + { + return StartRunAndRefresh(StartCoachRecommendedRun(DefaultCoachMaxCases)); + } + + const FHyperTwistTrainingRunState FallbackRunState = ExecutePrimaryCoachAction(); + return FallbackRunState.Session.IsStructurallyValid(); +} + bool UHyperTwistCoachDashboardWidget::SelectPreviousQueueEntry() { if (CachedCoachSessionQueueState.Entries.Num() <= 0) @@ -987,6 +1057,11 @@ void UHyperTwistCoachDashboardWidget::HandlePromoteClicked() PromoteFirstDeferredQueueEntry(); } +void UHyperTwistCoachDashboardWidget::HandleApplyQueueRecoveryClicked() +{ + ApplyQueueRecoveryPosture(); +} + void UHyperTwistCoachDashboardWidget::HandleRepeatRunClicked() { RestartRetainedRunRecapDeck(); @@ -1866,6 +1941,17 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt() PromoteLabelRaw ); PromoteButtonLabel = PromoteLabelRaw; + + UTextBlock* QueueRecoveryActionLabelRaw = nullptr; + QueueRecoveryActionButton = HyperTwistCoachDashboardWidgetInternal::AddButton( + WidgetTree, + ButtonRow, + TEXT("QueueRecoveryActionButton"), + TEXT("QueueRecoveryActionButtonLabel"), + TEXT("Apply Recovery"), + QueueRecoveryActionLabelRaw + ); + QueueRecoveryActionButtonLabel = QueueRecoveryActionLabelRaw; } UHorizontalBox* AttemptInputRow = WidgetTree->ConstructWidget( @@ -2164,6 +2250,10 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt() { PromoteButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandlePromoteClicked); } + if (QueueRecoveryActionButton != nullptr) + { + QueueRecoveryActionButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleApplyQueueRecoveryClicked); + } if (RepeatRunButton != nullptr) { RepeatRunButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleRepeatRunClicked); @@ -3427,10 +3517,14 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() : TEXT("Preference detail: build recommended or follow-up guidance to let the dashboard choose a next surfaced lane."); const bool bQueueRecoveryMemorySource = CachedCoachPanelState.PreferredGuidanceSourceLabel.StartsWith(TEXT("coach-memory-queue-")); + const bool bQueueRecoveryFollowUp = + CachedCoachPanelState.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp; + const bool bQueueRecoveryRecommended = + CachedCoachPanelState.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::Recommended; const FString QueueRecoveryLaneLabel = - CachedCoachPanelState.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp + bQueueRecoveryFollowUp ? TEXT("follow-up") - : (CachedCoachPanelState.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::Recommended + : (bQueueRecoveryRecommended ? TEXT("recommended") : TEXT("none")); const FString QueueRecoverySourceLabel = CachedCoachPanelState.PreferredGuidanceSourceLabel.IsEmpty() @@ -3469,6 +3563,33 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() CachedCoachSchedulePolicySummary.bHasArchiveAwareDeferrals ? TEXT("yes") : TEXT("no"), CachedCoachQueueExecutionSummary.bHasCompletionGap ? TEXT("yes") : TEXT("no") ); + const FString PreferredDeferredRecoveryEntryId = FindFirstDeferredQueueEntryId(); + const bool bCanApplyQueueRecovery = + !bHasLiveAttemptTimer + && (bQueueRecoveryMemorySource || bHasQueueRecoveryFriction) + && (!PreferredDeferredRecoveryEntryId.IsEmpty() + || CachedCoachPanelState.bCanStartQueuedRun + || (bQueueRecoveryFollowUp + && CachedCoachPanelState.bCanStartCoachFollowUpRun + && CachedCoachPanelState.bHasFollowUpGuidance) + || (bQueueRecoveryRecommended + && CachedCoachPanelState.bCanStartCoachRecommendedRun) + || (!bQueueRecoveryMemorySource + && (CachedCoachPanelState.bCanStartCoachFollowUpRun + || CachedCoachPanelState.bCanStartCoachRecommendedRun))); + const FString QueueRecoveryActionLabel = !PreferredDeferredRecoveryEntryId.IsEmpty() + ? (bQueueRecoveryFollowUp + ? TEXT("Promote Follow-Up Recovery") + : (bQueueRecoveryRecommended + ? TEXT("Promote Recommended Recovery") + : TEXT("Promote Recovery"))) + : (bQueueRecoveryFollowUp + ? TEXT("Start Follow-Up Recovery") + : (bQueueRecoveryRecommended + ? TEXT("Start Recommended Recovery") + : (CachedCoachPanelState.bCanStartQueuedRun + ? TEXT("Launch Queue Recovery") + : TEXT("Apply Recovery")))); const FString GuidancePreviewSelectionLine = SelectedGuidancePreviewCase != nullptr && ActivePreviewDeck != nullptr ? FString::Printf( @@ -4582,6 +4703,14 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() { PromoteButton->SetIsEnabled(!bHasLiveAttemptTimer && !FindFirstDeferredQueueEntryId().IsEmpty()); } + if (QueueRecoveryActionButtonLabel != nullptr) + { + QueueRecoveryActionButtonLabel->SetText(FText::FromString(QueueRecoveryActionLabel)); + } + if (QueueRecoveryActionButton != nullptr) + { + QueueRecoveryActionButton->SetIsEnabled(bCanApplyQueueRecovery); + } if (CompleteRunButtonLabel != nullptr) { CompleteRunButtonLabel->SetText(FText::FromString(TEXT("Complete Run"))); diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h index 59399af..add054f 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h @@ -171,6 +171,9 @@ public: UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard") bool PromoteFirstDeferredQueueEntry(); + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard") + bool ApplyQueueRecoveryPosture(); + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard") bool SelectPreviousQueueEntry(); @@ -552,6 +555,12 @@ protected: UPROPERTY(Transient) TObjectPtr PromoteButtonLabel = nullptr; + UPROPERTY(Transient) + TObjectPtr QueueRecoveryActionButton = nullptr; + + UPROPERTY(Transient) + TObjectPtr QueueRecoveryActionButtonLabel = nullptr; + UPROPERTY(Transient) TObjectPtr CompleteRunButton = nullptr; @@ -672,6 +681,9 @@ protected: UFUNCTION() void HandlePromoteClicked(); + UFUNCTION() + void HandleApplyQueueRecoveryClicked(); + UFUNCTION() void HandleRepeatRunClicked();