Explain launch budget memory in coaching

This commit is contained in:
axiomlogicnexus 2026-04-27 23:54:07 +02:00
parent 377b57954d
commit 620b3a1129
5 changed files with 259 additions and 4 deletions

View file

@ -129,10 +129,32 @@ namespace HyperTwistCoachDashboardWidgetInternal
{
return TEXT("balanced queue-recovery history");
}
if (SourceLabel == TEXT("recommendation-history-broadened-launch"))
{
return TEXT("successful broadened-launch history");
}
if (SourceLabel == TEXT("recommendation-history-tightened-launch"))
{
return TEXT("successful tightened-launch history");
}
return SourceLabel.IsEmpty() ? TEXT("n/a") : SourceLabel;
}
FString DescribeLaunchBudgetBias(const int32 Bias)
{
if (Bias > 0)
{
return TEXT("broaden");
}
if (Bias < 0)
{
return TEXT("tighten");
}
return TEXT("steady");
}
UTextBlock* AddTextRow(
UWidgetTree* WidgetTree,
UVerticalBox* Parent,
@ -4964,7 +4986,7 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
}
CoachHandoffRecommendationDetailLine = FString::Printf(
TEXT("Closure continuation bias %d | Closure suppression bias %d | Queue ready: %s | Follow-up ready: %s | Recommended ready: %s | Preferred lane: %s | Memory source: %s%s%s | Handoff history preferred: %s | accepted q/f/r %d/%d/%d | recommended q/f/r %d/%d/%d"),
TEXT("Closure continuation bias %d | Closure suppression bias %d | Queue ready: %s | Follow-up ready: %s | Recommended ready: %s | Preferred lane: %s | Memory source: %s%s%s | Launch bias: %s from %s | broadened/tightened history %d/%d | Handoff history preferred: %s | accepted q/f/r %d/%d/%d | recommended q/f/r %d/%d/%d"),
HandoffClosureBias.ContinuationBias,
HandoffClosureBias.SuppressionBias,
bQueueReadyForHandoff ? TEXT("yes") : TEXT("no"),
@ -4979,6 +5001,12 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
bHandoffRecommendationUsedHistory
? TEXT(" | Retained handoff history actively weighted this recommendation.")
: TEXT(""),
*HyperTwistCoachDashboardWidgetInternal::DescribeLaunchBudgetBias(
CachedCoachPanelState.PreferredLaunchBudgetBias),
*HyperTwistCoachDashboardWidgetInternal::DescribeCoachMemorySourceLabel(
CachedCoachPanelState.PreferredLaunchBudgetSourceLabel),
CachedCoachPanelState.BroadenedLaunchBudgetHistoryCount,
CachedCoachPanelState.TightenedLaunchBudgetHistoryCount,
*HyperTwistCoachDashboardWidgetInternal::DescribeHandoffRecommendationKind(
HandoffHistoryBias.PreferredKind),
HandoffHistoryBias.QueueAcceptedDeltaCount,
@ -5170,7 +5198,7 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
const 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 | The dashboard adopts this preferred lane whenever it needs to surface the next guidance preview."),
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 | The dashboard adopts this preferred lane whenever it needs to surface the next guidance preview."),
GuidanceLanePreference.RecommendedDecisionCount,
GuidanceLanePreference.RecommendedContinueBias,
GuidanceLanePreference.RecommendedAverageSuccessRate,
@ -5178,7 +5206,13 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
GuidanceLanePreference.FollowUpDecisionCount,
GuidanceLanePreference.FollowUpContinueBias,
GuidanceLanePreference.FollowUpAverageSuccessRate,
GuidanceLanePreference.ClosureContinuationBias
GuidanceLanePreference.ClosureContinuationBias,
*HyperTwistCoachDashboardWidgetInternal::DescribeLaunchBudgetBias(
CachedCoachPanelState.PreferredLaunchBudgetBias),
*HyperTwistCoachDashboardWidgetInternal::DescribeCoachMemorySourceLabel(
CachedCoachPanelState.PreferredLaunchBudgetSourceLabel),
CachedCoachPanelState.BroadenedLaunchBudgetHistoryCount,
CachedCoachPanelState.TightenedLaunchBudgetHistoryCount
)
: TEXT("Preference detail: build recommended or follow-up guidance to let the dashboard choose a next surfaced lane.");
const bool bQueueRecoveryMemorySource =
@ -5510,7 +5544,7 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
const FString GuidanceRationaleDetailLine =
ActivePreviewBrief != nullptr
? FString::Printf(
TEXT("Brief: %s | Action: %s | Deck: %s | Method: %s | Focus cases: %d | Review: %s | Cadence: %s | Variety: %s | Guidance source: %s | Selected focus: %s | Matching signals: %d | Case signal: %s | Top signals: %s"),
TEXT("Brief: %s | Action: %s | Deck: %s | Method: %s | Focus cases: %d | Review: %s | Cadence: %s | Variety: %s | Guidance source: %s | Launch bias: %s from %s | broadened/tightened history %d/%d | Selected focus: %s | Matching signals: %d | Case signal: %s | Top signals: %s"),
ActivePreviewBrief->Headline.IsEmpty() ? TEXT("n/a") : *ActivePreviewBrief->Headline,
ActivePreviewBrief->PrimaryActionLabel.IsEmpty() ? TEXT("n/a") : *ActivePreviewBrief->PrimaryActionLabel,
ActivePreviewBrief->FocusDeckId.IsEmpty() ? TEXT("n/a") : *ActivePreviewBrief->FocusDeckId,
@ -5521,6 +5555,12 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
ActivePreviewBrief->bNeedsVarietyRotation ? TEXT("yes") : TEXT("no"),
*HyperTwistCoachDashboardWidgetInternal::DescribeCoachMemorySourceLabel(
CachedCoachPanelState.PreferredGuidanceSourceLabel),
*HyperTwistCoachDashboardWidgetInternal::DescribeLaunchBudgetBias(
CachedCoachPanelState.PreferredLaunchBudgetBias),
*HyperTwistCoachDashboardWidgetInternal::DescribeCoachMemorySourceLabel(
CachedCoachPanelState.PreferredLaunchBudgetSourceLabel),
CachedCoachPanelState.BroadenedLaunchBudgetHistoryCount,
CachedCoachPanelState.TightenedLaunchBudgetHistoryCount,
bSelectedPreviewCaseIsExplicitFocus ? TEXT("yes") : TEXT("no"),
SelectedPreviewCaseSignalCount,
*SelectedPreviewCaseSignalLine,

View file

@ -82,6 +82,14 @@ namespace HyperTwistTrainingCoachLibraryInternal
{
return TEXT("balanced queue-recovery history");
}
if (SourceLabel == TEXT("recommendation-history-broadened-launch"))
{
return TEXT("successful broadened-launch history");
}
if (SourceLabel == TEXT("recommendation-history-tightened-launch"))
{
return TEXT("successful tightened-launch history");
}
return SourceLabel.IsEmpty() ? TEXT("none") : SourceLabel;
}
@ -1790,6 +1798,169 @@ FHyperTwistCoachBrief UHyperTwistTrainingCoachLibrary::RefineCoachBriefWithClosu
return Brief;
}
FHyperTwistCoachSignal UHyperTwistTrainingCoachLibrary::DeriveLaunchBudgetHistorySignal(
const FHyperTwistTrainingCoachMemorySnapshot& CoachMemorySnapshot,
const FHyperTwistCoachBrief& CurrentBrief)
{
if (CoachMemorySnapshot.PreferredLaunchBudgetBias == 0
|| (CoachMemorySnapshot.BroadenedLaunchBudgetHistoryCount <= 0
&& CoachMemorySnapshot.TightenedLaunchBudgetHistoryCount <= 0))
{
return FHyperTwistCoachSignal();
}
const bool bBroadenPreferred = CoachMemorySnapshot.PreferredLaunchBudgetBias > 0;
const bool bCompetingGuidancePressure =
CoachMemorySnapshot.RecommendedGuidanceContinueBias > 0
&& CoachMemorySnapshot.FollowUpGuidanceContinueBias > 0;
const float Score = FMath::Clamp(
58.0f
+ static_cast<float>(FMath::Max(
CoachMemorySnapshot.BroadenedLaunchBudgetHistoryCount,
CoachMemorySnapshot.TightenedLaunchBudgetHistoryCount)) * 4.0f
+ (bCompetingGuidancePressure ? 4.0f : 0.0f)
+ ((CurrentBrief.FocusLane == EHyperTwistCoachFocusLane::Cadence
|| CurrentBrief.FocusLane == EHyperTwistCoachFocusLane::Stability
|| CurrentBrief.FocusLane == EHyperTwistCoachFocusLane::ReviewBacklog) ? 3.0f : 0.0f),
0.0f,
100.0f
);
const FString SignalId = bBroadenPreferred
? TEXT("launch_budget_history_broaden")
: TEXT("launch_budget_history_tighten");
const EHyperTwistCoachFocusLane FocusLane = bBroadenPreferred
? EHyperTwistCoachFocusLane::ReviewBacklog
: EHyperTwistCoachFocusLane::Stability;
const FString Headline = bBroadenPreferred
? TEXT("Launch history favors a broader next coach run")
: TEXT("Launch history favors a tighter next coach run");
const FString PreferredGuidanceLaneLabel =
CoachMemorySnapshot.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp
? TEXT("follow-up")
: (CoachMemorySnapshot.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::Recommended
? TEXT("recommended")
: TEXT("none"));
const FString Detail = FString::Printf(
TEXT("launchBias=%s, broadenedHistory=%d, tightenedHistory=%d, guidanceLane=%s, handoff=%s, source=%s"),
bBroadenPreferred ? TEXT("broaden") : TEXT("tighten"),
CoachMemorySnapshot.BroadenedLaunchBudgetHistoryCount,
CoachMemorySnapshot.TightenedLaunchBudgetHistoryCount,
*PreferredGuidanceLaneLabel,
*HyperTwistTrainingCoachLibraryInternal::DescribeCoachHandoffKind(
CoachMemorySnapshot.PreferredCoachHandoffKind),
CoachMemorySnapshot.PreferredLaunchBudgetSourceLabel.IsEmpty()
? TEXT("n/a")
: *HyperTwistTrainingCoachLibraryInternal::DescribeCoachMemorySourceLabel(
CoachMemorySnapshot.PreferredLaunchBudgetSourceLabel)
);
const EHyperTwistTrainingDeliveryMode SuggestedMode =
CurrentBrief.SuggestedMode != EHyperTwistTrainingDeliveryMode::Timer
? CurrentBrief.SuggestedMode
: CoachMemorySnapshot.DominantCoachSuggestedMode;
const EHyperTwistTrainingSelectionPolicy SuggestedSelectionPolicy =
CurrentBrief.SuggestedSelectionPolicy != EHyperTwistTrainingSelectionPolicy::Weighted
? CurrentBrief.SuggestedSelectionPolicy
: 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 = CurrentBrief.FocusCaseIds.Num() > 0
? CurrentBrief.FocusCaseIds
: (CoachMemorySnapshot.WeakCaseIds.Num() > 0
? CoachMemorySnapshot.WeakCaseIds
: CoachMemorySnapshot.DueCaseIds);
HyperTwistTrainingCoachLibraryInternal::ApplyGuidancePreferenceToSignal(
Signal,
CoachMemorySnapshot.PreferredGuidanceLane,
CoachMemorySnapshot,
bCompetingGuidancePressure
);
return Signal;
}
FHyperTwistCoachBrief UHyperTwistTrainingCoachLibrary::RefineCoachBriefWithLaunchBudgetHistorySignal(
const FHyperTwistCoachBrief& BaseBrief,
const FHyperTwistCoachSignal& LaunchBudgetHistorySignal)
{
if (LaunchBudgetHistorySignal.SignalId.IsEmpty())
{
return BaseBrief;
}
FHyperTwistCoachBrief Brief = BaseBrief;
if (!Brief.Signals.ContainsByPredicate(
[&LaunchBudgetHistorySignal](const FHyperTwistCoachSignal& Candidate)
{
return Candidate.SignalId == LaunchBudgetHistorySignal.SignalId;
}))
{
Brief.Signals.Add(LaunchBudgetHistorySignal);
}
HyperTwistTrainingCoachLibraryInternal::SortSignals(Brief.Signals);
const bool bLaunchFacingBrief =
Brief.FocusLane == EHyperTwistCoachFocusLane::ReviewBacklog
|| Brief.FocusLane == EHyperTwistCoachFocusLane::Cadence
|| Brief.FocusLane == EHyperTwistCoachFocusLane::Stability
|| Brief.bNeedsCadenceRecovery;
const bool bShouldPromoteLaunchSignal =
bLaunchFacingBrief
&& LaunchBudgetHistorySignal.Score >= Brief.PriorityScore - 4.0f;
if (bShouldPromoteLaunchSignal)
{
for (FHyperTwistCoachSignal& Signal : Brief.Signals)
{
if (Signal.SignalId == LaunchBudgetHistorySignal.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<FString> 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,

View file

@ -434,6 +434,13 @@ FHyperTwistTrainingCoachPanelState UHyperTwistTrainingSubsystem::GetActiveCoachP
PanelState.PreferredQueueRecoveryAction = ActiveCoachMemorySnapshot.PreferredQueueRecoveryAction;
PanelState.PreferredQueueRecoveryActionSourceLabel =
ActiveCoachMemorySnapshot.PreferredQueueRecoveryActionSourceLabel;
PanelState.PreferredLaunchBudgetBias = ActiveCoachMemorySnapshot.PreferredLaunchBudgetBias;
PanelState.PreferredLaunchBudgetSourceLabel =
ActiveCoachMemorySnapshot.PreferredLaunchBudgetSourceLabel;
PanelState.BroadenedLaunchBudgetHistoryCount =
ActiveCoachMemorySnapshot.BroadenedLaunchBudgetHistoryCount;
PanelState.TightenedLaunchBudgetHistoryCount =
ActiveCoachMemorySnapshot.TightenedLaunchBudgetHistoryCount;
PanelState.bRepositoryVerificationPassed = ActiveRepositoryRoundTripVerification.bDidRoundTripPass;
PanelState.bRepositoryVerificationNeedsAttention = ActiveRepositoryRoundTripVerification.ResolvedPath.IsEmpty()
|| !ActiveRepositoryRoundTripVerification.bDidRoundTripPass;
@ -2458,6 +2465,19 @@ void UHyperTwistTrainingSubsystem::RefreshRepositoryViews()
);
ActiveCoachSignals = ActiveCoachBrief.Signals;
}
const FHyperTwistCoachSignal LaunchBudgetHistorySignal =
UHyperTwistTrainingCoachLibrary::DeriveLaunchBudgetHistorySignal(
ActiveCoachMemorySnapshot,
ActiveCoachBrief
);
if (!LaunchBudgetHistorySignal.SignalId.IsEmpty())
{
ActiveCoachBrief = UHyperTwistTrainingCoachLibrary::RefineCoachBriefWithLaunchBudgetHistorySignal(
ActiveCoachBrief,
LaunchBudgetHistorySignal
);
ActiveCoachSignals = ActiveCoachBrief.Signals;
}
ActiveCoachFollowUpBrief = ActiveCoachActionPlanOutcomeSummary.IsStructurallyValid()
&& (
ActiveCoachActionClosureSummary.bShouldSpawnFollowUp

View file

@ -491,6 +491,18 @@ public:
const FHyperTwistCoachSignal& ClosureRecoveryHistorySignal
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
static FHyperTwistCoachSignal DeriveLaunchBudgetHistorySignal(
const FHyperTwistTrainingCoachMemorySnapshot& CoachMemorySnapshot,
const FHyperTwistCoachBrief& CurrentBrief
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
static FHyperTwistCoachBrief RefineCoachBriefWithLaunchBudgetHistorySignal(
const FHyperTwistCoachBrief& BaseBrief,
const FHyperTwistCoachSignal& LaunchBudgetHistorySignal
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
static FHyperTwistCoachScheduleHorizonSummary DeriveCoachScheduleHorizonSummary(
const FHyperTwistTrainingCoachSessionQueueState& QueueState,

View file

@ -4464,6 +4464,18 @@ struct FHyperTwistTrainingCoachPanelState
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString PreferredQueueRecoveryActionSourceLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 PreferredLaunchBudgetBias = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString PreferredLaunchBudgetSourceLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 BroadenedLaunchBudgetHistoryCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 TightenedLaunchBudgetHistoryCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 SignalCount = 0;