From 620b3a1129bf2170d1b568403a20c49873345226 Mon Sep 17 00:00:00 2001 From: axiomlogicnexus Date: Mon, 27 Apr 2026 23:54:07 +0200 Subject: [PATCH] Explain launch budget memory in coaching --- .../HyperTwistCoachDashboardWidget.cpp | 48 ++++- .../HyperTwistTrainingCoachLibrary.cpp | 171 ++++++++++++++++++ .../HyperTwistTrainingSubsystem.cpp | 20 ++ .../HyperTwistTrainingCoachLibrary.h | 12 ++ .../HyperTwistTrainingTypes.h | 12 ++ 5 files changed, 259 insertions(+), 4 deletions(-) diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp index e02b51a..0df3b7d 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp @@ -129,10 +129,32 @@ namespace HyperTwistCoachDashboardWidgetInternal { return TEXT("balanced queue-recovery history"); } + if (SourceLabel == TEXT("recommendation-history-broadened-launch")) + { + return TEXT("successful broadened-launch history"); + } + if (SourceLabel == TEXT("recommendation-history-tightened-launch")) + { + return TEXT("successful tightened-launch history"); + } return SourceLabel.IsEmpty() ? TEXT("n/a") : SourceLabel; } + FString DescribeLaunchBudgetBias(const int32 Bias) + { + if (Bias > 0) + { + return TEXT("broaden"); + } + if (Bias < 0) + { + return TEXT("tighten"); + } + + return TEXT("steady"); + } + UTextBlock* AddTextRow( UWidgetTree* WidgetTree, UVerticalBox* Parent, @@ -4964,7 +4986,7 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() } 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 | Handoff history preferred: %s | accepted q/f/r %d/%d/%d | recommended q/f/r %d/%d/%d"), + 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 | Launch bias: %s from %s | broadened/tightened history %d/%d | Handoff history preferred: %s | accepted q/f/r %d/%d/%d | recommended q/f/r %d/%d/%d"), HandoffClosureBias.ContinuationBias, HandoffClosureBias.SuppressionBias, bQueueReadyForHandoff ? TEXT("yes") : TEXT("no"), @@ -4979,6 +5001,12 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() bHandoffRecommendationUsedHistory ? TEXT(" | Retained handoff history actively weighted this recommendation.") : TEXT(""), + *HyperTwistCoachDashboardWidgetInternal::DescribeLaunchBudgetBias( + CachedCoachPanelState.PreferredLaunchBudgetBias), + *HyperTwistCoachDashboardWidgetInternal::DescribeCoachMemorySourceLabel( + CachedCoachPanelState.PreferredLaunchBudgetSourceLabel), + CachedCoachPanelState.BroadenedLaunchBudgetHistoryCount, + CachedCoachPanelState.TightenedLaunchBudgetHistoryCount, *HyperTwistCoachDashboardWidgetInternal::DescribeHandoffRecommendationKind( HandoffHistoryBias.PreferredKind), HandoffHistoryBias.QueueAcceptedDeltaCount, @@ -5170,7 +5198,7 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() const FString GuidancePreferenceDetailLine = GuidanceLanePreference.IsStructurallyValid() ? FString::Printf( - TEXT("Recommended lane: %d decisions | Continue bias %d | Avg success %.2f | Closure suppress bias %d || Follow-up lane: %d decisions | Continue bias %d | Avg success %.2f | Closure continuation bias %d | The dashboard adopts this preferred lane whenever it needs to surface the next guidance preview."), + TEXT("Recommended lane: %d decisions | Continue bias %d | Avg success %.2f | Closure suppress bias %d || Follow-up lane: %d decisions | Continue bias %d | Avg success %.2f | Closure continuation bias %d | Launch bias: %s from %s | broadened/tightened history %d/%d | The dashboard adopts this preferred lane whenever it needs to surface the next guidance preview."), GuidanceLanePreference.RecommendedDecisionCount, GuidanceLanePreference.RecommendedContinueBias, GuidanceLanePreference.RecommendedAverageSuccessRate, @@ -5178,7 +5206,13 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() GuidanceLanePreference.FollowUpDecisionCount, GuidanceLanePreference.FollowUpContinueBias, GuidanceLanePreference.FollowUpAverageSuccessRate, - GuidanceLanePreference.ClosureContinuationBias + GuidanceLanePreference.ClosureContinuationBias, + *HyperTwistCoachDashboardWidgetInternal::DescribeLaunchBudgetBias( + CachedCoachPanelState.PreferredLaunchBudgetBias), + *HyperTwistCoachDashboardWidgetInternal::DescribeCoachMemorySourceLabel( + CachedCoachPanelState.PreferredLaunchBudgetSourceLabel), + CachedCoachPanelState.BroadenedLaunchBudgetHistoryCount, + CachedCoachPanelState.TightenedLaunchBudgetHistoryCount ) : TEXT("Preference detail: build recommended or follow-up guidance to let the dashboard choose a next surfaced lane."); const bool bQueueRecoveryMemorySource = @@ -5510,7 +5544,7 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() const FString GuidanceRationaleDetailLine = ActivePreviewBrief != nullptr ? FString::Printf( - TEXT("Brief: %s | Action: %s | Deck: %s | Method: %s | Focus cases: %d | Review: %s | Cadence: %s | Variety: %s | Guidance source: %s | Selected focus: %s | Matching signals: %d | Case signal: %s | Top signals: %s"), + TEXT("Brief: %s | Action: %s | Deck: %s | Method: %s | Focus cases: %d | Review: %s | Cadence: %s | Variety: %s | Guidance source: %s | Launch bias: %s from %s | broadened/tightened history %d/%d | Selected focus: %s | Matching signals: %d | Case signal: %s | Top signals: %s"), ActivePreviewBrief->Headline.IsEmpty() ? TEXT("n/a") : *ActivePreviewBrief->Headline, ActivePreviewBrief->PrimaryActionLabel.IsEmpty() ? TEXT("n/a") : *ActivePreviewBrief->PrimaryActionLabel, ActivePreviewBrief->FocusDeckId.IsEmpty() ? TEXT("n/a") : *ActivePreviewBrief->FocusDeckId, @@ -5521,6 +5555,12 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() ActivePreviewBrief->bNeedsVarietyRotation ? TEXT("yes") : TEXT("no"), *HyperTwistCoachDashboardWidgetInternal::DescribeCoachMemorySourceLabel( CachedCoachPanelState.PreferredGuidanceSourceLabel), + *HyperTwistCoachDashboardWidgetInternal::DescribeLaunchBudgetBias( + CachedCoachPanelState.PreferredLaunchBudgetBias), + *HyperTwistCoachDashboardWidgetInternal::DescribeCoachMemorySourceLabel( + CachedCoachPanelState.PreferredLaunchBudgetSourceLabel), + CachedCoachPanelState.BroadenedLaunchBudgetHistoryCount, + CachedCoachPanelState.TightenedLaunchBudgetHistoryCount, bSelectedPreviewCaseIsExplicitFocus ? TEXT("yes") : TEXT("no"), SelectedPreviewCaseSignalCount, *SelectedPreviewCaseSignalLine, diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp index 91abc61..2b128d9 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp @@ -82,6 +82,14 @@ namespace HyperTwistTrainingCoachLibraryInternal { return TEXT("balanced queue-recovery history"); } + if (SourceLabel == TEXT("recommendation-history-broadened-launch")) + { + return TEXT("successful broadened-launch history"); + } + if (SourceLabel == TEXT("recommendation-history-tightened-launch")) + { + return TEXT("successful tightened-launch history"); + } return SourceLabel.IsEmpty() ? TEXT("none") : SourceLabel; } @@ -1790,6 +1798,169 @@ FHyperTwistCoachBrief UHyperTwistTrainingCoachLibrary::RefineCoachBriefWithClosu return Brief; } +FHyperTwistCoachSignal UHyperTwistTrainingCoachLibrary::DeriveLaunchBudgetHistorySignal( + const FHyperTwistTrainingCoachMemorySnapshot& CoachMemorySnapshot, + const FHyperTwistCoachBrief& CurrentBrief) +{ + if (CoachMemorySnapshot.PreferredLaunchBudgetBias == 0 + || (CoachMemorySnapshot.BroadenedLaunchBudgetHistoryCount <= 0 + && CoachMemorySnapshot.TightenedLaunchBudgetHistoryCount <= 0)) + { + return FHyperTwistCoachSignal(); + } + + const bool bBroadenPreferred = CoachMemorySnapshot.PreferredLaunchBudgetBias > 0; + const bool bCompetingGuidancePressure = + CoachMemorySnapshot.RecommendedGuidanceContinueBias > 0 + && CoachMemorySnapshot.FollowUpGuidanceContinueBias > 0; + const float Score = FMath::Clamp( + 58.0f + + static_cast(FMath::Max( + CoachMemorySnapshot.BroadenedLaunchBudgetHistoryCount, + CoachMemorySnapshot.TightenedLaunchBudgetHistoryCount)) * 4.0f + + (bCompetingGuidancePressure ? 4.0f : 0.0f) + + ((CurrentBrief.FocusLane == EHyperTwistCoachFocusLane::Cadence + || CurrentBrief.FocusLane == EHyperTwistCoachFocusLane::Stability + || CurrentBrief.FocusLane == EHyperTwistCoachFocusLane::ReviewBacklog) ? 3.0f : 0.0f), + 0.0f, + 100.0f + ); + const FString SignalId = bBroadenPreferred + ? TEXT("launch_budget_history_broaden") + : TEXT("launch_budget_history_tighten"); + const EHyperTwistCoachFocusLane FocusLane = bBroadenPreferred + ? EHyperTwistCoachFocusLane::ReviewBacklog + : EHyperTwistCoachFocusLane::Stability; + const FString Headline = bBroadenPreferred + ? TEXT("Launch history favors a broader next coach run") + : TEXT("Launch history favors a tighter next coach run"); + const FString PreferredGuidanceLaneLabel = + CoachMemorySnapshot.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp + ? TEXT("follow-up") + : (CoachMemorySnapshot.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::Recommended + ? TEXT("recommended") + : TEXT("none")); + const FString Detail = FString::Printf( + TEXT("launchBias=%s, broadenedHistory=%d, tightenedHistory=%d, guidanceLane=%s, handoff=%s, source=%s"), + bBroadenPreferred ? TEXT("broaden") : TEXT("tighten"), + CoachMemorySnapshot.BroadenedLaunchBudgetHistoryCount, + CoachMemorySnapshot.TightenedLaunchBudgetHistoryCount, + *PreferredGuidanceLaneLabel, + *HyperTwistTrainingCoachLibraryInternal::DescribeCoachHandoffKind( + CoachMemorySnapshot.PreferredCoachHandoffKind), + CoachMemorySnapshot.PreferredLaunchBudgetSourceLabel.IsEmpty() + ? TEXT("n/a") + : *HyperTwistTrainingCoachLibraryInternal::DescribeCoachMemorySourceLabel( + CoachMemorySnapshot.PreferredLaunchBudgetSourceLabel) + ); + const EHyperTwistTrainingDeliveryMode SuggestedMode = + CurrentBrief.SuggestedMode != EHyperTwistTrainingDeliveryMode::Timer + ? CurrentBrief.SuggestedMode + : CoachMemorySnapshot.DominantCoachSuggestedMode; + const EHyperTwistTrainingSelectionPolicy SuggestedSelectionPolicy = + CurrentBrief.SuggestedSelectionPolicy != EHyperTwistTrainingSelectionPolicy::Weighted + ? CurrentBrief.SuggestedSelectionPolicy + : CoachMemorySnapshot.DominantCoachSelectionPolicy; + + FHyperTwistCoachSignal Signal = HyperTwistTrainingCoachLibraryInternal::MakeSignal( + SignalId, + FocusLane, + Score, + Headline, + Detail, + SuggestedMode, + SuggestedSelectionPolicy + ); + Signal.SuggestedDeckId = !CurrentBrief.FocusDeckId.IsEmpty() + ? CurrentBrief.FocusDeckId + : CoachMemorySnapshot.FocusDeckId; + Signal.SuggestedMethodSegmentId = !CurrentBrief.FocusMethodSegmentId.IsEmpty() + ? CurrentBrief.FocusMethodSegmentId + : CoachMemorySnapshot.FocusMethodSegmentId; + Signal.SuggestedCaseIds = CurrentBrief.FocusCaseIds.Num() > 0 + ? CurrentBrief.FocusCaseIds + : (CoachMemorySnapshot.WeakCaseIds.Num() > 0 + ? CoachMemorySnapshot.WeakCaseIds + : CoachMemorySnapshot.DueCaseIds); + HyperTwistTrainingCoachLibraryInternal::ApplyGuidancePreferenceToSignal( + Signal, + CoachMemorySnapshot.PreferredGuidanceLane, + CoachMemorySnapshot, + bCompetingGuidancePressure + ); + return Signal; +} + +FHyperTwistCoachBrief UHyperTwistTrainingCoachLibrary::RefineCoachBriefWithLaunchBudgetHistorySignal( + const FHyperTwistCoachBrief& BaseBrief, + const FHyperTwistCoachSignal& LaunchBudgetHistorySignal) +{ + if (LaunchBudgetHistorySignal.SignalId.IsEmpty()) + { + return BaseBrief; + } + + FHyperTwistCoachBrief Brief = BaseBrief; + if (!Brief.Signals.ContainsByPredicate( + [&LaunchBudgetHistorySignal](const FHyperTwistCoachSignal& Candidate) + { + return Candidate.SignalId == LaunchBudgetHistorySignal.SignalId; + })) + { + Brief.Signals.Add(LaunchBudgetHistorySignal); + } + HyperTwistTrainingCoachLibraryInternal::SortSignals(Brief.Signals); + + const bool bLaunchFacingBrief = + Brief.FocusLane == EHyperTwistCoachFocusLane::ReviewBacklog + || Brief.FocusLane == EHyperTwistCoachFocusLane::Cadence + || Brief.FocusLane == EHyperTwistCoachFocusLane::Stability + || Brief.bNeedsCadenceRecovery; + const bool bShouldPromoteLaunchSignal = + bLaunchFacingBrief + && LaunchBudgetHistorySignal.Score >= Brief.PriorityScore - 4.0f; + if (bShouldPromoteLaunchSignal) + { + for (FHyperTwistCoachSignal& Signal : Brief.Signals) + { + if (Signal.SignalId == LaunchBudgetHistorySignal.SignalId) + { + Signal.Score = FMath::Clamp( + FMath::Max(Signal.Score, Brief.Signals[0].Score + 0.5f), + 0.0f, + 100.0f + ); + Signal.Priority = HyperTwistTrainingCoachLibraryInternal::PriorityFromScore(Signal.Score); + Signal.bNeedsImmediateAttention = Signal.Priority == EHyperTwistCoachPriority::Critical; + break; + } + } + HyperTwistTrainingCoachLibraryInternal::SortSignals(Brief.Signals); + } + + if (Brief.Signals.Num() > 0) + { + const FString OriginalDeckId = Brief.FocusDeckId; + const FString OriginalMethodSegmentId = Brief.FocusMethodSegmentId; + const TArray OriginalCaseIds = Brief.FocusCaseIds; + HyperTwistTrainingCoachLibraryInternal::ApplyPrimarySignalToBrief(Brief, Brief.Signals[0]); + if (Brief.FocusDeckId.IsEmpty()) + { + Brief.FocusDeckId = OriginalDeckId; + } + if (Brief.FocusMethodSegmentId.IsEmpty()) + { + Brief.FocusMethodSegmentId = OriginalMethodSegmentId; + } + if (Brief.FocusCaseIds.Num() <= 0) + { + Brief.FocusCaseIds = OriginalCaseIds; + } + } + + return Brief; +} + FHyperTwistCoachScheduleHorizonSummary UHyperTwistTrainingCoachLibrary::DeriveCoachScheduleHorizonSummary( const FHyperTwistTrainingCoachSessionQueueState& QueueState, const FHyperTwistTrainingCoachArchivePolicySummary& ArchivePolicySummary, diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp index 985d83a..bbda244 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp @@ -434,6 +434,13 @@ FHyperTwistTrainingCoachPanelState UHyperTwistTrainingSubsystem::GetActiveCoachP PanelState.PreferredQueueRecoveryAction = ActiveCoachMemorySnapshot.PreferredQueueRecoveryAction; PanelState.PreferredQueueRecoveryActionSourceLabel = ActiveCoachMemorySnapshot.PreferredQueueRecoveryActionSourceLabel; + PanelState.PreferredLaunchBudgetBias = ActiveCoachMemorySnapshot.PreferredLaunchBudgetBias; + PanelState.PreferredLaunchBudgetSourceLabel = + ActiveCoachMemorySnapshot.PreferredLaunchBudgetSourceLabel; + PanelState.BroadenedLaunchBudgetHistoryCount = + ActiveCoachMemorySnapshot.BroadenedLaunchBudgetHistoryCount; + PanelState.TightenedLaunchBudgetHistoryCount = + ActiveCoachMemorySnapshot.TightenedLaunchBudgetHistoryCount; PanelState.bRepositoryVerificationPassed = ActiveRepositoryRoundTripVerification.bDidRoundTripPass; PanelState.bRepositoryVerificationNeedsAttention = ActiveRepositoryRoundTripVerification.ResolvedPath.IsEmpty() || !ActiveRepositoryRoundTripVerification.bDidRoundTripPass; @@ -2458,6 +2465,19 @@ void UHyperTwistTrainingSubsystem::RefreshRepositoryViews() ); ActiveCoachSignals = ActiveCoachBrief.Signals; } + const FHyperTwistCoachSignal LaunchBudgetHistorySignal = + UHyperTwistTrainingCoachLibrary::DeriveLaunchBudgetHistorySignal( + ActiveCoachMemorySnapshot, + ActiveCoachBrief + ); + if (!LaunchBudgetHistorySignal.SignalId.IsEmpty()) + { + ActiveCoachBrief = UHyperTwistTrainingCoachLibrary::RefineCoachBriefWithLaunchBudgetHistorySignal( + ActiveCoachBrief, + LaunchBudgetHistorySignal + ); + ActiveCoachSignals = ActiveCoachBrief.Signals; + } ActiveCoachFollowUpBrief = ActiveCoachActionPlanOutcomeSummary.IsStructurallyValid() && ( ActiveCoachActionClosureSummary.bShouldSpawnFollowUp diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h index 473ab0d..20e2111 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h @@ -491,6 +491,18 @@ public: const FHyperTwistCoachSignal& ClosureRecoveryHistorySignal ); + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + static FHyperTwistCoachSignal DeriveLaunchBudgetHistorySignal( + const FHyperTwistTrainingCoachMemorySnapshot& CoachMemorySnapshot, + const FHyperTwistCoachBrief& CurrentBrief + ); + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + static FHyperTwistCoachBrief RefineCoachBriefWithLaunchBudgetHistorySignal( + const FHyperTwistCoachBrief& BaseBrief, + const FHyperTwistCoachSignal& LaunchBudgetHistorySignal + ); + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") static FHyperTwistCoachScheduleHorizonSummary DeriveCoachScheduleHorizonSummary( const FHyperTwistTrainingCoachSessionQueueState& QueueState, diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h index e9c0634..4dacdbc 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h @@ -4464,6 +4464,18 @@ struct FHyperTwistTrainingCoachPanelState UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FString PreferredQueueRecoveryActionSourceLabel; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 PreferredLaunchBudgetBias = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString PreferredLaunchBudgetSourceLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 BroadenedLaunchBudgetHistoryCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 TightenedLaunchBudgetHistoryCount = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") int32 SignalCount = 0;