Feed drill follow-up outcomes into coach memory

This commit is contained in:
axiomlogicnexus 2026-04-29 03:01:59 +02:00
parent 64c92dc978
commit 6d02a2a3fc
5 changed files with 247 additions and 1 deletions

View file

@ -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);

View file

@ -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;
}

View file

@ -25,6 +25,10 @@ namespace HyperTwistTrainingRepositoryLibraryInternal
const FString& EntryId
);
TArray<FHyperTwistTrainingRunRecord> SortRunRecordsByUpdateUtc(
const TArray<FHyperTwistTrainingRunRecord>& 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<float>(Summary.TotalMistakes) * 4.0f, 18.0f);
const float FailurePenalty = static_cast<float>(Summary.FailureCount) * 6.0f;
const float TimeoutPenalty = static_cast<float>(Summary.TimeoutCount) * 8.0f;
const float DnfPenalty = static_cast<float>(Summary.DNFCount) * 10.0f;
const float AbortPenalty = static_cast<float>(Summary.AbortedCount) * 9.0f;
const float CompletionCredit = FMath::Min(static_cast<float>(Summary.CompletedAttemptCount) * 1.5f, 6.0f);
return FMath::Clamp(
SuccessPressure
- MistakePenalty
- FailurePenalty
- TimeoutPenalty
- DnfPenalty
- AbortPenalty
+ CompletionCredit,
0.0f,
100.0f
);
}
TArray<FString> DeduplicateTemplateCaseIds(const TArray<FString>& FocusCaseIds)
{
TArray<FString> UniqueCaseIds;
@ -5979,6 +6022,127 @@ FHyperTwistTrainingCoachMemorySnapshot UHyperTwistTrainingRepositoryLibrary::Der
Snapshot.bNeedsCoachIntervention = Snapshot.bNeedsCoachIntervention
|| RecommendationHistory.bHasRepeatRecommendationPressure;
}
TArray<FHyperTwistTrainingRunRecord> 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<float>(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;

View file

@ -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;

View file

@ -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;