diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp index ea78b79..ff6e6b0 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp @@ -11153,6 +11153,229 @@ UHyperTwistCoachDashboardWidget::GetDisplayedAttemptResultActionEnablementInspec return Surface; } +FHyperTwistTrainingCoachDashboardAttemptReviewNavigationInspectSurface +UHyperTwistCoachDashboardWidget::GetDisplayedAttemptReviewNavigationInspectSurface() const +{ + FHyperTwistTrainingCoachDashboardAttemptReviewNavigationInspectSurface Surface; + Surface.Headline = TEXT("Attempt-review navigation"); + + const bool bHasReviewedAttempt = + SelectedAttemptIndex >= 0 && SelectedAttemptIndex < CachedRunState.Attempts.Num(); + const int32 TotalAttemptCount = CachedRunState.Attempts.Num(); + const bool bNavigationEnabled = TotalAttemptCount > 1; + + Surface.PreviousLabel = !DisplayedPreviousAttemptNavigationLabel.IsEmpty() + ? DisplayedPreviousAttemptNavigationLabel + : TEXT("Prev Attempt"); + Surface.NextLabel = !DisplayedNextAttemptNavigationLabel.IsEmpty() + ? DisplayedNextAttemptNavigationLabel + : TEXT("Next Attempt"); + Surface.SelectedAttemptIndex = SelectedAttemptIndex; + Surface.TotalAttemptCount = TotalAttemptCount; + Surface.bHasReviewedAttempt = bHasReviewedAttempt; + Surface.bCanSelectPreviousAttempt = bNavigationEnabled; + Surface.bCanSelectNextAttempt = bNavigationEnabled; + Surface.AttemptOrdinal = bHasReviewedAttempt + ? FString::Printf(TEXT("%d/%d"), SelectedAttemptIndex + 1, TotalAttemptCount) + : TEXT("none"); + Surface.StatusLine = FString::Printf( + TEXT("%s: %s | %s: %s | selected attempt: %s"), + *Surface.PreviousLabel, + Surface.bCanSelectPreviousAttempt ? TEXT("enabled") : TEXT("disabled"), + *Surface.NextLabel, + Surface.bCanSelectNextAttempt ? TEXT("enabled") : TEXT("disabled"), + *Surface.AttemptOrdinal); + return Surface; +} + +FHyperTwistTrainingCoachDashboardRunControlEnablementInspectSurface +UHyperTwistCoachDashboardWidget::GetDisplayedRunControlEnablementInspectSurface() const +{ + FHyperTwistTrainingCoachDashboardRunControlEnablementInspectSurface Surface; + Surface.Headline = TEXT("Run-control enablement"); + + const FHyperTwistTrainingCoachDashboardTransitionStatusInspectSurface TransitionSurface = + GetDisplayedTransitionStatusInspectSurface(); + const FHyperTwistTrainingCoachDashboardRunActionLabelsInspectSurface RunActionSurface = + GetDisplayedRunActionLabelsInspectSurface(); + + FString PrimaryLabel = !DisplayedPrimaryActionLabel.IsEmpty() + ? DisplayedPrimaryActionLabel + : CachedCoachPanelState.PrimaryActionLabel; + if (PrimaryLabel.IsEmpty()) + { + PrimaryLabel = TEXT("Refresh"); + } + + Surface.PrimaryLabel = PrimaryLabel; + Surface.ContinueLabel = RunActionSurface.ContinueRunLabel; + Surface.CompleteLabel = !DisplayedCompleteRunControlLabel.IsEmpty() + ? DisplayedCompleteRunControlLabel + : TEXT("Complete Run"); + Surface.ClearLabel = !DisplayedClearRunControlLabel.IsEmpty() + ? DisplayedClearRunControlLabel + : TEXT("Clear Run"); + Surface.bHasActiveRun = TransitionSurface.bHasActiveRun; + Surface.bHasRunnableCurrentCase = + TransitionSurface.bHasActiveRun + && CachedRunState.CurrentSelection.TrainingCase.IsStructurallyValid(); + Surface.bHasLiveAttemptTimer = TransitionSurface.bHasLiveAttemptTimer; + Surface.bLiveInspectionPhase = TransitionSurface.bLiveInspectionPhase; + Surface.bCanContinueCurrentRun = TransitionSurface.bCanContinueCurrentRun; + Surface.bCanLaunchCoachRun = TransitionSurface.bCanLaunchCoachRun; + Surface.bPrimaryEnabled = TransitionSurface.bCanLaunchCoachRun; + Surface.bContinueEnabled = + (!TransitionSurface.bHasLiveAttemptTimer && TransitionSurface.bCanContinueCurrentRun) + || (TransitionSurface.bLiveInspectionPhase && Surface.bHasRunnableCurrentCase) + || TransitionSurface.bCanLaunchCoachRun; + Surface.bCompleteEnabled = + TransitionSurface.bHasActiveRun && !TransitionSurface.bHasLiveAttemptTimer; + Surface.bClearEnabled = + TransitionSurface.bHasActiveRun && !TransitionSurface.bHasLiveAttemptTimer; + Surface.StatusLine = FString::Printf( + TEXT("%s: %s | %s: %s | %s: %s | %s: %s"), + *Surface.PrimaryLabel, + Surface.bPrimaryEnabled ? TEXT("enabled") : TEXT("disabled"), + *Surface.ContinueLabel, + Surface.bContinueEnabled ? TEXT("enabled") : TEXT("disabled"), + *Surface.CompleteLabel, + Surface.bCompleteEnabled ? TEXT("enabled") : TEXT("disabled"), + *Surface.ClearLabel, + Surface.bClearEnabled ? TEXT("enabled") : TEXT("disabled")); + Surface.DetailLine = FString::Printf( + TEXT("Run-control detail: active run %s | runnable case %s | live timer %s | inspection %s | continue-current-run %s | coach-launch %s"), + Surface.bHasActiveRun ? TEXT("yes") : TEXT("no"), + Surface.bHasRunnableCurrentCase ? TEXT("yes") : TEXT("no"), + Surface.bHasLiveAttemptTimer ? TEXT("yes") : TEXT("no"), + Surface.bLiveInspectionPhase ? TEXT("yes") : TEXT("no"), + Surface.bCanContinueCurrentRun ? TEXT("yes") : TEXT("no"), + Surface.bCanLaunchCoachRun ? TEXT("yes") : TEXT("no")); + return Surface; +} + +FHyperTwistTrainingCoachDashboardQueueActionEnablementInspectSurface +UHyperTwistCoachDashboardWidget::GetDisplayedQueueActionEnablementInspectSurface() const +{ + FHyperTwistTrainingCoachDashboardQueueActionEnablementInspectSurface Surface; + Surface.Headline = TEXT("Queue-action enablement"); + + const FHyperTwistTrainingCoachSessionQueueStateEntry* SelectedQueueEntry = FindSelectedQueueEntry(); + const FString DeferredQueueEntryId = FindFirstDeferredQueueEntryId(); + const bool bQueueRecoveryMemorySource = + HyperTwistCoachDashboardWidgetInternal::IsQueueRecoveryMemorySource( + CachedCoachPanelState.PreferredGuidanceSourceLabel); + const bool bQueueRecoveryFollowUp = + CachedCoachPanelState.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp; + const bool bQueueRecoveryRecommended = + CachedCoachPanelState.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::Recommended; + const FHyperTwistCoachSignal* QueueDeferredFirstSuppressionSignal = + HyperTwistCoachDashboardWidgetInternal::FindCoachSignalById( + CachedCoachSignals, + TEXT("queue_suppression_deferred_first_continuation")); + const bool bHasDeferredFirstQueueSuppressionSignal = + QueueDeferredFirstSuppressionSignal != nullptr; + const bool bCanLaunchQueueRecovery = + CachedCoachPanelState.bCanStartQueuedRun + || (CachedCoachPanelState.bCanStartCoachFollowUpRun && CachedCoachPanelState.bHasFollowUpGuidance) + || CachedCoachPanelState.bCanStartCoachRecommendedRun; + const bool bUpstreamPrefersLaunchRecovery = + CachedCoachPanelState.PreferredQueueRecoveryAction + == EHyperTwistTrainingQueueRecoveryAction::LaunchRecovery; + const FHyperTwistCoachDashboardQueueRecoveryWeighting QueueRecoveryWeighting = + BuildQueueRecoveryWeighting(); + const bool bPreferLaunchRecoveryAction = + !DeferredQueueEntryId.IsEmpty() + && bCanLaunchQueueRecovery + && !(bHasDeferredFirstQueueSuppressionSignal && bQueueRecoveryFollowUp) + && (QueueRecoveryWeighting.PreferredActionKind + == FHyperTwistCoachDashboardQueueRecoveryOutcome::EActionKind::LaunchRecovery + || (!QueueRecoveryWeighting.IsStructurallyValid() && bUpstreamPrefersLaunchRecovery)); + const bool bHasQueueRecoveryFriction = + CachedCoachSchedulePolicySummary.bHasBlockedFollowUpDeferrals + || CachedCoachSchedulePolicySummary.bHasArchiveAwareDeferrals + || CachedCoachSchedulePolicySummary.bNeedsManualRescheduleReview + || CachedCoachQueueExecutionSummary.bHasOutstandingDeferredWork + || CachedCoachQueueExecutionSummary.bHasCompletionGap; + const bool bClosedLoopIdleDashboard = + IsCoachLoopActionabilityClosed() + && ( + bQueueRecoveryMemorySource + || !CachedCoachPanelState.PreferredGuidanceSourceLabel.IsEmpty() + || DisplayedCoachHandoffRecommendationKind != EHyperTwistTrainingCoachHandoffKind::None); + const bool bCanApplyQueueRecovery = + !bClosedLoopIdleDashboard + && !bHasLiveAttemptTimer + && (bQueueRecoveryMemorySource || bHasQueueRecoveryFriction) + && (!DeferredQueueEntryId.IsEmpty() + || CachedCoachPanelState.bCanStartQueuedRun + || (bQueueRecoveryFollowUp + && CachedCoachPanelState.bCanStartCoachFollowUpRun + && CachedCoachPanelState.bHasFollowUpGuidance) + || (bQueueRecoveryRecommended + && CachedCoachPanelState.bCanStartCoachRecommendedRun) + || (!bQueueRecoveryMemorySource + && (CachedCoachPanelState.bCanStartCoachFollowUpRun + || CachedCoachPanelState.bCanStartCoachRecommendedRun))); + const FString RecoveryLabel = !DisplayedQueueRecoveryActionLabel.IsEmpty() + ? DisplayedQueueRecoveryActionLabel + : (bClosedLoopIdleDashboard + ? TEXT("No Recovery Action") + : (!DeferredQueueEntryId.IsEmpty() && !bPreferLaunchRecoveryAction + ? (bQueueRecoveryFollowUp + ? TEXT("Promote Follow-Up Recovery") + : (bQueueRecoveryRecommended + ? TEXT("Promote Recommended Recovery") + : TEXT("Promote Recovery"))) + : (bQueueRecoveryFollowUp + ? TEXT("Start Follow-Up Recovery") + : (bQueueRecoveryRecommended + ? TEXT("Start Recommended Recovery") + : (CachedCoachPanelState.bCanStartQueuedRun + ? TEXT("Launch Queue Recovery") + : TEXT("Apply Recovery")))))); + + Surface.DeferLabel = !DisplayedDeferQueueActionLabel.IsEmpty() + ? DisplayedDeferQueueActionLabel + : TEXT("Defer 24h"); + Surface.PromoteLabel = !DisplayedPromoteQueueActionLabel.IsEmpty() + ? DisplayedPromoteQueueActionLabel + : TEXT("Promote Deferred"); + Surface.RecoveryLabel = RecoveryLabel; + Surface.SelectedQueueEntryId = + SelectedQueueEntry != nullptr && !SelectedQueueEntry->EntryId.IsEmpty() + ? SelectedQueueEntry->EntryId + : TEXT("none"); + Surface.DeferredQueueEntryId = DeferredQueueEntryId.IsEmpty() + ? TEXT("none") + : DeferredQueueEntryId; + Surface.bHasLiveAttemptTimer = bHasLiveAttemptTimer; + Surface.bHasSelectedQueueEntry = SelectedQueueEntry != nullptr; + Surface.bHasDeferredQueueEntry = !DeferredQueueEntryId.IsEmpty(); + Surface.bQueueRecoveryMemorySource = bQueueRecoveryMemorySource; + Surface.bHasQueueRecoveryFriction = bHasQueueRecoveryFriction; + Surface.bClosedLoopIdleDashboard = bClosedLoopIdleDashboard; + Surface.bDeferEnabled = !bHasLiveAttemptTimer && SelectedQueueEntry != nullptr; + Surface.bPromoteEnabled = !bHasLiveAttemptTimer && !DeferredQueueEntryId.IsEmpty(); + Surface.bRecoveryEnabled = bCanApplyQueueRecovery; + Surface.StatusLine = FString::Printf( + TEXT("%s: %s | %s: %s | %s: %s"), + *Surface.DeferLabel, + Surface.bDeferEnabled ? TEXT("enabled") : TEXT("disabled"), + *Surface.PromoteLabel, + Surface.bPromoteEnabled ? TEXT("enabled") : TEXT("disabled"), + *Surface.RecoveryLabel, + Surface.bRecoveryEnabled ? TEXT("enabled") : TEXT("disabled")); + Surface.DetailLine = FString::Printf( + TEXT("Queue-action detail: selected entry %s | deferred entry %s | live timer %s | recovery-memory-source %s | recovery-friction %s | closed-loop idle %s"), + *Surface.SelectedQueueEntryId, + *Surface.DeferredQueueEntryId, + Surface.bHasLiveAttemptTimer ? TEXT("yes") : TEXT("no"), + Surface.bQueueRecoveryMemorySource ? TEXT("yes") : TEXT("no"), + Surface.bHasQueueRecoveryFriction ? TEXT("yes") : TEXT("no"), + Surface.bClosedLoopIdleDashboard ? TEXT("yes") : TEXT("no")); + return Surface; +} + FHyperTwistTrainingCoachDashboardLastAttemptReviewSummaryInspectSurface UHyperTwistCoachDashboardWidget::GetDisplayedLastAttemptReviewSummaryInspectSurface() const { @@ -23976,7 +24199,8 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() } if (PrimaryActionButtonLabel != nullptr) { - PrimaryActionButtonLabel->SetText(FText::FromString(PrimaryLabel)); + DisplayedPrimaryActionLabel = PrimaryLabel; + PrimaryActionButtonLabel->SetText(FText::FromString(DisplayedPrimaryActionLabel)); } if (PrimaryActionButton != nullptr) { @@ -23996,7 +24220,8 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() } if (DeferButtonLabel != nullptr) { - DeferButtonLabel->SetText(FText::FromString(TEXT("Defer 24h"))); + DisplayedDeferQueueActionLabel = TEXT("Defer 24h"); + DeferButtonLabel->SetText(FText::FromString(DisplayedDeferQueueActionLabel)); } if (DeferButton != nullptr) { @@ -24004,7 +24229,8 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() } if (PromoteButtonLabel != nullptr) { - PromoteButtonLabel->SetText(FText::FromString(TEXT("Promote Deferred"))); + DisplayedPromoteQueueActionLabel = TEXT("Promote Deferred"); + PromoteButtonLabel->SetText(FText::FromString(DisplayedPromoteQueueActionLabel)); } if (PromoteButton != nullptr) { @@ -24012,7 +24238,8 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() } if (QueueRecoveryActionButtonLabel != nullptr) { - QueueRecoveryActionButtonLabel->SetText(FText::FromString(QueueRecoveryActionLabel)); + DisplayedQueueRecoveryActionLabel = QueueRecoveryActionLabel; + QueueRecoveryActionButtonLabel->SetText(FText::FromString(DisplayedQueueRecoveryActionLabel)); } if (QueueRecoveryActionButton != nullptr) { @@ -24020,7 +24247,8 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() } if (CompleteRunButtonLabel != nullptr) { - CompleteRunButtonLabel->SetText(FText::FromString(TEXT("Complete Run"))); + DisplayedCompleteRunControlLabel = TEXT("Complete Run"); + CompleteRunButtonLabel->SetText(FText::FromString(DisplayedCompleteRunControlLabel)); } if (CompleteRunButton != nullptr) { @@ -24040,7 +24268,8 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() } if (ClearRunButtonLabel != nullptr) { - ClearRunButtonLabel->SetText(FText::FromString(TEXT("Clear Run"))); + DisplayedClearRunControlLabel = TEXT("Clear Run"); + ClearRunButtonLabel->SetText(FText::FromString(DisplayedClearRunControlLabel)); } if (ClearRunButton != nullptr) { @@ -24154,7 +24383,8 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() } if (PreviousAttemptButtonLabel != nullptr) { - PreviousAttemptButtonLabel->SetText(FText::FromString(TEXT("Prev Attempt"))); + DisplayedPreviousAttemptNavigationLabel = TEXT("Prev Attempt"); + PreviousAttemptButtonLabel->SetText(FText::FromString(DisplayedPreviousAttemptNavigationLabel)); } if (PreviousAttemptButton != nullptr) { @@ -24162,7 +24392,8 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() } if (NextAttemptButtonLabel != nullptr) { - NextAttemptButtonLabel->SetText(FText::FromString(TEXT("Next Attempt"))); + DisplayedNextAttemptNavigationLabel = TEXT("Next Attempt"); + NextAttemptButtonLabel->SetText(FText::FromString(DisplayedNextAttemptNavigationLabel)); } if (NextAttemptButton != nullptr) { diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h index f7b6365..ad6256f 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h @@ -839,6 +839,18 @@ public: FHyperTwistTrainingCoachDashboardAttemptResultActionEnablementInspectSurface GetDisplayedAttemptResultActionEnablementInspectSurface() const; + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach|Dashboard") + FHyperTwistTrainingCoachDashboardAttemptReviewNavigationInspectSurface + GetDisplayedAttemptReviewNavigationInspectSurface() const; + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach|Dashboard") + FHyperTwistTrainingCoachDashboardRunControlEnablementInspectSurface + GetDisplayedRunControlEnablementInspectSurface() const; + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach|Dashboard") + FHyperTwistTrainingCoachDashboardQueueActionEnablementInspectSurface + GetDisplayedQueueActionEnablementInspectSurface() const; + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach|Dashboard") FHyperTwistTrainingCoachDashboardLastAttemptReviewSummaryInspectSurface GetDisplayedLastAttemptReviewSummaryInspectSurface() const; @@ -1915,10 +1927,18 @@ private: FString DisplayedTimerStatusLine; FString DisplayedRecommendedDeckPreviewLine; FString DisplayedFollowUpDeckPreviewLine; + FString DisplayedPrimaryActionLabel; + FString DisplayedDeferQueueActionLabel; + FString DisplayedPromoteQueueActionLabel; + FString DisplayedQueueRecoveryActionLabel; + FString DisplayedCompleteRunControlLabel; + FString DisplayedClearRunControlLabel; FString DisplayedContinueRunLabel; FString DisplayedQueuedRunLabel; FString DisplayedFollowUpRunLabel; FString DisplayedRecommendedRunLabel; + FString DisplayedPreviousAttemptNavigationLabel; + FString DisplayedNextAttemptNavigationLabel; EHyperTwistCoachDashboardGuidancePreviewLane ActiveGuidancePreviewLane = EHyperTwistCoachDashboardGuidancePreviewLane::None; int32 SelectedGuidancePreviewCaseIndex = INDEX_NONE; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h index 332ccc6..ded0cf7 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h @@ -11006,6 +11006,183 @@ struct FHyperTwistTrainingCoachDashboardAttemptResultActionEnablementInspectSurf } }; +USTRUCT(BlueprintType) +struct FHyperTwistTrainingCoachDashboardAttemptReviewNavigationInspectSurface +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString Headline; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString StatusLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString PreviousLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString NextLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString AttemptOrdinal; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 SelectedAttemptIndex = INDEX_NONE; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 TotalAttemptCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasReviewedAttempt = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCanSelectPreviousAttempt = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCanSelectNextAttempt = false; + + bool IsStructurallyValid() const + { + return !StatusLine.IsEmpty() + || bHasReviewedAttempt + || bCanSelectPreviousAttempt + || bCanSelectNextAttempt; + } +}; + +USTRUCT(BlueprintType) +struct FHyperTwistTrainingCoachDashboardRunControlEnablementInspectSurface +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString Headline; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString StatusLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString DetailLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString PrimaryLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ContinueLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString CompleteLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ClearLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasActiveRun = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasRunnableCurrentCase = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasLiveAttemptTimer = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bLiveInspectionPhase = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCanContinueCurrentRun = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCanLaunchCoachRun = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bPrimaryEnabled = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bContinueEnabled = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCompleteEnabled = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bClearEnabled = false; + + bool IsStructurallyValid() const + { + return !StatusLine.IsEmpty() + || !DetailLine.IsEmpty() + || bPrimaryEnabled + || bContinueEnabled + || bCompleteEnabled + || bClearEnabled; + } +}; + +USTRUCT(BlueprintType) +struct FHyperTwistTrainingCoachDashboardQueueActionEnablementInspectSurface +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString Headline; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString StatusLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString DetailLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString DeferLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString PromoteLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString RecoveryLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString SelectedQueueEntryId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString DeferredQueueEntryId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasLiveAttemptTimer = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasSelectedQueueEntry = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasDeferredQueueEntry = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bQueueRecoveryMemorySource = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasQueueRecoveryFriction = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bClosedLoopIdleDashboard = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bDeferEnabled = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bPromoteEnabled = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bRecoveryEnabled = false; + + bool IsStructurallyValid() const + { + return !StatusLine.IsEmpty() + || !DetailLine.IsEmpty() + || bDeferEnabled + || bPromoteEnabled + || bRecoveryEnabled; + } +}; + USTRUCT(BlueprintType) struct FHyperTwistTrainingCoachDashboardLastAttemptReviewSummaryInspectSurface { diff --git a/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md b/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md index 9736a4d..6581b41 100644 --- a/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md +++ b/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md @@ -69,8 +69,8 @@ Current correction call from the source-exposed audit and current execution refr - the latest landed bounded `Phase 4` packet closes the retained-idle comparison/outcome actionability gap at the end of the recognition-assisted coach-to-review continuity lane, and the final retained-surface confirmation pass now leaves that closure lane functionally complete - the retained idle widget surface now keeps retained comparison and decision history inspectable without continuing to advertise live launch or guidance-outcome actions once the coach-to-review loop has already collapsed to truthful closed-loop idle - the now-complete deliberate `UHyperTwistTrainingRuntimeLibrary` world-context consumer lane reaches through active generated-mode selector option owned-execution split-phase current-selection merged ordered roster phase-id lookup, normalized-order lookup, and evaluation-order lookup inspect surfaces, so gameplay/Blueprint callers can inspect one retained merged-roster row directly by phase id, normalized order, or evaluation order without scanning the merged roster array or re-running inspect derivation details directly - - the latest landed bounded `Phase 4` packet stays on that same live `UHyperTwistCoachDashboardWidget` guidance-consumer lane and adds attempt-time input hint, live timer / split control label, and attempt result-action enablement inspect surfaces over already-derived live dashboard execution-control presentation state, so gameplay/Blueprint callers can inspect the current attempt-time hint, quick-fill affordance posture, timer/split control labels, and result-action enablement without scraping widget controls or reopening execution-control derivation - - the next clean move is to stay on that same live dashboard guidance-consumer lane and land the attempt-review navigation, primary / continue / clear / complete run-control enablement, and queue defer / promote / recovery action enablement inspect surface group rather than drifting back into backend continuity or reopening the already-complete retained-history seam + - the latest landed bounded `Phase 4` packet stays on that same live `UHyperTwistCoachDashboardWidget` guidance-consumer lane and adds attempt-review navigation, primary / continue / clear / complete run-control enablement, and queue defer / promote / recovery action enablement inspect surfaces over already-derived live dashboard review-navigation and execution-control presentation state, so gameplay/Blueprint callers can inspect retained attempt-review cycling plus the current run / queue-control labels and enablement without scraping widget buttons or reopening execution-control derivation + - the next clean move is to stay on that same live dashboard guidance-consumer lane and land the verification action, selected-queue navigation, and start-selected action enablement inspect surface group rather than drifting back into backend continuity or reopening the already-complete retained-history seam - the current execution-discipline rule that broad refactor / monolith-splitting work should not interrupt the active bounded roadmap packet unless structure is actually blocking it ## Current execution-reality references diff --git a/docs/arch/HYPERTWIST_PHASE4_DASHBOARD_LIVE_ATTEMPT_REVIEW_NAVIGATION_RUN_CONTROL_QUEUE_ACTION_ENABLEMENT_INSPECT_SURFACE_PACKET_2026-05-09.md b/docs/arch/HYPERTWIST_PHASE4_DASHBOARD_LIVE_ATTEMPT_REVIEW_NAVIGATION_RUN_CONTROL_QUEUE_ACTION_ENABLEMENT_INSPECT_SURFACE_PACKET_2026-05-09.md new file mode 100644 index 0000000..a84d50d --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE4_DASHBOARD_LIVE_ATTEMPT_REVIEW_NAVIGATION_RUN_CONTROL_QUEUE_ACTION_ENABLEMENT_INSPECT_SURFACE_PACKET_2026-05-09.md @@ -0,0 +1,55 @@ +# HyperTwist Phase 4 dashboard live attempt-review navigation, run-control, and queue-action enablement inspect surface packet + +- bounded Phase `4` dashboard-consumer slice +- date: `2026-05-09` + +## Intent + +Stay on the deliberate live `UHyperTwistCoachDashboardWidget` guidance-consumer lane and expose the remaining attempt-review navigation plus primary run-control and queue-action control state directly to gameplay and Blueprint callers. + +## Why this packet exists + +The dashboard already derived: + +- the displayed previous/next attempt-review navigation labels and enablement posture +- the displayed primary / continue / complete / clear run-control labels and enablement posture +- the displayed defer / promote / recovery queue-action labels and enablement posture + +But that live state still only surfaced through widget buttons. Callers still had to scrape button labels or enabled state to inspect what the dashboard was currently presenting. + +## What changed + +- added `FHyperTwistTrainingCoachDashboardAttemptReviewNavigationInspectSurface` +- added `FHyperTwistTrainingCoachDashboardRunControlEnablementInspectSurface` +- added `FHyperTwistTrainingCoachDashboardQueueActionEnablementInspectSurface` +- added `GetDisplayedAttemptReviewNavigationInspectSurface()` +- added `GetDisplayedRunControlEnablementInspectSurface()` +- added `GetDisplayedQueueActionEnablementInspectSurface()` +- retained the exact displayed primary, defer, promote, queue-recovery, complete-run, clear-run, and attempt-navigation labels inside the widget so the new inspect surfaces report the same live strings the dashboard is already presenting +- kept the packet bounded to `UHyperTwistCoachDashboardWidget` plus shared surface types, with no dashboard redesign and no backend continuity changes + +## Files + +- `C:\HyperTwist\UnrealHyperTwist\Source\UnrealHyperTwist\Public\HyperTwistTraining\HyperTwistTrainingTypes.h` +- `C:\HyperTwist\UnrealHyperTwist\Source\UnrealHyperTwist\Public\HyperTwistTraining\HyperTwistCoachDashboardWidget.h` +- `C:\HyperTwist\UnrealHyperTwist\Source\UnrealHyperTwist\Private\HyperTwistTraining\HyperTwistCoachDashboardWidget.cpp` +- `C:\HyperTwist\docs\HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md` + +## Outcome + +Gameplay and Blueprint callers can now inspect: + +- previous/next retained attempt-review navigation labels and their enablement posture +- the primary, continue, complete-run, and clear-run labels plus their enablement posture +- the defer, promote, and queue-recovery labels plus their enablement posture + +without scraping widget buttons or reopening run / queue-control derivation. + +## Validation + +- full `Development Editor | Win64` MSBuild on `UnrealHyperTwist.sln` + - passed with `0 Warning(s), 0 Error(s)` + +## Next clean slice + +Stay on the same live dashboard guidance-consumer lane and land the verification action, selected-queue navigation, and start-selected action enablement inspect surface group.