diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp index c7daeaa..b32fd5b 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp @@ -108,6 +108,37 @@ namespace HyperTwistCoachDashboardWidgetInternal } } + EMethodDrillRecoveryMemoryBias ResolveMethodDrillRecoveryMemoryBias( + const int32 HistoryCount, + const int32 StrongCount, + const int32 WeakCount, + const float AverageOutcomeScore + ) + { + if (HistoryCount >= 2 && StrongCount >= WeakCount && AverageOutcomeScore >= 55.0f) + { + return EMethodDrillRecoveryMemoryBias::Strong; + } + if (HistoryCount >= 2 && WeakCount > StrongCount && AverageOutcomeScore <= 45.0f) + { + return EMethodDrillRecoveryMemoryBias::Weak; + } + return EMethodDrillRecoveryMemoryBias::None; + } + + FString DescribeMethodDrillRecoveryMemoryBias(const EMethodDrillRecoveryMemoryBias Bias) + { + switch (Bias) + { + case EMethodDrillRecoveryMemoryBias::Strong: + return TEXT("strong retained drill-recovery continuation"); + case EMethodDrillRecoveryMemoryBias::Weak: + return TEXT("weak retained drill-recovery redirect"); + default: + return TEXT("balanced drill-recovery posture"); + } + } + bool IsDashboardHandoffGuidanceSource(const FString& SourceLabel) { return SourceLabel.StartsWith(TEXT("coach-memory-dashboard-")); @@ -6716,22 +6747,32 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() bool bHandoffRecommendationUsedDeferredFirstSuppressionSignal = false; bool bHandoffRecommendationUsedMethodDrillWeighting = false; bool bHandoffRecommendationUsedMethodDrillRecoveryMemory = false; + const HyperTwistCoachDashboardWidgetInternal::EMethodDrillRecoveryMemoryBias + CurrentMethodDrillRecoveryMemoryBias = + HyperTwistCoachDashboardWidgetInternal::ResolveMethodDrillRecoveryMemoryBias( + CachedCoachPanelState.MethodDrillRecoveryHistoryCount, + CachedCoachPanelState.StrongMethodDrillRecoveryHistoryCount, + CachedCoachPanelState.WeakMethodDrillRecoveryHistoryCount, + CachedCoachPanelState.MethodDrillRecoveryAverageOutcomeScore + ); const bool bStrongMethodDrillRecoveryMemory = - CachedCoachPanelState.MethodDrillRecoveryHistoryCount >= 2 - && CachedCoachPanelState.StrongMethodDrillRecoveryHistoryCount - >= CachedCoachPanelState.WeakMethodDrillRecoveryHistoryCount - && CachedCoachPanelState.MethodDrillRecoveryAverageOutcomeScore >= 55.0f; + CurrentMethodDrillRecoveryMemoryBias + == HyperTwistCoachDashboardWidgetInternal::EMethodDrillRecoveryMemoryBias::Strong; const bool bWeakMethodDrillRecoveryMemory = - CachedCoachPanelState.MethodDrillRecoveryHistoryCount >= 2 - && CachedCoachPanelState.WeakMethodDrillRecoveryHistoryCount - > CachedCoachPanelState.StrongMethodDrillRecoveryHistoryCount - && CachedCoachPanelState.MethodDrillRecoveryAverageOutcomeScore <= 45.0f; + CurrentMethodDrillRecoveryMemoryBias + == HyperTwistCoachDashboardWidgetInternal::EMethodDrillRecoveryMemoryBias::Weak; const HyperTwistCoachDashboardWidgetInternal::EMethodDrillRecoveryMemoryBias RetainedMethodDrillRecoveryMemoryHandoffBias = HyperTwistCoachDashboardWidgetInternal::DeriveRetainedMethodDrillRecoveryMemoryBias( MethodDrillRecoveryMemoryHistoryEntries, false ); + const HyperTwistCoachDashboardWidgetInternal::EMethodDrillRecoveryMemoryBias + EffectiveMethodDrillRecoveryMemoryHandoffBias = + RetainedMethodDrillRecoveryMemoryHandoffBias + != HyperTwistCoachDashboardWidgetInternal::EMethodDrillRecoveryMemoryBias::None + ? RetainedMethodDrillRecoveryMemoryHandoffBias + : CurrentMethodDrillRecoveryMemoryBias; if (!bCanUseCoachHandoff) { CoachHandoffRecommendationStatusLine = @@ -7109,6 +7150,15 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() TEXT("Recommendation: no coach-directed handoff is currently ready."); break; } + if (EffectiveMethodDrillRecoveryMemoryHandoffBias + != HyperTwistCoachDashboardWidgetInternal::EMethodDrillRecoveryMemoryBias::None) + { + CoachHandoffRecommendationStatusLine += FString::Printf( + TEXT(" | Posture: %s"), + *HyperTwistCoachDashboardWidgetInternal::DescribeMethodDrillRecoveryMemoryBias( + EffectiveMethodDrillRecoveryMemoryHandoffBias) + ); + } switch (HandoffRecommendationKind) { case HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::Queue: @@ -7211,6 +7261,11 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() CachedCoachPanelState.MethodDrillRecoveryHistoryCount, CachedCoachPanelState.MethodDrillRecoveryAverageOutcomeScore ); + CoachHandoffRecommendationDetailLine += FString::Printf( + TEXT(" | retained drill-recovery stance: %s"), + *HyperTwistCoachDashboardWidgetInternal::DescribeMethodDrillRecoveryMemoryBias( + EffectiveMethodDrillRecoveryMemoryHandoffBias) + ); if (bCanLaunchMethodDrillFollowUp && MethodDrillFollowUpWeighting.IsStructurallyValid()) { CoachHandoffRecommendationDetailLine += MethodDrillFollowUpWeighting.bPreferDrillFollowUp @@ -7464,6 +7519,14 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() : HyperTwistCoachDashboardWidgetInternal::DescribeCoachMemorySourceLabel( CachedCoachPanelState.PreferredQueueRecoveryActionSourceLabel); const FHyperTwistCoachDashboardQueueRecoveryWeighting QueueRecoveryWeighting = BuildQueueRecoveryWeighting(); + const HyperTwistCoachDashboardWidgetInternal::EMethodDrillRecoveryMemoryBias + QueueRecoveryMethodDrillMemoryBias = + HyperTwistCoachDashboardWidgetInternal::ResolveMethodDrillRecoveryMemoryBias( + QueueRecoveryWeighting.MethodDrillUpstreamHistoryCount, + QueueRecoveryWeighting.MethodDrillUpstreamStrongCount, + QueueRecoveryWeighting.MethodDrillUpstreamWeakCount, + QueueRecoveryWeighting.MethodDrillUpstreamAverageOutcomeScore + ); const FHyperTwistCoachSignal* QueueDeferredFirstSuppressionSignal = HyperTwistCoachDashboardWidgetInternal::FindCoachSignalById( CachedCoachSignals, @@ -7525,7 +7588,7 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() || CachedCoachQueueExecutionSummary.bHasCompletionGap; const FString QueueRecoveryStatusLine = bQueueRecoveryMemorySource ? FString::Printf( - TEXT("Recovery: %s lane | Source: %s | Action: %s | Mode: %s | Selection: %s | Upstream: %s | Learned: %s%s%s"), + TEXT("Recovery: %s lane | Source: %s | Action: %s | Mode: %s | Selection: %s | Upstream: %s | Learned: %s | Stance: %s%s%s"), *QueueRecoveryLaneLabel, *QueueRecoverySourceLabel, CachedCoachPanelState.PrimaryActionLabel.IsEmpty() @@ -7535,6 +7598,8 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() *HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(CachedCoachPanelState.SuggestedSelectionPolicy), *QueueRecoveryUpstreamActionLabel, *QueueRecoveryWeightingLabel, + *HyperTwistCoachDashboardWidgetInternal::DescribeMethodDrillRecoveryMemoryBias( + QueueRecoveryMethodDrillMemoryBias), bHasDeferredFirstQueueSuppressionSignal && bQueueRecoveryFollowUp ? TEXT(" | Deferred-first suppression is keeping narrowed follow-up recovery primary.") : TEXT(""), @@ -7542,18 +7607,22 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() ) : (bHasQueueRecoveryFriction ? FString::Printf( - TEXT("Recovery: queue friction is present, but no queue-memory recovery override is currently active | Upstream: %s | Learned: %s%s%s"), + TEXT("Recovery: queue friction is present, but no queue-memory recovery override is currently active | Upstream: %s | Learned: %s | Stance: %s%s%s"), *QueueRecoveryUpstreamActionLabel, *QueueRecoveryWeightingLabel, + *HyperTwistCoachDashboardWidgetInternal::DescribeMethodDrillRecoveryMemoryBias( + QueueRecoveryMethodDrillMemoryBias), bHasDeferredFirstQueueSuppressionSignal ? TEXT(" | Deferred-first suppression is still visible in the live coaching signal.") : TEXT(""), *QueueRecoveryMethodDrillStatus ) : FString::Printf( - TEXT("Recovery: no queue-memory recovery override is currently active | Upstream: %s | Learned: %s%s%s"), + TEXT("Recovery: no queue-memory recovery override is currently active | Upstream: %s | Learned: %s | Stance: %s%s%s"), *QueueRecoveryUpstreamActionLabel, *QueueRecoveryWeightingLabel, + *HyperTwistCoachDashboardWidgetInternal::DescribeMethodDrillRecoveryMemoryBias( + QueueRecoveryMethodDrillMemoryBias), bHasDeferredFirstQueueSuppressionSignal ? TEXT(" | Deferred-first suppression is still visible in the live coaching signal.") : TEXT(""), @@ -7569,7 +7638,7 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() : *QueueDeferredFirstSuppressionSignal->Detail) : TEXT(""); const FString QueueRecoveryDetailLine = FString::Printf( - TEXT("Deferred events %d | Promoted %d | Manual %d | Pressure %.2f | Dominant reason: %s | Follow-up deferrals: %s | Archive-aware deferrals: %s | Completion gap: %s | Upstream action source: %s%s || Learned promote %d/%d reduced, %d failed, avg delta %.2f | launch %d/%d reduced, %d failed, avg delta %.2f | deferred-first promote/launch reduced %d/%d over %d/%d | drill-driven outcome promote/launch reduced %d/%d over %d/%d | drill-driven bias promote/launch %d/%d | upstream drill recovery strong/weak/history %d/%d/%d avg %.1f"), + TEXT("Deferred events %d | Promoted %d | Manual %d | Pressure %.2f | Dominant reason: %s | Follow-up deferrals: %s | Archive-aware deferrals: %s | Completion gap: %s | Upstream action source: %s%s || Learned promote %d/%d reduced, %d failed, avg delta %.2f | launch %d/%d reduced, %d failed, avg delta %.2f | deferred-first promote/launch reduced %d/%d over %d/%d | drill-driven outcome promote/launch reduced %d/%d over %d/%d | drill-driven bias promote/launch %d/%d | upstream drill recovery strong/weak/history %d/%d/%d avg %.1f | retained drill-recovery stance: %s"), CachedCoachQueueExecutionSummary.DeferredEventCount, CachedCoachQueueExecutionSummary.PromotedEventCount, CachedCoachQueueExecutionSummary.ManualRescheduleEventCount, @@ -7603,7 +7672,9 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() QueueRecoveryWeighting.MethodDrillUpstreamStrongCount, QueueRecoveryWeighting.MethodDrillUpstreamWeakCount, QueueRecoveryWeighting.MethodDrillUpstreamHistoryCount, - QueueRecoveryWeighting.MethodDrillUpstreamAverageOutcomeScore + QueueRecoveryWeighting.MethodDrillUpstreamAverageOutcomeScore, + *HyperTwistCoachDashboardWidgetInternal::DescribeMethodDrillRecoveryMemoryBias( + QueueRecoveryMethodDrillMemoryBias) ); RecordMethodDrillRecoveryMemoryHistorySnapshot( QueueRecoveryWeighting, @@ -7814,29 +7885,42 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() : (SelectedMethodDrillRecoveryMemoryHistoryEntry->bWeakPressureDominant ? TEXT("weak") : TEXT("balanced"))); + const HyperTwistCoachDashboardWidgetInternal::EMethodDrillRecoveryMemoryBias + SelectedMethodDrillRecoveryMemoryHistoryBias = + SelectedMethodDrillRecoveryMemoryHistoryEntry == nullptr + ? HyperTwistCoachDashboardWidgetInternal::EMethodDrillRecoveryMemoryBias::None + : (SelectedMethodDrillRecoveryMemoryHistoryEntry->bStrongPressureDominant + ? HyperTwistCoachDashboardWidgetInternal::EMethodDrillRecoveryMemoryBias::Strong + : (SelectedMethodDrillRecoveryMemoryHistoryEntry->bWeakPressureDominant + ? HyperTwistCoachDashboardWidgetInternal::EMethodDrillRecoveryMemoryBias::Weak + : HyperTwistCoachDashboardWidgetInternal::EMethodDrillRecoveryMemoryBias::None)); const FString MethodDrillRecoveryMemoryHistoryStatusLine = SelectedMethodDrillRecoveryMemoryHistoryEntry != nullptr && SelectedMethodDrillRecoveryMemoryHistoryEntry->IsStructurallyValid() ? FString::Printf( - TEXT("Drill recovery memory history: %d entries | selected %d/%d | %s | %s | Recorded: %s"), + TEXT("Drill recovery memory history: %d entries | selected %d/%d | %s | %s | Stance: %s | Recorded: %s"), MethodDrillRecoveryMemoryHistoryEntries.Num(), SelectedMethodDrillRecoveryMemoryHistoryIndex + 1, MethodDrillRecoveryMemoryHistoryEntries.Num(), *MethodDrillRecoveryMemoryActionLabel, *SelectedMethodDrillRecoveryMemoryHistoryEntry->RecommendedHandoffKindLabel, + *HyperTwistCoachDashboardWidgetInternal::DescribeMethodDrillRecoveryMemoryBias( + SelectedMethodDrillRecoveryMemoryHistoryBias), *SelectedMethodDrillRecoveryMemoryHistoryEntry->RecordedAtUtc) : TEXT("Drill recovery memory history: no retained upstream drill-recovery memory shifts recorded yet."); const FString MethodDrillRecoveryMemoryHistoryDetailLine = SelectedMethodDrillRecoveryMemoryHistoryEntry != nullptr && SelectedMethodDrillRecoveryMemoryHistoryEntry->IsStructurallyValid() ? FString::Printf( - TEXT("Source: %s | Strong/weak/history %d/%d/%d avg %.1f | Dominant pressure: %s | Recovery memory tipped: %s | Handoff memory tipped: %s | Recovery posture: %s | Handoff posture: %s"), + TEXT("Source: %s | Strong/weak/history %d/%d/%d avg %.1f | Dominant pressure: %s | Stance: %s | Recovery memory tipped: %s | Handoff memory tipped: %s | Recovery posture: %s | Handoff posture: %s"), *MethodDrillRecoveryMemorySourceLine, SelectedMethodDrillRecoveryMemoryHistoryEntry->StrongMethodDrillRecoveryHistoryCount, SelectedMethodDrillRecoveryMemoryHistoryEntry->WeakMethodDrillRecoveryHistoryCount, SelectedMethodDrillRecoveryMemoryHistoryEntry->MethodDrillRecoveryHistoryCount, SelectedMethodDrillRecoveryMemoryHistoryEntry->MethodDrillRecoveryAverageOutcomeScore, *MethodDrillRecoveryMemoryDominantPressureLine, + *HyperTwistCoachDashboardWidgetInternal::DescribeMethodDrillRecoveryMemoryBias( + SelectedMethodDrillRecoveryMemoryHistoryBias), SelectedMethodDrillRecoveryMemoryHistoryEntry->bUsedMethodDrillRecoveryMemoryForRecovery ? TEXT("yes") : TEXT("no"),