Add playable coach dashboard run controls

This commit is contained in:
axiomlogicnexus 2026-04-27 05:26:52 +02:00
parent 9c46be27b9
commit cc61288e82
2 changed files with 149 additions and 0 deletions

View file

@ -203,6 +203,35 @@ FHyperTwistTrainingRunState UHyperTwistCoachDashboardWidget::StartSelectedQueueE
return FHyperTwistTrainingRunState();
}
FHyperTwistTrainingRunStepResult UHyperTwistCoachDashboardWidget::SubmitSyntheticCurrentAttempt(
const EHyperTwistTrainingAttemptResult Result,
const int32 TimeMs
)
{
if (!CachedRunState.Session.IsStructurallyValid() || !CachedRunState.CurrentSelection.TrainingCase.IsStructurallyValid())
{
return FHyperTwistTrainingRunStepResult();
}
FHyperTwistTrainingAttempt Attempt;
Attempt.AttemptId = FString::Printf(
TEXT("attempt_%s_%s"),
*CachedRunState.Session.TrainingSessionId,
*FGuid::NewGuid().ToString(EGuidFormats::Digits)
);
Attempt.TrainingSessionId = CachedRunState.Session.TrainingSessionId;
Attempt.CaseId = CachedRunState.CurrentSelection.TrainingCase.CaseId;
Attempt.Result = Result;
Attempt.TotalTimeMs = FMath::Max(TimeMs, 0);
Attempt.ExecutionTimeMs = FMath::Max(TimeMs, 0);
Attempt.TimingBreakdown.RawSolveTimeMs = FMath::Max(TimeMs, 0);
Attempt.TimingBreakdown.FinalTimeMs = FMath::Max(TimeMs, 0);
Attempt.ReplayId = CachedRunState.ReplayPacket.ReplayId;
Attempt.CompletedAtUtc = FDateTime::UtcNow().ToIso8601();
return SubmitTrainingAttempt(Attempt, TimeMs);
}
void UHyperTwistCoachDashboardWidget::HandlePrimaryActionClicked()
{
ExecutePrimaryCoachAction();
@ -252,6 +281,20 @@ void UHyperTwistCoachDashboardWidget::HandleClearRunClicked()
UpdateDashboardPresentation();
}
void UHyperTwistCoachDashboardWidget::HandleRecordSuccessClicked()
{
SubmitSyntheticCurrentAttempt(EHyperTwistTrainingAttemptResult::Success, DefaultSuccessAttemptTimeMs);
SyncSelectedQueueEntry();
UpdateDashboardPresentation();
}
void UHyperTwistCoachDashboardWidget::HandleRecordFailureClicked()
{
SubmitSyntheticCurrentAttempt(EHyperTwistTrainingAttemptResult::Failure, DefaultFailureAttemptTimeMs);
SyncSelectedQueueEntry();
UpdateDashboardPresentation();
}
void UHyperTwistCoachDashboardWidget::HandlePreviousQueueClicked()
{
SelectPreviousQueueEntry();
@ -316,6 +359,12 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt()
WidgetTree,
RootLayout,
TEXT("CoachActiveRunSummary"),
2
);
LastAttemptTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
WidgetTree,
RootLayout,
TEXT("CoachLastAttempt"),
8
);
FollowUpTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow(
@ -479,6 +528,28 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt()
ClearRunLabelRaw
);
ClearRunButtonLabel = ClearRunLabelRaw;
UTextBlock* RecordSuccessLabelRaw = nullptr;
RecordSuccessButton = HyperTwistCoachDashboardWidgetInternal::AddButton(
WidgetTree,
RunControlRow,
TEXT("RecordSuccessButton"),
TEXT("RecordSuccessButtonLabel"),
TEXT("Record Success"),
RecordSuccessLabelRaw
);
RecordSuccessButtonLabel = RecordSuccessLabelRaw;
UTextBlock* RecordFailureLabelRaw = nullptr;
RecordFailureButton = HyperTwistCoachDashboardWidgetInternal::AddButton(
WidgetTree,
RunControlRow,
TEXT("RecordFailureButton"),
TEXT("RecordFailureButtonLabel"),
TEXT("Record Failure"),
RecordFailureLabelRaw
);
RecordFailureButtonLabel = RecordFailureLabelRaw;
}
UHorizontalBox* QueueNavRow = WidgetTree->ConstructWidget<UHorizontalBox>(
@ -550,6 +621,14 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt()
{
ClearRunButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleClearRunClicked);
}
if (RecordSuccessButton != nullptr)
{
RecordSuccessButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleRecordSuccessClicked);
}
if (RecordFailureButton != nullptr)
{
RecordFailureButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandleRecordFailureClicked);
}
if (PreviousQueueButton != nullptr)
{
PreviousQueueButton->OnClicked.AddDynamic(this, &UHyperTwistCoachDashboardWidget::HandlePreviousQueueClicked);
@ -628,6 +707,7 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
const FString ActiveDeckLabel = CachedRunState.ActiveDeck.Title.IsEmpty()
? (CachedRunState.ActiveDeck.DeckId.IsEmpty() ? TEXT("n/a") : CachedRunState.ActiveDeck.DeckId)
: CachedRunState.ActiveDeck.Title;
const bool bHasRunnableCurrentCase = bHasActiveRun && CachedRunState.CurrentSelection.TrainingCase.IsStructurallyValid();
const FString VerificationNotePreview = CachedRepositoryRoundTripVerification.VerificationNotes.Num() > 0
? CachedRepositoryRoundTripVerification.VerificationNotes[0]
: TEXT("none");
@ -683,6 +763,26 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
CachedRunSummary.LastCaseId.IsEmpty() ? TEXT("n/a") : *CachedRunSummary.LastCaseId
)));
}
if (LastAttemptTextBlock != nullptr)
{
if (!LastStepResult.SessionSummary.TrainingSessionId.IsEmpty())
{
LastAttemptTextBlock->SetText(FText::FromString(FString::Printf(
TEXT("Last recorded attempt: case %s | completed at %s | session completed attempts %d"),
LastStepResult.SessionSummary.LastCaseId.IsEmpty() ? TEXT("n/a") : *LastStepResult.SessionSummary.LastCaseId,
LastStepResult.SessionSummary.LastCompletedAtUtc.IsEmpty()
? TEXT("n/a")
: *LastStepResult.SessionSummary.LastCompletedAtUtc,
LastStepResult.SessionSummary.CompletedAttemptCount
)));
}
else
{
LastAttemptTextBlock->SetText(FText::FromString(
TEXT("Last recorded attempt: none in this dashboard session yet.")
));
}
}
if (FollowUpTextBlock != nullptr)
{
const FString FollowUpLine =
@ -912,6 +1012,22 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
{
ClearRunButton->SetIsEnabled(bHasActiveRun);
}
if (RecordSuccessButtonLabel != nullptr)
{
RecordSuccessButtonLabel->SetText(FText::FromString(TEXT("Record Success")));
}
if (RecordSuccessButton != nullptr)
{
RecordSuccessButton->SetIsEnabled(bHasRunnableCurrentCase);
}
if (RecordFailureButtonLabel != nullptr)
{
RecordFailureButtonLabel->SetText(FText::FromString(TEXT("Record Failure")));
}
if (RecordFailureButton != nullptr)
{
RecordFailureButton->SetIsEnabled(bHasRunnableCurrentCase);
}
if (PreviousQueueButtonLabel != nullptr)
{
PreviousQueueButtonLabel->SetText(FText::FromString(TEXT("Prev Queue")));

View file

@ -19,6 +19,12 @@ public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Training|Coach|Dashboard")
int32 DefaultDeferredHours = 24;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Training|Coach|Dashboard")
int32 DefaultSuccessAttemptTimeMs = 15000;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Training|Coach|Dashboard")
int32 DefaultFailureAttemptTimeMs = 30000;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Training|Coach|Dashboard")
bool bPreferActiveQueueSelectionOnRefresh = true;
@ -46,6 +52,12 @@ public:
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard")
FHyperTwistTrainingRunState StartSelectedQueueEntry();
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard")
FHyperTwistTrainingRunStepResult SubmitSyntheticCurrentAttempt(
EHyperTwistTrainingAttemptResult Result,
int32 TimeMs
);
protected:
UPROPERTY(Transient)
TObjectPtr<UVerticalBox> RootLayout = nullptr;
@ -68,6 +80,9 @@ protected:
UPROPERTY(Transient)
TObjectPtr<UTextBlock> ActiveRunSummaryTextBlock = nullptr;
UPROPERTY(Transient)
TObjectPtr<UTextBlock> LastAttemptTextBlock = nullptr;
UPROPERTY(Transient)
TObjectPtr<UTextBlock> QueueStatusTextBlock = nullptr;
@ -140,6 +155,18 @@ protected:
UPROPERTY(Transient)
TObjectPtr<UTextBlock> ClearRunButtonLabel = nullptr;
UPROPERTY(Transient)
TObjectPtr<UButton> RecordSuccessButton = nullptr;
UPROPERTY(Transient)
TObjectPtr<UTextBlock> RecordSuccessButtonLabel = nullptr;
UPROPERTY(Transient)
TObjectPtr<UButton> RecordFailureButton = nullptr;
UPROPERTY(Transient)
TObjectPtr<UTextBlock> RecordFailureButtonLabel = nullptr;
UPROPERTY(Transient)
TObjectPtr<UButton> PreviousQueueButton = nullptr;
@ -176,6 +203,12 @@ protected:
UFUNCTION()
void HandleClearRunClicked();
UFUNCTION()
void HandleRecordSuccessClicked();
UFUNCTION()
void HandleRecordFailureClicked();
UFUNCTION()
void HandlePreviousQueueClicked();