diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp index 7c50816..d727dc6 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp @@ -83,6 +83,25 @@ namespace HyperTwistCoachDashboardWidgetInternal bool bHasSignal = false; }; + struct FMethodDrillRecoveryPacketDashboardBias + { + int32 QueueEntryHistoryCount = 0; + int32 FollowUpPacketHistoryCount = 0; + int32 RecommendedBriefHistoryCount = 0; + int32 StrongQueueEntryCount = 0; + int32 StrongFollowUpPacketCount = 0; + int32 WeakRecommendedBriefCount = 0; + int32 LaunchRecoveryBias = 0; + int32 PromoteRecoveryBias = 0; + int32 QueueHandoffBias = 0; + int32 FollowUpHandoffBias = 0; + int32 RecommendedHandoffBias = 0; + EHandoffRecommendationKind PreferredHandoffKind = EHandoffRecommendationKind::None; + bool bHasStrongContinuationSignal = false; + bool bHasWeakRedirectSignal = false; + bool bHasSignal = false; + }; + template FString EnumDisplayName(const TEnum Value) { @@ -1083,6 +1102,129 @@ namespace HyperTwistCoachDashboardWidgetInternal + Bias.WeakRetainedMethodDrillRecommendedRecommendationCount * 2; } + FMethodDrillRecoveryPacketDashboardBias DeriveMethodDrillRecoveryPacketDashboardBias( + const TArray& Entries, + const int32 MaxEntriesToInspect = 8) + { + FMethodDrillRecoveryPacketDashboardBias Bias; + if (Entries.Num() <= 0) + { + return Bias; + } + + const int32 StartIndex = FMath::Max(0, Entries.Num() - MaxEntriesToInspect); + for (int32 Index = StartIndex; Index < Entries.Num(); ++Index) + { + const FHyperTwistCoachDashboardMethodDrillRecoveryMemoryHistoryEntry& Entry = Entries[Index]; + if (!Entry.IsStructurallyValid() + || Entry.GeneratedGuidancePacketKindLabel.IsEmpty() + || (!Entry.bUsedMethodDrillRecoveryMemoryForRecovery + && !Entry.bUsedMethodDrillRecoveryMemoryForHandoff)) + { + continue; + } + + int32 HistoryWeight = 1; + if (Entry.MethodDrillRecoveryAverageOutcomeScore >= 70.0f) + { + ++HistoryWeight; + } + if (Entry.MethodDrillRecoveryHistoryCount >= 4) + { + ++HistoryWeight; + } + HistoryWeight = FMath::Clamp(HistoryWeight, 1, 3); + + const bool bStrongPacket = + Entry.bGeneratedPacketUsedStrongRetainedMethodDrillRecoveryPosture + || Entry.bStrongPressureDominant; + const bool bWeakPacket = + Entry.bGeneratedPacketUsedWeakRetainedMethodDrillRecoveryPosture + || Entry.bWeakPressureDominant; + const bool bGenericPacket = + Entry.bGeneratedPacketUsedGenericMethodDrillRecoveryMemory + && !bStrongPacket + && !bWeakPacket; + + if (Entry.GeneratedGuidancePacketKindLabel.Equals(TEXT("queue-entry"), ESearchCase::IgnoreCase)) + { + ++Bias.QueueEntryHistoryCount; + if (bStrongPacket) + { + Bias.StrongQueueEntryCount += HistoryWeight; + Bias.QueueHandoffBias += HistoryWeight * 2; + Bias.LaunchRecoveryBias += HistoryWeight; + Bias.bHasStrongContinuationSignal = true; + Bias.bHasSignal = true; + } + else if (bGenericPacket) + { + Bias.QueueHandoffBias += HistoryWeight; + Bias.LaunchRecoveryBias += HistoryWeight; + Bias.bHasSignal = true; + } + } + else if ( + Entry.GeneratedGuidancePacketKindLabel.Equals( + TEXT("follow-up-packet"), + ESearchCase::IgnoreCase)) + { + ++Bias.FollowUpPacketHistoryCount; + if (bStrongPacket) + { + Bias.StrongFollowUpPacketCount += HistoryWeight; + Bias.FollowUpHandoffBias += HistoryWeight * 3; + Bias.LaunchRecoveryBias += HistoryWeight * 2; + Bias.bHasStrongContinuationSignal = true; + Bias.bHasSignal = true; + } + else if (bGenericPacket) + { + Bias.FollowUpHandoffBias += HistoryWeight; + Bias.LaunchRecoveryBias += HistoryWeight; + Bias.bHasSignal = true; + } + } + else if ( + Entry.GeneratedGuidancePacketKindLabel.Equals( + TEXT("recommended-brief"), + ESearchCase::IgnoreCase)) + { + ++Bias.RecommendedBriefHistoryCount; + if (bWeakPacket) + { + Bias.WeakRecommendedBriefCount += HistoryWeight; + Bias.RecommendedHandoffBias += HistoryWeight * 3; + Bias.PromoteRecoveryBias += HistoryWeight * 2; + Bias.bHasWeakRedirectSignal = true; + Bias.bHasSignal = true; + } + else if (bGenericPacket) + { + Bias.RecommendedHandoffBias += HistoryWeight; + Bias.PromoteRecoveryBias += HistoryWeight; + Bias.bHasSignal = true; + } + } + } + + int32 BestScore = 0; + const auto ConsiderKind = [&BestScore, &Bias]( + const EHandoffRecommendationKind Kind, + const int32 Score) + { + if (Score > BestScore) + { + BestScore = Score; + Bias.PreferredHandoffKind = Kind; + } + }; + ConsiderKind(EHandoffRecommendationKind::Queue, Bias.QueueHandoffBias); + ConsiderKind(EHandoffRecommendationKind::FollowUp, Bias.FollowUpHandoffBias); + ConsiderKind(EHandoffRecommendationKind::Recommended, Bias.RecommendedHandoffBias); + return Bias; + } + void CollectMethodDrillRecoveryOutcomeIndices( const TArray& Outcomes, TArray& OutIndices) @@ -5402,6 +5544,11 @@ FHyperTwistCoachDashboardQueueRecoveryWeighting UHyperTwistCoachDashboardWidget: HyperTwistCoachDashboardWidgetInternal::DeriveHandoffHistoryDashboardBias( HandoffHistoryEntries ); + const HyperTwistCoachDashboardWidgetInternal::FMethodDrillRecoveryPacketDashboardBias + MethodDrillRecoveryPacketBias = + HyperTwistCoachDashboardWidgetInternal::DeriveMethodDrillRecoveryPacketDashboardBias( + MethodDrillRecoveryMemoryHistoryEntries + ); auto ConsumeOutcome = [ &Weighting, @@ -5577,6 +5724,11 @@ FHyperTwistCoachDashboardQueueRecoveryWeighting UHyperTwistCoachDashboardWidget: ); Weighting.bHasMethodDrillMemorySignal = true; } + if (MethodDrillRecoveryPacketBias.bHasSignal) + { + Weighting.MethodDrillLaunchBiasCount += MethodDrillRecoveryPacketBias.LaunchRecoveryBias; + Weighting.MethodDrillPromoteBiasCount += MethodDrillRecoveryPacketBias.PromoteRecoveryBias; + } if (CachedCoachPanelState.PreferredGuidanceSourceLabel == TEXT("coach-memory-method-drill-follow-up-strong")) { @@ -5592,7 +5744,8 @@ FHyperTwistCoachDashboardQueueRecoveryWeighting UHyperTwistCoachDashboardWidget: Weighting.MethodDrillLaunchBiasCount > 0 || Weighting.MethodDrillPromoteBiasCount > 0 || Weighting.bHasMethodDrillOutcomeSignal - || Weighting.bHasMethodDrillMemorySignal; + || Weighting.bHasMethodDrillMemorySignal + || MethodDrillRecoveryPacketBias.bHasSignal; const bool bHasDirectOutcomeSignal = Weighting.PromoteCount > 0 || Weighting.LaunchCount > 0; Weighting.bHasSignal = bHasDirectOutcomeSignal || Weighting.bHasMethodDrillSignal; @@ -7171,6 +7324,11 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() MethodDrillRecoveryMemoryHistoryEntries, false ); + const HyperTwistCoachDashboardWidgetInternal::FMethodDrillRecoveryPacketDashboardBias + MethodDrillRecoveryPacketHandoffBias = + HyperTwistCoachDashboardWidgetInternal::DeriveMethodDrillRecoveryPacketDashboardBias( + MethodDrillRecoveryMemoryHistoryEntries + ); const HyperTwistCoachDashboardWidgetInternal::EMethodDrillRecoveryMemoryBias EffectiveMethodDrillRecoveryMemoryHandoffBias = RetainedMethodDrillRecoveryMemoryHandoffBias @@ -7215,6 +7373,24 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() return false; } }; + const bool bPacketPrefersQueueContinuation = + MethodDrillRecoveryPacketHandoffBias.PreferredHandoffKind + == HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::Queue + && bQueueReadyForHandoff + && bQueueLooksFollowUpOrRecovery; + const bool bPacketPrefersFollowUpContinuation = + MethodDrillRecoveryPacketHandoffBias.PreferredHandoffKind + == HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::FollowUp + && bFollowUpReadyForHandoff; + const bool bPacketPrefersQueueRedirect = + MethodDrillRecoveryPacketHandoffBias.PreferredHandoffKind + == HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::Queue + && bQueueReadyForHandoff + && bQueueLooksRecommendedOrPrimary; + const bool bPacketPrefersRecommendedRedirect = + MethodDrillRecoveryPacketHandoffBias.PreferredHandoffKind + == HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::Recommended + && bRecommendedReadyForHandoff; if (!bCanUseCoachHandoff) { CoachHandoffRecommendationStatusLine = @@ -7271,6 +7447,18 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() bHandoffRecommendationUsedDeferredOverflowHistory = true; bHandoffRecommendationUsedMethodDrillWeighting = true; } + else if (bPacketPrefersQueueContinuation) + { + HandoffRecommendationKind = + HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::Queue; + bHandoffRecommendationUsedMethodDrillWeighting = true; + } + else if (bPacketPrefersFollowUpContinuation) + { + HandoffRecommendationKind = + HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::FollowUp; + bHandoffRecommendationUsedMethodDrillWeighting = true; + } else if (HandoffHistoryBias.PreferredKind == HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::Queue && bQueueReadyForHandoff @@ -7307,7 +7495,19 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() && (bFollowUpReadyForHandoff || (bQueueReadyForHandoff && bQueueLooksFollowUpOrRecovery))) { - if (HandoffHistoryBias.PreferredKind + if (bPacketPrefersQueueContinuation) + { + HandoffRecommendationKind = + HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::Queue; + bHandoffRecommendationUsedMethodDrillRecoveryMemory = true; + } + else if (bPacketPrefersFollowUpContinuation) + { + HandoffRecommendationKind = + HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::FollowUp; + bHandoffRecommendationUsedMethodDrillRecoveryMemory = true; + } + else if (HandoffHistoryBias.PreferredKind == HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::Queue && bQueueReadyForHandoff && bQueueLooksFollowUpOrRecovery) @@ -7354,6 +7554,18 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() bHandoffRecommendationUsedDeferredOverflowHistory = true; bHandoffRecommendationUsedMethodDrillWeighting = true; } + else if (bPacketPrefersRecommendedRedirect) + { + HandoffRecommendationKind = + HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::Recommended; + bHandoffRecommendationUsedMethodDrillWeighting = true; + } + else if (bPacketPrefersQueueRedirect) + { + HandoffRecommendationKind = + HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::Queue; + bHandoffRecommendationUsedMethodDrillWeighting = true; + } else if (HandoffHistoryBias.PreferredKind == HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::Queue && bQueueReadyForHandoff @@ -7390,7 +7602,19 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() && (bRecommendedReadyForHandoff || (bQueueReadyForHandoff && bQueueLooksRecommendedOrPrimary))) { - if (HandoffHistoryBias.PreferredKind + if (bPacketPrefersRecommendedRedirect) + { + HandoffRecommendationKind = + HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::Recommended; + bHandoffRecommendationUsedMethodDrillRecoveryMemory = true; + } + else if (bPacketPrefersQueueRedirect) + { + HandoffRecommendationKind = + HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::Queue; + bHandoffRecommendationUsedMethodDrillRecoveryMemory = true; + } + else if (HandoffHistoryBias.PreferredKind == HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::Recommended && bRecommendedReadyForHandoff) { @@ -7742,6 +7966,17 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() *HyperTwistCoachDashboardWidgetInternal::DescribeMethodDrillRecoveryMemoryBias( EffectiveMethodDrillRecoveryMemoryHandoffBias) ); + CoachHandoffRecommendationDetailLine += FString::Printf( + TEXT(" | packet history queue/follow-up/recommended %d/%d/%d | strong packet queue/follow-up %d/%d | weak packet recommended %d | packet preferred: %s"), + MethodDrillRecoveryPacketHandoffBias.QueueEntryHistoryCount, + MethodDrillRecoveryPacketHandoffBias.FollowUpPacketHistoryCount, + MethodDrillRecoveryPacketHandoffBias.RecommendedBriefHistoryCount, + MethodDrillRecoveryPacketHandoffBias.StrongQueueEntryCount, + MethodDrillRecoveryPacketHandoffBias.StrongFollowUpPacketCount, + MethodDrillRecoveryPacketHandoffBias.WeakRecommendedBriefCount, + *HyperTwistCoachDashboardWidgetInternal::DescribeHandoffRecommendationKind( + MethodDrillRecoveryPacketHandoffBias.PreferredHandoffKind) + ); CoachHandoffRecommendationDetailLine += FString::Printf( TEXT(" | strong posture q/f/r %d/%d/%d, weak posture q/f/r %d/%d/%d"), HandoffHistoryBias.StrongRetainedMethodDrillQueueAcceptedDeltaCount, @@ -7759,6 +7994,14 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() ? TEXT(" | Drill history says broader coach handoff is now stronger than another narrow drill follow-up.") : TEXT(" | Drill history is balanced against broader coach handoff.")); } + if (MethodDrillRecoveryPacketHandoffBias.bHasSignal) + { + CoachHandoffRecommendationDetailLine += MethodDrillRecoveryPacketHandoffBias.bHasStrongContinuationSignal + ? TEXT(" | Retained drill-recovery packet history is reinforcing narrower queue/follow-up continuation.") + : (MethodDrillRecoveryPacketHandoffBias.bHasWeakRedirectSignal + ? TEXT(" | Retained drill-recovery packet history is reinforcing broader recommended redirect.") + : TEXT(" | Retained drill-recovery packet history is present but balanced.")); + } if (!MethodDrillUpstreamMemoryLine.IsEmpty()) { CoachHandoffRecommendationDetailLine += MethodDrillUpstreamMemoryLine; @@ -8012,6 +8255,11 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() : HyperTwistCoachDashboardWidgetInternal::DescribeCoachMemorySourceLabel( CachedCoachPanelState.PreferredQueueRecoveryActionSourceLabel); const FHyperTwistCoachDashboardQueueRecoveryWeighting QueueRecoveryWeighting = BuildQueueRecoveryWeighting(); + const HyperTwistCoachDashboardWidgetInternal::FMethodDrillRecoveryPacketDashboardBias + MethodDrillRecoveryPacketQueueBias = + HyperTwistCoachDashboardWidgetInternal::DeriveMethodDrillRecoveryPacketDashboardBias( + MethodDrillRecoveryMemoryHistoryEntries + ); const HyperTwistCoachDashboardWidgetInternal::EMethodDrillRecoveryMemoryBias QueueRecoveryMethodDrillMemoryBias = HyperTwistCoachDashboardWidgetInternal::ResolveMethodDrillRecoveryMemoryBias( @@ -8057,7 +8305,12 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() ? TEXT("prefer launch-recovery") : TEXT("no upstream preference")); const FString QueueRecoveryMethodDrillStatus = - QueueRecoveryWeighting.bHasMethodDrillMemorySignal + MethodDrillRecoveryPacketQueueBias.bHasSignal + ? (MethodDrillRecoveryPacketQueueBias.LaunchRecoveryBias + >= MethodDrillRecoveryPacketQueueBias.PromoteRecoveryBias + ? TEXT(" | Retained drill-recovery packet history is reinforcing launch recovery.") + : TEXT(" | Retained drill-recovery packet history is reinforcing promote-deferred recovery.")) + : (QueueRecoveryWeighting.bHasMethodDrillMemorySignal ? (QueueRecoveryWeighting.PreferredActionKind == FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::LaunchRecovery ? TEXT(" | Upstream drill-driven recovery memory is reinforcing launch recovery.") @@ -8072,7 +8325,7 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() == FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::LaunchRecovery ? TEXT(" | Drill-driven accepted handoff history is reinforcing launch recovery.") : TEXT(" | Drill-driven accepted handoff history is present, but promote-deferred recovery still holds.")) - : TEXT(""))); + : TEXT("")))); const bool bHasQueueRecoveryFriction = CachedCoachSchedulePolicySummary.bHasBlockedFollowUpDeferrals || CachedCoachSchedulePolicySummary.bHasArchiveAwareDeferrals @@ -8131,7 +8384,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 | retained strong-launch %d/%d, weak-promote %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"), + 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 | retained strong-launch %d/%d, weak-promote %d/%d | drill-driven bias promote/launch %d/%d | packet history queue/follow-up/recommended %d/%d/%d | packet 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, @@ -8166,6 +8419,11 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() QueueRecoveryWeighting.WeakRetainedMethodDrillPromoteCount, QueueRecoveryWeighting.MethodDrillPromoteBiasCount, QueueRecoveryWeighting.MethodDrillLaunchBiasCount, + MethodDrillRecoveryPacketQueueBias.QueueEntryHistoryCount, + MethodDrillRecoveryPacketQueueBias.FollowUpPacketHistoryCount, + MethodDrillRecoveryPacketQueueBias.RecommendedBriefHistoryCount, + MethodDrillRecoveryPacketQueueBias.PromoteRecoveryBias, + MethodDrillRecoveryPacketQueueBias.LaunchRecoveryBias, QueueRecoveryWeighting.MethodDrillUpstreamStrongCount, QueueRecoveryWeighting.MethodDrillUpstreamWeakCount, QueueRecoveryWeighting.MethodDrillUpstreamHistoryCount,