diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp index 0c0aeb4..ecb5a1e 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp @@ -301,6 +301,111 @@ namespace HyperTwistTrainingCoachLibraryInternal ); } + float ResolveMethodDrillFollowUpContinuationPressure( + const FHyperTwistTrainingCoachMemorySnapshot& CoachMemorySnapshot) + { + const bool bPreferredGuidanceSource = + CoachMemorySnapshot.PreferredGuidanceSourceLabel + == TEXT("coach-memory-method-drill-follow-up-strong"); + const bool bPreferredHandoffSource = + CoachMemorySnapshot.PreferredCoachHandoffSourceLabel + == TEXT("coach-memory-method-drill-follow-up-strong"); + if (CoachMemorySnapshot.MethodDrillFollowUpHistoryCount <= 0 + && !bPreferredGuidanceSource + && !bPreferredHandoffSource) + { + return 0.0f; + } + + const bool bStrongHistoryDominant = + CoachMemorySnapshot.StrongMethodDrillFollowUpHistoryCount + >= CoachMemorySnapshot.WeakMethodDrillFollowUpHistoryCount + && CoachMemorySnapshot.MethodDrillFollowUpAverageOutcomeScore >= 60.0f; + if (!bStrongHistoryDominant && !bPreferredGuidanceSource && !bPreferredHandoffSource) + { + return 0.0f; + } + + return FMath::Clamp( + (static_cast(CoachMemorySnapshot.StrongMethodDrillFollowUpHistoryCount) * 1.25f) + + (static_cast(CoachMemorySnapshot.MethodDrillFollowUpHistoryCount) * 0.35f) + + (bPreferredGuidanceSource ? 2.0f : 0.0f) + + (bPreferredHandoffSource ? 2.0f : 0.0f), + 0.0f, + 8.0f + ); + } + + float ResolveMethodDrillFollowUpRedirectPressure( + const FHyperTwistTrainingCoachMemorySnapshot& CoachMemorySnapshot) + { + const bool bPreferredGuidanceSource = + CoachMemorySnapshot.PreferredGuidanceSourceLabel + == TEXT("coach-memory-method-drill-follow-up-weak"); + const bool bPreferredHandoffSource = + CoachMemorySnapshot.PreferredCoachHandoffSourceLabel + == TEXT("coach-memory-method-drill-follow-up-weak"); + if (CoachMemorySnapshot.MethodDrillFollowUpHistoryCount <= 0 + && !bPreferredGuidanceSource + && !bPreferredHandoffSource) + { + return 0.0f; + } + + const bool bWeakHistoryDominant = + CoachMemorySnapshot.WeakMethodDrillFollowUpHistoryCount + > CoachMemorySnapshot.StrongMethodDrillFollowUpHistoryCount + && CoachMemorySnapshot.MethodDrillFollowUpAverageOutcomeScore <= 55.0f; + if (!bWeakHistoryDominant && !bPreferredGuidanceSource && !bPreferredHandoffSource) + { + return 0.0f; + } + + return FMath::Clamp( + (static_cast(CoachMemorySnapshot.WeakMethodDrillFollowUpHistoryCount) * 1.5f) + + (static_cast(CoachMemorySnapshot.MethodDrillFollowUpHistoryCount) * 0.25f) + + (bPreferredGuidanceSource ? 2.0f : 0.0f) + + (bPreferredHandoffSource ? 2.0f : 0.0f), + 0.0f, + 8.0f + ); + } + + float ResolveMethodDrillFollowUpQueueBias( + const FString& SourceLabel, + const FHyperTwistTrainingCoachMemorySnapshot& CoachMemorySnapshot, + const bool bHasCompetingGuidanceEntry) + { + const float ContinuationPressure = + ResolveMethodDrillFollowUpContinuationPressure(CoachMemorySnapshot); + const float RedirectPressure = + ResolveMethodDrillFollowUpRedirectPressure(CoachMemorySnapshot); + if (FMath::IsNearlyZero(ContinuationPressure) + && FMath::IsNearlyZero(RedirectPressure)) + { + return 0.0f; + } + + if (SourceLabel == TEXT("follow-up-brief")) + { + return FMath::Clamp( + ContinuationPressure - (RedirectPressure * 0.75f), + -5.0f, + 6.0f + ); + } + if (SourceLabel == TEXT("primary-brief") && bHasCompetingGuidanceEntry) + { + return FMath::Clamp( + RedirectPressure - (ContinuationPressure * 0.55f), + -4.0f, + 5.0f + ); + } + + return 0.0f; + } + float ResolvePreferredGuidanceQueueBias( const FString& SourceLabel, const FHyperTwistTrainingCoachMemorySnapshot& CoachMemorySnapshot, @@ -556,6 +661,10 @@ namespace HyperTwistTrainingCoachLibraryInternal ) : TEXT("successful deferred-first continuation history")); const bool bDeferredFirstFollowUpHistoryPreferred = DeferredFirstPacketPressure > 0.0f; + const float MethodDrillContinuationPressure = + ResolveMethodDrillFollowUpContinuationPressure(CoachMemorySnapshot); + const float MethodDrillRedirectPressure = + ResolveMethodDrillFollowUpRedirectPressure(CoachMemorySnapshot); const bool bScheduleRecoveryPrefersFollowUp = CoachMemorySnapshot.bNeedsScheduleFrictionRecovery && CoachMemorySnapshot.PreferredQueueRecoveryAction @@ -563,7 +672,9 @@ namespace HyperTwistTrainingCoachLibraryInternal && CoachMemorySnapshot.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp; if (!bScheduleRecoveryPrefersFollowUp && !bNeedsDeferredLaunchCompetition - && !bDeferredFirstFollowUpHistoryPreferred) + && !bDeferredFirstFollowUpHistoryPreferred + && FMath::IsNearlyZero(MethodDrillContinuationPressure) + && FMath::IsNearlyZero(MethodDrillRedirectPressure)) { return true; } @@ -571,14 +682,16 @@ namespace HyperTwistTrainingCoachLibraryInternal const float DeferredLaunchPressure = ResolveDeferredLaunchRecoveryPressure(FollowUpBrief); const float RecommendedRetentionScore = CurrentBrief.PriorityScore - + static_cast(CoachMemorySnapshot.RecommendedGuidanceContinueBias); + + static_cast(CoachMemorySnapshot.RecommendedGuidanceContinueBias) + + (MethodDrillRedirectPressure * 1.5f); const float FollowUpPressureScore = FollowUpBrief.PriorityScore + static_cast(CoachMemorySnapshot.FollowUpGuidanceContinueBias) + (static_cast(CoachMemorySnapshot.SuppressedPrimaryQueuePressureCount) * 2.0f) + (FollowUpBrief.bNeedsCoachReview ? 8.0f : 0.0f) + DeferredLaunchPressure - + DeferredFirstPacketPressure; + + DeferredFirstPacketPressure + + (MethodDrillContinuationPressure * 1.5f); const float RecommendedRetentionThreshold = bDeferredFirstFollowUpHistoryPreferred ? 10.0f : 8.0f; const bool bShouldCreate = @@ -615,6 +728,16 @@ namespace HyperTwistTrainingCoachLibraryInternal CoachMemorySnapshot.DeferredFirstFollowUpPacketHistoryCount ); } + else if (MethodDrillContinuationPressure > MethodDrillRedirectPressure) + { + *OutSuppressionReason = FString::Printf( + TEXT("Recommended queue creation was suppressed because successful method-drill follow-up history is reinforcing narrow continuation before broader recommendation work resumes (recommended %.1f vs follow-up %.1f, drillHistory=%d, avgDrillOutcome=%.1f)."), + RecommendedRetentionScore, + FollowUpPressureScore, + CoachMemorySnapshot.MethodDrillFollowUpHistoryCount, + CoachMemorySnapshot.MethodDrillFollowUpAverageOutcomeScore + ); + } else { *OutSuppressionReason = FString::Printf( @@ -645,10 +768,17 @@ namespace HyperTwistTrainingCoachLibraryInternal { return true; } - if (!CoachMemorySnapshot.bNeedsScheduleFrictionRecovery - || CoachMemorySnapshot.PreferredQueueRecoveryAction - != EHyperTwistTrainingQueueRecoveryAction::LaunchRecovery - || CoachMemorySnapshot.PreferredGuidanceLane != EHyperTwistTrainingCoachGuidanceLane::Recommended) + const float MethodDrillContinuationPressure = + ResolveMethodDrillFollowUpContinuationPressure(CoachMemorySnapshot); + const float MethodDrillRedirectPressure = + ResolveMethodDrillFollowUpRedirectPressure(CoachMemorySnapshot); + const bool bRecommendedLaunchRecoveryGate = + CoachMemorySnapshot.bNeedsScheduleFrictionRecovery + && CoachMemorySnapshot.PreferredQueueRecoveryAction + == EHyperTwistTrainingQueueRecoveryAction::LaunchRecovery + && CoachMemorySnapshot.PreferredGuidanceLane + == EHyperTwistTrainingCoachGuidanceLane::Recommended; + if (!bRecommendedLaunchRecoveryGate && FMath::IsNearlyZero(MethodDrillRedirectPressure)) { return true; } @@ -661,18 +791,33 @@ namespace HyperTwistTrainingCoachLibraryInternal CurrentBrief.PriorityScore + static_cast(CoachMemorySnapshot.RecommendedGuidanceContinueBias) + (static_cast(CoachMemorySnapshot.SuppressedFollowUpQueuePressureCount) * 2.0f) - + (CoachMemorySnapshot.bHasRepeatRecommendationPressure ? 4.0f : 0.0f); + + (CoachMemorySnapshot.bHasRepeatRecommendationPressure ? 4.0f : 0.0f) + + (MethodDrillRedirectPressure * 1.5f); const float FollowUpRetentionScore = FollowUpBrief.PriorityScore - + static_cast(CoachMemorySnapshot.FollowUpGuidanceContinueBias); + + static_cast(CoachMemorySnapshot.FollowUpGuidanceContinueBias) + + (MethodDrillContinuationPressure * 1.25f); const bool bShouldCreate = FollowUpRetentionScore >= RecommendedPressureScore + 8.0f; if (!bShouldCreate && OutSuppressionReason != nullptr) { - *OutSuppressionReason = FString::Printf( - TEXT("Follow-up queue creation was suppressed because recommended-lane launch recovery is preferred (follow-up %.1f vs recommended %.1f)."), - FollowUpRetentionScore, - RecommendedPressureScore - ); + if (MethodDrillRedirectPressure > MethodDrillContinuationPressure) + { + *OutSuppressionReason = FString::Printf( + TEXT("Follow-up queue creation was suppressed because weak method-drill follow-up history is pushing control back toward broader recommended work (follow-up %.1f vs recommended %.1f, drillHistory=%d, avgDrillOutcome=%.1f)."), + FollowUpRetentionScore, + RecommendedPressureScore, + CoachMemorySnapshot.MethodDrillFollowUpHistoryCount, + CoachMemorySnapshot.MethodDrillFollowUpAverageOutcomeScore + ); + } + else + { + *OutSuppressionReason = FString::Printf( + TEXT("Follow-up queue creation was suppressed because recommended-lane launch recovery is preferred (follow-up %.1f vs recommended %.1f)."), + FollowUpRetentionScore, + RecommendedPressureScore + ); + } } return bShouldCreate; } @@ -1896,12 +2041,19 @@ FHyperTwistCoachSessionQueueSummary UHyperTwistTrainingCoachLibrary::DeriveCoach bHasCompetingGuidanceEntry, bHasArchiveEntry ); + const float MethodDrillQueueBias = + HyperTwistTrainingCoachLibraryInternal::ResolveMethodDrillFollowUpQueueBias( + SourceLabel, + CoachMemorySnapshot, + bHasCompetingGuidanceEntry + ); Entry.PriorityScore = FMath::Clamp( Entry.PriorityScore + GuidanceBias + ArchiveCompetitionBias + RecoveryActionBias - + DeferredLaunchBias, + + DeferredLaunchBias + + MethodDrillQueueBias, 0.0f, 100.0f );