From 77c8e66539e0a0a7a398aacfcbaa9080dbe1cd60 Mon Sep 17 00:00:00 2001 From: axiomlogicnexus Date: Mon, 27 Apr 2026 20:40:38 +0200 Subject: [PATCH] Surface closure recovery on coach dashboard --- .../HyperTwistCoachDashboardWidget.cpp | 104 ++++++++++++++++++ .../HyperTwistCoachDashboardWidget.h | 9 ++ 2 files changed, 113 insertions(+) diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp index dda24dd..07c161d 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp @@ -2018,6 +2018,24 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt() TEXT("CoachSchedulePolicy"), 2 ); + ClosureRecoveryHeaderTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow( + WidgetTree, + RootLayout, + TEXT("CoachClosureRecoveryHeader"), + 2 + ); + ClosureRecoveryStatusTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow( + WidgetTree, + RootLayout, + TEXT("CoachClosureRecoveryStatus"), + 2 + ); + ClosureRecoveryDetailTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow( + WidgetTree, + RootLayout, + TEXT("CoachClosureRecoveryDetail"), + 8 + ); QueueRecoveryHeaderTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow( WidgetTree, RootLayout, @@ -5301,6 +5319,92 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() : *CachedCoachSchedulePolicySummary.ClosurePolicyLine ))); } + const int32 ClosureKeepHistoryCount = + CachedCoachSchedulePolicySummary.HistoricalFollowUpContinuationCount; + const int32 ClosureSuppressHistoryCount = + CachedCoachSchedulePolicySummary.HistoricalBorderlineFollowUpSuppressionCount; + const bool bHasClosureRecoveryHistory = + ClosureKeepHistoryCount > 0 || ClosureSuppressHistoryCount > 0; + const FHyperTwistCoachSignal* ClosureRecoverySignal = CachedCoachSignals.FindByPredicate( + [](const FHyperTwistCoachSignal& Signal) + { + return Signal.SignalId == TEXT("closure_history_follow_up_continuation") + || Signal.SignalId == TEXT("closure_history_recommended_suppression"); + } + ); + const bool bClosureRecoverySignalPromoted = + ClosureRecoverySignal != nullptr + && CachedCoachBrief.Signals.Num() > 0 + && CachedCoachBrief.Signals[0].SignalId == ClosureRecoverySignal->SignalId; + const FString ClosureGuidanceLaneLabel = + CachedCoachPanelState.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp + ? TEXT("follow-up") + : (CachedCoachPanelState.PreferredGuidanceLane + == EHyperTwistTrainingCoachGuidanceLane::Recommended + ? TEXT("recommended") + : TEXT("none")); + const FString ClosureRecoveryHistoryLabel = + ClosureKeepHistoryCount > ClosureSuppressHistoryCount + ? TEXT("follow-up continuity history is dominant") + : (ClosureSuppressHistoryCount > ClosureKeepHistoryCount + ? TEXT("recommended-side suppression history is dominant") + : (bHasClosureRecoveryHistory + ? TEXT("retained closure history is balanced") + : TEXT("no retained closure history"))); + const FString ClosureRecoveryStatusLine = + ClosureRecoverySignal != nullptr + ? FString::Printf( + TEXT("Closure recovery: %s | promoted: %s | headline: %s | priority: %s | score %.1f"), + *ClosureRecoveryHistoryLabel, + bClosureRecoverySignalPromoted ? TEXT("yes") : TEXT("no"), + ClosureRecoverySignal->Headline.IsEmpty() + ? TEXT("n/a") + : *ClosureRecoverySignal->Headline, + *HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(ClosureRecoverySignal->Priority), + ClosureRecoverySignal->Score + ) + : (bHasClosureRecoveryHistory + ? FString::Printf( + TEXT("Closure recovery: retained history is present without an active closure-history signal | policy: %s"), + CachedCoachSchedulePolicySummary.ClosurePolicyLine.IsEmpty() + ? TEXT("n/a") + : *CachedCoachSchedulePolicySummary.ClosurePolicyLine + ) + : TEXT("Closure recovery: no retained closure-recovery history is recorded yet.")); + const FString ClosureRecoveryDetailLine = FString::Printf( + TEXT("Policy: %s | keep %d | suppress %d | continuation active: %s | borderline suppression active: %s | guidance lane: %s | source: %s%s%s"), + CachedCoachSchedulePolicySummary.ClosurePolicyLine.IsEmpty() + ? TEXT("n/a") + : *CachedCoachSchedulePolicySummary.ClosurePolicyLine, + ClosureKeepHistoryCount, + ClosureSuppressHistoryCount, + CachedCoachSchedulePolicySummary.bFollowUpContinuationFromRecoveryHistory + ? TEXT("yes") + : TEXT("no"), + CachedCoachSchedulePolicySummary.bBorderlineFollowUpSuppressedByRecoveryHistory + ? TEXT("yes") + : TEXT("no"), + *ClosureGuidanceLaneLabel, + CachedCoachPanelState.PreferredGuidanceSourceLabel.IsEmpty() + ? TEXT("n/a") + : *CachedCoachPanelState.PreferredGuidanceSourceLabel, + ClosureRecoverySignal != nullptr ? TEXT(" || Signal detail: ") : TEXT(""), + ClosureRecoverySignal != nullptr + ? *ClosureRecoverySignal->Detail + : TEXT("") + ); + if (ClosureRecoveryHeaderTextBlock != nullptr) + { + ClosureRecoveryHeaderTextBlock->SetText(FText::FromString(TEXT("[Closure Recovery]"))); + } + if (ClosureRecoveryStatusTextBlock != nullptr) + { + ClosureRecoveryStatusTextBlock->SetText(FText::FromString(ClosureRecoveryStatusLine)); + } + if (ClosureRecoveryDetailTextBlock != nullptr) + { + ClosureRecoveryDetailTextBlock->SetText(FText::FromString(ClosureRecoveryDetailLine)); + } if (QueueRecoveryHeaderTextBlock != nullptr) { QueueRecoveryHeaderTextBlock->SetText(FText::FromString(TEXT("[Queue Recovery]"))); diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h index bd8faab..23b9432 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h @@ -584,6 +584,15 @@ protected: UPROPERTY(Transient) TObjectPtr SchedulePolicyTextBlock = nullptr; + UPROPERTY(Transient) + TObjectPtr ClosureRecoveryHeaderTextBlock = nullptr; + + UPROPERTY(Transient) + TObjectPtr ClosureRecoveryStatusTextBlock = nullptr; + + UPROPERTY(Transient) + TObjectPtr ClosureRecoveryDetailTextBlock = nullptr; + UPROPERTY(Transient) TObjectPtr QueueRecoveryHeaderTextBlock = nullptr;