From 49f7c118400bb593346af9d327cef2d8fd4d46d1 Mon Sep 17 00:00:00 2001 From: axiomlogicnexus Date: Mon, 27 Apr 2026 16:47:20 +0200 Subject: [PATCH] Persist coach guidance lane preference --- .../HyperTwistCoachDashboardWidget.cpp | 27 ++++++-- .../HyperTwistTrainingRepositoryLibrary.cpp | 61 +++++++++++++++++++ .../HyperTwistTrainingSubsystem.cpp | 16 +++++ .../HyperTwistTrainingTypes.h | 50 +++++++++++++++ 4 files changed, 149 insertions(+), 5 deletions(-) diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp index e981547..bd23229 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp @@ -2799,8 +2799,18 @@ FHyperTwistCoachDashboardGuidanceLanePreference UHyperTwistCoachDashboardWidget: Preference.bHasDecisionSignal = Preference.RecommendedDecisionCount > 0 || Preference.FollowUpDecisionCount > 0; - float RecommendedLaneScore = 1.0f; - float FollowUpLaneScore = CachedCoachPanelState.bHasFollowUpGuidance ? 1.25f : 1.0f; + const EHyperTwistCoachDashboardGuidancePreviewLane MemoryPreferredLane = + CachedCoachPanelState.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp + ? EHyperTwistCoachDashboardGuidancePreviewLane::FollowUp + : (CachedCoachPanelState.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::Recommended + ? EHyperTwistCoachDashboardGuidancePreviewLane::Recommended + : EHyperTwistCoachDashboardGuidancePreviewLane::None); + float RecommendedLaneScore = + MemoryPreferredLane == EHyperTwistCoachDashboardGuidancePreviewLane::Recommended ? 1.5f : 1.0f; + float FollowUpLaneScore = + MemoryPreferredLane == EHyperTwistCoachDashboardGuidancePreviewLane::FollowUp + ? 1.75f + : (CachedCoachPanelState.bHasFollowUpGuidance ? 1.25f : 1.0f); if (Preference.RecommendedDecisionCount > 0) { RecommendedLaneScore += static_cast(Preference.RecommendedContinueBias); @@ -2823,11 +2833,13 @@ FHyperTwistCoachDashboardGuidanceLanePreference UHyperTwistCoachDashboardWidget: else { Preference.PreferredLane = - ActiveGuidancePreviewLane != EHyperTwistCoachDashboardGuidancePreviewLane::None + MemoryPreferredLane != EHyperTwistCoachDashboardGuidancePreviewLane::None + ? MemoryPreferredLane + : (ActiveGuidancePreviewLane != EHyperTwistCoachDashboardGuidancePreviewLane::None ? ActiveGuidancePreviewLane : (CachedCoachPanelState.bHasFollowUpGuidance ? EHyperTwistCoachDashboardGuidancePreviewLane::FollowUp - : EHyperTwistCoachDashboardGuidancePreviewLane::Recommended); + : EHyperTwistCoachDashboardGuidancePreviewLane::Recommended)); } return Preference; @@ -3324,13 +3336,18 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() : (GuidanceLanePreference.PreferredLane == EHyperTwistCoachDashboardGuidancePreviewLane::FollowUp ? TEXT("follow-up") : TEXT("none")); + const FString GuidancePreferenceSourceLabel = GuidanceLanePreference.bHasDecisionSignal + ? TEXT("decision-informed") + : (CachedCoachPanelState.PreferredGuidanceSourceLabel.IsEmpty() + ? TEXT("availability fallback") + : CachedCoachPanelState.PreferredGuidanceSourceLabel); const FString GuidancePreferenceStatusLine = GuidanceLanePreference.IsStructurallyValid() ? FString::Printf( TEXT("Preference: next surfaced lane %s | Active preview: %s | Source: %s"), *PreferredGuidanceLaneLabel, *ActivePreviewLaneLabel, - GuidanceLanePreference.bHasDecisionSignal ? TEXT("decision-informed") : TEXT("availability fallback") + *GuidancePreferenceSourceLabel ) : TEXT("Preference: no coach preview lane is currently available."); const FString GuidancePreferenceDetailLine = diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp index 93cd9c9..e36ea59 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp @@ -4851,6 +4851,12 @@ FHyperTwistTrainingCoachMemorySnapshot UHyperTwistTrainingRepositoryLibrary::Der Snapshot.RepeatRecommendationSequenceCount = RecommendationHistory.RepeatRecommendationSequenceCount; Snapshot.DominantCoachSuggestedMode = RecommendationHistory.DominantSuggestedMode; Snapshot.DominantCoachSelectionPolicy = RecommendationHistory.DominantSelectionPolicy; + Snapshot.PreferredGuidanceLane = RecommendationHistory.PreferredGuidanceLane; + Snapshot.PreferredGuidanceSourceLabel = TEXT("recommendation-history"); + Snapshot.RecommendedGuidancePlanCount = RecommendationHistory.RecommendedLanePlanCount; + Snapshot.FollowUpGuidancePlanCount = RecommendationHistory.FollowUpLanePlanCount; + Snapshot.RecommendedGuidanceContinueBias = RecommendationHistory.RecommendedLaneContinueBias; + Snapshot.FollowUpGuidanceContinueBias = RecommendationHistory.FollowUpLaneContinueBias; Snapshot.bHasRepeatRecommendationPressure = RecommendationHistory.bHasRepeatRecommendationPressure; if (!RecommendationHistory.FocusDeckId.IsEmpty() @@ -5169,6 +5175,22 @@ FHyperTwistTrainingCoachMemorySnapshot UHyperTwistTrainingRepositoryLibrary::Der Snapshot.ReferenceUtc ) > 3.0; + if (Snapshot.bHasBlockedFollowUpScheduleFriction + || Snapshot.ScheduleBlockedFollowUpEventCount > 0 + || Snapshot.FollowUpReadyCoachSequenceCount > 0) + { + Snapshot.PreferredGuidanceLane = EHyperTwistTrainingCoachGuidanceLane::FollowUp; + Snapshot.PreferredGuidanceSourceLabel = TEXT("coach-memory-follow-up"); + } + else if (Snapshot.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::None) + { + Snapshot.PreferredGuidanceLane = Snapshot.bHasRepeatRecommendationPressure + || Snapshot.RecurringCoachCaseIds.Num() > 0 + ? EHyperTwistTrainingCoachGuidanceLane::Recommended + : EHyperTwistTrainingCoachGuidanceLane::FollowUp; + Snapshot.PreferredGuidanceSourceLabel = TEXT("coach-memory-default"); + } + return Snapshot; } @@ -7751,6 +7773,18 @@ FHyperTwistTrainingCoachRecommendationHistorySummary UHyperTwistTrainingReposito } Summary.PlanCount += 1; + const bool bIsFollowUpLanePlan = + NormalizedPlan.FollowUpDepth > 0 || !NormalizedPlan.ParentPlanId.IsEmpty(); + if (bIsFollowUpLanePlan) + { + Summary.FollowUpLanePlanCount += 1; + Summary.FollowUpLaneContinueBias += 1; + } + else + { + Summary.RecommendedLanePlanCount += 1; + Summary.RecommendedLaneContinueBias += 1; + } FollowUpDepthAccumulator += static_cast(NormalizedPlan.FollowUpDepth); ModeCountMap.FindOrAdd(NormalizedPlan.SuggestedMode) += 1; PolicyCountMap.FindOrAdd(NormalizedPlan.SuggestedSelectionPolicy) += 1; @@ -7887,6 +7921,8 @@ FHyperTwistTrainingCoachRecommendationHistorySummary UHyperTwistTrainingReposito Summary.SequenceCount += 1; Summary.EscalatedSequenceCount += ClosureSummary.bNeedsEscalation ? 1 : 0; Summary.FollowUpReadySequenceCount += ClosureSummary.bShouldSpawnFollowUp ? 1 : 0; + Summary.FollowUpLaneContinueBias += ClosureSummary.bShouldSpawnFollowUp ? 2 : 0; + Summary.FollowUpLaneContinueBias += ClosureSummary.bNeedsEscalation ? 2 : 0; ClosureScoreAccumulator += ClosureSummary.ClosureScore; bool bHasRecurringCaseInSequence = false; @@ -7902,6 +7938,9 @@ FHyperTwistTrainingCoachRecommendationHistorySummary UHyperTwistTrainingReposito } } Summary.RepeatRecommendationSequenceCount += bHasRecurringCaseInSequence ? 1 : 0; + Summary.RecommendedLaneContinueBias += bHasRecurringCaseInSequence ? 1 : 0; + Summary.RecommendedLaneContinueBias += + (ClosureSummary.bSequenceClosed && !ClosureSummary.bShouldSpawnFollowUp) ? 1 : 0; if (ClosureSummary.bShouldArchiveResolvedCases) { @@ -7980,6 +8019,28 @@ FHyperTwistTrainingCoachRecommendationHistorySummary UHyperTwistTrainingReposito || (Summary.bHasRepeatRecommendationPressure && Summary.DominantSuggestedMode == EHyperTwistTrainingDeliveryMode::CoachReviewed); + const bool bHasFollowUpLaneSignal = + Summary.FollowUpLanePlanCount > 0 + || Summary.FollowUpReadySequenceCount > 0 + || Summary.EscalatedSequenceCount > 0 + || Summary.AverageFollowUpDepth >= 1.0f; + const float RecommendedLaneScore = + 1.0f + + static_cast(Summary.RecommendedLaneContinueBias) + + static_cast(Summary.RepeatRecommendationSequenceCount) * 1.5f + + (Summary.bHasRepeatRecommendationPressure ? 2.0f : 0.0f) + + (Summary.DominantSuggestedMode == EHyperTwistTrainingDeliveryMode::CoachReviewed ? 1.0f : 0.0f); + const float FollowUpLaneScore = + 1.0f + + static_cast(Summary.FollowUpLaneContinueBias) + + static_cast(Summary.FollowUpReadySequenceCount) * 2.0f + + static_cast(Summary.EscalatedSequenceCount) * 1.5f + + Summary.AverageFollowUpDepth; + Summary.PreferredGuidanceLane = + bHasFollowUpLaneSignal && FollowUpLaneScore > RecommendedLaneScore + ? EHyperTwistTrainingCoachGuidanceLane::FollowUp + : EHyperTwistTrainingCoachGuidanceLane::Recommended; + return Summary; } diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp index ea9d649..30cf73d 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp @@ -276,6 +276,8 @@ FHyperTwistTrainingCoachPanelState UHyperTwistTrainingSubsystem::GetActiveCoachP PanelState.bHasFollowUpGuidance = !ActiveCoachFollowUpBrief.UserId.IsEmpty() && !ActiveCoachFollowUpBrief.Headline.IsEmpty(); PanelState.SignalCount = ActiveCoachSignals.Num(); + PanelState.PreferredGuidanceLane = ActiveCoachMemorySnapshot.PreferredGuidanceLane; + PanelState.PreferredGuidanceSourceLabel = ActiveCoachMemorySnapshot.PreferredGuidanceSourceLabel; PanelState.bRepositoryVerificationPassed = ActiveRepositoryRoundTripVerification.bDidRoundTripPass; PanelState.bRepositoryVerificationNeedsAttention = ActiveRepositoryRoundTripVerification.ResolvedPath.IsEmpty() || !ActiveRepositoryRoundTripVerification.bDidRoundTripPass; @@ -320,6 +322,9 @@ FHyperTwistTrainingCoachPanelState UHyperTwistTrainingSubsystem::GetActiveCoachP 3 ).IsStructurallyValid()); + const bool bPreferFollowUpGuidance = + PanelState.bHasFollowUpGuidance + && ActiveCoachMemorySnapshot.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp; if (NextRunnableEntry != nullptr) { PanelState.Headline = NextRunnableEntry->Headline; @@ -335,6 +340,17 @@ FHyperTwistTrainingCoachPanelState UHyperTwistTrainingSubsystem::GetActiveCoachP PanelState.SuggestedMode = NextRunnableEntry->SuggestedMode; PanelState.SuggestedSelectionPolicy = NextRunnableEntry->SuggestedSelectionPolicy; } + else if (bPreferFollowUpGuidance) + { + PanelState.Headline = ActiveCoachFollowUpBrief.Headline; + PanelState.SecondaryLine = ActiveCoachFollowUpBrief.PrimaryActionLabel; + PanelState.PrimaryActionLabel = ActiveCoachFollowUpBrief.PrimaryActionLabel; + PanelState.FocusDeckId = ActiveCoachFollowUpBrief.FocusDeckId; + PanelState.FocusMethodSegmentId = ActiveCoachFollowUpBrief.FocusMethodSegmentId; + PanelState.FocusCaseCount = ActiveCoachFollowUpBrief.FocusCaseIds.Num(); + PanelState.SuggestedMode = ActiveCoachFollowUpBrief.SuggestedMode; + PanelState.SuggestedSelectionPolicy = ActiveCoachFollowUpBrief.SuggestedSelectionPolicy; + } else if (PanelState.bHasCoachGuidance) { PanelState.Headline = ActiveCoachBrief.Headline; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h index 6e7d8a0..d418b7f 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h @@ -24,6 +24,14 @@ enum class EHyperTwistTrainingSelectionPolicy : uint8 Scripted UMETA(DisplayName = "Scripted") }; +UENUM(BlueprintType) +enum class EHyperTwistTrainingCoachGuidanceLane : uint8 +{ + None UMETA(DisplayName = "None"), + Recommended UMETA(DisplayName = "Recommended"), + FollowUp UMETA(DisplayName = "Follow-Up") +}; + UENUM(BlueprintType) enum class EHyperTwistTrainingPromptKind : uint8 { @@ -1810,6 +1818,10 @@ struct FHyperTwistTrainingCoachMemorySnapshot EHyperTwistTrainingSelectionPolicy DominantCoachSelectionPolicy = EHyperTwistTrainingSelectionPolicy::Weighted; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + EHyperTwistTrainingCoachGuidanceLane PreferredGuidanceLane = + EHyperTwistTrainingCoachGuidanceLane::None; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") float ArchiveStabilityScore = 0.0f; @@ -1822,6 +1834,21 @@ struct FHyperTwistTrainingCoachMemorySnapshot UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FString DominantScheduleDeferralReasonLabel; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString PreferredGuidanceSourceLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 RecommendedGuidancePlanCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 FollowUpGuidancePlanCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 RecommendedGuidanceContinueBias = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 FollowUpGuidanceContinueBias = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") bool bNeedsCoachIntervention = false; @@ -3681,6 +3708,10 @@ struct FHyperTwistTrainingCoachRecommendationHistorySummary EHyperTwistTrainingSelectionPolicy DominantSelectionPolicy = EHyperTwistTrainingSelectionPolicy::Weighted; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + EHyperTwistTrainingCoachGuidanceLane PreferredGuidanceLane = + EHyperTwistTrainingCoachGuidanceLane::None; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") int32 SequenceCount = 0; @@ -3696,6 +3727,18 @@ struct FHyperTwistTrainingCoachRecommendationHistorySummary UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") int32 FollowUpReadySequenceCount = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 RecommendedLanePlanCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 FollowUpLanePlanCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 RecommendedLaneContinueBias = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 FollowUpLaneContinueBias = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") int32 RecurringCaseCount = 0; @@ -4224,6 +4267,13 @@ struct FHyperTwistTrainingCoachPanelState UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") EHyperTwistTrainingSelectionPolicy SuggestedSelectionPolicy = EHyperTwistTrainingSelectionPolicy::Weighted; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + EHyperTwistTrainingCoachGuidanceLane PreferredGuidanceLane = + EHyperTwistTrainingCoachGuidanceLane::None; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString PreferredGuidanceSourceLabel; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") int32 SignalCount = 0;