diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp index 0d30513..72bdd3a 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp @@ -1044,7 +1044,8 @@ FHyperTwistTrainingCoachMemorySnapshot UHyperTwistContractLibrary::MakeSampleTra QueueState, TEXT("primary-brief"), TEXT("coach_training_session_queued_01"), - TEXT("2026-04-28T12:22:00Z") + TEXT("2026-04-28T12:22:00Z"), + FString() ); RepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertCoachSessionQueueState( RepositoryState, @@ -1594,7 +1595,8 @@ FHyperTwistTrainingCoachSessionQueueExecutionSummary UHyperTwistContractLibrary: QueueState, TEXT("primary-brief"), TEXT("coach_training_session_queued_01"), - TEXT("2026-04-28T12:20:00Z") + TEXT("2026-04-28T12:20:00Z"), + FString() ); RepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertCoachSessionQueueState( RepositoryState, diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp index 60c45b0..1b50d58 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp @@ -262,15 +262,15 @@ FHyperTwistTrainingRunState UHyperTwistCoachDashboardWidget::ExecutePrimaryCoach FHyperTwistTrainingRunState RunState; if (CachedCoachPanelState.bCanStartQueuedRun) { - RunState = StartNextQueuedCoachRun(DefaultCoachMaxCases); + RunState = StartNextQueuedCoachRun(DefaultCoachMaxCases, FString()); } else if (CachedCoachPanelState.bCanStartCoachFollowUpRun && CachedCoachPanelState.bHasFollowUpGuidance) { - RunState = StartCoachFollowUpRun(DefaultCoachMaxCases); + RunState = StartCoachFollowUpRun(DefaultCoachMaxCases, FString()); } else if (CachedCoachPanelState.bCanStartCoachRecommendedRun) { - RunState = StartCoachRecommendedRun(DefaultCoachMaxCases); + RunState = StartCoachRecommendedRun(DefaultCoachMaxCases, FString()); } SyncRetainedRunRecap(); @@ -413,7 +413,7 @@ bool UHyperTwistCoachDashboardWidget::ApplyQueueRecoveryPosture() && CachedCoachPanelState.NextQueueSourceLabel == TEXT("primary-brief")); if (bQueueMatchesPreference) { - const bool bSucceeded = StartRunAndRefresh(StartNextQueuedCoachRun(DefaultCoachMaxCases)); + const bool bSucceeded = StartRunAndRefresh(StartNextQueuedCoachRun(DefaultCoachMaxCases, FString())); return RecordAndFinalizeOutcome( TEXT("launch-queued-recovery"), FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::LaunchRecovery, @@ -426,7 +426,7 @@ bool UHyperTwistCoachDashboardWidget::ApplyQueueRecoveryPosture() && CachedCoachPanelState.bCanStartCoachFollowUpRun && CachedCoachPanelState.bHasFollowUpGuidance) { - const bool bSucceeded = StartRunAndRefresh(StartCoachFollowUpRun(DefaultCoachMaxCases)); + const bool bSucceeded = StartRunAndRefresh(StartCoachFollowUpRun(DefaultCoachMaxCases, FString())); return RecordAndFinalizeOutcome( TEXT("launch-follow-up-recovery"), FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::LaunchRecovery, @@ -435,7 +435,7 @@ bool UHyperTwistCoachDashboardWidget::ApplyQueueRecoveryPosture() } if (bPrefersRecommended && CachedCoachPanelState.bCanStartCoachRecommendedRun) { - const bool bSucceeded = StartRunAndRefresh(StartCoachRecommendedRun(DefaultCoachMaxCases)); + const bool bSucceeded = StartRunAndRefresh(StartCoachRecommendedRun(DefaultCoachMaxCases, FString())); return RecordAndFinalizeOutcome( TEXT("launch-recommended-recovery"), FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::LaunchRecovery, @@ -444,7 +444,7 @@ bool UHyperTwistCoachDashboardWidget::ApplyQueueRecoveryPosture() } if (CachedCoachPanelState.bCanStartQueuedRun) { - const bool bSucceeded = StartRunAndRefresh(StartNextQueuedCoachRun(DefaultCoachMaxCases)); + const bool bSucceeded = StartRunAndRefresh(StartNextQueuedCoachRun(DefaultCoachMaxCases, FString())); return RecordAndFinalizeOutcome( TEXT("launch-queued-fallback"), FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::LaunchRecovery, @@ -453,7 +453,7 @@ bool UHyperTwistCoachDashboardWidget::ApplyQueueRecoveryPosture() } if (CachedCoachPanelState.bCanStartCoachFollowUpRun && CachedCoachPanelState.bHasFollowUpGuidance) { - const bool bSucceeded = StartRunAndRefresh(StartCoachFollowUpRun(DefaultCoachMaxCases)); + const bool bSucceeded = StartRunAndRefresh(StartCoachFollowUpRun(DefaultCoachMaxCases, FString())); return RecordAndFinalizeOutcome( TEXT("launch-follow-up-fallback"), FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::LaunchRecovery, @@ -462,7 +462,7 @@ bool UHyperTwistCoachDashboardWidget::ApplyQueueRecoveryPosture() } if (CachedCoachPanelState.bCanStartCoachRecommendedRun) { - const bool bSucceeded = StartRunAndRefresh(StartCoachRecommendedRun(DefaultCoachMaxCases)); + const bool bSucceeded = StartRunAndRefresh(StartCoachRecommendedRun(DefaultCoachMaxCases, FString())); return RecordAndFinalizeOutcome( TEXT("launch-recommended-fallback"), FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::LaunchRecovery, @@ -972,15 +972,15 @@ bool UHyperTwistCoachDashboardWidget::ApplyGuidanceOutcomeAction() FHyperTwistTrainingRunState RunState; if (CachedCoachPanelState.bCanStartCoachFollowUpRun && CachedCoachPanelState.bHasFollowUpGuidance) { - RunState = StartCoachFollowUpRun(DefaultCoachMaxCases); + RunState = StartCoachFollowUpRun(DefaultCoachMaxCases, FString()); } else if (CachedCoachPanelState.bCanStartQueuedRun) { - RunState = StartNextQueuedCoachRun(DefaultCoachMaxCases); + RunState = StartNextQueuedCoachRun(DefaultCoachMaxCases, FString()); } else if (CachedCoachPanelState.bCanStartCoachRecommendedRun) { - RunState = StartCoachRecommendedRun(DefaultCoachMaxCases); + RunState = StartCoachRecommendedRun(DefaultCoachMaxCases, FString()); } else { @@ -1335,7 +1335,7 @@ void UHyperTwistCoachDashboardWidget::HandleDismissRecapClicked() void UHyperTwistCoachDashboardWidget::HandleStartQueuedRunClicked() { - StartNextQueuedCoachRun(DefaultCoachMaxCases); + StartNextQueuedCoachRun(DefaultCoachMaxCases, TEXT("dashboard-handoff-queued")); SyncRetainedRunRecap(); SyncSelectedQueueEntry(); SyncSelectedAttempt(); @@ -1344,7 +1344,7 @@ void UHyperTwistCoachDashboardWidget::HandleStartQueuedRunClicked() void UHyperTwistCoachDashboardWidget::HandleStartFollowUpRunClicked() { - StartCoachFollowUpRun(DefaultCoachMaxCases); + StartCoachFollowUpRun(DefaultCoachMaxCases, TEXT("dashboard-handoff-follow-up")); SyncRetainedRunRecap(); SyncSelectedQueueEntry(); SyncSelectedAttempt(); @@ -1353,7 +1353,7 @@ void UHyperTwistCoachDashboardWidget::HandleStartFollowUpRunClicked() void UHyperTwistCoachDashboardWidget::HandleStartRecommendedRunClicked() { - StartCoachRecommendedRun(DefaultCoachMaxCases); + StartCoachRecommendedRun(DefaultCoachMaxCases, TEXT("dashboard-handoff-recommended")); SyncRetainedRunRecap(); SyncSelectedQueueEntry(); SyncSelectedAttempt(); diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp index fed0eff..b4ad341 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp @@ -118,37 +118,49 @@ FHyperTwistTrainingDeck UHyperTwistTrainingPanelWidget::BuildActiveCoachFollowUp return CachedCoachFollowUpDeck; } -FHyperTwistTrainingRunState UHyperTwistTrainingPanelWidget::StartCoachRecommendedRun(const int32 MaxCases) +FHyperTwistTrainingRunState UHyperTwistTrainingPanelWidget::StartCoachRecommendedRun( + const int32 MaxCases, + const FString& StartActionSourceLabel +) { LastStepResult = FHyperTwistTrainingRunStepResult(); CachedRunState = UHyperTwistTrainingRuntimeLibrary::StartCoachRecommendedRun( this, DefaultSessionId + TEXT("_coach"), - MaxCases > 0 ? MaxCases : DefaultCoachMaxCases + MaxCases > 0 ? MaxCases : DefaultCoachMaxCases, + StartActionSourceLabel ); RefreshTrainingState(); return CachedRunState; } -FHyperTwistTrainingRunState UHyperTwistTrainingPanelWidget::StartCoachFollowUpRun(const int32 MaxCases) +FHyperTwistTrainingRunState UHyperTwistTrainingPanelWidget::StartCoachFollowUpRun( + const int32 MaxCases, + const FString& StartActionSourceLabel +) { LastStepResult = FHyperTwistTrainingRunStepResult(); CachedRunState = UHyperTwistTrainingRuntimeLibrary::StartCoachFollowUpRun( this, DefaultSessionId + TEXT("_follow_up"), - MaxCases > 0 ? MaxCases : DefaultCoachMaxCases + MaxCases > 0 ? MaxCases : DefaultCoachMaxCases, + StartActionSourceLabel ); RefreshTrainingState(); return CachedRunState; } -FHyperTwistTrainingRunState UHyperTwistTrainingPanelWidget::StartNextQueuedCoachRun(const int32 MaxCases) +FHyperTwistTrainingRunState UHyperTwistTrainingPanelWidget::StartNextQueuedCoachRun( + const int32 MaxCases, + const FString& StartActionSourceLabel +) { LastStepResult = FHyperTwistTrainingRunStepResult(); CachedRunState = UHyperTwistTrainingRuntimeLibrary::StartNextQueuedCoachRun( this, DefaultSessionId + TEXT("_queue"), - MaxCases > 0 ? MaxCases : DefaultCoachMaxCases + MaxCases > 0 ? MaxCases : DefaultCoachMaxCases, + StartActionSourceLabel ); RefreshTrainingState(); return CachedRunState; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp index c238a19..a242a31 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp @@ -259,16 +259,35 @@ namespace HyperTwistTrainingRepositoryLibraryInternal 0, 5 ); + const int32 ExplicitQueuedHandoffSignal = FMath::Clamp( + QueueExecutionSummary.StartedQueuedHandoffEventCount, + 0, + 3 + ); + const int32 ExplicitFollowUpHandoffSignal = FMath::Clamp( + QueueExecutionSummary.StartedFollowUpHandoffEventCount * 2, + 0, + 4 + ); + const int32 ExplicitRecommendedHandoffSignal = FMath::Clamp( + QueueExecutionSummary.StartedRecommendedHandoffEventCount * 2, + 0, + 4 + ); + const int32 WeightedFollowUpRecoverySignal = + ExplicitFollowUpRecoverySignal + ExplicitFollowUpHandoffSignal; + const int32 WeightedRecommendedRecoverySignal = + ExplicitRecommendedRecoverySignal + ExplicitRecommendedHandoffSignal; const bool bQueueHistoryPrefersFollowUp = DominantDeferredLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp || (DominantCompletedLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp && QueueExecutionSummary.PromotedEventCount > 0) - || ExplicitFollowUpRecoverySignal > ExplicitRecommendedRecoverySignal; + || WeightedFollowUpRecoverySignal > WeightedRecommendedRecoverySignal; const bool bQueueHistoryPrefersRecommended = DominantDeferredLane == EHyperTwistTrainingCoachGuidanceLane::Recommended || (DominantCompletedLane == EHyperTwistTrainingCoachGuidanceLane::Recommended && QueueExecutionSummary.PromotedEventCount > 0) - || ExplicitRecommendedRecoverySignal > ExplicitFollowUpRecoverySignal; + || WeightedRecommendedRecoverySignal > WeightedFollowUpRecoverySignal; const float PromoteRecoveryActionPressure = (QueueExecutionSummary.bHasOutstandingDeferredWork ? 4.0f : 0.0f) + FMath::Min(static_cast(QueueExecutionSummary.DeferredEventCount) * 0.75f, 3.0f) @@ -281,7 +300,8 @@ namespace HyperTwistTrainingRepositoryLibraryInternal + (QueueExecutionSummary.bHasRepeatManualRescheduleFriction ? 2.0f : 0.0f) + (QueueExecutionSummary.bHasArchiveAwareFriction ? 1.0f : 0.0f) + FMath::Min(static_cast(QueueExecutionSummary.CompletedEventCount), 3.0f) - + FMath::Min(static_cast(QueueExecutionSummary.ManualRescheduleEventCount) * 0.5f, 2.0f); + + FMath::Min(static_cast(QueueExecutionSummary.ManualRescheduleEventCount) * 0.5f, 2.0f) + + static_cast(ExplicitQueuedHandoffSignal); if (PromoteRecoveryActionPressure > 0.0f || LaunchRecoveryActionPressure > 0.0f) { if (PromoteRecoveryActionPressure > LaunchRecoveryActionPressure) @@ -294,7 +314,9 @@ namespace HyperTwistTrainingRepositoryLibraryInternal { Snapshot.PreferredQueueRecoveryAction = EHyperTwistTrainingQueueRecoveryAction::LaunchRecovery; - Snapshot.PreferredQueueRecoveryActionSourceLabel = TEXT("queue-history-launch-pressure"); + Snapshot.PreferredQueueRecoveryActionSourceLabel = ExplicitQueuedHandoffSignal > 0 + ? TEXT("dashboard-handoff-queued") + : TEXT("queue-history-launch-pressure"); } else { @@ -311,14 +333,14 @@ namespace HyperTwistTrainingRepositoryLibraryInternal { Snapshot.FollowUpGuidanceContinueBias += FMath::Max( 1, - DeferredRecoverySignal + PromotionRecoverySignal + ExplicitFollowUpRecoverySignal + DeferredRecoverySignal + PromotionRecoverySignal + WeightedFollowUpRecoverySignal ); } if (bQueueHistoryPrefersRecommended) { Snapshot.RecommendedGuidanceContinueBias += FMath::Max( 1, - DeferredRecoverySignal + PromotionRecoverySignal + ExplicitRecommendedRecoverySignal + DeferredRecoverySignal + PromotionRecoverySignal + WeightedRecommendedRecoverySignal - (QueueExecutionSummary.bHasBlockedFollowUpFriction ? 1 : 0) ); } @@ -328,7 +350,7 @@ namespace HyperTwistTrainingRepositoryLibraryInternal Snapshot.SuppressedPrimaryQueuePressureCount += FMath::Max( 1, DeferredRecoverySignal - + ExplicitFollowUpRecoverySignal + + WeightedFollowUpRecoverySignal + (QueueExecutionSummary.bHasBlockedFollowUpFriction ? 1 : 0) ); } @@ -338,7 +360,7 @@ namespace HyperTwistTrainingRepositoryLibraryInternal Snapshot.SuppressedFollowUpQueuePressureCount += FMath::Max( 1, PromotionRecoverySignal - + ExplicitRecommendedRecoverySignal + + WeightedRecommendedRecoverySignal + (QueueExecutionSummary.bHasCompletionGap ? 1 : 0) ); } @@ -349,14 +371,18 @@ namespace HyperTwistTrainingRepositoryLibraryInternal || QueueExecutionSummary.BlockedFollowUpEventCount > 0)) { Snapshot.PreferredGuidanceLane = EHyperTwistTrainingCoachGuidanceLane::FollowUp; - Snapshot.PreferredGuidanceSourceLabel = TEXT("coach-memory-queue-follow-up"); + Snapshot.PreferredGuidanceSourceLabel = ExplicitFollowUpHandoffSignal > 0 + ? TEXT("coach-memory-dashboard-follow-up") + : TEXT("coach-memory-queue-follow-up"); } else if (bQueueHistoryPrefersRecommended && (QueueExecutionSummary.PromotedEventCount > 0 || QueueExecutionSummary.CompletedEventCount > 0)) { Snapshot.PreferredGuidanceLane = EHyperTwistTrainingCoachGuidanceLane::Recommended; - Snapshot.PreferredGuidanceSourceLabel = TEXT("coach-memory-queue-recommended"); + Snapshot.PreferredGuidanceSourceLabel = ExplicitRecommendedHandoffSignal > 0 + ? TEXT("coach-memory-dashboard-recommended") + : TEXT("coach-memory-queue-recommended"); } return Snapshot; @@ -1097,6 +1123,7 @@ namespace HyperTwistTrainingRepositoryLibraryInternal || PreviousEntry.CompletedAtUtc != CurrentEntry.CompletedAtUtc || PreviousEntry.RescheduleCount != CurrentEntry.RescheduleCount || PreviousEntry.SourceTrainingSessionId != CurrentEntry.SourceTrainingSessionId + || PreviousEntry.StartActionSourceLabel != CurrentEntry.StartActionSourceLabel || PreviousEntry.bWasManuallyRescheduled != CurrentEntry.bWasManuallyRescheduled; } @@ -1165,13 +1192,14 @@ namespace HyperTwistTrainingRepositoryLibraryInternal ) { return FString::Printf( - TEXT("%s|%s|%d|%s|%s|%s"), + TEXT("%s|%s|%d|%s|%s|%s|%s"), *Entry.QueueId, *Entry.EntryId, static_cast(Entry.EventKind), *Entry.EventAtUtc, *Entry.WorkFingerprint, - *Entry.SourceTrainingSessionId + *Entry.SourceTrainingSessionId, + *Entry.StartActionSourceLabel ); } @@ -1212,6 +1240,7 @@ namespace HyperTwistTrainingRepositoryLibraryInternal : CurrentEntry.EntryState; HistoryEntry.NewEntryState = CurrentEntry.EntryState; HistoryEntry.SourceTrainingSessionId = CurrentEntry.SourceTrainingSessionId; + HistoryEntry.StartActionSourceLabel = CurrentEntry.StartActionSourceLabel; HistoryEntry.DeferredUntilUtc = CurrentEntry.DeferredUntilUtc; HistoryEntry.DeferralReasonLabel = CurrentEntry.DeferralReasonLabel; HistoryEntry.FocusDeckId = CurrentEntry.FocusDeckId; @@ -5443,16 +5472,35 @@ FHyperTwistTrainingCoachMemorySnapshot UHyperTwistTrainingRepositoryLibrary::Der 0, 5 ); + const int32 ExplicitQueuedHandoffSignal = FMath::Clamp( + QueueExecutionSummary.StartedQueuedHandoffEventCount, + 0, + 3 + ); + const int32 ExplicitFollowUpHandoffSignal = FMath::Clamp( + QueueExecutionSummary.StartedFollowUpHandoffEventCount * 2, + 0, + 4 + ); + const int32 ExplicitRecommendedHandoffSignal = FMath::Clamp( + QueueExecutionSummary.StartedRecommendedHandoffEventCount * 2, + 0, + 4 + ); + const int32 WeightedFollowUpRecoverySignal = + ExplicitFollowUpRecoverySignal + ExplicitFollowUpHandoffSignal; + const int32 WeightedRecommendedRecoverySignal = + ExplicitRecommendedRecoverySignal + ExplicitRecommendedHandoffSignal; const bool bQueueHistoryPrefersFollowUp = DominantDeferredLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp || (DominantCompletedLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp && QueueExecutionSummary.PromotedEventCount > 0) - || ExplicitFollowUpRecoverySignal > ExplicitRecommendedRecoverySignal; + || WeightedFollowUpRecoverySignal > WeightedRecommendedRecoverySignal; const bool bQueueHistoryPrefersRecommended = DominantDeferredLane == EHyperTwistTrainingCoachGuidanceLane::Recommended || (DominantCompletedLane == EHyperTwistTrainingCoachGuidanceLane::Recommended && QueueExecutionSummary.PromotedEventCount > 0) - || ExplicitRecommendedRecoverySignal > ExplicitFollowUpRecoverySignal; + || WeightedRecommendedRecoverySignal > WeightedFollowUpRecoverySignal; const float PromoteRecoveryActionPressure = (QueueExecutionSummary.bHasOutstandingDeferredWork ? 4.0f : 0.0f) + FMath::Min(static_cast(QueueExecutionSummary.DeferredEventCount) * 0.75f, 3.0f) @@ -5465,7 +5513,8 @@ FHyperTwistTrainingCoachMemorySnapshot UHyperTwistTrainingRepositoryLibrary::Der + (QueueExecutionSummary.bHasRepeatManualRescheduleFriction ? 2.0f : 0.0f) + (QueueExecutionSummary.bHasArchiveAwareFriction ? 1.0f : 0.0f) + FMath::Min(static_cast(QueueExecutionSummary.CompletedEventCount), 3.0f) - + FMath::Min(static_cast(QueueExecutionSummary.ManualRescheduleEventCount) * 0.5f, 2.0f); + + FMath::Min(static_cast(QueueExecutionSummary.ManualRescheduleEventCount) * 0.5f, 2.0f) + + static_cast(ExplicitQueuedHandoffSignal); if (PromoteRecoveryActionPressure > 0.0f || LaunchRecoveryActionPressure > 0.0f) { if (PromoteRecoveryActionPressure > LaunchRecoveryActionPressure) @@ -5478,7 +5527,9 @@ FHyperTwistTrainingCoachMemorySnapshot UHyperTwistTrainingRepositoryLibrary::Der { Snapshot.PreferredQueueRecoveryAction = EHyperTwistTrainingQueueRecoveryAction::LaunchRecovery; - Snapshot.PreferredQueueRecoveryActionSourceLabel = TEXT("queue-history-launch-pressure"); + Snapshot.PreferredQueueRecoveryActionSourceLabel = ExplicitQueuedHandoffSignal > 0 + ? TEXT("dashboard-handoff-queued") + : TEXT("queue-history-launch-pressure"); } else { @@ -5495,14 +5546,14 @@ FHyperTwistTrainingCoachMemorySnapshot UHyperTwistTrainingRepositoryLibrary::Der { Snapshot.FollowUpGuidanceContinueBias += FMath::Max( 1, - DeferredRecoverySignal + PromotionRecoverySignal + ExplicitFollowUpRecoverySignal + DeferredRecoverySignal + PromotionRecoverySignal + WeightedFollowUpRecoverySignal ); } if (bQueueHistoryPrefersRecommended) { Snapshot.RecommendedGuidanceContinueBias += FMath::Max( 1, - DeferredRecoverySignal + PromotionRecoverySignal + ExplicitRecommendedRecoverySignal + DeferredRecoverySignal + PromotionRecoverySignal + WeightedRecommendedRecoverySignal - (QueueExecutionSummary.bHasBlockedFollowUpFriction ? 1 : 0) ); } @@ -5512,7 +5563,7 @@ FHyperTwistTrainingCoachMemorySnapshot UHyperTwistTrainingRepositoryLibrary::Der Snapshot.SuppressedPrimaryQueuePressureCount += FMath::Max( 1, DeferredRecoverySignal - + ExplicitFollowUpRecoverySignal + + WeightedFollowUpRecoverySignal + (QueueExecutionSummary.bHasBlockedFollowUpFriction ? 1 : 0) ); } @@ -5522,7 +5573,7 @@ FHyperTwistTrainingCoachMemorySnapshot UHyperTwistTrainingRepositoryLibrary::Der Snapshot.SuppressedFollowUpQueuePressureCount += FMath::Max( 1, PromotionRecoverySignal - + ExplicitRecommendedRecoverySignal + + WeightedRecommendedRecoverySignal + (QueueExecutionSummary.bHasCompletionGap ? 1 : 0) ); } @@ -5617,7 +5668,9 @@ FHyperTwistTrainingCoachMemorySnapshot UHyperTwistTrainingRepositoryLibrary::Der || QueueExecutionSummary.BlockedFollowUpEventCount > 0)) { Snapshot.PreferredGuidanceLane = EHyperTwistTrainingCoachGuidanceLane::FollowUp; - Snapshot.PreferredGuidanceSourceLabel = TEXT("coach-memory-queue-follow-up"); + Snapshot.PreferredGuidanceSourceLabel = ExplicitFollowUpHandoffSignal > 0 + ? TEXT("coach-memory-dashboard-follow-up") + : TEXT("coach-memory-queue-follow-up"); } else if (bQueueHistoryPrefersRecommended && Snapshot.PreferredGuidanceLane != EHyperTwistTrainingCoachGuidanceLane::FollowUp @@ -5625,7 +5678,9 @@ FHyperTwistTrainingCoachMemorySnapshot UHyperTwistTrainingRepositoryLibrary::Der || QueueExecutionSummary.CompletedEventCount > 0)) { Snapshot.PreferredGuidanceLane = EHyperTwistTrainingCoachGuidanceLane::Recommended; - Snapshot.PreferredGuidanceSourceLabel = TEXT("coach-memory-queue-recommended"); + Snapshot.PreferredGuidanceSourceLabel = ExplicitRecommendedHandoffSignal > 0 + ? TEXT("coach-memory-dashboard-recommended") + : TEXT("coach-memory-queue-recommended"); } } } @@ -6689,7 +6744,8 @@ FHyperTwistTrainingCoachSessionQueueState UHyperTwistTrainingRepositoryLibrary:: const FHyperTwistTrainingCoachSessionQueueState& QueueState, const FString& SourceLabel, const FString& TrainingSessionId, - const FString& StartedAtUtc + const FString& StartedAtUtc, + const FString& StartActionSourceLabel ) { FHyperTwistTrainingCoachSessionQueueState UpdatedQueueState = @@ -6724,6 +6780,7 @@ FHyperTwistTrainingCoachSessionQueueState UHyperTwistTrainingRepositoryLibrary:: { Entry.EntryState = EHyperTwistTrainingCoachSessionQueueEntryState::Pending; Entry.SourceTrainingSessionId.Reset(); + Entry.StartActionSourceLabel.Reset(); Entry.StartedAtUtc.Reset(); Entry.bCanStartImmediately = false; } @@ -6733,6 +6790,9 @@ FHyperTwistTrainingCoachSessionQueueState UHyperTwistTrainingRepositoryLibrary:: UpdatedQueueState.Entries[ActivatedIndex]; ActiveEntry.EntryState = EHyperTwistTrainingCoachSessionQueueEntryState::InProgress; ActiveEntry.SourceTrainingSessionId = TrainingSessionId; + ActiveEntry.StartActionSourceLabel = !StartActionSourceLabel.IsEmpty() + ? StartActionSourceLabel + : SourceLabel; ActiveEntry.ScheduledAtUtc = !ActiveEntry.ScheduledAtUtc.IsEmpty() ? ActiveEntry.ScheduledAtUtc : ResolvedStartedAtUtc; @@ -7223,6 +7283,18 @@ FHyperTwistTrainingCoachSessionQueueExecutionSummary UHyperTwistTrainingReposito { Summary.StartedRecommendedEventCount += 1; } + if (HistoryEntry.StartActionSourceLabel == TEXT("dashboard-handoff-queued")) + { + Summary.StartedQueuedHandoffEventCount += 1; + } + else if (HistoryEntry.StartActionSourceLabel == TEXT("dashboard-handoff-follow-up")) + { + Summary.StartedFollowUpHandoffEventCount += 1; + } + else if (HistoryEntry.StartActionSourceLabel == TEXT("dashboard-handoff-recommended")) + { + Summary.StartedRecommendedHandoffEventCount += 1; + } if (Summary.LastStartedAtUtc.IsEmpty() || HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings( HistoryEntry.EventAtUtc, diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.cpp index ef34513..1f1ef17 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.cpp @@ -1004,12 +1004,13 @@ FHyperTwistTrainingRunState UHyperTwistTrainingRuntimeLibrary::StartRecommendedR FHyperTwistTrainingRunState UHyperTwistTrainingRuntimeLibrary::StartCoachRecommendedRun( UObject* WorldContextObject, const FString& SessionId, - int32 MaxCases + int32 MaxCases, + const FString& StartActionSourceLabel ) { if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject)) { - return TrainingSubsystem->StartCoachRecommendedRun(SessionId, MaxCases); + return TrainingSubsystem->StartCoachRecommendedRun(SessionId, MaxCases, StartActionSourceLabel); } return FHyperTwistTrainingRunState(); @@ -1018,12 +1019,13 @@ FHyperTwistTrainingRunState UHyperTwistTrainingRuntimeLibrary::StartCoachRecomme FHyperTwistTrainingRunState UHyperTwistTrainingRuntimeLibrary::StartCoachFollowUpRun( UObject* WorldContextObject, const FString& SessionId, - int32 MaxCases + int32 MaxCases, + const FString& StartActionSourceLabel ) { if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject)) { - return TrainingSubsystem->StartCoachFollowUpRun(SessionId, MaxCases); + return TrainingSubsystem->StartCoachFollowUpRun(SessionId, MaxCases, StartActionSourceLabel); } return FHyperTwistTrainingRunState(); @@ -1032,12 +1034,13 @@ FHyperTwistTrainingRunState UHyperTwistTrainingRuntimeLibrary::StartCoachFollowU FHyperTwistTrainingRunState UHyperTwistTrainingRuntimeLibrary::StartNextQueuedCoachRun( UObject* WorldContextObject, const FString& SessionId, - int32 MaxCases + int32 MaxCases, + const FString& StartActionSourceLabel ) { if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject)) { - return TrainingSubsystem->StartNextQueuedCoachRun(SessionId, MaxCases); + return TrainingSubsystem->StartNextQueuedCoachRun(SessionId, MaxCases, StartActionSourceLabel); } return FHyperTwistTrainingRunState(); diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp index e345356..a722960 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp @@ -129,7 +129,8 @@ FHyperTwistTrainingRunState AHyperTwistTrainingSessionActor::StartCoachRecommend CachedRunState = UHyperTwistTrainingRuntimeLibrary::StartCoachRecommendedRun( this, AutoStartSessionId + TEXT("_coach"), - MaxCases > 0 ? MaxCases : DefaultCoachMaxCases + MaxCases > 0 ? MaxCases : DefaultCoachMaxCases, + FString() ); RefreshCachedTrainingState(); return CachedRunState; @@ -142,7 +143,8 @@ FHyperTwistTrainingRunState AHyperTwistTrainingSessionActor::StartCoachFollowUpT CachedRunState = UHyperTwistTrainingRuntimeLibrary::StartCoachFollowUpRun( this, AutoStartSessionId + TEXT("_follow_up"), - MaxCases > 0 ? MaxCases : DefaultCoachMaxCases + MaxCases > 0 ? MaxCases : DefaultCoachMaxCases, + FString() ); RefreshCachedTrainingState(); return CachedRunState; @@ -155,7 +157,8 @@ FHyperTwistTrainingRunState AHyperTwistTrainingSessionActor::StartNextQueuedCoac CachedRunState = UHyperTwistTrainingRuntimeLibrary::StartNextQueuedCoachRun( this, AutoStartSessionId + TEXT("_queue"), - MaxCases > 0 ? MaxCases : DefaultCoachMaxCases + MaxCases > 0 ? MaxCases : DefaultCoachMaxCases, + FString() ); RefreshCachedTrainingState(); return CachedRunState; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp index d9f2df3..6de7301 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp @@ -1389,7 +1389,8 @@ FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartRecommendedReview FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartCoachRecommendedRun( const FString& SessionId, - int32 MaxCases + int32 MaxCases, + const FString& StartActionSourceLabel ) { const FHyperTwistTrainingDeck CoachDeck = BuildActiveCoachRecommendedDeck(MaxCases); @@ -1434,7 +1435,8 @@ FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartCoachRecommendedR QueueStateBeforeStart, TEXT("primary-brief"), RunState.Session.TrainingSessionId, - RunState.Session.StartedAtUtc + RunState.Session.StartedAtUtc, + StartActionSourceLabel ); if (UpdatedQueueState.IsStructurallyValid()) { @@ -1451,7 +1453,8 @@ FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartCoachRecommendedR FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartCoachFollowUpRun( const FString& SessionId, - int32 MaxCases + int32 MaxCases, + const FString& StartActionSourceLabel ) { const FHyperTwistTrainingDeck FollowUpDeck = BuildActiveCoachFollowUpDeck(MaxCases); @@ -1496,7 +1499,8 @@ FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartCoachFollowUpRun( QueueStateBeforeStart, TEXT("follow-up-brief"), RunState.Session.TrainingSessionId, - RunState.Session.StartedAtUtc + RunState.Session.StartedAtUtc, + StartActionSourceLabel ); if (UpdatedQueueState.IsStructurallyValid()) { @@ -1513,7 +1517,8 @@ FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartCoachFollowUpRun( FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartNextQueuedCoachRun( const FString& SessionId, - int32 MaxCases + int32 MaxCases, + const FString& StartActionSourceLabel ) { const FHyperTwistTrainingCoachSessionQueueStateEntry* NextRunnableEntry = @@ -1525,11 +1530,11 @@ FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartNextQueuedCoachRu if (NextRunnableEntry->SourceLabel == TEXT("primary-brief")) { - return StartCoachRecommendedRun(SessionId, MaxCases); + return StartCoachRecommendedRun(SessionId, MaxCases, StartActionSourceLabel); } if (NextRunnableEntry->SourceLabel == TEXT("follow-up-brief")) { - return StartCoachFollowUpRun(SessionId, MaxCases); + return StartCoachFollowUpRun(SessionId, MaxCases, StartActionSourceLabel); } const FHyperTwistTrainingDeck QueueDeck = HyperTwistTrainingSubsystemInternal::BuildCoachQueueDeckFromEntry( @@ -1590,7 +1595,8 @@ FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartNextQueuedCoachRu QueueStateBeforeStart, NextRunnableEntry->SourceLabel, RunState.Session.TrainingSessionId, - RunState.Session.StartedAtUtc + RunState.Session.StartedAtUtc, + StartActionSourceLabel ); if (UpdatedQueueState.IsStructurallyValid()) { diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h index d146496..d8178a2 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h @@ -110,13 +110,22 @@ public: FHyperTwistTrainingDeck BuildActiveCoachFollowUpDeck(int32 MaxCases); UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach") - FHyperTwistTrainingRunState StartCoachRecommendedRun(int32 MaxCases); + FHyperTwistTrainingRunState StartCoachRecommendedRun( + int32 MaxCases, + const FString& StartActionSourceLabel + ); UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach") - FHyperTwistTrainingRunState StartCoachFollowUpRun(int32 MaxCases); + FHyperTwistTrainingRunState StartCoachFollowUpRun( + int32 MaxCases, + const FString& StartActionSourceLabel + ); UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach") - FHyperTwistTrainingRunState StartNextQueuedCoachRun(int32 MaxCases); + FHyperTwistTrainingRunState StartNextQueuedCoachRun( + int32 MaxCases, + const FString& StartActionSourceLabel + ); UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach") FHyperTwistTrainingRunState StartCoachQueueEntry( diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.h index 696fed0..abec327 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.h @@ -367,7 +367,8 @@ public: const FHyperTwistTrainingCoachSessionQueueState& QueueState, const FString& SourceLabel, const FString& TrainingSessionId, - const FString& StartedAtUtc + const FString& StartedAtUtc, + const FString& StartActionSourceLabel ); UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Coach") diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.h index 0db8837..b189838 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.h @@ -407,21 +407,24 @@ public: static FHyperTwistTrainingRunState StartCoachRecommendedRun( UObject* WorldContextObject, const FString& SessionId, - int32 MaxCases + int32 MaxCases, + const FString& StartActionSourceLabel ); UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject")) static FHyperTwistTrainingRunState StartCoachFollowUpRun( UObject* WorldContextObject, const FString& SessionId, - int32 MaxCases + int32 MaxCases, + const FString& StartActionSourceLabel ); UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject")) static FHyperTwistTrainingRunState StartNextQueuedCoachRun( UObject* WorldContextObject, const FString& SessionId, - int32 MaxCases + int32 MaxCases, + const FString& StartActionSourceLabel ); UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject")) diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSubsystem.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSubsystem.h index 7492ee2..e10d919 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSubsystem.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSubsystem.h @@ -327,19 +327,22 @@ public: UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach") FHyperTwistTrainingRunState StartCoachRecommendedRun( const FString& SessionId, - int32 MaxCases + int32 MaxCases, + const FString& StartActionSourceLabel ); UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach") FHyperTwistTrainingRunState StartCoachFollowUpRun( const FString& SessionId, - int32 MaxCases + int32 MaxCases, + const FString& StartActionSourceLabel ); UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach") FHyperTwistTrainingRunState StartNextQueuedCoachRun( const FString& SessionId, - int32 MaxCases + int32 MaxCases, + const FString& StartActionSourceLabel ); UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach") diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h index 473acfa..0e0fd4e 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h @@ -3001,6 +3001,9 @@ struct FHyperTwistTrainingCoachSessionQueueStateEntry UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FString SourceTrainingSessionId; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString StartActionSourceLabel; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") int32 SlotIndex = 0; @@ -3189,6 +3192,9 @@ struct FHyperTwistTrainingCoachSessionQueueHistoryEntry UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FString SourceTrainingSessionId; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString StartActionSourceLabel; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FString DeferredUntilUtc; @@ -3273,6 +3279,15 @@ struct FHyperTwistTrainingCoachSessionQueueExecutionSummary UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") int32 StartedRecommendedEventCount = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 StartedQueuedHandoffEventCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 StartedFollowUpHandoffEventCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 StartedRecommendedHandoffEventCount = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") int32 PromotedFollowUpEventCount = 0;