From c8c879dc7e192e595b50f5a673b3e9888e5a68ed Mon Sep 17 00:00:00 2001 From: axiomlogicnexus Date: Mon, 27 Apr 2026 20:35:20 +0200 Subject: [PATCH] Explain closure recovery through coach brief --- .../HyperTwistTrainingCoachLibrary.cpp | 173 ++++++++++++++++++ .../HyperTwistTrainingSubsystem.cpp | 14 ++ .../HyperTwistTrainingCoachLibrary.h | 13 ++ 3 files changed, 200 insertions(+) diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp index 489d0e1..0fe1a85 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp @@ -1494,6 +1494,179 @@ FHyperTwistCoachBrief UHyperTwistTrainingCoachLibrary::RefineCoachBriefWithQueue return Brief; } +FHyperTwistCoachSignal UHyperTwistTrainingCoachLibrary::DeriveClosureRecoveryHistorySignal( + const FHyperTwistTrainingCoachActionClosureSummary& ClosureSummary, + const FHyperTwistTrainingCoachMemorySnapshot& CoachMemorySnapshot, + const FHyperTwistCoachBrief& CurrentBrief) +{ + if (!ClosureSummary.IsStructurallyValid()) + { + return FHyperTwistCoachSignal(); + } + + const int32 SustainedHistoryCount = CoachMemorySnapshot.RecoverySustainedFollowUpHistoryCount; + const int32 SuppressedHistoryCount = + CoachMemorySnapshot.RecoverySuppressedBorderlineFollowUpHistoryCount; + if (SustainedHistoryCount <= 0 && SuppressedHistoryCount <= 0) + { + return FHyperTwistCoachSignal(); + } + + const bool bHistoryPrefersFollowUp = SustainedHistoryCount > SuppressedHistoryCount; + const bool bHistoryPrefersRecommended = SuppressedHistoryCount > SustainedHistoryCount; + if (!bHistoryPrefersFollowUp && !bHistoryPrefersRecommended) + { + return FHyperTwistCoachSignal(); + } + + const bool bCurrentFollowUpContinuation = + ClosureSummary.bSpawnFollowUpFromSuppressionMemory; + const bool bCurrentBorderlineSuppression = + ClosureSummary.bSuppressedBorderlineFollowUpByRecoveryHistory; + const bool bCompetingGuidancePressure = + CoachMemorySnapshot.RecommendedGuidanceContinueBias > 0 + && CoachMemorySnapshot.FollowUpGuidanceContinueBias > 0; + + const float Score = FMath::Clamp( + (bHistoryPrefersFollowUp ? 64.0f : 60.0f) + + static_cast(FMath::Max(SustainedHistoryCount, SuppressedHistoryCount)) * 4.0f + + (bCurrentFollowUpContinuation || bCurrentBorderlineSuppression ? 6.0f : 0.0f) + + (bCompetingGuidancePressure ? 4.0f : 0.0f), + 0.0f, + 100.0f + ); + const FString SignalId = bHistoryPrefersFollowUp + ? TEXT("closure_history_follow_up_continuation") + : TEXT("closure_history_recommended_suppression"); + const EHyperTwistCoachFocusLane FocusLane = bHistoryPrefersFollowUp + ? EHyperTwistCoachFocusLane::Cadence + : EHyperTwistCoachFocusLane::Stability; + const FString Headline = bHistoryPrefersFollowUp + ? (bCurrentFollowUpContinuation + ? TEXT("Recovery history keeps follow-up continuity alive") + : TEXT("Recovery history favors sustained follow-up continuity")) + : (bCurrentBorderlineSuppression + ? TEXT("Recovery history suppresses borderline follow-up regeneration") + : TEXT("Recovery history favors recommended consolidation over borderline follow-up")); + const FString Detail = FString::Printf( + TEXT("keepHistory=%d, suppressHistory=%d, currentClosure=%s, followUpReady=%d, recoverySource=%s"), + SustainedHistoryCount, + SuppressedHistoryCount, + ClosureSummary.ClosureLabel.IsEmpty() ? TEXT("n/a") : *ClosureSummary.ClosureLabel, + CoachMemorySnapshot.FollowUpReadyCoachSequenceCount, + CoachMemorySnapshot.PreferredGuidanceSourceLabel.IsEmpty() + ? TEXT("n/a") + : *CoachMemorySnapshot.PreferredGuidanceSourceLabel + ); + const EHyperTwistTrainingDeliveryMode SuggestedMode = bHistoryPrefersFollowUp + ? EHyperTwistTrainingDeliveryMode::CoachReviewed + : CoachMemorySnapshot.DominantCoachSuggestedMode; + const EHyperTwistTrainingSelectionPolicy SuggestedSelectionPolicy = bHistoryPrefersFollowUp + ? EHyperTwistTrainingSelectionPolicy::Scripted + : 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 = bHistoryPrefersFollowUp + ? (CoachMemorySnapshot.SchedulePressureCaseIds.Num() > 0 + ? CoachMemorySnapshot.SchedulePressureCaseIds + : CoachMemorySnapshot.PersistentCoachWeakCaseIds) + : (CoachMemorySnapshot.RecurringCoachCaseIds.Num() > 0 + ? CoachMemorySnapshot.RecurringCoachCaseIds + : CoachMemorySnapshot.DueCaseIds); + HyperTwistTrainingCoachLibraryInternal::ApplyGuidancePreferenceToSignal( + Signal, + bHistoryPrefersFollowUp + ? EHyperTwistTrainingCoachGuidanceLane::FollowUp + : EHyperTwistTrainingCoachGuidanceLane::Recommended, + CoachMemorySnapshot, + bCompetingGuidancePressure + ); + return Signal; +} + +FHyperTwistCoachBrief UHyperTwistTrainingCoachLibrary::RefineCoachBriefWithClosureRecoveryHistorySignal( + const FHyperTwistCoachBrief& BaseBrief, + const FHyperTwistCoachSignal& ClosureRecoveryHistorySignal) +{ + if (ClosureRecoveryHistorySignal.SignalId.IsEmpty()) + { + return BaseBrief; + } + + FHyperTwistCoachBrief Brief = BaseBrief; + if (!Brief.Signals.ContainsByPredicate( + [&ClosureRecoveryHistorySignal](const FHyperTwistCoachSignal& Candidate) + { + return Candidate.SignalId == ClosureRecoveryHistorySignal.SignalId; + })) + { + Brief.Signals.Add(ClosureRecoveryHistorySignal); + } + HyperTwistTrainingCoachLibraryInternal::SortSignals(Brief.Signals); + + const bool bPolicyFacingBrief = + Brief.FocusLane == EHyperTwistCoachFocusLane::Cadence + || Brief.FocusLane == EHyperTwistCoachFocusLane::Stability + || Brief.bNeedsCadenceRecovery; + const bool bShouldPromoteHistorySignal = + bPolicyFacingBrief + && ClosureRecoveryHistorySignal.Score >= Brief.PriorityScore - 5.0f; + if (bShouldPromoteHistorySignal) + { + for (FHyperTwistCoachSignal& Signal : Brief.Signals) + { + if (Signal.SignalId == ClosureRecoveryHistorySignal.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 609a041..d9f2df3 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp @@ -2229,6 +2229,20 @@ void UHyperTwistTrainingSubsystem::RefreshRepositoryViews() ActiveReplayReviewAnalytics, ActiveRecognitionReplaySummary ); + const FHyperTwistCoachSignal ClosureRecoveryHistorySignal = + UHyperTwistTrainingCoachLibrary::DeriveClosureRecoveryHistorySignal( + ActiveCoachActionClosureSummary, + ActiveCoachMemorySnapshot, + ActiveCoachBrief + ); + if (!ClosureRecoveryHistorySignal.SignalId.IsEmpty()) + { + ActiveCoachBrief = UHyperTwistTrainingCoachLibrary::RefineCoachBriefWithClosureRecoveryHistorySignal( + ActiveCoachBrief, + ClosureRecoveryHistorySignal + ); + 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 9c42cc9..473ab0d 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h @@ -478,6 +478,19 @@ public: const FHyperTwistCoachSignal& QueueSuppressionSignal ); + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + static FHyperTwistCoachSignal DeriveClosureRecoveryHistorySignal( + const FHyperTwistTrainingCoachActionClosureSummary& ClosureSummary, + const FHyperTwistTrainingCoachMemorySnapshot& CoachMemorySnapshot, + const FHyperTwistCoachBrief& CurrentBrief + ); + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + static FHyperTwistCoachBrief RefineCoachBriefWithClosureRecoveryHistorySignal( + const FHyperTwistCoachBrief& BaseBrief, + const FHyperTwistCoachSignal& ClosureRecoveryHistorySignal + ); + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") static FHyperTwistCoachScheduleHorizonSummary DeriveCoachScheduleHorizonSummary( const FHyperTwistTrainingCoachSessionQueueState& QueueState,