From 6d02a2a3fcbe24404fe8a67ee1098d810b642de4 Mon Sep 17 00:00:00 2001 From: axiomlogicnexus Date: Wed, 29 Apr 2026 03:01:59 +0200 Subject: [PATCH] Feed drill follow-up outcomes into coach memory --- .../HyperTwistCoachDashboardWidget.cpp | 44 ++++- .../HyperTwistTrainingCoachLibrary.cpp | 8 + .../HyperTwistTrainingRepositoryLibrary.cpp | 164 ++++++++++++++++++ .../HyperTwistTrainingSubsystem.cpp | 8 + .../HyperTwistTrainingTypes.h | 24 +++ 5 files changed, 247 insertions(+), 1 deletion(-) diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp index 8953a19..7318fd0 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp @@ -214,6 +214,14 @@ namespace HyperTwistCoachDashboardWidgetInternal { return TEXT("successful deferred-first follow-up packet history"); } + if (SourceLabel == TEXT("coach-memory-method-drill-follow-up-strong")) + { + return TEXT("successful method-drill follow-up history"); + } + if (SourceLabel == TEXT("coach-memory-method-drill-follow-up-weak")) + { + return TEXT("weak method-drill follow-up history"); + } return SourceLabel.IsEmpty() ? TEXT("n/a") : SourceLabel; } @@ -5657,6 +5665,32 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() { StartMethodDrillFollowUpLabel = TEXT("Drill Follow-Up Optional"); } + const bool bMethodDrillUpstreamStrongSource = + CachedCoachPanelState.PreferredGuidanceSourceLabel + == TEXT("coach-memory-method-drill-follow-up-strong") + || CachedCoachPanelState.PreferredCoachHandoffSourceLabel + == TEXT("coach-memory-method-drill-follow-up-strong"); + const bool bMethodDrillUpstreamWeakSource = + CachedCoachPanelState.PreferredGuidanceSourceLabel + == TEXT("coach-memory-method-drill-follow-up-weak") + || CachedCoachPanelState.PreferredCoachHandoffSourceLabel + == TEXT("coach-memory-method-drill-follow-up-weak"); + const FString MethodDrillUpstreamMemoryLine = + CachedCoachPanelState.MethodDrillFollowUpHistoryCount > 0 + ? FString::Printf( + TEXT(" | Upstream drill memory count %d | strong %d | weak %d | avg outcome %.1f%s%s"), + CachedCoachPanelState.MethodDrillFollowUpHistoryCount, + CachedCoachPanelState.StrongMethodDrillFollowUpHistoryCount, + CachedCoachPanelState.WeakMethodDrillFollowUpHistoryCount, + CachedCoachPanelState.MethodDrillFollowUpAverageOutcomeScore, + bMethodDrillUpstreamStrongSource + ? TEXT(" | Successful drill follow-up history is actively reinforcing narrow continuation.") + : TEXT(""), + bMethodDrillUpstreamWeakSource + ? TEXT(" | Weak drill follow-up history is actively pushing control back toward broader coach guidance.") + : TEXT("") + ) + : FString(); const bool bHasCurrentRunRecap = bHasActiveRun && (CachedRunState.Session.SessionState == EHyperTwistTrainingSessionState::Completed || CachedRunState.Session.SessionState == EHyperTwistTrainingSessionState::Aborted); @@ -6087,6 +6121,10 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() ? TEXT(" | Drill history says broader coach handoff is now stronger than another narrow drill follow-up.") : TEXT(" | Drill history is balanced against broader coach handoff.")); } + if (!MethodDrillUpstreamMemoryLine.IsEmpty()) + { + CoachHandoffRecommendationDetailLine += MethodDrillUpstreamMemoryLine; + } } RecordCoachHandoffHistorySnapshot( HyperTwistCoachDashboardWidgetInternal::DescribeHandoffRecommendationKind( @@ -6271,7 +6309,7 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() *GuidancePreferenceSourceLabel ) : TEXT("Preference: no coach preview lane is currently available."); - const FString GuidancePreferenceDetailLine = + 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 | Launch bias: %s from %s | broadened/tightened history %d/%d | deferred tightened launches %d | deferred launch cases %d%s | The dashboard adopts this preferred lane whenever it needs to surface the next guidance preview."), @@ -6297,6 +6335,10 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() : TEXT("") ) : TEXT("Preference detail: build recommended or follow-up guidance to let the dashboard choose a next surfaced lane."); + if (!MethodDrillUpstreamMemoryLine.IsEmpty()) + { + GuidancePreferenceDetailLine += MethodDrillUpstreamMemoryLine; + } const bool bQueueRecoveryMemorySource = HyperTwistCoachDashboardWidgetInternal::IsQueueRecoveryMemorySource( CachedCoachPanelState.PreferredGuidanceSourceLabel); diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp index adae20d..0c0aeb4 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp @@ -167,6 +167,14 @@ namespace HyperTwistTrainingCoachLibraryInternal { return TEXT("successful deferred-first follow-up packet history"); } + if (SourceLabel == TEXT("coach-memory-method-drill-follow-up-strong")) + { + return TEXT("successful method-drill follow-up history"); + } + if (SourceLabel == TEXT("coach-memory-method-drill-follow-up-weak")) + { + return TEXT("weak method-drill follow-up history"); + } return SourceLabel.IsEmpty() ? TEXT("none") : SourceLabel; } diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp index a54e778..ad41def 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp @@ -25,6 +25,10 @@ namespace HyperTwistTrainingRepositoryLibraryInternal const FString& EntryId ); + TArray SortRunRecordsByUpdateUtc( + const TArray& RunRecords + ); + struct FMethodSegmentAggregate { FHyperTwistTrainingMethodSegment Segment; @@ -174,6 +178,45 @@ namespace HyperTwistTrainingRepositoryLibraryInternal && DeferredPrefixCount == FMath::Min(DeferredLaunchCaseSet.Num(), ActionPlan.FocusCaseIds.Num()); } + bool IsMethodDrillFollowUpTrainingSessionId(const FString& TrainingSessionId) + { + return !TrainingSessionId.IsEmpty() + && TrainingSessionId.Contains(TEXT("method_drill_follow_up"), ESearchCase::IgnoreCase); + } + + float ResolveMethodDrillFollowUpOutcomeScore(const FHyperTwistTrainingRunRecord& RunRecord) + { + if (!RunRecord.IsStructurallyValid()) + { + return 0.0f; + } + + const FHyperTwistTrainingSessionSummary& Summary = RunRecord.Summary; + if (Summary.CompletedAttemptCount <= 0) + { + return Summary.SessionState == EHyperTwistTrainingSessionState::Completed ? 15.0f : 0.0f; + } + + const float SuccessPressure = FMath::Clamp(Summary.SuccessRate * 100.0f, 0.0f, 100.0f); + const float MistakePenalty = FMath::Min(static_cast(Summary.TotalMistakes) * 4.0f, 18.0f); + const float FailurePenalty = static_cast(Summary.FailureCount) * 6.0f; + const float TimeoutPenalty = static_cast(Summary.TimeoutCount) * 8.0f; + const float DnfPenalty = static_cast(Summary.DNFCount) * 10.0f; + const float AbortPenalty = static_cast(Summary.AbortedCount) * 9.0f; + const float CompletionCredit = FMath::Min(static_cast(Summary.CompletedAttemptCount) * 1.5f, 6.0f); + return FMath::Clamp( + SuccessPressure + - MistakePenalty + - FailurePenalty + - TimeoutPenalty + - DnfPenalty + - AbortPenalty + + CompletionCredit, + 0.0f, + 100.0f + ); + } + TArray DeduplicateTemplateCaseIds(const TArray& FocusCaseIds) { TArray UniqueCaseIds; @@ -5979,6 +6022,127 @@ FHyperTwistTrainingCoachMemorySnapshot UHyperTwistTrainingRepositoryLibrary::Der Snapshot.bNeedsCoachIntervention = Snapshot.bNeedsCoachIntervention || RecommendationHistory.bHasRepeatRecommendationPressure; } + + TArray MethodDrillFollowUpRunRecords; + MethodDrillFollowUpRunRecords.Reserve(RepositoryState.RunRecords.Num()); + for (const FHyperTwistTrainingRunRecord& RunRecord : RepositoryState.RunRecords) + { + if (!RunRecord.IsStructurallyValid() + || RunRecord.Session.UserId != UserId + || !HyperTwistTrainingRepositoryLibraryInternal::IsMethodDrillFollowUpTrainingSessionId( + RunRecord.Session.TrainingSessionId)) + { + continue; + } + + MethodDrillFollowUpRunRecords.Add(RunRecord); + } + + if (MethodDrillFollowUpRunRecords.Num() > 0) + { + MethodDrillFollowUpRunRecords = + HyperTwistTrainingRepositoryLibraryInternal::SortRunRecordsByUpdateUtc( + MethodDrillFollowUpRunRecords); + const int32 FirstRelevantIndex = FMath::Max(0, MethodDrillFollowUpRunRecords.Num() - 8); + float MethodDrillOutcomeAccumulator = 0.0f; + for (int32 Index = FirstRelevantIndex; Index < MethodDrillFollowUpRunRecords.Num(); ++Index) + { + const FHyperTwistTrainingRunRecord& RunRecord = MethodDrillFollowUpRunRecords[Index]; + const float OutcomeScore = + HyperTwistTrainingRepositoryLibraryInternal::ResolveMethodDrillFollowUpOutcomeScore( + RunRecord); + Snapshot.MethodDrillFollowUpHistoryCount += 1; + MethodDrillOutcomeAccumulator += OutcomeScore; + if (OutcomeScore >= 70.0f) + { + Snapshot.StrongMethodDrillFollowUpHistoryCount += 1; + } + else if (OutcomeScore <= 45.0f) + { + Snapshot.WeakMethodDrillFollowUpHistoryCount += 1; + } + } + + if (Snapshot.MethodDrillFollowUpHistoryCount > 0) + { + Snapshot.MethodDrillFollowUpAverageOutcomeScore = + MethodDrillOutcomeAccumulator + / static_cast(Snapshot.MethodDrillFollowUpHistoryCount); + } + + const bool bStrongMethodDrillHistory = + Snapshot.MethodDrillFollowUpHistoryCount >= 2 + && Snapshot.StrongMethodDrillFollowUpHistoryCount + >= Snapshot.WeakMethodDrillFollowUpHistoryCount + && Snapshot.MethodDrillFollowUpAverageOutcomeScore >= 60.0f; + const bool bWeakMethodDrillHistory = + Snapshot.MethodDrillFollowUpHistoryCount >= 2 + && Snapshot.WeakMethodDrillFollowUpHistoryCount + > Snapshot.StrongMethodDrillFollowUpHistoryCount + && Snapshot.MethodDrillFollowUpAverageOutcomeScore <= 50.0f; + if (bStrongMethodDrillHistory) + { + const int32 MethodDrillFollowUpBias = FMath::Clamp( + Snapshot.StrongMethodDrillFollowUpHistoryCount + - Snapshot.WeakMethodDrillFollowUpHistoryCount + + (Snapshot.MethodDrillFollowUpAverageOutcomeScore >= 75.0f ? 1 : 0), + 1, + 3 + ); + Snapshot.FollowUpGuidanceContinueBias += MethodDrillFollowUpBias; + Snapshot.SuppressedPrimaryQueuePressureCount += FMath::Max( + 1, + MethodDrillFollowUpBias - 1 + ); + if (Snapshot.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::None + || Snapshot.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::Recommended + || Snapshot.PreferredGuidanceSourceLabel.StartsWith(TEXT("recommendation-history")) + || Snapshot.PreferredGuidanceSourceLabel.StartsWith(TEXT("coach-memory-method-drill"))) + { + Snapshot.PreferredGuidanceLane = EHyperTwistTrainingCoachGuidanceLane::FollowUp; + Snapshot.PreferredGuidanceSourceLabel = + TEXT("coach-memory-method-drill-follow-up-strong"); + } + if (Snapshot.PreferredCoachHandoffKind == EHyperTwistTrainingCoachHandoffKind::None + || Snapshot.PreferredCoachHandoffKind == EHyperTwistTrainingCoachHandoffKind::Recommended + || Snapshot.PreferredCoachHandoffSourceLabel.IsEmpty() + || Snapshot.PreferredCoachHandoffSourceLabel.StartsWith(TEXT("recommendation-history")) + || Snapshot.PreferredCoachHandoffSourceLabel.StartsWith(TEXT("coach-memory-method-drill"))) + { + Snapshot.PreferredCoachHandoffKind = EHyperTwistTrainingCoachHandoffKind::FollowUp; + Snapshot.PreferredCoachHandoffSourceLabel = + TEXT("coach-memory-method-drill-follow-up-strong"); + } + } + else if (bWeakMethodDrillHistory) + { + const int32 MethodDrillRedirectBias = FMath::Clamp( + Snapshot.WeakMethodDrillFollowUpHistoryCount + - Snapshot.StrongMethodDrillFollowUpHistoryCount, + 1, + 2 + ); + Snapshot.RecommendedGuidanceContinueBias += MethodDrillRedirectBias; + Snapshot.SuppressedFollowUpQueuePressureCount += MethodDrillRedirectBias; + if (Snapshot.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::None + || Snapshot.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp + || Snapshot.PreferredGuidanceSourceLabel.StartsWith(TEXT("coach-memory-method-drill"))) + { + Snapshot.PreferredGuidanceLane = EHyperTwistTrainingCoachGuidanceLane::Recommended; + Snapshot.PreferredGuidanceSourceLabel = + TEXT("coach-memory-method-drill-follow-up-weak"); + } + if (Snapshot.PreferredCoachHandoffKind == EHyperTwistTrainingCoachHandoffKind::None + || Snapshot.PreferredCoachHandoffKind == EHyperTwistTrainingCoachHandoffKind::FollowUp + || Snapshot.PreferredCoachHandoffSourceLabel.IsEmpty() + || Snapshot.PreferredCoachHandoffSourceLabel.StartsWith(TEXT("coach-memory-method-drill"))) + { + Snapshot.PreferredCoachHandoffKind = EHyperTwistTrainingCoachHandoffKind::Recommended; + Snapshot.PreferredCoachHandoffSourceLabel = + TEXT("coach-memory-method-drill-follow-up-weak"); + } + } + } if (CarryForwardPolicy.IsStructurallyValid()) { Snapshot.SuppressedResolvedCoachCaseIds = CarryForwardPolicy.SuppressedResolvedCaseIds; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp index 358922c..a96af3b 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp @@ -483,6 +483,14 @@ FHyperTwistTrainingCoachPanelState UHyperTwistTrainingSubsystem::GetActiveCoachP ActiveCoachMemorySnapshot.DeferredTightenedLaunchHistoryCount; PanelState.DeferredLaunchHistoryCaseCount = ActiveCoachMemorySnapshot.DeferredLaunchHistoryCaseCount; + PanelState.MethodDrillFollowUpHistoryCount = + ActiveCoachMemorySnapshot.MethodDrillFollowUpHistoryCount; + PanelState.StrongMethodDrillFollowUpHistoryCount = + ActiveCoachMemorySnapshot.StrongMethodDrillFollowUpHistoryCount; + PanelState.WeakMethodDrillFollowUpHistoryCount = + ActiveCoachMemorySnapshot.WeakMethodDrillFollowUpHistoryCount; + PanelState.MethodDrillFollowUpAverageOutcomeScore = + ActiveCoachMemorySnapshot.MethodDrillFollowUpAverageOutcomeScore; PanelState.bRepositoryVerificationPassed = ActiveRepositoryRoundTripVerification.bDidRoundTripPass; PanelState.bRepositoryVerificationNeedsAttention = ActiveRepositoryRoundTripVerification.ResolvedPath.IsEmpty() || !ActiveRepositoryRoundTripVerification.bDidRoundTripPass; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h index e9f4aeb..2429c7c 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h @@ -1923,6 +1923,18 @@ struct FHyperTwistTrainingCoachMemorySnapshot UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") int32 DeferredFirstFollowUpPacketHistoryCount = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 MethodDrillFollowUpHistoryCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 StrongMethodDrillFollowUpHistoryCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 WeakMethodDrillFollowUpHistoryCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float MethodDrillFollowUpAverageOutcomeScore = 0.0f; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") bool bNeedsCoachIntervention = false; @@ -4798,6 +4810,18 @@ struct FHyperTwistTrainingCoachPanelState UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") int32 DeferredLaunchHistoryCaseCount = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 MethodDrillFollowUpHistoryCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 StrongMethodDrillFollowUpHistoryCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 WeakMethodDrillFollowUpHistoryCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float MethodDrillFollowUpAverageOutcomeScore = 0.0f; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") int32 SignalCount = 0;