Expose dashboard execution-control hint timer and result surfaces

This commit is contained in:
axiomlogicnexus 2026-05-09 20:50:16 +02:00
parent 4b53d20926
commit ea0739b680
5 changed files with 448 additions and 23 deletions

View file

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

View file

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

View file

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

View file

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

View file

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