diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp index 96f058d..ea78b79 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp @@ -10997,6 +10997,162 @@ UHyperTwistCoachDashboardWidget::GetDisplayedCurrentCaseDeckStatusInspectSurface return Surface; } +FHyperTwistTrainingCoachDashboardAttemptTimeInputHintInspectSurface +UHyperTwistCoachDashboardWidget::GetDisplayedAttemptTimeInputHintInspectSurface() const +{ + FHyperTwistTrainingCoachDashboardAttemptTimeInputHintInspectSurface Surface; + Surface.Headline = TEXT("Attempt-time input hint"); + + const bool bHasActiveRun = CachedRunState.Session.IsStructurallyValid() + && CachedRunState.ActiveDeck.IsStructurallyValid(); + const bool bHasRunnableCurrentCase = + bHasActiveRun && CachedRunState.CurrentSelection.TrainingCase.IsStructurallyValid(); + const FHyperTwistTrainingCase& CurrentTrainingCase = CachedRunState.CurrentSelection.TrainingCase; + + Surface.HintLabel = !DisplayedAttemptTimeHintLabel.IsEmpty() + ? DisplayedAttemptTimeHintLabel + : (CurrentTrainingCase.TimeTargetMs > 0 + ? FString::Printf(TEXT("Enter attempt ms (target %d)"), CurrentTrainingCase.TimeTargetMs) + : TEXT("Enter attempt ms")); + Surface.UseTargetLabel = !DisplayedUseTargetTimeLabel.IsEmpty() + ? DisplayedUseTargetTimeLabel + : TEXT("Use Target"); + Surface.UseAverageLabel = !DisplayedUseAverageTimeLabel.IsEmpty() + ? DisplayedUseAverageTimeLabel + : TEXT("Use Avg"); + Surface.TargetTimeMs = CurrentTrainingCase.TimeTargetMs; + Surface.AverageTotalTimeMs = CachedRunSummary.AverageTotalTimeMs; + Surface.bHasActiveRun = bHasActiveRun; + Surface.bHasRunnableCurrentCase = bHasRunnableCurrentCase; + Surface.bHasLiveAttemptTimer = bHasLiveAttemptTimer; + Surface.bAttemptInputEnabled = !bHasLiveAttemptTimer && bHasRunnableCurrentCase; + Surface.bCanUseTargetTime = + !bHasLiveAttemptTimer && bHasRunnableCurrentCase && CurrentTrainingCase.TimeTargetMs > 0; + Surface.bCanUseAverageTime = + !bHasLiveAttemptTimer && bHasActiveRun && CachedRunSummary.AverageTotalTimeMs > 0; + Surface.StatusLine = FString::Printf( + TEXT("Hint: %s | input enabled: %s | %s: %s | %s: %s"), + *Surface.HintLabel, + Surface.bAttemptInputEnabled ? TEXT("yes") : TEXT("no"), + *Surface.UseTargetLabel, + Surface.bCanUseTargetTime ? TEXT("yes") : TEXT("no"), + *Surface.UseAverageLabel, + Surface.bCanUseAverageTime ? TEXT("yes") : TEXT("no")); + return Surface; +} + +FHyperTwistTrainingCoachDashboardLiveTimerSplitControlLabelInspectSurface +UHyperTwistCoachDashboardWidget::GetDisplayedLiveTimerSplitControlLabelInspectSurface() const +{ + FHyperTwistTrainingCoachDashboardLiveTimerSplitControlLabelInspectSurface Surface; + Surface.Headline = TEXT("Live timer / split control labels"); + + const bool bHasActiveRun = CachedRunState.Session.IsStructurallyValid() + && CachedRunState.ActiveDeck.IsStructurallyValid(); + const bool bHasRunnableCurrentCase = + bHasActiveRun && CachedRunState.CurrentSelection.TrainingCase.IsStructurallyValid(); + const bool bLiveInspectionPhase = IsLiveAttemptInspectionPhase(); + const bool bLiveSolvePhase = bHasLiveAttemptTimer + && LiveAttemptPhase == EHyperTwistCoachDashboardAttemptPhase::Solving; + const FHyperTwistTrainingSplitPhaseDefinition* NextSplitPhaseDefinition = FindNextSplitPhaseToCapture(); + const FString NextSplitLabel = NextSplitPhaseDefinition == nullptr + ? TEXT("none") + : (NextSplitPhaseDefinition->Label.IsEmpty() + ? NextSplitPhaseDefinition->PhaseId + : NextSplitPhaseDefinition->Label); + + Surface.StartTimerLabel = !DisplayedStartTimerControlLabel.IsEmpty() + ? DisplayedStartTimerControlLabel + : (bLiveInspectionPhase + ? TEXT("Start Solve") + : (bHasLiveAttemptTimer ? TEXT("Timer Active") : TEXT("Start Attempt"))); + Surface.CancelTimerLabel = !DisplayedCancelTimerControlLabel.IsEmpty() + ? DisplayedCancelTimerControlLabel + : TEXT("Cancel Timer"); + Surface.CaptureSplitLabel = !DisplayedCaptureSplitControlLabel.IsEmpty() + ? DisplayedCaptureSplitControlLabel + : (NextSplitPhaseDefinition == nullptr + ? TEXT("All Splits Captured") + : FString::Printf(TEXT("Capture %s"), *NextSplitLabel)); + Surface.UndoSplitLabel = !DisplayedUndoSplitControlLabel.IsEmpty() + ? DisplayedUndoSplitControlLabel + : TEXT("Undo Split"); + Surface.NextSplitLabel = NextSplitLabel; + Surface.CapturedSplitCount = LiveAttemptSplitCaptures.Num(); + Surface.bHasRunnableCurrentCase = bHasRunnableCurrentCase; + Surface.bHasLiveAttemptTimer = bHasLiveAttemptTimer; + Surface.bLiveInspectionPhase = bLiveInspectionPhase; + Surface.bLiveSolvePhase = bLiveSolvePhase; + Surface.bCanStartOrAdvanceTimer = + bHasRunnableCurrentCase && (!bHasLiveAttemptTimer || bLiveInspectionPhase); + Surface.bCanCancelTimer = bHasLiveAttemptTimer; + Surface.bCanCaptureSplit = bLiveSolvePhase && NextSplitPhaseDefinition != nullptr; + Surface.bCanUndoSplit = bLiveSolvePhase && LiveAttemptSplitCaptures.Num() > 0; + Surface.TimerControlLine = FString::Printf( + TEXT("%s: %s | %s: %s"), + *Surface.StartTimerLabel, + Surface.bCanStartOrAdvanceTimer ? TEXT("enabled") : TEXT("disabled"), + *Surface.CancelTimerLabel, + Surface.bCanCancelTimer ? TEXT("enabled") : TEXT("disabled")); + Surface.SplitControlLine = FString::Printf( + TEXT("%s: %s | %s: %s | next split: %s | captured: %d"), + *Surface.CaptureSplitLabel, + Surface.bCanCaptureSplit ? TEXT("enabled") : TEXT("disabled"), + *Surface.UndoSplitLabel, + Surface.bCanUndoSplit ? TEXT("enabled") : TEXT("disabled"), + *Surface.NextSplitLabel, + Surface.CapturedSplitCount); + return Surface; +} + +FHyperTwistTrainingCoachDashboardAttemptResultActionEnablementInspectSurface +UHyperTwistCoachDashboardWidget::GetDisplayedAttemptResultActionEnablementInspectSurface() const +{ + FHyperTwistTrainingCoachDashboardAttemptResultActionEnablementInspectSurface Surface; + Surface.Headline = TEXT("Attempt result-action enablement"); + + const bool bHasActiveRun = CachedRunState.Session.IsStructurallyValid() + && CachedRunState.ActiveDeck.IsStructurallyValid(); + const bool bHasRunnableCurrentCase = + bHasActiveRun && CachedRunState.CurrentSelection.TrainingCase.IsStructurallyValid(); + const bool bLiveSolvePhase = bHasLiveAttemptTimer + && LiveAttemptPhase == EHyperTwistCoachDashboardAttemptPhase::Solving; + + Surface.SuccessLabel = !DisplayedRecordSuccessActionLabel.IsEmpty() + ? DisplayedRecordSuccessActionLabel + : (bLiveSolvePhase ? TEXT("Stop Success") : TEXT("Record Success")); + Surface.FailureLabel = !DisplayedRecordFailureActionLabel.IsEmpty() + ? DisplayedRecordFailureActionLabel + : (bLiveSolvePhase ? TEXT("Stop Failure") : TEXT("Record Failure")); + Surface.TimeoutLabel = !DisplayedRecordTimeoutActionLabel.IsEmpty() + ? DisplayedRecordTimeoutActionLabel + : (bLiveSolvePhase ? TEXT("Stop Timeout") : TEXT("Record Timeout")); + Surface.DnfLabel = !DisplayedRecordDnfActionLabel.IsEmpty() + ? DisplayedRecordDnfActionLabel + : (bLiveSolvePhase ? TEXT("Stop DNF") : TEXT("Record DNF")); + Surface.bHasRunnableCurrentCase = bHasRunnableCurrentCase; + Surface.bHasLiveAttemptTimer = bHasLiveAttemptTimer; + Surface.bLiveSolvePhase = bLiveSolvePhase; + Surface.bCanRecordSyntheticAttempt = !bHasLiveAttemptTimer && bHasRunnableCurrentCase; + Surface.bCanStopLiveTimedAttempt = bLiveSolvePhase; + Surface.bSuccessEnabled = bLiveSolvePhase || (!bHasLiveAttemptTimer && bHasRunnableCurrentCase); + Surface.bFailureEnabled = Surface.bSuccessEnabled; + Surface.bTimeoutEnabled = Surface.bSuccessEnabled; + Surface.bDnfEnabled = Surface.bSuccessEnabled; + Surface.StatusLine = FString::Printf( + TEXT("%s | %s | %s | %s"), + *Surface.SuccessLabel, + *Surface.FailureLabel, + *Surface.TimeoutLabel, + *Surface.DnfLabel); + Surface.DetailLine = FString::Printf( + TEXT("Result-action detail: live solve %s | synthetic attempt entry %s | stop-live-attempt %s"), + Surface.bLiveSolvePhase ? TEXT("yes") : TEXT("no"), + Surface.bCanRecordSyntheticAttempt ? TEXT("enabled") : TEXT("disabled"), + Surface.bCanStopLiveTimedAttempt ? TEXT("enabled") : TEXT("disabled")); + return Surface; +} + FHyperTwistTrainingCoachDashboardLastAttemptReviewSummaryInspectSurface UHyperTwistCoachDashboardWidget::GetDisplayedLastAttemptReviewSummaryInspectSurface() const { @@ -23288,7 +23444,8 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() const FString HintLabel = CurrentTrainingCase.TimeTargetMs > 0 ? FString::Printf(TEXT("Enter attempt ms (target %d)"), CurrentTrainingCase.TimeTargetMs) : TEXT("Enter attempt ms"); - AttemptTimeTextBox->SetHintText(FText::FromString(HintLabel)); + DisplayedAttemptTimeHintLabel = HintLabel; + AttemptTimeTextBox->SetHintText(FText::FromString(DisplayedAttemptTimeHintLabel)); AttemptTimeTextBox->SetIsEnabled(!bHasLiveAttemptTimer && bHasRunnableCurrentCase); } if (FollowUpTextBlock != nullptr) @@ -23891,9 +24048,9 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() } if (RecordSuccessButtonLabel != nullptr) { - RecordSuccessButtonLabel->SetText(FText::FromString( - bLiveSolvePhase ? TEXT("Stop Success") : TEXT("Record Success") - )); + DisplayedRecordSuccessActionLabel = + bLiveSolvePhase ? TEXT("Stop Success") : TEXT("Record Success"); + RecordSuccessButtonLabel->SetText(FText::FromString(DisplayedRecordSuccessActionLabel)); } if (RecordSuccessButton != nullptr) { @@ -23901,9 +24058,9 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() } if (RecordFailureButtonLabel != nullptr) { - RecordFailureButtonLabel->SetText(FText::FromString( - bLiveSolvePhase ? TEXT("Stop Failure") : TEXT("Record Failure") - )); + DisplayedRecordFailureActionLabel = + bLiveSolvePhase ? TEXT("Stop Failure") : TEXT("Record Failure"); + RecordFailureButtonLabel->SetText(FText::FromString(DisplayedRecordFailureActionLabel)); } if (RecordFailureButton != nullptr) { @@ -23911,9 +24068,9 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() } if (RecordTimeoutButtonLabel != nullptr) { - RecordTimeoutButtonLabel->SetText(FText::FromString( - bLiveSolvePhase ? TEXT("Stop Timeout") : TEXT("Record Timeout") - )); + DisplayedRecordTimeoutActionLabel = + bLiveSolvePhase ? TEXT("Stop Timeout") : TEXT("Record Timeout"); + RecordTimeoutButtonLabel->SetText(FText::FromString(DisplayedRecordTimeoutActionLabel)); } if (RecordTimeoutButton != nullptr) { @@ -23921,9 +24078,9 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() } if (RecordDnfButtonLabel != nullptr) { - RecordDnfButtonLabel->SetText(FText::FromString( - bLiveSolvePhase ? TEXT("Stop DNF") : TEXT("Record DNF") - )); + DisplayedRecordDnfActionLabel = + bLiveSolvePhase ? TEXT("Stop DNF") : TEXT("Record DNF"); + RecordDnfButtonLabel->SetText(FText::FromString(DisplayedRecordDnfActionLabel)); } if (RecordDnfButton != nullptr) { @@ -23931,7 +24088,8 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() } if (UseTargetTimeButtonLabel != nullptr) { - UseTargetTimeButtonLabel->SetText(FText::FromString(TEXT("Use Target"))); + DisplayedUseTargetTimeLabel = TEXT("Use Target"); + UseTargetTimeButtonLabel->SetText(FText::FromString(DisplayedUseTargetTimeLabel)); } if (UseTargetTimeButton != nullptr) { @@ -23941,7 +24099,8 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() } if (UseAverageTimeButtonLabel != nullptr) { - UseAverageTimeButtonLabel->SetText(FText::FromString(TEXT("Use Avg"))); + DisplayedUseAverageTimeLabel = TEXT("Use Avg"); + UseAverageTimeButtonLabel->SetText(FText::FromString(DisplayedUseAverageTimeLabel)); } if (UseAverageTimeButton != nullptr) { @@ -23951,10 +24110,10 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() } if (StartTimerButtonLabel != nullptr) { - StartTimerButtonLabel->SetText(FText::FromString( + DisplayedStartTimerControlLabel = bLiveInspectionPhase ? TEXT("Start Solve") - : (bHasLiveAttemptTimer ? TEXT("Timer Active") : TEXT("Start Attempt")) - )); + : (bHasLiveAttemptTimer ? TEXT("Timer Active") : TEXT("Start Attempt")); + StartTimerButtonLabel->SetText(FText::FromString(DisplayedStartTimerControlLabel)); } if (StartTimerButton != nullptr) { @@ -23965,7 +24124,8 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() } if (CancelTimerButtonLabel != nullptr) { - CancelTimerButtonLabel->SetText(FText::FromString(TEXT("Cancel Timer"))); + DisplayedCancelTimerControlLabel = TEXT("Cancel Timer"); + CancelTimerButtonLabel->SetText(FText::FromString(DisplayedCancelTimerControlLabel)); } if (CancelTimerButton != nullptr) { @@ -23976,7 +24136,8 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() const FString CaptureSplitLabel = NextSplitPhaseDefinition == nullptr ? TEXT("All Splits Captured") : FString::Printf(TEXT("Capture %s"), *NextSplitLabel); - CaptureSplitButtonLabel->SetText(FText::FromString(CaptureSplitLabel)); + DisplayedCaptureSplitControlLabel = CaptureSplitLabel; + CaptureSplitButtonLabel->SetText(FText::FromString(DisplayedCaptureSplitControlLabel)); } if (CaptureSplitButton != nullptr) { @@ -23984,7 +24145,8 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() } if (UndoSplitButtonLabel != nullptr) { - UndoSplitButtonLabel->SetText(FText::FromString(TEXT("Undo Split"))); + DisplayedUndoSplitControlLabel = TEXT("Undo Split"); + UndoSplitButtonLabel->SetText(FText::FromString(DisplayedUndoSplitControlLabel)); } if (UndoSplitButton != nullptr) { diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h index bc1e31e..f7b6365 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h @@ -827,6 +827,18 @@ public: FHyperTwistTrainingCoachDashboardCurrentCaseDeckStatusInspectSurface GetDisplayedCurrentCaseDeckStatusInspectSurface() const; + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach|Dashboard") + FHyperTwistTrainingCoachDashboardAttemptTimeInputHintInspectSurface + GetDisplayedAttemptTimeInputHintInspectSurface() const; + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach|Dashboard") + FHyperTwistTrainingCoachDashboardLiveTimerSplitControlLabelInspectSurface + GetDisplayedLiveTimerSplitControlLabelInspectSurface() const; + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach|Dashboard") + FHyperTwistTrainingCoachDashboardAttemptResultActionEnablementInspectSurface + GetDisplayedAttemptResultActionEnablementInspectSurface() const; + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach|Dashboard") FHyperTwistTrainingCoachDashboardLastAttemptReviewSummaryInspectSurface GetDisplayedLastAttemptReviewSummaryInspectSurface() const; @@ -1884,6 +1896,17 @@ private: FString DisplayedActiveRunSummaryLine; FString DisplayedCurrentCasePromptLine; FString DisplayedCurrentCaseNotationLine; + FString DisplayedAttemptTimeHintLabel; + FString DisplayedUseTargetTimeLabel; + FString DisplayedUseAverageTimeLabel; + FString DisplayedStartTimerControlLabel; + FString DisplayedCancelTimerControlLabel; + FString DisplayedCaptureSplitControlLabel; + FString DisplayedUndoSplitControlLabel; + FString DisplayedRecordSuccessActionLabel; + FString DisplayedRecordFailureActionLabel; + FString DisplayedRecordTimeoutActionLabel; + FString DisplayedRecordDnfActionLabel; FString DisplayedLastAttemptReviewSummaryLine; FString DisplayedLastAttemptTimingLine; FString DisplayedLastAttemptDerivedPhaseLine; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h index 7aa92dc..332ccc6 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h @@ -10821,6 +10821,191 @@ struct FHyperTwistTrainingCoachDashboardHandoffRecommendationInspectSurface } }; +USTRUCT(BlueprintType) +struct FHyperTwistTrainingCoachDashboardAttemptTimeInputHintInspectSurface +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString Headline; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString StatusLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString HintLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString UseTargetLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString UseAverageLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 TargetTimeMs = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 AverageTotalTimeMs = 0; + + 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 bAttemptInputEnabled = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCanUseTargetTime = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCanUseAverageTime = false; + + bool IsStructurallyValid() const + { + return !StatusLine.IsEmpty() + || !HintLabel.IsEmpty() + || bAttemptInputEnabled + || bCanUseTargetTime + || bCanUseAverageTime; + } +}; + +USTRUCT(BlueprintType) +struct FHyperTwistTrainingCoachDashboardLiveTimerSplitControlLabelInspectSurface +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString Headline; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString TimerControlLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString SplitControlLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString StartTimerLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString CancelTimerLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString CaptureSplitLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString UndoSplitLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString NextSplitLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 CapturedSplitCount = 0; + + 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 bLiveSolvePhase = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCanStartOrAdvanceTimer = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCanCancelTimer = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCanCaptureSplit = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCanUndoSplit = false; + + bool IsStructurallyValid() const + { + return !TimerControlLine.IsEmpty() + || !SplitControlLine.IsEmpty() + || bCanStartOrAdvanceTimer + || bCanCancelTimer + || bCanCaptureSplit + || bCanUndoSplit; + } +}; + +USTRUCT(BlueprintType) +struct FHyperTwistTrainingCoachDashboardAttemptResultActionEnablementInspectSurface +{ + 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 SuccessLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString FailureLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString TimeoutLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString DnfLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasRunnableCurrentCase = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasLiveAttemptTimer = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bLiveSolvePhase = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCanRecordSyntheticAttempt = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCanStopLiveTimedAttempt = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bSuccessEnabled = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bFailureEnabled = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bTimeoutEnabled = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bDnfEnabled = false; + + bool IsStructurallyValid() const + { + return !StatusLine.IsEmpty() + || !DetailLine.IsEmpty() + || bSuccessEnabled + || bFailureEnabled + || bTimeoutEnabled + || bDnfEnabled; + } +}; + USTRUCT(BlueprintType) struct FHyperTwistTrainingCoachDashboardLastAttemptReviewSummaryInspectSurface { diff --git a/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md b/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md index 759bb43..9736a4d 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 displayed last-attempt review summary, displayed last-attempt timing / derived-phase review, and live split-capture status inspect surfaces over already-derived live dashboard review/timing/split presentation state, so gameplay/Blueprint callers can inspect the active reviewed attempt line, reviewed timing and derived-phase lines, and live split-capture posture without scraping dashboard text blocks or reopening attempt-review or split-capture derivation - - the next clean move is to stay on that same live dashboard guidance-consumer lane and land the attempt-time input hint, live timer / split control label, and attempt result-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-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 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_EXECUTION_CONTROL_HINT_TIMER_SPLIT_RESULT_ACTION_INSPECT_SURFACE_PACKET_2026-05-09.md b/docs/arch/HYPERTWIST_PHASE4_DASHBOARD_LIVE_EXECUTION_CONTROL_HINT_TIMER_SPLIT_RESULT_ACTION_INSPECT_SURFACE_PACKET_2026-05-09.md new file mode 100644 index 0000000..46ba0c0 --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE4_DASHBOARD_LIVE_EXECUTION_CONTROL_HINT_TIMER_SPLIT_RESULT_ACTION_INSPECT_SURFACE_PACKET_2026-05-09.md @@ -0,0 +1,55 @@ +# HyperTwist Phase 4 dashboard live execution-control hint, timer/split control, and result-action 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 live attempt-input, timer/split control, and result-action execution-control state directly to gameplay and Blueprint callers. + +## Why this packet exists + +The dashboard already derived: + +- the displayed attempt-time input hint +- the displayed timer and split-control labels +- the displayed result-action labels and their enablement posture + +But that live state still only surfaced through widget controls. Callers still had to scrape editable-text hints, button labels, or button enabled state to inspect what the dashboard was currently showing. + +## What changed + +- added `FHyperTwistTrainingCoachDashboardAttemptTimeInputHintInspectSurface` +- added `FHyperTwistTrainingCoachDashboardLiveTimerSplitControlLabelInspectSurface` +- added `FHyperTwistTrainingCoachDashboardAttemptResultActionEnablementInspectSurface` +- added `GetDisplayedAttemptTimeInputHintInspectSurface()` +- added `GetDisplayedLiveTimerSplitControlLabelInspectSurface()` +- added `GetDisplayedAttemptResultActionEnablementInspectSurface()` +- retained the exact displayed attempt-time hint, timer/split control labels, and result-action 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: + +- the attempt-time input hint and quick-fill affordance posture +- the live timer and split-control labels plus their current enablement posture +- the success / failure / timeout / DNF action labels plus their current enablement posture + +without scraping widget controls or reopening execution-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 attempt-review navigation, primary / continue / clear / complete run-control enablement, and queue defer / promote / recovery action enablement inspect surface group.