diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp index 383fa28..2441783 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp @@ -118,6 +118,18 @@ namespace HyperTwistCoachDashboardWidgetInternal { return TEXT("accepted deferred-overflow queued handoff history"); } + if (SourceLabel == TEXT("coach-memory-dashboard-deferred-first-queued")) + { + return TEXT("accepted deferred-first queued handoff history"); + } + if (SourceLabel == TEXT("coach-memory-dashboard-deferred-first-follow-up")) + { + return TEXT("accepted deferred-first follow-up handoff history"); + } + if (SourceLabel == TEXT("coach-memory-dashboard-deferred-first-recommended")) + { + return TEXT("accepted deferred-first recommended handoff history"); + } if (SourceLabel == TEXT("coach-memory-dashboard-queued")) { return TEXT("accepted queued handoff history"); @@ -142,14 +154,26 @@ namespace HyperTwistCoachDashboardWidgetInternal { return TEXT("accepted queued dashboard handoff"); } + if (SourceLabel == TEXT("dashboard-handoff-deferred-first-queued")) + { + return TEXT("accepted deferred-first queued dashboard handoff"); + } if (SourceLabel == TEXT("dashboard-handoff-follow-up")) { return TEXT("accepted follow-up dashboard handoff"); } + if (SourceLabel == TEXT("dashboard-handoff-deferred-first-follow-up")) + { + return TEXT("accepted deferred-first follow-up dashboard handoff"); + } if (SourceLabel == TEXT("dashboard-handoff-recommended")) { return TEXT("accepted recommended dashboard handoff"); } + if (SourceLabel == TEXT("dashboard-handoff-deferred-first-recommended")) + { + return TEXT("accepted deferred-first recommended dashboard handoff"); + } if (SourceLabel == TEXT("dashboard-guidance-outcome-deferred-overflow-queued")) { return TEXT("accepted deferred-overflow queued outcome handoff"); @@ -208,6 +232,29 @@ namespace HyperTwistCoachDashboardWidgetInternal return TEXT("steady"); } + FString DashboardHandoffSourceLabel( + const EHyperTwistTrainingCoachHandoffKind HandoffKind, + const bool bDeferredFirstSuppressionDriven) + { + switch (HandoffKind) + { + case EHyperTwistTrainingCoachHandoffKind::Queue: + return bDeferredFirstSuppressionDriven + ? TEXT("dashboard-handoff-deferred-first-queued") + : TEXT("dashboard-handoff-queued"); + case EHyperTwistTrainingCoachHandoffKind::FollowUp: + return bDeferredFirstSuppressionDriven + ? TEXT("dashboard-handoff-deferred-first-follow-up") + : TEXT("dashboard-handoff-follow-up"); + case EHyperTwistTrainingCoachHandoffKind::Recommended: + return bDeferredFirstSuppressionDriven + ? TEXT("dashboard-handoff-deferred-first-recommended") + : TEXT("dashboard-handoff-recommended"); + default: + return FString(); + } + } + const FHyperTwistCoachSignal* FindCoachSignalById( const TArray& Signals, const TCHAR* SignalId) @@ -815,7 +862,14 @@ bool UHyperTwistCoachDashboardWidget::ApplyQueueRecoveryPosture() && CachedCoachPanelState.NextQueueSourceLabel == TEXT("primary-brief")); if (bQueueMatchesPreference) { - const bool bSucceeded = StartRunAndRefresh(StartNextQueuedCoachRun(DefaultCoachMaxCases, FString())); + const FString StartActionSourceLabel = + bHasDeferredFirstSuppressionSignal && bPrefersFollowUp + ? HyperTwistCoachDashboardWidgetInternal::DashboardHandoffSourceLabel( + EHyperTwistTrainingCoachHandoffKind::Queue, + true) + : FString(); + const bool bSucceeded = StartRunAndRefresh( + StartNextQueuedCoachRun(DefaultCoachMaxCases, StartActionSourceLabel)); return RecordAndFinalizeOutcome( TEXT("launch-queued-recovery"), FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::LaunchRecovery, @@ -828,7 +882,14 @@ bool UHyperTwistCoachDashboardWidget::ApplyQueueRecoveryPosture() && CachedCoachPanelState.bCanStartCoachFollowUpRun && CachedCoachPanelState.bHasFollowUpGuidance) { - const bool bSucceeded = StartRunAndRefresh(StartCoachFollowUpRun(DefaultCoachMaxCases, FString())); + const FString StartActionSourceLabel = + bHasDeferredFirstSuppressionSignal + ? HyperTwistCoachDashboardWidgetInternal::DashboardHandoffSourceLabel( + EHyperTwistTrainingCoachHandoffKind::FollowUp, + true) + : FString(); + const bool bSucceeded = StartRunAndRefresh( + StartCoachFollowUpRun(DefaultCoachMaxCases, StartActionSourceLabel)); return RecordAndFinalizeOutcome( TEXT("launch-follow-up-recovery"), FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::LaunchRecovery, @@ -837,7 +898,14 @@ bool UHyperTwistCoachDashboardWidget::ApplyQueueRecoveryPosture() } if (bPrefersRecommended && CachedCoachPanelState.bCanStartCoachRecommendedRun) { - const bool bSucceeded = StartRunAndRefresh(StartCoachRecommendedRun(DefaultCoachMaxCases, FString())); + const FString StartActionSourceLabel = + bHasDeferredFirstSuppressionSignal + ? HyperTwistCoachDashboardWidgetInternal::DashboardHandoffSourceLabel( + EHyperTwistTrainingCoachHandoffKind::Recommended, + true) + : FString(); + const bool bSucceeded = StartRunAndRefresh( + StartCoachRecommendedRun(DefaultCoachMaxCases, StartActionSourceLabel)); return RecordAndFinalizeOutcome( TEXT("launch-recommended-recovery"), FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::LaunchRecovery, @@ -846,7 +914,14 @@ bool UHyperTwistCoachDashboardWidget::ApplyQueueRecoveryPosture() } if (CachedCoachPanelState.bCanStartQueuedRun) { - const bool bSucceeded = StartRunAndRefresh(StartNextQueuedCoachRun(DefaultCoachMaxCases, FString())); + const FString StartActionSourceLabel = + bHasDeferredFirstSuppressionSignal && bPrefersFollowUp + ? HyperTwistCoachDashboardWidgetInternal::DashboardHandoffSourceLabel( + EHyperTwistTrainingCoachHandoffKind::Queue, + true) + : FString(); + const bool bSucceeded = StartRunAndRefresh( + StartNextQueuedCoachRun(DefaultCoachMaxCases, StartActionSourceLabel)); return RecordAndFinalizeOutcome( TEXT("launch-queued-fallback"), FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::LaunchRecovery, @@ -855,7 +930,14 @@ bool UHyperTwistCoachDashboardWidget::ApplyQueueRecoveryPosture() } if (CachedCoachPanelState.bCanStartCoachFollowUpRun && CachedCoachPanelState.bHasFollowUpGuidance) { - const bool bSucceeded = StartRunAndRefresh(StartCoachFollowUpRun(DefaultCoachMaxCases, FString())); + const FString StartActionSourceLabel = + bHasDeferredFirstSuppressionSignal + ? HyperTwistCoachDashboardWidgetInternal::DashboardHandoffSourceLabel( + EHyperTwistTrainingCoachHandoffKind::FollowUp, + true) + : FString(); + const bool bSucceeded = StartRunAndRefresh( + StartCoachFollowUpRun(DefaultCoachMaxCases, StartActionSourceLabel)); return RecordAndFinalizeOutcome( TEXT("launch-follow-up-fallback"), FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::LaunchRecovery, @@ -864,7 +946,14 @@ bool UHyperTwistCoachDashboardWidget::ApplyQueueRecoveryPosture() } if (CachedCoachPanelState.bCanStartCoachRecommendedRun) { - const bool bSucceeded = StartRunAndRefresh(StartCoachRecommendedRun(DefaultCoachMaxCases, FString())); + const FString StartActionSourceLabel = + bHasDeferredFirstSuppressionSignal + ? HyperTwistCoachDashboardWidgetInternal::DashboardHandoffSourceLabel( + EHyperTwistTrainingCoachHandoffKind::Recommended, + true) + : FString(); + const bool bSucceeded = StartRunAndRefresh( + StartCoachRecommendedRun(DefaultCoachMaxCases, StartActionSourceLabel)); return RecordAndFinalizeOutcome( TEXT("launch-recommended-fallback"), FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::LaunchRecovery, @@ -1801,7 +1890,9 @@ void UHyperTwistCoachDashboardWidget::HandleDismissRecapClicked() void UHyperTwistCoachDashboardWidget::HandleStartQueuedRunClicked() { - StartNextQueuedCoachRun(DefaultCoachMaxCases, TEXT("dashboard-handoff-queued")); + StartNextQueuedCoachRun( + DefaultCoachMaxCases, + ResolveDashboardHandoffStartActionSourceLabel(EHyperTwistTrainingCoachHandoffKind::Queue)); SyncRetainedRunRecap(); SyncSelectedQueueEntry(); SyncSelectedAttempt(); @@ -1810,7 +1901,9 @@ void UHyperTwistCoachDashboardWidget::HandleStartQueuedRunClicked() void UHyperTwistCoachDashboardWidget::HandleStartFollowUpRunClicked() { - StartCoachFollowUpRun(DefaultCoachMaxCases, TEXT("dashboard-handoff-follow-up")); + StartCoachFollowUpRun( + DefaultCoachMaxCases, + ResolveDashboardHandoffStartActionSourceLabel(EHyperTwistTrainingCoachHandoffKind::FollowUp)); SyncRetainedRunRecap(); SyncSelectedQueueEntry(); SyncSelectedAttempt(); @@ -1819,7 +1912,10 @@ void UHyperTwistCoachDashboardWidget::HandleStartFollowUpRunClicked() void UHyperTwistCoachDashboardWidget::HandleStartRecommendedRunClicked() { - StartCoachRecommendedRun(DefaultCoachMaxCases, TEXT("dashboard-handoff-recommended")); + StartCoachRecommendedRun( + DefaultCoachMaxCases, + ResolveDashboardHandoffStartActionSourceLabel( + EHyperTwistTrainingCoachHandoffKind::Recommended)); SyncRetainedRunRecap(); SyncSelectedQueueEntry(); SyncSelectedAttempt(); @@ -3667,6 +3763,18 @@ FHyperTwistTrainingDeck UHyperTwistCoachDashboardWidget::BuildSelectedGuidancePr return SingleCaseDeck; } +FString UHyperTwistCoachDashboardWidget::ResolveDashboardHandoffStartActionSourceLabel( + const EHyperTwistTrainingCoachHandoffKind HandoffKind) const +{ + const bool bUseDeferredFirstSuppressionLabel = + bDisplayedCoachHandoffRecommendationUsedDeferredFirstSuppressionSignal + && DisplayedCoachHandoffRecommendationKind == HandoffKind; + return HyperTwistCoachDashboardWidgetInternal::DashboardHandoffSourceLabel( + HandoffKind, + bUseDeferredFirstSuppressionLabel + ); +} + bool UHyperTwistCoachDashboardWidget::ResolveDisplayedGuidanceComparisonContext( const FHyperTwistTrainingSessionSummary*& OutDisplayedRunRecap, const FHyperTwistCoachBrief*& OutDisplayedGuidanceComparisonBrief, @@ -5108,6 +5216,8 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() == TEXT("queue_suppression_deferred_first_follow_up"); HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind HandoffRecommendationKind = HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::None; + DisplayedCoachHandoffRecommendationKind = EHyperTwistTrainingCoachHandoffKind::None; + bDisplayedCoachHandoffRecommendationUsedDeferredFirstSuppressionSignal = false; FString CoachHandoffRecommendationStatusLine; FString CoachHandoffRecommendationDetailLine; const bool bQueueReadyForHandoff = CachedCoachPanelState.bCanStartQueuedRun; @@ -5328,6 +5438,24 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() TEXT("Recommendation: no coach-directed handoff is currently ready."); break; } + switch (HandoffRecommendationKind) + { + case HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::Queue: + DisplayedCoachHandoffRecommendationKind = EHyperTwistTrainingCoachHandoffKind::Queue; + break; + case HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::FollowUp: + DisplayedCoachHandoffRecommendationKind = EHyperTwistTrainingCoachHandoffKind::FollowUp; + break; + case HyperTwistCoachDashboardWidgetInternal::EHandoffRecommendationKind::Recommended: + DisplayedCoachHandoffRecommendationKind = + EHyperTwistTrainingCoachHandoffKind::Recommended; + break; + default: + DisplayedCoachHandoffRecommendationKind = EHyperTwistTrainingCoachHandoffKind::None; + break; + } + bDisplayedCoachHandoffRecommendationUsedDeferredFirstSuppressionSignal = + bHandoffRecommendationUsedDeferredFirstSuppressionSignal; CoachHandoffRecommendationDetailLine = FString::Printf( TEXT("Closure continuation bias %d | Closure suppression bias %d | Queue ready: %s | Follow-up ready: %s | Recommended ready: %s | Preferred lane: %s | Memory source: %s%s%s%s%s | Launch bias: %s from %s | broadened/tightened history %d/%d | deferred tightened launches %d | deferred launch cases %d%s | Handoff history preferred: %s | accepted q/f/r %d/%d/%d | deferred-overflow accepted q/f/r %d/%d/%d | deferred-first recommendation q/f/r %d/%d/%d | recommended q/f/r %d/%d/%d"), diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp index 001dd07..5bdf050 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp @@ -26,6 +26,8 @@ namespace HyperTwistTrainingCoachLibraryInternal bool IsDeferredOverflowDashboardHandoffSource(const FString& SourceLabel) { return SourceLabel.StartsWith(TEXT("coach-memory-dashboard-deferred-overflow-")) + || SourceLabel.StartsWith(TEXT("coach-memory-dashboard-deferred-first-")) + || SourceLabel.StartsWith(TEXT("dashboard-handoff-deferred-first-")) || SourceLabel == TEXT("recommendation-history-deferred-first-follow-up"); } @@ -52,6 +54,18 @@ namespace HyperTwistTrainingCoachLibraryInternal FString DescribeCoachMemorySourceLabel(const FString& SourceLabel) { + if (SourceLabel == TEXT("coach-memory-dashboard-deferred-first-follow-up")) + { + return TEXT("accepted deferred-first follow-up handoff history"); + } + if (SourceLabel == TEXT("coach-memory-dashboard-deferred-first-recommended")) + { + return TEXT("accepted deferred-first recommended handoff history"); + } + if (SourceLabel == TEXT("coach-memory-dashboard-deferred-first-queued")) + { + return TEXT("accepted deferred-first queued handoff history"); + } if (SourceLabel == TEXT("coach-memory-dashboard-deferred-overflow-follow-up")) { return TEXT("accepted deferred-overflow follow-up handoff history"); @@ -88,14 +102,26 @@ namespace HyperTwistTrainingCoachLibraryInternal { return TEXT("accepted queued dashboard handoff"); } + if (SourceLabel == TEXT("dashboard-handoff-deferred-first-queued")) + { + return TEXT("accepted deferred-first queued dashboard handoff"); + } if (SourceLabel == TEXT("dashboard-handoff-follow-up")) { return TEXT("accepted follow-up dashboard handoff"); } + if (SourceLabel == TEXT("dashboard-handoff-deferred-first-follow-up")) + { + return TEXT("accepted deferred-first follow-up dashboard handoff"); + } if (SourceLabel == TEXT("dashboard-handoff-recommended")) { return TEXT("accepted recommended dashboard handoff"); } + if (SourceLabel == TEXT("dashboard-handoff-deferred-first-recommended")) + { + return TEXT("accepted deferred-first recommended dashboard handoff"); + } if (SourceLabel == TEXT("dashboard-guidance-outcome-deferred-overflow-queued")) { return TEXT("accepted deferred-overflow queued outcome handoff"); diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp index a244189..caf0a0c 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp @@ -59,20 +59,31 @@ namespace HyperTwistTrainingRepositoryLibraryInternal || SourceLabel == TEXT("dashboard-guidance-outcome-deferred-overflow-recommended"); } + bool IsDeferredFirstDashboardHandoffSource(const FString& SourceLabel) + { + return SourceLabel == TEXT("dashboard-handoff-deferred-first-queued") + || SourceLabel == TEXT("dashboard-handoff-deferred-first-follow-up") + || SourceLabel == TEXT("dashboard-handoff-deferred-first-recommended") + || SourceLabel.StartsWith(TEXT("coach-memory-dashboard-deferred-first-")); + } + EHyperTwistTrainingCoachHandoffKind DashboardHandoffKindFromSourceLabel(const FString& SourceLabel) { if (SourceLabel == TEXT("dashboard-handoff-queued") - || SourceLabel == TEXT("dashboard-guidance-outcome-deferred-overflow-queued")) + || SourceLabel == TEXT("dashboard-guidance-outcome-deferred-overflow-queued") + || SourceLabel == TEXT("dashboard-handoff-deferred-first-queued")) { return EHyperTwistTrainingCoachHandoffKind::Queue; } if (SourceLabel == TEXT("dashboard-handoff-follow-up") - || SourceLabel == TEXT("dashboard-guidance-outcome-deferred-overflow-follow-up")) + || SourceLabel == TEXT("dashboard-guidance-outcome-deferred-overflow-follow-up") + || SourceLabel == TEXT("dashboard-handoff-deferred-first-follow-up")) { return EHyperTwistTrainingCoachHandoffKind::FollowUp; } if (SourceLabel == TEXT("dashboard-handoff-recommended") - || SourceLabel == TEXT("dashboard-guidance-outcome-deferred-overflow-recommended")) + || SourceLabel == TEXT("dashboard-guidance-outcome-deferred-overflow-recommended") + || SourceLabel == TEXT("dashboard-handoff-deferred-first-recommended")) { return EHyperTwistTrainingCoachHandoffKind::Recommended; } @@ -82,20 +93,33 @@ namespace HyperTwistTrainingRepositoryLibraryInternal FString CoachMemoryDashboardSourceLabel( const EHyperTwistTrainingCoachHandoffKind HandoffKind, - const bool bDeferredOverflowDriven + const bool bDeferredOverflowDriven, + const bool bDeferredFirstDriven = false ) { switch (HandoffKind) { case EHyperTwistTrainingCoachHandoffKind::Queue: + if (bDeferredFirstDriven) + { + return TEXT("coach-memory-dashboard-deferred-first-queued"); + } return bDeferredOverflowDriven ? TEXT("coach-memory-dashboard-deferred-overflow-queued") : TEXT("coach-memory-dashboard-queued"); case EHyperTwistTrainingCoachHandoffKind::FollowUp: + if (bDeferredFirstDriven) + { + return TEXT("coach-memory-dashboard-deferred-first-follow-up"); + } return bDeferredOverflowDriven ? TEXT("coach-memory-dashboard-deferred-overflow-follow-up") : TEXT("coach-memory-dashboard-follow-up"); case EHyperTwistTrainingCoachHandoffKind::Recommended: + if (bDeferredFirstDriven) + { + return TEXT("coach-memory-dashboard-deferred-first-recommended"); + } return bDeferredOverflowDriven ? TEXT("coach-memory-dashboard-deferred-overflow-recommended") : TEXT("coach-memory-dashboard-recommended"); @@ -107,6 +131,7 @@ namespace HyperTwistTrainingRepositoryLibraryInternal bool IsDeferredOverflowDashboardHandoffSource(const FString& SourceLabel) { return SourceLabel.StartsWith(TEXT("coach-memory-dashboard-deferred-overflow-")) + || SourceLabel.StartsWith(TEXT("coach-memory-dashboard-deferred-first-")) || SourceLabel == TEXT("recommendation-history-deferred-first-follow-up"); } @@ -386,6 +411,21 @@ namespace HyperTwistTrainingRepositoryLibraryInternal 0, 4 ); + const int32 ExplicitDeferredFirstQueuedHandoffSignal = FMath::Clamp( + QueueExecutionSummary.StartedDeferredFirstQueuedHandoffEventCount * 2, + 0, + 4 + ); + const int32 ExplicitDeferredFirstFollowUpHandoffSignal = FMath::Clamp( + QueueExecutionSummary.StartedDeferredFirstFollowUpHandoffEventCount * 2, + 0, + 4 + ); + const int32 ExplicitDeferredFirstRecommendedHandoffSignal = FMath::Clamp( + QueueExecutionSummary.StartedDeferredFirstRecommendedHandoffEventCount * 2, + 0, + 4 + ); if (ExplicitQueuedHandoffSignal > 0 || ExplicitFollowUpHandoffSignal > 0 || ExplicitRecommendedHandoffSignal > 0) @@ -397,7 +437,8 @@ namespace HyperTwistTrainingRepositoryLibraryInternal Snapshot.PreferredCoachHandoffSourceLabel = HyperTwistTrainingRepositoryLibraryInternal::CoachMemoryDashboardSourceLabel( EHyperTwistTrainingCoachHandoffKind::Queue, - ExplicitDeferredOverflowQueuedHandoffSignal > 0 + ExplicitDeferredOverflowQueuedHandoffSignal > 0, + ExplicitDeferredFirstQueuedHandoffSignal > 0 ); } else if (ExplicitFollowUpHandoffSignal >= ExplicitRecommendedHandoffSignal) @@ -406,7 +447,8 @@ namespace HyperTwistTrainingRepositoryLibraryInternal Snapshot.PreferredCoachHandoffSourceLabel = HyperTwistTrainingRepositoryLibraryInternal::CoachMemoryDashboardSourceLabel( EHyperTwistTrainingCoachHandoffKind::FollowUp, - ExplicitDeferredOverflowFollowUpHandoffSignal > 0 + ExplicitDeferredOverflowFollowUpHandoffSignal > 0, + ExplicitDeferredFirstFollowUpHandoffSignal > 0 ); } else @@ -415,18 +457,22 @@ namespace HyperTwistTrainingRepositoryLibraryInternal Snapshot.PreferredCoachHandoffSourceLabel = HyperTwistTrainingRepositoryLibraryInternal::CoachMemoryDashboardSourceLabel( EHyperTwistTrainingCoachHandoffKind::Recommended, - ExplicitDeferredOverflowRecommendedHandoffSignal > 0 + ExplicitDeferredOverflowRecommendedHandoffSignal > 0, + ExplicitDeferredFirstRecommendedHandoffSignal > 0 ); } } const int32 WeightedFollowUpRecoverySignal = ExplicitFollowUpRecoverySignal + ExplicitFollowUpHandoffSignal - + ExplicitDeferredOverflowFollowUpHandoffSignal; + + ExplicitDeferredOverflowFollowUpHandoffSignal + + ExplicitDeferredFirstQueuedHandoffSignal + + ExplicitDeferredFirstFollowUpHandoffSignal; const int32 WeightedRecommendedRecoverySignal = ExplicitRecommendedRecoverySignal + ExplicitRecommendedHandoffSignal - + ExplicitDeferredOverflowRecommendedHandoffSignal; + + ExplicitDeferredOverflowRecommendedHandoffSignal + + ExplicitDeferredFirstRecommendedHandoffSignal; const bool bQueueHistoryPrefersFollowUp = DominantDeferredLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp || (DominantCompletedLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp @@ -451,7 +497,9 @@ namespace HyperTwistTrainingRepositoryLibraryInternal + FMath::Min(static_cast(QueueExecutionSummary.CompletedEventCount), 3.0f) + FMath::Min(static_cast(QueueExecutionSummary.ManualRescheduleEventCount) * 0.5f, 2.0f) + static_cast(ExplicitQueuedHandoffSignal) - + static_cast(ExplicitDeferredOverflowQueuedHandoffSignal); + + static_cast(ExplicitDeferredOverflowQueuedHandoffSignal) + + static_cast(ExplicitDeferredFirstQueuedHandoffSignal) + + static_cast(ExplicitDeferredFirstFollowUpHandoffSignal) * 0.5f; if (PromoteRecoveryActionPressure > 0.0f || LaunchRecoveryActionPressure > 0.0f) { if (PromoteRecoveryActionPressure > LaunchRecoveryActionPressure) @@ -466,10 +514,16 @@ namespace HyperTwistTrainingRepositoryLibraryInternal EHyperTwistTrainingQueueRecoveryAction::LaunchRecovery; Snapshot.PreferredQueueRecoveryActionSourceLabel = ExplicitQueuedHandoffSignal > 0 - ? (ExplicitDeferredOverflowQueuedHandoffSignal > 0 - ? TEXT("dashboard-guidance-outcome-deferred-overflow-queued") - : TEXT("dashboard-handoff-queued")) - : TEXT("queue-history-launch-pressure"); + ? (ExplicitDeferredFirstQueuedHandoffSignal > 0 + ? TEXT("dashboard-handoff-deferred-first-queued") + : (ExplicitDeferredOverflowQueuedHandoffSignal > 0 + ? TEXT("dashboard-guidance-outcome-deferred-overflow-queued") + : TEXT("dashboard-handoff-queued"))) + : (ExplicitDeferredFirstFollowUpHandoffSignal > 0 + ? TEXT("dashboard-handoff-deferred-first-follow-up") + : (ExplicitFollowUpHandoffSignal > 0 + ? TEXT("dashboard-handoff-follow-up") + : TEXT("queue-history-launch-pressure"))); } else { @@ -527,14 +581,17 @@ namespace HyperTwistTrainingRepositoryLibraryInternal Snapshot.PreferredGuidanceSourceLabel = ExplicitFollowUpHandoffSignal > 0 ? HyperTwistTrainingRepositoryLibraryInternal::CoachMemoryDashboardSourceLabel( EHyperTwistTrainingCoachHandoffKind::FollowUp, - ExplicitDeferredOverflowFollowUpHandoffSignal > 0 + ExplicitDeferredOverflowFollowUpHandoffSignal > 0, + ExplicitDeferredFirstFollowUpHandoffSignal > 0 + || ExplicitDeferredFirstQueuedHandoffSignal > 0 ) : TEXT("coach-memory-queue-follow-up"); } - else if (bQueueHistoryPrefersRecommended - && (QueueExecutionSummary.PromotedEventCount > 0 - || QueueExecutionSummary.CompletedEventCount > 0)) - { + else if (bQueueHistoryPrefersRecommended + && Snapshot.PreferredGuidanceLane != EHyperTwistTrainingCoachGuidanceLane::FollowUp + && (QueueExecutionSummary.PromotedEventCount > 0 + || QueueExecutionSummary.CompletedEventCount > 0)) + { Snapshot.PreferredGuidanceLane = EHyperTwistTrainingCoachGuidanceLane::Recommended; Snapshot.PreferredGuidanceSourceLabel = ExplicitRecommendedHandoffSignal > 0 ? HyperTwistTrainingRepositoryLibraryInternal::CoachMemoryDashboardSourceLabel( @@ -5889,6 +5946,21 @@ FHyperTwistTrainingCoachMemorySnapshot UHyperTwistTrainingRepositoryLibrary::Der 0, 4 ); + const int32 ExplicitDeferredFirstQueuedHandoffSignal = FMath::Clamp( + QueueExecutionSummary.StartedDeferredFirstQueuedHandoffEventCount * 2, + 0, + 4 + ); + const int32 ExplicitDeferredFirstFollowUpHandoffSignal = FMath::Clamp( + QueueExecutionSummary.StartedDeferredFirstFollowUpHandoffEventCount * 2, + 0, + 4 + ); + const int32 ExplicitDeferredFirstRecommendedHandoffSignal = FMath::Clamp( + QueueExecutionSummary.StartedDeferredFirstRecommendedHandoffEventCount * 2, + 0, + 4 + ); if (ExplicitQueuedHandoffSignal > 0 || ExplicitFollowUpHandoffSignal > 0 || ExplicitRecommendedHandoffSignal > 0) @@ -5900,7 +5972,8 @@ FHyperTwistTrainingCoachMemorySnapshot UHyperTwistTrainingRepositoryLibrary::Der Snapshot.PreferredCoachHandoffSourceLabel = HyperTwistTrainingRepositoryLibraryInternal::CoachMemoryDashboardSourceLabel( EHyperTwistTrainingCoachHandoffKind::Queue, - ExplicitDeferredOverflowQueuedHandoffSignal > 0 + ExplicitDeferredOverflowQueuedHandoffSignal > 0, + ExplicitDeferredFirstQueuedHandoffSignal > 0 ); } else if (ExplicitFollowUpHandoffSignal >= ExplicitRecommendedHandoffSignal) @@ -5909,7 +5982,8 @@ FHyperTwistTrainingCoachMemorySnapshot UHyperTwistTrainingRepositoryLibrary::Der Snapshot.PreferredCoachHandoffSourceLabel = HyperTwistTrainingRepositoryLibraryInternal::CoachMemoryDashboardSourceLabel( EHyperTwistTrainingCoachHandoffKind::FollowUp, - ExplicitDeferredOverflowFollowUpHandoffSignal > 0 + ExplicitDeferredOverflowFollowUpHandoffSignal > 0, + ExplicitDeferredFirstFollowUpHandoffSignal > 0 ); } else @@ -5918,18 +5992,22 @@ FHyperTwistTrainingCoachMemorySnapshot UHyperTwistTrainingRepositoryLibrary::Der Snapshot.PreferredCoachHandoffSourceLabel = HyperTwistTrainingRepositoryLibraryInternal::CoachMemoryDashboardSourceLabel( EHyperTwistTrainingCoachHandoffKind::Recommended, - ExplicitDeferredOverflowRecommendedHandoffSignal > 0 + ExplicitDeferredOverflowRecommendedHandoffSignal > 0, + ExplicitDeferredFirstRecommendedHandoffSignal > 0 ); } } const int32 WeightedFollowUpRecoverySignal = ExplicitFollowUpRecoverySignal + ExplicitFollowUpHandoffSignal - + ExplicitDeferredOverflowFollowUpHandoffSignal; + + ExplicitDeferredOverflowFollowUpHandoffSignal + + ExplicitDeferredFirstQueuedHandoffSignal + + ExplicitDeferredFirstFollowUpHandoffSignal; const int32 WeightedRecommendedRecoverySignal = ExplicitRecommendedRecoverySignal + ExplicitRecommendedHandoffSignal - + ExplicitDeferredOverflowRecommendedHandoffSignal; + + ExplicitDeferredOverflowRecommendedHandoffSignal + + ExplicitDeferredFirstRecommendedHandoffSignal; const bool bQueueHistoryPrefersFollowUp = DominantDeferredLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp || (DominantCompletedLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp @@ -5954,7 +6032,9 @@ FHyperTwistTrainingCoachMemorySnapshot UHyperTwistTrainingRepositoryLibrary::Der + FMath::Min(static_cast(QueueExecutionSummary.CompletedEventCount), 3.0f) + FMath::Min(static_cast(QueueExecutionSummary.ManualRescheduleEventCount) * 0.5f, 2.0f) + static_cast(ExplicitQueuedHandoffSignal) - + static_cast(ExplicitDeferredOverflowQueuedHandoffSignal); + + static_cast(ExplicitDeferredOverflowQueuedHandoffSignal) + + static_cast(ExplicitDeferredFirstQueuedHandoffSignal) + + static_cast(ExplicitDeferredFirstFollowUpHandoffSignal) * 0.5f; if (PromoteRecoveryActionPressure > 0.0f || LaunchRecoveryActionPressure > 0.0f) { if (PromoteRecoveryActionPressure > LaunchRecoveryActionPressure) @@ -5969,10 +6049,16 @@ FHyperTwistTrainingCoachMemorySnapshot UHyperTwistTrainingRepositoryLibrary::Der EHyperTwistTrainingQueueRecoveryAction::LaunchRecovery; Snapshot.PreferredQueueRecoveryActionSourceLabel = ExplicitQueuedHandoffSignal > 0 - ? (ExplicitDeferredOverflowQueuedHandoffSignal > 0 - ? TEXT("dashboard-guidance-outcome-deferred-overflow-queued") - : TEXT("dashboard-handoff-queued")) - : TEXT("queue-history-launch-pressure"); + ? (ExplicitDeferredFirstQueuedHandoffSignal > 0 + ? TEXT("dashboard-handoff-deferred-first-queued") + : (ExplicitDeferredOverflowQueuedHandoffSignal > 0 + ? TEXT("dashboard-guidance-outcome-deferred-overflow-queued") + : TEXT("dashboard-handoff-queued"))) + : (ExplicitDeferredFirstFollowUpHandoffSignal > 0 + ? TEXT("dashboard-handoff-deferred-first-follow-up") + : (ExplicitFollowUpHandoffSignal > 0 + ? TEXT("dashboard-handoff-follow-up") + : TEXT("queue-history-launch-pressure"))); } else { @@ -6114,7 +6200,9 @@ FHyperTwistTrainingCoachMemorySnapshot UHyperTwistTrainingRepositoryLibrary::Der Snapshot.PreferredGuidanceSourceLabel = ExplicitFollowUpHandoffSignal > 0 ? HyperTwistTrainingRepositoryLibraryInternal::CoachMemoryDashboardSourceLabel( EHyperTwistTrainingCoachHandoffKind::FollowUp, - ExplicitDeferredOverflowFollowUpHandoffSignal > 0 + ExplicitDeferredOverflowFollowUpHandoffSignal > 0, + ExplicitDeferredFirstFollowUpHandoffSignal > 0 + || ExplicitDeferredFirstQueuedHandoffSignal > 0 ) : TEXT("coach-memory-queue-follow-up"); } @@ -7780,6 +7868,9 @@ FHyperTwistTrainingCoachSessionQueueExecutionSummary UHyperTwistTrainingReposito const bool bDeferredOverflowDashboardOutcome = HyperTwistTrainingRepositoryLibraryInternal::IsDeferredOverflowDashboardOutcomeSource( HistoryEntry.StartActionSourceLabel); + const bool bDeferredFirstDashboardHandoff = + HyperTwistTrainingRepositoryLibraryInternal::IsDeferredFirstDashboardHandoffSource( + HistoryEntry.StartActionSourceLabel); if (DashboardHandoffKind == EHyperTwistTrainingCoachHandoffKind::Queue) { Summary.StartedQueuedHandoffEventCount += 1; @@ -7787,6 +7878,10 @@ FHyperTwistTrainingCoachSessionQueueExecutionSummary UHyperTwistTrainingReposito { Summary.StartedDeferredOverflowQueuedHandoffEventCount += 1; } + if (bDeferredFirstDashboardHandoff) + { + Summary.StartedDeferredFirstQueuedHandoffEventCount += 1; + } } else if (DashboardHandoffKind == EHyperTwistTrainingCoachHandoffKind::FollowUp) { @@ -7795,6 +7890,10 @@ FHyperTwistTrainingCoachSessionQueueExecutionSummary UHyperTwistTrainingReposito { Summary.StartedDeferredOverflowFollowUpHandoffEventCount += 1; } + if (bDeferredFirstDashboardHandoff) + { + Summary.StartedDeferredFirstFollowUpHandoffEventCount += 1; + } } else if (DashboardHandoffKind == EHyperTwistTrainingCoachHandoffKind::Recommended) { @@ -7803,6 +7902,10 @@ FHyperTwistTrainingCoachSessionQueueExecutionSummary UHyperTwistTrainingReposito { Summary.StartedDeferredOverflowRecommendedHandoffEventCount += 1; } + if (bDeferredFirstDashboardHandoff) + { + Summary.StartedDeferredFirstRecommendedHandoffEventCount += 1; + } } if (Summary.LastStartedAtUtc.IsEmpty() || HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings( diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h index 3e9c6ef..fa99cbb 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h @@ -1102,6 +1102,8 @@ private: const FHyperTwistTrainingTimingPolicy* FindActiveTimingPolicy() const; const FHyperTwistTrainingDeck* GetActiveGuidancePreviewDeck() const; const FHyperTwistCoachBrief* GetActiveGuidancePreviewBrief() const; + FString ResolveDashboardHandoffStartActionSourceLabel( + EHyperTwistTrainingCoachHandoffKind HandoffKind) const; FHyperTwistTrainingDeck BuildSelectedGuidancePreviewCaseDeck() const; bool ResolveDisplayedGuidanceComparisonContext( const FHyperTwistTrainingSessionSummary*& OutDisplayedRunRecap, @@ -1198,6 +1200,9 @@ private: TArray QueueRecoveryOutcomeHistory; TArray QueueSuppressionHistoryEntries; TArray ClosureRecoveryHistoryEntries; + EHyperTwistTrainingCoachHandoffKind DisplayedCoachHandoffRecommendationKind = + EHyperTwistTrainingCoachHandoffKind::None; + bool bDisplayedCoachHandoffRecommendationUsedDeferredFirstSuppressionSignal = false; EHyperTwistCoachDashboardGuidancePreviewLane ActiveGuidancePreviewLane = EHyperTwistCoachDashboardGuidancePreviewLane::None; int32 SelectedGuidancePreviewCaseIndex = INDEX_NONE; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h index fa80b49..c9ddde3 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h @@ -3359,6 +3359,15 @@ struct FHyperTwistTrainingCoachSessionQueueExecutionSummary UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") int32 StartedDeferredOverflowRecommendedHandoffEventCount = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 StartedDeferredFirstQueuedHandoffEventCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 StartedDeferredFirstFollowUpHandoffEventCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 StartedDeferredFirstRecommendedHandoffEventCount = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") int32 PromotedFollowUpEventCount = 0;