Expose dashboard last-attempt review and split-capture surfaces

This commit is contained in:
axiomlogicnexus 2026-05-09 20:31:48 +02:00
parent b0d89dbca4
commit 4b53d20926
5 changed files with 438 additions and 56 deletions

View file

@ -10997,6 +10997,201 @@ UHyperTwistCoachDashboardWidget::GetDisplayedCurrentCaseDeckStatusInspectSurface
return Surface;
}
FHyperTwistTrainingCoachDashboardLastAttemptReviewSummaryInspectSurface
UHyperTwistCoachDashboardWidget::GetDisplayedLastAttemptReviewSummaryInspectSurface() const
{
FHyperTwistTrainingCoachDashboardLastAttemptReviewSummaryInspectSurface Surface;
Surface.Headline = TEXT("Last attempt review summary");
const FHyperTwistTrainingAttempt* ReviewedAttempt =
SelectedAttemptIndex >= 0 && SelectedAttemptIndex < CachedRunState.Attempts.Num()
? &CachedRunState.Attempts[SelectedAttemptIndex]
: nullptr;
const bool bHasSessionScopedLastStep = !LastStepResult.SessionSummary.TrainingSessionId.IsEmpty()
&& LastStepResult.SessionSummary.TrainingSessionId == CachedRunState.Session.TrainingSessionId;
const FString ReviewedAttemptOrdinal = ReviewedAttempt != nullptr
? FString::Printf(TEXT("%d/%d"), SelectedAttemptIndex + 1, CachedRunState.Attempts.Num())
: TEXT("none");
Surface.AttemptOrdinal = ReviewedAttemptOrdinal;
Surface.AttemptId = ReviewedAttempt != nullptr && !ReviewedAttempt->AttemptId.IsEmpty()
? ReviewedAttempt->AttemptId
: TEXT("n/a");
Surface.CaseId = ReviewedAttempt != nullptr && !ReviewedAttempt->CaseId.IsEmpty()
? ReviewedAttempt->CaseId
: TEXT("n/a");
Surface.ResultLabel = ReviewedAttempt != nullptr
? HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(ReviewedAttempt->Result)
: TEXT("n/a");
Surface.CompletedAtUtc = ReviewedAttempt != nullptr && !ReviewedAttempt->CompletedAtUtc.IsEmpty()
? ReviewedAttempt->CompletedAtUtc
: TEXT("n/a");
Surface.LatestSubmissionLabel = bHasSessionScopedLastStep ? TEXT("yes") : TEXT("no");
Surface.SelectedAttemptIndex = SelectedAttemptIndex;
Surface.TotalAttemptCount = CachedRunState.Attempts.Num();
Surface.bHasReviewedAttempt = ReviewedAttempt != nullptr;
Surface.bLatestSubmissionMatchesActiveSession = bHasSessionScopedLastStep;
Surface.StatusLine = !DisplayedLastAttemptReviewSummaryLine.IsEmpty()
? DisplayedLastAttemptReviewSummaryLine
: (ReviewedAttempt != nullptr
? FString::Printf(
TEXT("Attempt review: %s | case %s | result %s | completed at %s | latest submission: %s"),
*Surface.AttemptOrdinal,
*Surface.CaseId,
*Surface.ResultLabel,
*Surface.CompletedAtUtc,
*Surface.LatestSubmissionLabel)
: TEXT("Attempt review: no attempts recorded in the active run yet."));
return Surface;
}
FHyperTwistTrainingCoachDashboardLastAttemptTimingDerivedPhaseReviewInspectSurface
UHyperTwistCoachDashboardWidget::GetDisplayedLastAttemptTimingDerivedPhaseReviewInspectSurface() const
{
FHyperTwistTrainingCoachDashboardLastAttemptTimingDerivedPhaseReviewInspectSurface Surface;
Surface.Headline = TEXT("Last attempt timing / derived-phase review");
const FHyperTwistTrainingAttempt* ReviewedAttempt =
SelectedAttemptIndex >= 0 && SelectedAttemptIndex < CachedRunState.Attempts.Num()
? &CachedRunState.Attempts[SelectedAttemptIndex]
: nullptr;
FString ReviewedAttemptPhaseSummary = TEXT("none");
if (ReviewedAttempt != nullptr && ReviewedAttempt->TimingBreakdown.DerivedPhaseTimings.Num() > 0)
{
TArray<FString> PhaseParts;
PhaseParts.Reserve(ReviewedAttempt->TimingBreakdown.DerivedPhaseTimings.Num());
for (const FHyperTwistPhaseTiming& PhaseTiming : ReviewedAttempt->TimingBreakdown.DerivedPhaseTimings)
{
PhaseParts.Add(FString::Printf(
TEXT("%s %d-%d (%d ms)"),
PhaseTiming.PhaseKind.IsEmpty() ? TEXT("phase") : *PhaseTiming.PhaseKind,
PhaseTiming.StartedAtMs,
PhaseTiming.EndedAtMs,
PhaseTiming.DurationMs
));
}
ReviewedAttemptPhaseSummary = FString::Join(PhaseParts, TEXT(" | "));
}
Surface.AttemptId = ReviewedAttempt != nullptr && !ReviewedAttempt->AttemptId.IsEmpty()
? ReviewedAttempt->AttemptId
: TEXT("n/a");
Surface.AppliedPenaltyLabel = ReviewedAttempt != nullptr
? HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(
ReviewedAttempt->TimingBreakdown.AppliedPenalty)
: TEXT("n/a");
Surface.DerivedPhaseSummary = ReviewedAttemptPhaseSummary;
Surface.InspectionElapsedMs =
ReviewedAttempt != nullptr ? ReviewedAttempt->TimingBreakdown.InspectionElapsedMs : 0;
Surface.RawSolveTimeMs =
ReviewedAttempt != nullptr ? ReviewedAttempt->TimingBreakdown.RawSolveTimeMs : 0;
Surface.FinalTimeMs =
ReviewedAttempt != nullptr ? ReviewedAttempt->TimingBreakdown.FinalTimeMs : 0;
Surface.SplitCaptureCount =
ReviewedAttempt != nullptr ? ReviewedAttempt->TimingBreakdown.SplitCaptures.Num() : 0;
Surface.DerivedPhaseCount =
ReviewedAttempt != nullptr ? ReviewedAttempt->TimingBreakdown.DerivedPhaseTimings.Num() : 0;
Surface.bHasReviewedAttempt = ReviewedAttempt != nullptr;
Surface.bDidNotFinish =
ReviewedAttempt != nullptr && ReviewedAttempt->TimingBreakdown.bDidNotFinish;
Surface.TimingLine = !DisplayedLastAttemptTimingLine.IsEmpty()
? DisplayedLastAttemptTimingLine
: (ReviewedAttempt != nullptr
? FString::Printf(
TEXT("Reviewed timing: inspection %d ms | raw solve %d ms | final %d ms | penalty %s | split captures %d | did-not-finish: %s"),
Surface.InspectionElapsedMs,
Surface.RawSolveTimeMs,
Surface.FinalTimeMs,
*Surface.AppliedPenaltyLabel,
Surface.SplitCaptureCount,
Surface.bDidNotFinish ? TEXT("yes") : TEXT("no"))
: TEXT("Reviewed timing: unavailable until the active run records an attempt."));
Surface.DerivedPhaseLine = !DisplayedLastAttemptDerivedPhaseLine.IsEmpty()
? DisplayedLastAttemptDerivedPhaseLine
: (ReviewedAttempt != nullptr
? FString::Printf(
TEXT("Reviewed derived phases: %s"),
*Surface.DerivedPhaseSummary)
: TEXT("Reviewed derived phases: none yet."));
return Surface;
}
FHyperTwistTrainingCoachDashboardLiveSplitCaptureStatusInspectSurface
UHyperTwistCoachDashboardWidget::GetDisplayedLiveSplitCaptureStatusInspectSurface() const
{
FHyperTwistTrainingCoachDashboardLiveSplitCaptureStatusInspectSurface Surface;
Surface.Headline = TEXT("Live split capture status");
const FHyperTwistTrainingTimingPolicy* TimingPolicy = FindActiveTimingPolicy();
const int32 MaxManualSplitCaptureCount = GetMaxManualSplitCaptureCount();
const FHyperTwistTrainingSplitPhaseDefinition* NextSplitPhaseDefinition = FindNextSplitPhaseToCapture();
const FString NextSplitLabel = NextSplitPhaseDefinition == nullptr
? TEXT("none")
: (NextSplitPhaseDefinition->Label.IsEmpty()
? NextSplitPhaseDefinition->PhaseId
: NextSplitPhaseDefinition->Label);
FString CapturedSplitSummary = TEXT("none");
if (LiveAttemptSplitCaptures.Num() > 0)
{
TArray<FString> CapturedSplitParts;
CapturedSplitParts.Reserve(LiveAttemptSplitCaptures.Num());
for (const FHyperTwistTrainingSplitCapture& SplitCapture : LiveAttemptSplitCaptures)
{
const FString Label = SplitCapture.Label.IsEmpty() ? SplitCapture.PhaseId : SplitCapture.Label;
CapturedSplitParts.Add(FString::Printf(TEXT("%s@%d"), *Label, SplitCapture.TimestampMs));
}
CapturedSplitSummary = FString::Join(CapturedSplitParts, TEXT(" | "));
}
const FString FinalSplitLabel =
TimingPolicy != nullptr
&& TimingPolicy->SplitPhases.IsValidIndex(MaxManualSplitCaptureCount)
? (TimingPolicy->SplitPhases[MaxManualSplitCaptureCount].Label.IsEmpty()
? TimingPolicy->SplitPhases[MaxManualSplitCaptureCount].PhaseId
: TimingPolicy->SplitPhases[MaxManualSplitCaptureCount].Label)
: TEXT("solve end");
const bool bLiveInspectionPhase = IsLiveAttemptInspectionPhase();
const bool bLiveSolvePhase = bHasLiveAttemptTimer
&& LiveAttemptPhase == EHyperTwistCoachDashboardAttemptPhase::Solving;
Surface.NextSplitLabel = NextSplitLabel;
Surface.FinalAutoCloseLabel = FinalSplitLabel;
Surface.CapturedSummary = CapturedSplitSummary;
Surface.CapturedSplitCount = LiveAttemptSplitCaptures.Num();
Surface.MaxManualSplitCaptureCount = MaxManualSplitCaptureCount;
Surface.bHasConfiguredManualSplitPhases = MaxManualSplitCaptureCount > 0;
Surface.bHasLiveAttemptTimer = bHasLiveAttemptTimer;
Surface.bLiveInspectionPhase = bLiveInspectionPhase;
Surface.bLiveSolvePhase = bLiveSolvePhase;
if (!DisplayedSplitCaptureStatusLine.IsEmpty())
{
Surface.StatusLine = DisplayedSplitCaptureStatusLine;
}
else if (MaxManualSplitCaptureCount <= 0)
{
Surface.StatusLine =
TEXT("Split capture: no manual split phases are configured for this timing policy.");
}
else
{
const FString SplitStateLine = FString::Printf(
TEXT("Manual splits %d/%d | Next: %s | Final auto-close: %s | Captured: %s"),
Surface.CapturedSplitCount,
Surface.MaxManualSplitCaptureCount,
*Surface.NextSplitLabel,
*Surface.FinalAutoCloseLabel,
*Surface.CapturedSummary);
Surface.StatusLine = bLiveSolvePhase
? SplitStateLine + TEXT(" | capture phase ends during solve; final phase closes on attempt stop")
: (bLiveInspectionPhase
? SplitStateLine + TEXT(" | split capture unlocks after solve start")
: SplitStateLine + TEXT(" | start a live attempt to record manual split boundaries"));
}
return Surface;
}
bool UHyperTwistCoachDashboardWidget::SelectPreviousMethodDrillFollowUpHistoryEntry()
{
if (MethodDrillFollowUpHistoryEntries.Num() <= 0)
@ -22908,9 +23103,8 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
}
if (LastAttemptTextBlock != nullptr)
{
if (ReviewedAttempt != nullptr)
{
LastAttemptTextBlock->SetText(FText::FromString(FString::Printf(
DisplayedLastAttemptReviewSummaryLine = ReviewedAttempt != nullptr
? FString::Printf(
TEXT("Attempt review: %s | case %s | result %s | completed at %s | latest submission: %s"),
*ReviewedAttemptOrdinal,
ReviewedAttempt->CaseId.IsEmpty() ? TEXT("n/a") : *ReviewedAttempt->CaseId,
@ -22919,20 +23113,14 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
? TEXT("n/a")
: *ReviewedAttempt->CompletedAtUtc,
bHasSessionScopedLastStep ? TEXT("yes") : TEXT("no")
)));
}
else
{
LastAttemptTextBlock->SetText(FText::FromString(
TEXT("Attempt review: no attempts recorded in the active run yet.")
));
}
)
: TEXT("Attempt review: no attempts recorded in the active run yet.");
LastAttemptTextBlock->SetText(FText::FromString(DisplayedLastAttemptReviewSummaryLine));
}
if (LastAttemptTimingTextBlock != nullptr)
{
if (ReviewedAttempt != nullptr)
{
LastAttemptTimingTextBlock->SetText(FText::FromString(FString::Printf(
DisplayedLastAttemptTimingLine = ReviewedAttempt != nullptr
? FString::Printf(
TEXT("Reviewed timing: inspection %d ms | raw solve %d ms | final %d ms | penalty %s | split captures %d | did-not-finish: %s"),
ReviewedAttempt->TimingBreakdown.InspectionElapsedMs,
ReviewedAttempt->TimingBreakdown.RawSolveTimeMs,
@ -22940,30 +23128,19 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
*HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(ReviewedAttempt->TimingBreakdown.AppliedPenalty),
ReviewedAttempt->TimingBreakdown.SplitCaptures.Num(),
ReviewedAttempt->TimingBreakdown.bDidNotFinish ? TEXT("yes") : TEXT("no")
)));
}
else
{
LastAttemptTimingTextBlock->SetText(FText::FromString(
TEXT("Reviewed timing: unavailable until the active run records an attempt.")
));
}
)
: TEXT("Reviewed timing: unavailable until the active run records an attempt.");
LastAttemptTimingTextBlock->SetText(FText::FromString(DisplayedLastAttemptTimingLine));
}
if (LastAttemptPhaseTextBlock != nullptr)
{
if (ReviewedAttempt != nullptr)
{
LastAttemptPhaseTextBlock->SetText(FText::FromString(FString::Printf(
DisplayedLastAttemptDerivedPhaseLine = ReviewedAttempt != nullptr
? FString::Printf(
TEXT("Reviewed derived phases: %s"),
*ReviewedAttemptPhaseSummary
)));
}
else
{
LastAttemptPhaseTextBlock->SetText(FText::FromString(
TEXT("Reviewed derived phases: none yet.")
));
}
)
: TEXT("Reviewed derived phases: none yet.");
LastAttemptPhaseTextBlock->SetText(FText::FromString(DisplayedLastAttemptDerivedPhaseLine));
}
if (CurrentCaseHeaderTextBlock != nullptr)
{
@ -23085,9 +23262,8 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
{
if (MaxManualSplitCaptureCount <= 0)
{
SplitStatusTextBlock->SetText(FText::FromString(
TEXT("Split capture: no manual split phases are configured for this timing policy.")
));
DisplayedSplitCaptureStatusLine =
TEXT("Split capture: no manual split phases are configured for this timing policy.");
}
else
{
@ -23099,25 +23275,13 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
*FinalSplitLabel,
*CapturedSplitSummary
);
if (bLiveSolvePhase)
{
SplitStatusTextBlock->SetText(FText::FromString(
SplitStateLine + TEXT(" | capture phase ends during solve; final phase closes on attempt stop")
));
}
else if (bLiveInspectionPhase)
{
SplitStatusTextBlock->SetText(FText::FromString(
SplitStateLine + TEXT(" | split capture unlocks after solve start")
));
}
else
{
SplitStatusTextBlock->SetText(FText::FromString(
SplitStateLine + TEXT(" | start a live attempt to record manual split boundaries")
));
}
DisplayedSplitCaptureStatusLine = bLiveSolvePhase
? SplitStateLine + TEXT(" | capture phase ends during solve; final phase closes on attempt stop")
: (bLiveInspectionPhase
? SplitStateLine + TEXT(" | split capture unlocks after solve start")
: SplitStateLine + TEXT(" | start a live attempt to record manual split boundaries"));
}
SplitStatusTextBlock->SetText(FText::FromString(DisplayedSplitCaptureStatusLine));
}
if (AttemptTimeTextBox != nullptr)
{

View file

@ -827,6 +827,18 @@ public:
FHyperTwistTrainingCoachDashboardCurrentCaseDeckStatusInspectSurface
GetDisplayedCurrentCaseDeckStatusInspectSurface() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach|Dashboard")
FHyperTwistTrainingCoachDashboardLastAttemptReviewSummaryInspectSurface
GetDisplayedLastAttemptReviewSummaryInspectSurface() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach|Dashboard")
FHyperTwistTrainingCoachDashboardLastAttemptTimingDerivedPhaseReviewInspectSurface
GetDisplayedLastAttemptTimingDerivedPhaseReviewInspectSurface() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach|Dashboard")
FHyperTwistTrainingCoachDashboardLiveSplitCaptureStatusInspectSurface
GetDisplayedLiveSplitCaptureStatusInspectSurface() const;
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach|Dashboard")
bool SelectPreviousMethodDrillFollowUpHistoryEntry();
@ -1872,7 +1884,11 @@ private:
FString DisplayedActiveRunSummaryLine;
FString DisplayedCurrentCasePromptLine;
FString DisplayedCurrentCaseNotationLine;
FString DisplayedLastAttemptReviewSummaryLine;
FString DisplayedLastAttemptTimingLine;
FString DisplayedLastAttemptDerivedPhaseLine;
FString DisplayedAttemptInputStatusLine;
FString DisplayedSplitCaptureStatusLine;
FString DisplayedTimerStatusLine;
FString DisplayedRecommendedDeckPreviewLine;
FString DisplayedFollowUpDeckPreviewLine;

View file

@ -10821,6 +10821,152 @@ struct FHyperTwistTrainingCoachDashboardHandoffRecommendationInspectSurface
}
};
USTRUCT(BlueprintType)
struct FHyperTwistTrainingCoachDashboardLastAttemptReviewSummaryInspectSurface
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString Headline;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString StatusLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString AttemptOrdinal;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString AttemptId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString CaseId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString ResultLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString CompletedAtUtc;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString LatestSubmissionLabel;
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 bLatestSubmissionMatchesActiveSession = false;
bool IsStructurallyValid() const
{
return !StatusLine.IsEmpty()
|| bHasReviewedAttempt;
}
};
USTRUCT(BlueprintType)
struct FHyperTwistTrainingCoachDashboardLastAttemptTimingDerivedPhaseReviewInspectSurface
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString Headline;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString TimingLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString DerivedPhaseLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString AttemptId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString AppliedPenaltyLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString DerivedPhaseSummary;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 InspectionElapsedMs = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 RawSolveTimeMs = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 FinalTimeMs = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 SplitCaptureCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 DerivedPhaseCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasReviewedAttempt = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bDidNotFinish = false;
bool IsStructurallyValid() const
{
return !TimingLine.IsEmpty()
|| !DerivedPhaseLine.IsEmpty()
|| bHasReviewedAttempt;
}
};
USTRUCT(BlueprintType)
struct FHyperTwistTrainingCoachDashboardLiveSplitCaptureStatusInspectSurface
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString Headline;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString StatusLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString NextSplitLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString FinalAutoCloseLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString CapturedSummary;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 CapturedSplitCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 MaxManualSplitCaptureCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasConfiguredManualSplitPhases = 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;
bool IsStructurallyValid() const
{
return !StatusLine.IsEmpty()
|| bHasConfiguredManualSplitPhases
|| bHasLiveAttemptTimer;
}
};
USTRUCT(BlueprintType)
struct FHyperTwistTrainingCoachDashboardActiveRunStatusInspectSurface
{

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 active-run status, displayed active-attempt/timer status, and displayed current-case / current-deck status inspect surfaces over already-derived live dashboard run-state, timer-state, and current-selection presentation state, so gameplay/Blueprint callers can inspect the current active-run line, run-summary line, attempt-input/timer posture, and current-case/current-deck presentation without scraping dashboard text blocks or reopening run/timer/current-case derivation
- the next clean move is to stay on that same live dashboard guidance-consumer lane and land the displayed last-attempt review summary, displayed last-attempt timing / derived-phase review, and live split-capture status 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 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 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,56 @@
# HyperTwist Phase 4 dashboard live last-attempt review and split-capture status 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 last-attempt review and split-capture presentation state directly to gameplay and Blueprint callers.
## Why this packet exists
The dashboard already derived:
- the displayed last-attempt review summary line
- the displayed reviewed timing line
- the displayed reviewed derived-phase line
- the displayed split-capture status line
But that live state still only surfaced through dashboard text blocks. Callers still had to scrape widget presentation or reopen last-attempt review and split-capture derivation to inspect what the dashboard was currently showing.
## What changed
- added `FHyperTwistTrainingCoachDashboardLastAttemptReviewSummaryInspectSurface`
- added `FHyperTwistTrainingCoachDashboardLastAttemptTimingDerivedPhaseReviewInspectSurface`
- added `FHyperTwistTrainingCoachDashboardLiveSplitCaptureStatusInspectSurface`
- added `GetDisplayedLastAttemptReviewSummaryInspectSurface()`
- added `GetDisplayedLastAttemptTimingDerivedPhaseReviewInspectSurface()`
- added `GetDisplayedLiveSplitCaptureStatusInspectSurface()`
- retained the exact displayed last-attempt review line, reviewed timing line, reviewed derived-phase line, and split-capture status line 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 displayed reviewed-attempt summary line and selected attempt identity/posture
- the displayed reviewed timing line and reviewed derived-phase line
- the displayed live split-capture status line, including next split, final auto-close, and captured manual boundaries
without scraping dashboard text blocks or reopening attempt-review / split-capture 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-time input hint, live timer / split control label, and attempt result-action enablement inspect surface group.