diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp index e720192..4cf9d6d 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp @@ -124,6 +124,23 @@ namespace HyperTwistTrainingCoachLibraryInternal return TEXT("stable"); } + float ResolveDeferredLaunchRecoveryPressure(const FHyperTwistCoachBrief& FollowUpBrief) + { + if (!FollowUpBrief.bNeedsDeferredLaunchRecovery || FollowUpBrief.DeferredLaunchCaseCount <= 0) + { + return 0.0f; + } + + return FMath::Clamp( + 6.0f + + (static_cast(FollowUpBrief.DeferredLaunchCaseCount) * 2.0f) + + (FollowUpBrief.bHasTightenedLaunchOverflow ? 4.0f : 0.0f) + + (FollowUpBrief.bNeedsCoachReview ? 3.0f : 0.0f), + 0.0f, + 18.0f + ); + } + TArray MergeFocusCaseIds( const TArray& PrimaryCaseIds, const TArray& SecondaryCaseIds) @@ -311,6 +328,56 @@ namespace HyperTwistTrainingCoachLibraryInternal return 0.0f; } + float ResolveDeferredLaunchCompetitionQueueBias( + const FString& SourceLabel, + const FHyperTwistCoachBrief& FollowUpBrief, + const bool bHasCompetingGuidanceEntry, + const bool bHasArchiveEntry) + { + const float DeferredLaunchPressure = ResolveDeferredLaunchRecoveryPressure(FollowUpBrief); + if (DeferredLaunchPressure <= 0.0f) + { + return 0.0f; + } + + const float FollowUpBoost = FMath::Clamp( + (DeferredLaunchPressure * 0.6f) + + (bHasCompetingGuidanceEntry ? 1.5f : 0.0f) + + (FollowUpBrief.bHasTightenedLaunchOverflow ? 1.5f : 0.0f), + 0.0f, + 9.0f + ); + const float PrimaryPenalty = FMath::Clamp( + (DeferredLaunchPressure * 0.4f) + + (bHasCompetingGuidanceEntry ? 1.0f : 0.0f), + 0.0f, + 6.0f + ); + const float ArchivePenalty = FMath::Clamp( + (DeferredLaunchPressure * 0.22f) + + (FollowUpBrief.bHasTightenedLaunchOverflow ? 0.5f : 0.0f), + 0.0f, + 3.0f + ); + + if (SourceLabel == TEXT("follow-up-brief")) + { + return FollowUpBoost; + } + if (SourceLabel == TEXT("primary-brief") && bHasCompetingGuidanceEntry) + { + return -PrimaryPenalty; + } + if (SourceLabel == TEXT("archive-policy") + && bHasArchiveEntry + && FollowUpBrief.bHasTightenedLaunchOverflow) + { + return -ArchivePenalty; + } + + return 0.0f; + } + bool ShouldCreatePrimaryQueueEntry( const FHyperTwistCoachBrief& CurrentBrief, const FHyperTwistCoachBrief& FollowUpBrief, @@ -330,14 +397,19 @@ namespace HyperTwistTrainingCoachLibraryInternal { return true; } - if (!CoachMemorySnapshot.bNeedsScheduleFrictionRecovery - || CoachMemorySnapshot.PreferredQueueRecoveryAction - != EHyperTwistTrainingQueueRecoveryAction::PromoteDeferred - || CoachMemorySnapshot.PreferredGuidanceLane != EHyperTwistTrainingCoachGuidanceLane::FollowUp) + const bool bNeedsDeferredLaunchCompetition = + FollowUpBrief.bNeedsDeferredLaunchRecovery && FollowUpBrief.DeferredLaunchCaseCount > 0; + const bool bScheduleRecoveryPrefersFollowUp = + CoachMemorySnapshot.bNeedsScheduleFrictionRecovery + && CoachMemorySnapshot.PreferredQueueRecoveryAction + == EHyperTwistTrainingQueueRecoveryAction::PromoteDeferred + && CoachMemorySnapshot.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp; + if (!bScheduleRecoveryPrefersFollowUp && !bNeedsDeferredLaunchCompetition) { return true; } + const float DeferredLaunchPressure = ResolveDeferredLaunchRecoveryPressure(FollowUpBrief); const float RecommendedRetentionScore = CurrentBrief.PriorityScore + static_cast(CoachMemorySnapshot.RecommendedGuidanceContinueBias); @@ -345,15 +417,28 @@ namespace HyperTwistTrainingCoachLibraryInternal FollowUpBrief.PriorityScore + static_cast(CoachMemorySnapshot.FollowUpGuidanceContinueBias) + (static_cast(CoachMemorySnapshot.SuppressedPrimaryQueuePressureCount) * 2.0f) - + (FollowUpBrief.bNeedsCoachReview ? 8.0f : 0.0f); + + (FollowUpBrief.bNeedsCoachReview ? 8.0f : 0.0f) + + DeferredLaunchPressure; const bool bShouldCreate = RecommendedRetentionScore >= FollowUpPressureScore + 8.0f; if (!bShouldCreate && OutSuppressionReason != nullptr) { - *OutSuppressionReason = FString::Printf( - TEXT("Recommended queue creation was suppressed because follow-up recovery is preferred for promote-deferred recovery (recommended %.1f vs follow-up %.1f)."), - RecommendedRetentionScore, - FollowUpPressureScore - ); + if (bNeedsDeferredLaunchCompetition) + { + *OutSuppressionReason = FString::Printf( + TEXT("Recommended queue creation was suppressed because tightened-budget deferred coach work is still unresolved (recommended %.1f vs follow-up %.1f, deferredLaunchCases=%d)."), + RecommendedRetentionScore, + FollowUpPressureScore, + FollowUpBrief.DeferredLaunchCaseCount + ); + } + else + { + *OutSuppressionReason = FString::Printf( + TEXT("Recommended queue creation was suppressed because follow-up recovery is preferred for promote-deferred recovery (recommended %.1f vs follow-up %.1f)."), + RecommendedRetentionScore, + FollowUpPressureScore + ); + } } return bShouldCreate; } @@ -372,6 +457,10 @@ namespace HyperTwistTrainingCoachLibraryInternal { return false; } + if (FollowUpBrief.bNeedsDeferredLaunchRecovery && FollowUpBrief.DeferredLaunchCaseCount > 0) + { + return true; + } if (!CoachMemorySnapshot.bNeedsScheduleFrictionRecovery || CoachMemorySnapshot.PreferredQueueRecoveryAction != EHyperTwistTrainingQueueRecoveryAction::LaunchRecovery @@ -1189,6 +1278,10 @@ FHyperTwistCoachBrief UHyperTwistTrainingCoachLibrary::DeriveCoachFollowUpBrief( || OutcomeSummary.RecognitionAttemptCount > 0; const bool bHasDeferredLaunchCases = OutcomeSummary.DeferredLaunchCaseIds.Num() > 0; const bool bHasLaunchedFollowUpCases = OutcomeSummary.FollowUpCaseIds.Num() > 0; + Brief.DeferredLaunchCaseCount = OutcomeSummary.DeferredLaunchCaseIds.Num(); + Brief.bNeedsDeferredLaunchRecovery = bHasDeferredLaunchCases; + Brief.bHasTightenedLaunchOverflow = + OutcomeSummary.bTightenedLaunchBudget && bHasDeferredLaunchCases; Brief.FocusCaseIds = bHasLaunchedFollowUpCases ? HyperTwistTrainingCoachLibraryInternal::MergeFocusCaseIds( OutcomeSummary.FollowUpCaseIds, @@ -1383,6 +1476,7 @@ FHyperTwistCoachSessionQueueSummary UHyperTwistTrainingCoachLibrary::DeriveCoach auto AddBriefEntry = [ &Summary, + &FollowUpBrief, &CoachMemorySnapshot, &ArchivePolicySummary, bHasCompetingGuidanceEntry, @@ -1460,8 +1554,19 @@ FHyperTwistCoachSessionQueueSummary UHyperTwistTrainingCoachLibrary::DeriveCoach bHasCompetingGuidanceEntry, bHasArchiveEntry ); + const float DeferredLaunchBias = + HyperTwistTrainingCoachLibraryInternal::ResolveDeferredLaunchCompetitionQueueBias( + SourceLabel, + FollowUpBrief, + bHasCompetingGuidanceEntry, + bHasArchiveEntry + ); Entry.PriorityScore = FMath::Clamp( - Entry.PriorityScore + GuidanceBias + ArchiveCompetitionBias + RecoveryActionBias, + Entry.PriorityScore + + GuidanceBias + + ArchiveCompetitionBias + + RecoveryActionBias + + DeferredLaunchBias, 0.0f, 100.0f ); diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h index 20e2111..1f51729 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h @@ -126,6 +126,15 @@ struct FHyperTwistCoachBrief UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") TArray FocusCaseIds; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 DeferredLaunchCaseCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bNeedsDeferredLaunchRecovery = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasTightenedLaunchOverflow = false; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") TArray Signals;