Retain drill recovery memory history on dashboard

This commit is contained in:
axiomlogicnexus 2026-04-29 04:36:57 +02:00
parent 66917d0edd
commit 1b55663331
2 changed files with 430 additions and 0 deletions

View file

@ -835,6 +835,7 @@ void UHyperTwistCoachDashboardWidget::RefreshCoachDashboardView()
SyncSelectedClosureRecoveryHistoryEntry();
SyncSelectedMethodDrillFollowUpHistoryEntry();
SyncSelectedMethodDrillRecoveryOutcomeEntry();
SyncSelectedMethodDrillRecoveryMemoryHistoryEntry();
SyncPreferredGuidancePreviewLane(false);
UpdateDashboardPresentation();
}
@ -1969,6 +1970,49 @@ bool UHyperTwistCoachDashboardWidget::SelectNextMethodDrillRecoveryOutcomeEntry(
return true;
}
bool UHyperTwistCoachDashboardWidget::SelectPreviousMethodDrillRecoveryMemoryHistoryEntry()
{
if (MethodDrillRecoveryMemoryHistoryEntries.Num() <= 0)
{
SelectedMethodDrillRecoveryMemoryHistoryIndex = INDEX_NONE;
return false;
}
if (SelectedMethodDrillRecoveryMemoryHistoryIndex == INDEX_NONE)
{
SyncSelectedMethodDrillRecoveryMemoryHistoryEntry();
return SelectedMethodDrillRecoveryMemoryHistoryIndex != INDEX_NONE;
}
SelectedMethodDrillRecoveryMemoryHistoryIndex =
(SelectedMethodDrillRecoveryMemoryHistoryIndex - 1
+ MethodDrillRecoveryMemoryHistoryEntries.Num())
% MethodDrillRecoveryMemoryHistoryEntries.Num();
UpdateDashboardPresentation();
return true;
}
bool UHyperTwistCoachDashboardWidget::SelectNextMethodDrillRecoveryMemoryHistoryEntry()
{
if (MethodDrillRecoveryMemoryHistoryEntries.Num() <= 0)
{
SelectedMethodDrillRecoveryMemoryHistoryIndex = INDEX_NONE;
return false;
}
if (SelectedMethodDrillRecoveryMemoryHistoryIndex == INDEX_NONE)
{
SyncSelectedMethodDrillRecoveryMemoryHistoryEntry();
return SelectedMethodDrillRecoveryMemoryHistoryIndex != INDEX_NONE;
}
SelectedMethodDrillRecoveryMemoryHistoryIndex =
(SelectedMethodDrillRecoveryMemoryHistoryIndex + 1)
% MethodDrillRecoveryMemoryHistoryEntries.Num();
UpdateDashboardPresentation();
return true;
}
bool UHyperTwistCoachDashboardWidget::StartLiveAttemptTimer()
{
if (!CachedRunState.Session.IsStructurallyValid() || !CachedRunState.CurrentSelection.TrainingCase.IsStructurallyValid())
@ -2325,6 +2369,16 @@ void UHyperTwistCoachDashboardWidget::HandleNextMethodDrillRecoveryHistoryClicke
SelectNextMethodDrillRecoveryOutcomeEntry();
}
void UHyperTwistCoachDashboardWidget::HandlePreviousMethodDrillRecoveryMemoryHistoryClicked()
{
SelectPreviousMethodDrillRecoveryMemoryHistoryEntry();
}
void UHyperTwistCoachDashboardWidget::HandleNextMethodDrillRecoveryMemoryHistoryClicked()
{
SelectNextMethodDrillRecoveryMemoryHistoryEntry();
}
void UHyperTwistCoachDashboardWidget::HandleClearRunClicked()
{
ClearActiveTrainingRun();
@ -3267,6 +3321,27 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt()
TEXT("CoachMethodDrillRecoveryHistoryDetail"),
8
);
MethodDrillRecoveryMemoryHistoryHeaderTextBlock =
HyperTwistCoachDashboardWidgetInternal::AddTextRow(
WidgetTree,
RootLayout,
TEXT("CoachMethodDrillRecoveryMemoryHistoryHeader"),
2
);
MethodDrillRecoveryMemoryHistoryStatusTextBlock =
HyperTwistCoachDashboardWidgetInternal::AddTextRow(
WidgetTree,
RootLayout,
TEXT("CoachMethodDrillRecoveryMemoryHistoryStatus"),
2
);
MethodDrillRecoveryMemoryHistoryDetailTextBlock =
HyperTwistCoachDashboardWidgetInternal::AddTextRow(
WidgetTree,
RootLayout,
TEXT("CoachMethodDrillRecoveryMemoryHistoryDetail"),
8
);
QueueSuppressionHistoryHeaderTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
WidgetTree,
RootLayout,
@ -3474,6 +3549,45 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt()
NextMethodDrillRecoveryHistoryButtonLabel = NextMethodDrillRecoveryLabelRaw;
}
UHorizontalBox* MethodDrillRecoveryMemoryNavRow = WidgetTree->ConstructWidget<UHorizontalBox>(
UHorizontalBox::StaticClass(),
TEXT("CoachDashboardMethodDrillRecoveryMemoryNavRow")
);
if (MethodDrillRecoveryMemoryNavRow != nullptr)
{
if (UVerticalBoxSlot* MethodDrillRecoveryMemoryNavRowSlot =
RootLayout->AddChildToVerticalBox(MethodDrillRecoveryMemoryNavRow))
{
MethodDrillRecoveryMemoryNavRowSlot->SetPadding(FMargin(0.0f, 4.0f, 0.0f, 0.0f));
}
UTextBlock* PreviousMethodDrillRecoveryMemoryLabelRaw = nullptr;
PreviousMethodDrillRecoveryMemoryHistoryButton =
HyperTwistCoachDashboardWidgetInternal::AddButton(
WidgetTree,
MethodDrillRecoveryMemoryNavRow,
TEXT("PreviousMethodDrillRecoveryMemoryHistoryButton"),
TEXT("PreviousMethodDrillRecoveryMemoryHistoryButtonLabel"),
TEXT("Prev Drill Memory"),
PreviousMethodDrillRecoveryMemoryLabelRaw
);
PreviousMethodDrillRecoveryMemoryHistoryButtonLabel =
PreviousMethodDrillRecoveryMemoryLabelRaw;
UTextBlock* NextMethodDrillRecoveryMemoryLabelRaw = nullptr;
NextMethodDrillRecoveryMemoryHistoryButton =
HyperTwistCoachDashboardWidgetInternal::AddButton(
WidgetTree,
MethodDrillRecoveryMemoryNavRow,
TEXT("NextMethodDrillRecoveryMemoryHistoryButton"),
TEXT("NextMethodDrillRecoveryMemoryHistoryButtonLabel"),
TEXT("Next Drill Memory"),
NextMethodDrillRecoveryMemoryLabelRaw
);
NextMethodDrillRecoveryMemoryHistoryButtonLabel =
NextMethodDrillRecoveryMemoryLabelRaw;
}
UHorizontalBox* AttemptInputRow = WidgetTree->ConstructWidget<UHorizontalBox>(
UHorizontalBox::StaticClass(),
TEXT("CoachDashboardAttemptInputRow")
@ -3901,6 +4015,20 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt()
&UHyperTwistCoachDashboardWidget::HandleNextMethodDrillRecoveryHistoryClicked
);
}
if (PreviousMethodDrillRecoveryMemoryHistoryButton != nullptr)
{
PreviousMethodDrillRecoveryMemoryHistoryButton->OnClicked.AddDynamic(
this,
&UHyperTwistCoachDashboardWidget::HandlePreviousMethodDrillRecoveryMemoryHistoryClicked
);
}
if (NextMethodDrillRecoveryMemoryHistoryButton != nullptr)
{
NextMethodDrillRecoveryMemoryHistoryButton->OnClicked.AddDynamic(
this,
&UHyperTwistCoachDashboardWidget::HandleNextMethodDrillRecoveryMemoryHistoryClicked
);
}
if (CompleteRunButton != nullptr)
{
CompleteRunButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleCompleteRunClicked);
@ -4201,6 +4329,23 @@ void UHyperTwistCoachDashboardWidget::SyncSelectedMethodDrillRecoveryOutcomeEntr
}
}
void UHyperTwistCoachDashboardWidget::SyncSelectedMethodDrillRecoveryMemoryHistoryEntry()
{
if (MethodDrillRecoveryMemoryHistoryEntries.Num() <= 0)
{
SelectedMethodDrillRecoveryMemoryHistoryIndex = INDEX_NONE;
return;
}
if (SelectedMethodDrillRecoveryMemoryHistoryIndex < 0
|| SelectedMethodDrillRecoveryMemoryHistoryIndex
>= MethodDrillRecoveryMemoryHistoryEntries.Num())
{
SelectedMethodDrillRecoveryMemoryHistoryIndex =
MethodDrillRecoveryMemoryHistoryEntries.Num() - 1;
}
}
void UHyperTwistCoachDashboardWidget::SyncPreferredGuidancePreviewLane(const bool bForceAdoptPreferredLane)
{
const FHyperTwistCoachDashboardGuidanceLanePreference Preference = BuildGuidanceLanePreference();
@ -5705,6 +5850,115 @@ void UHyperTwistCoachDashboardWidget::RecordMethodDrillFollowUpHistoryEntry(
SelectedMethodDrillFollowUpHistoryIndex = MethodDrillFollowUpHistoryEntries.Num() - 1;
}
void UHyperTwistCoachDashboardWidget::RecordMethodDrillRecoveryMemoryHistorySnapshot(
const FHyperTwistCoachDashboardQueueRecoveryWeighting& QueueRecoveryWeighting,
const FString& QueueRecoveryStatusLine,
const FString& HandoffRecommendationKindLabel,
const FString& HandoffRecommendationStatusLine,
const bool bUsedMethodDrillRecoveryMemoryForHandoff
)
{
const bool bHasMethodDrillRecoveryHistory =
CachedCoachPanelState.MethodDrillRecoveryHistoryCount > 0;
const bool bDrillRecoverySourceActive =
CachedCoachPanelState.PreferredQueueRecoveryActionSourceLabel
== TEXT("coach-memory-method-drill-recovery-strong")
|| CachedCoachPanelState.PreferredQueueRecoveryActionSourceLabel
== TEXT("coach-memory-method-drill-recovery-weak");
if (!bHasMethodDrillRecoveryHistory
&& !QueueRecoveryWeighting.bHasMethodDrillMemorySignal
&& !bUsedMethodDrillRecoveryMemoryForHandoff
&& !bDrillRecoverySourceActive)
{
return;
}
FHyperTwistCoachDashboardMethodDrillRecoveryMemoryHistoryEntry Entry;
Entry.RecordedAtUtc = FDateTime::UtcNow().ToIso8601();
Entry.PreferredRecoveryAction = CachedCoachPanelState.PreferredQueueRecoveryAction;
Entry.PreferredRecoveryActionSourceLabel =
CachedCoachPanelState.PreferredQueueRecoveryActionSourceLabel;
Entry.RecommendedHandoffKindLabel =
HandoffRecommendationKindLabel.IsEmpty() ? TEXT("none") : HandoffRecommendationKindLabel;
Entry.QueueRecoveryStatusLine = QueueRecoveryStatusLine;
Entry.HandoffRecommendationStatusLine = HandoffRecommendationStatusLine;
Entry.MethodDrillRecoveryHistoryCount =
CachedCoachPanelState.MethodDrillRecoveryHistoryCount;
Entry.StrongMethodDrillRecoveryHistoryCount =
CachedCoachPanelState.StrongMethodDrillRecoveryHistoryCount;
Entry.WeakMethodDrillRecoveryHistoryCount =
CachedCoachPanelState.WeakMethodDrillRecoveryHistoryCount;
Entry.MethodDrillRecoveryAverageOutcomeScore =
CachedCoachPanelState.MethodDrillRecoveryAverageOutcomeScore;
Entry.bStrongPressureDominant =
Entry.MethodDrillRecoveryHistoryCount >= 2
&& Entry.StrongMethodDrillRecoveryHistoryCount
>= Entry.WeakMethodDrillRecoveryHistoryCount
&& Entry.MethodDrillRecoveryAverageOutcomeScore >= 55.0f;
Entry.bWeakPressureDominant =
Entry.MethodDrillRecoveryHistoryCount >= 2
&& Entry.WeakMethodDrillRecoveryHistoryCount
> Entry.StrongMethodDrillRecoveryHistoryCount
&& Entry.MethodDrillRecoveryAverageOutcomeScore <= 45.0f;
Entry.bUsedMethodDrillRecoveryMemoryForRecovery =
QueueRecoveryWeighting.bHasMethodDrillMemorySignal;
Entry.bUsedMethodDrillRecoveryMemoryForHandoff =
bUsedMethodDrillRecoveryMemoryForHandoff;
if (!Entry.IsStructurallyValid())
{
return;
}
const bool bMatchesLastEntry = MethodDrillRecoveryMemoryHistoryEntries.Num() > 0
&& MethodDrillRecoveryMemoryHistoryEntries.Last().PreferredRecoveryAction
== Entry.PreferredRecoveryAction
&& MethodDrillRecoveryMemoryHistoryEntries.Last().PreferredRecoveryActionSourceLabel
== Entry.PreferredRecoveryActionSourceLabel
&& MethodDrillRecoveryMemoryHistoryEntries.Last().RecommendedHandoffKindLabel
== Entry.RecommendedHandoffKindLabel
&& MethodDrillRecoveryMemoryHistoryEntries.Last().QueueRecoveryStatusLine
== Entry.QueueRecoveryStatusLine
&& MethodDrillRecoveryMemoryHistoryEntries.Last().HandoffRecommendationStatusLine
== Entry.HandoffRecommendationStatusLine
&& MethodDrillRecoveryMemoryHistoryEntries.Last().MethodDrillRecoveryHistoryCount
== Entry.MethodDrillRecoveryHistoryCount
&& MethodDrillRecoveryMemoryHistoryEntries.Last().StrongMethodDrillRecoveryHistoryCount
== Entry.StrongMethodDrillRecoveryHistoryCount
&& MethodDrillRecoveryMemoryHistoryEntries.Last().WeakMethodDrillRecoveryHistoryCount
== Entry.WeakMethodDrillRecoveryHistoryCount
&& FMath::IsNearlyEqual(
MethodDrillRecoveryMemoryHistoryEntries.Last().MethodDrillRecoveryAverageOutcomeScore,
Entry.MethodDrillRecoveryAverageOutcomeScore)
&& MethodDrillRecoveryMemoryHistoryEntries.Last().bStrongPressureDominant
== Entry.bStrongPressureDominant
&& MethodDrillRecoveryMemoryHistoryEntries.Last().bWeakPressureDominant
== Entry.bWeakPressureDominant
&& MethodDrillRecoveryMemoryHistoryEntries.Last().bUsedMethodDrillRecoveryMemoryForRecovery
== Entry.bUsedMethodDrillRecoveryMemoryForRecovery
&& MethodDrillRecoveryMemoryHistoryEntries.Last().bUsedMethodDrillRecoveryMemoryForHandoff
== Entry.bUsedMethodDrillRecoveryMemoryForHandoff;
if (bMatchesLastEntry)
{
return;
}
MethodDrillRecoveryMemoryHistoryEntries.Add(Entry);
constexpr int32 MaxRetainedMethodDrillRecoveryMemoryHistoryEntries = 16;
if (MethodDrillRecoveryMemoryHistoryEntries.Num()
> MaxRetainedMethodDrillRecoveryMemoryHistoryEntries)
{
const int32 ExcessCount =
MethodDrillRecoveryMemoryHistoryEntries.Num()
- MaxRetainedMethodDrillRecoveryMemoryHistoryEntries;
MethodDrillRecoveryMemoryHistoryEntries.RemoveAt(
0,
ExcessCount,
EAllowShrinking::No);
}
SelectedMethodDrillRecoveryMemoryHistoryIndex =
MethodDrillRecoveryMemoryHistoryEntries.Num() - 1;
}
void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
{
const bool bHasActiveRun = CachedRunState.Session.IsStructurallyValid()
@ -7178,6 +7432,14 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
QueueRecoveryWeighting.MethodDrillUpstreamHistoryCount,
QueueRecoveryWeighting.MethodDrillUpstreamAverageOutcomeScore
);
RecordMethodDrillRecoveryMemoryHistorySnapshot(
QueueRecoveryWeighting,
QueueRecoveryStatusLine,
HyperTwistCoachDashboardWidgetInternal::DescribeHandoffRecommendationKind(
HandoffRecommendationKind),
CoachHandoffRecommendationStatusLine,
bHandoffRecommendationUsedMethodDrillRecoveryMemory
);
const bool bHasPrimaryQueueSuppression =
CachedCoachSessionQueueSummary.bSuppressedPrimaryQueueCreation
&& !CachedCoachSessionQueueSummary.PrimaryQueueSuppressionReason.IsEmpty();
@ -7350,6 +7612,67 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
SelectedMethodDrillRecoveryOutcome->bAfterCompletionGap ? TEXT("yes") : TEXT("no"),
SelectedMethodDrillRecoveryOutcome->bUsedDeferredFirstSuppressionSignal ? TEXT("yes") : TEXT("no"))
: TEXT("Drill recovery detail: when drill-weighted queue recovery actions are applied, their retained before/after friction outcomes will accumulate here for later comparison against generic recovery.");
const FHyperTwistCoachDashboardMethodDrillRecoveryMemoryHistoryEntry*
SelectedMethodDrillRecoveryMemoryHistoryEntry =
SelectedMethodDrillRecoveryMemoryHistoryIndex >= 0
&& SelectedMethodDrillRecoveryMemoryHistoryIndex
< MethodDrillRecoveryMemoryHistoryEntries.Num()
? &MethodDrillRecoveryMemoryHistoryEntries[
SelectedMethodDrillRecoveryMemoryHistoryIndex]
: nullptr;
const FString MethodDrillRecoveryMemoryActionLabel =
SelectedMethodDrillRecoveryMemoryHistoryEntry == nullptr
|| SelectedMethodDrillRecoveryMemoryHistoryEntry->PreferredRecoveryAction
== EHyperTwistTrainingQueueRecoveryAction::None
? TEXT("none")
: HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(
SelectedMethodDrillRecoveryMemoryHistoryEntry->PreferredRecoveryAction);
const FString MethodDrillRecoveryMemorySourceLine =
SelectedMethodDrillRecoveryMemoryHistoryEntry == nullptr
|| SelectedMethodDrillRecoveryMemoryHistoryEntry->PreferredRecoveryActionSourceLabel.IsEmpty()
? TEXT("n/a")
: HyperTwistCoachDashboardWidgetInternal::DescribeCoachMemorySourceLabel(
SelectedMethodDrillRecoveryMemoryHistoryEntry->PreferredRecoveryActionSourceLabel);
const FString MethodDrillRecoveryMemoryDominantPressureLine =
SelectedMethodDrillRecoveryMemoryHistoryEntry == nullptr
? TEXT("n/a")
: (SelectedMethodDrillRecoveryMemoryHistoryEntry->bStrongPressureDominant
? TEXT("strong")
: (SelectedMethodDrillRecoveryMemoryHistoryEntry->bWeakPressureDominant
? TEXT("weak")
: TEXT("balanced")));
const FString MethodDrillRecoveryMemoryHistoryStatusLine =
SelectedMethodDrillRecoveryMemoryHistoryEntry != nullptr
&& SelectedMethodDrillRecoveryMemoryHistoryEntry->IsStructurallyValid()
? FString::Printf(
TEXT("Drill recovery memory history: %d entries | selected %d/%d | %s | %s | Recorded: %s"),
MethodDrillRecoveryMemoryHistoryEntries.Num(),
SelectedMethodDrillRecoveryMemoryHistoryIndex + 1,
MethodDrillRecoveryMemoryHistoryEntries.Num(),
*MethodDrillRecoveryMemoryActionLabel,
*SelectedMethodDrillRecoveryMemoryHistoryEntry->RecommendedHandoffKindLabel,
*SelectedMethodDrillRecoveryMemoryHistoryEntry->RecordedAtUtc)
: TEXT("Drill recovery memory history: no retained upstream drill-recovery memory shifts recorded yet.");
const FString MethodDrillRecoveryMemoryHistoryDetailLine =
SelectedMethodDrillRecoveryMemoryHistoryEntry != nullptr
&& SelectedMethodDrillRecoveryMemoryHistoryEntry->IsStructurallyValid()
? FString::Printf(
TEXT("Source: %s | Strong/weak/history %d/%d/%d avg %.1f | Dominant pressure: %s | Recovery memory tipped: %s | Handoff memory tipped: %s | Recovery posture: %s | Handoff posture: %s"),
*MethodDrillRecoveryMemorySourceLine,
SelectedMethodDrillRecoveryMemoryHistoryEntry->StrongMethodDrillRecoveryHistoryCount,
SelectedMethodDrillRecoveryMemoryHistoryEntry->WeakMethodDrillRecoveryHistoryCount,
SelectedMethodDrillRecoveryMemoryHistoryEntry->MethodDrillRecoveryHistoryCount,
SelectedMethodDrillRecoveryMemoryHistoryEntry->MethodDrillRecoveryAverageOutcomeScore,
*MethodDrillRecoveryMemoryDominantPressureLine,
SelectedMethodDrillRecoveryMemoryHistoryEntry->bUsedMethodDrillRecoveryMemoryForRecovery
? TEXT("yes")
: TEXT("no"),
SelectedMethodDrillRecoveryMemoryHistoryEntry->bUsedMethodDrillRecoveryMemoryForHandoff
? TEXT("yes")
: TEXT("no"),
*SelectedMethodDrillRecoveryMemoryHistoryEntry->QueueRecoveryStatusLine,
*SelectedMethodDrillRecoveryMemoryHistoryEntry->HandoffRecommendationStatusLine)
: TEXT("Drill recovery memory detail: retained upstream strong-vs-weak drill-recovery pressure, resulting recovery posture, and resulting handoff posture will accumulate here for later comparison.");
const FString PreferredDeferredRecoveryEntryId = FindFirstDeferredQueueEntryId();
const bool bCanApplyQueueRecovery =
!bHasLiveAttemptTimer
@ -8780,6 +9103,41 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
NextMethodDrillRecoveryHistoryButton->SetIsEnabled(
MethodDrillRecoveryHistoryIndices.Num() > 1);
}
if (MethodDrillRecoveryMemoryHistoryHeaderTextBlock != nullptr)
{
MethodDrillRecoveryMemoryHistoryHeaderTextBlock->SetText(FText::FromString(
TEXT("[Drill Recovery Memory History]")));
}
if (MethodDrillRecoveryMemoryHistoryStatusTextBlock != nullptr)
{
MethodDrillRecoveryMemoryHistoryStatusTextBlock->SetText(FText::FromString(
MethodDrillRecoveryMemoryHistoryStatusLine));
}
if (MethodDrillRecoveryMemoryHistoryDetailTextBlock != nullptr)
{
MethodDrillRecoveryMemoryHistoryDetailTextBlock->SetText(FText::FromString(
MethodDrillRecoveryMemoryHistoryDetailLine));
}
if (PreviousMethodDrillRecoveryMemoryHistoryButtonLabel != nullptr)
{
PreviousMethodDrillRecoveryMemoryHistoryButtonLabel->SetText(FText::FromString(
TEXT("Prev Drill Memory")));
}
if (PreviousMethodDrillRecoveryMemoryHistoryButton != nullptr)
{
PreviousMethodDrillRecoveryMemoryHistoryButton->SetIsEnabled(
MethodDrillRecoveryMemoryHistoryEntries.Num() > 1);
}
if (NextMethodDrillRecoveryMemoryHistoryButtonLabel != nullptr)
{
NextMethodDrillRecoveryMemoryHistoryButtonLabel->SetText(FText::FromString(
TEXT("Next Drill Memory")));
}
if (NextMethodDrillRecoveryMemoryHistoryButton != nullptr)
{
NextMethodDrillRecoveryMemoryHistoryButton->SetIsEnabled(
MethodDrillRecoveryMemoryHistoryEntries.Num() > 1);
}
if (QueueSuppressionHistoryHeaderTextBlock != nullptr)
{
QueueSuppressionHistoryHeaderTextBlock->SetText(FText::FromString(TEXT("[Queue Suppression History]")));

View file

@ -320,6 +320,32 @@ struct FHyperTwistCoachDashboardMethodDrillFollowUpHistoryEntry
}
};
struct FHyperTwistCoachDashboardMethodDrillRecoveryMemoryHistoryEntry
{
FString RecordedAtUtc;
EHyperTwistTrainingQueueRecoveryAction PreferredRecoveryAction =
EHyperTwistTrainingQueueRecoveryAction::None;
FString PreferredRecoveryActionSourceLabel;
FString RecommendedHandoffKindLabel;
FString QueueRecoveryStatusLine;
FString HandoffRecommendationStatusLine;
int32 MethodDrillRecoveryHistoryCount = 0;
int32 StrongMethodDrillRecoveryHistoryCount = 0;
int32 WeakMethodDrillRecoveryHistoryCount = 0;
float MethodDrillRecoveryAverageOutcomeScore = 0.0f;
bool bStrongPressureDominant = false;
bool bWeakPressureDominant = false;
bool bUsedMethodDrillRecoveryMemoryForRecovery = false;
bool bUsedMethodDrillRecoveryMemoryForHandoff = false;
bool IsStructurallyValid() const
{
return !RecordedAtUtc.IsEmpty()
&& MethodDrillRecoveryHistoryCount > 0
&& (!QueueRecoveryStatusLine.IsEmpty() || !HandoffRecommendationStatusLine.IsEmpty());
}
};
struct FHyperTwistCoachDashboardMethodDrillFollowUpWeighting
{
int32 MatchingHistoryCount = 0;
@ -395,6 +421,9 @@ public:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training|Coach|Dashboard")
int32 SelectedMethodDrillRecoveryOutcomeIndex = INDEX_NONE;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training|Coach|Dashboard")
int32 SelectedMethodDrillRecoveryMemoryHistoryIndex = INDEX_NONE;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|Training|Coach|Dashboard")
bool bHasLiveAttemptTimer = false;
@ -512,6 +541,12 @@ public:
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard")
bool SelectNextMethodDrillRecoveryOutcomeEntry();
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard")
bool SelectPreviousMethodDrillRecoveryMemoryHistoryEntry();
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard")
bool SelectNextMethodDrillRecoveryMemoryHistoryEntry();
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard")
bool StartLiveAttemptTimer();
@ -914,6 +949,27 @@ protected:
UPROPERTY(Transient)
TObjectPtr<UTextBlock> NextMethodDrillRecoveryHistoryButtonLabel = nullptr;
UPROPERTY(Transient)
TObjectPtr<UTextBlock> MethodDrillRecoveryMemoryHistoryHeaderTextBlock = nullptr;
UPROPERTY(Transient)
TObjectPtr<UTextBlock> MethodDrillRecoveryMemoryHistoryStatusTextBlock = nullptr;
UPROPERTY(Transient)
TObjectPtr<UTextBlock> MethodDrillRecoveryMemoryHistoryDetailTextBlock = nullptr;
UPROPERTY(Transient)
TObjectPtr<UButton> PreviousMethodDrillRecoveryMemoryHistoryButton = nullptr;
UPROPERTY(Transient)
TObjectPtr<UTextBlock> PreviousMethodDrillRecoveryMemoryHistoryButtonLabel = nullptr;
UPROPERTY(Transient)
TObjectPtr<UButton> NextMethodDrillRecoveryMemoryHistoryButton = nullptr;
UPROPERTY(Transient)
TObjectPtr<UTextBlock> NextMethodDrillRecoveryMemoryHistoryButtonLabel = nullptr;
UPROPERTY(Transient)
TObjectPtr<UTextBlock> QueueSuppressionHistoryHeaderTextBlock = nullptr;
@ -1193,6 +1249,12 @@ protected:
UFUNCTION()
void HandleNextMethodDrillRecoveryHistoryClicked();
UFUNCTION()
void HandlePreviousMethodDrillRecoveryMemoryHistoryClicked();
UFUNCTION()
void HandleNextMethodDrillRecoveryMemoryHistoryClicked();
UFUNCTION()
void HandleClearRunClicked();
@ -1253,6 +1315,7 @@ private:
void SyncSelectedClosureRecoveryHistoryEntry();
void SyncSelectedMethodDrillFollowUpHistoryEntry();
void SyncSelectedMethodDrillRecoveryOutcomeEntry();
void SyncSelectedMethodDrillRecoveryMemoryHistoryEntry();
void SyncPreferredGuidancePreviewLane(bool bForceAdoptPreferredLane);
void UpdateDashboardPresentation();
void RefreshLiveAttemptClock();
@ -1313,6 +1376,13 @@ private:
void RecordQueueSuppressionSnapshot();
void RecordClosureRecoverySnapshot();
void RecordMethodDrillFollowUpHistoryEntry(const FHyperTwistTrainingSessionSummary& CompletedRunRecap);
void RecordMethodDrillRecoveryMemoryHistorySnapshot(
const FHyperTwistCoachDashboardQueueRecoveryWeighting& QueueRecoveryWeighting,
const FString& QueueRecoveryStatusLine,
const FString& HandoffRecommendationKindLabel,
const FString& HandoffRecommendationStatusLine,
bool bUsedMethodDrillRecoveryMemoryForHandoff
);
void RecordGuidanceOutcomeDecision(
EHyperTwistCoachDashboardOutcomeAction RecommendedAction,
EHyperTwistCoachDashboardOutcomeAction AppliedAction,
@ -1367,6 +1437,8 @@ private:
TArray<FHyperTwistCoachDashboardDecisionHistoryEntry> GuidanceDecisionHistoryEntries;
TArray<FHyperTwistCoachDashboardHandoffHistoryEntry> HandoffHistoryEntries;
TArray<FHyperTwistCoachDashboardMethodDrillFollowUpHistoryEntry> MethodDrillFollowUpHistoryEntries;
TArray<FHyperTwistCoachDashboardMethodDrillRecoveryMemoryHistoryEntry>
MethodDrillRecoveryMemoryHistoryEntries;
bool bHasQueueRecoveryOutcome = false;
FHyperTwistCoachDashboardQueueRecoveryOutcome QueueRecoveryOutcome;
TArray<FHyperTwistCoachDashboardQueueRecoveryOutcome> QueueRecoveryOutcomeHistory;