Expose dashboard decision history navigation surface

This commit is contained in:
axiomlogicnexus 2026-05-10 02:54:38 +02:00
parent 0e70c24da1
commit e76ee5814d
5 changed files with 275 additions and 4 deletions

View file

@ -8998,6 +8998,137 @@ UHyperTwistCoachDashboardWidget::GetSelectedGuidanceDecisionHistoryInspectSurfac
return Surface;
}
FHyperTwistTrainingCoachDashboardGuidanceDecisionHistoryNavigationInspectSurface
UHyperTwistCoachDashboardWidget::GetDisplayedGuidanceDecisionHistoryNavigationInspectSurface() const
{
FHyperTwistTrainingCoachDashboardGuidanceDecisionHistoryNavigationInspectSurface Surface;
Surface.Headline = TEXT("Decision history navigation");
Surface.bClosedLoopIdleActionability = IsCoachLoopActionabilityClosed();
const auto BuildDecisionActionLabel =
[this](const EHyperTwistCoachDashboardOutcomeAction Action) -> FString
{
return Action == EHyperTwistCoachDashboardOutcomeAction::None
? TEXT("none")
: BuildGuidanceOutcomeActionLabel(Action);
};
const EHyperTwistCoachDashboardOutcomeAction BaseAction = DeriveGuidanceOutcomeAction();
const FHyperTwistCoachDashboardDecisionWeighting Weighting =
BuildGuidanceDecisionWeighting(BaseAction);
const FHyperTwistCoachDashboardDecisionHistoryEntry* SelectedEntry =
SelectedGuidanceDecisionIndex >= 0
&& SelectedGuidanceDecisionIndex < GuidanceDecisionHistoryEntries.Num()
? &GuidanceDecisionHistoryEntries[SelectedGuidanceDecisionIndex]
: nullptr;
const bool bNavigationEnabled = GuidanceDecisionHistoryEntries.Num() > 1;
Surface.PreviousLabel = !DisplayedPreviousGuidanceDecisionNavigationLabel.IsEmpty()
? DisplayedPreviousGuidanceDecisionNavigationLabel
: TEXT("Prev Decision");
Surface.NextLabel = !DisplayedNextGuidanceDecisionNavigationLabel.IsEmpty()
? DisplayedNextGuidanceDecisionNavigationLabel
: TEXT("Next Decision");
Surface.SelectedEntryIndex = SelectedGuidanceDecisionIndex;
Surface.HistoryEntryCount = GuidanceDecisionHistoryEntries.Num();
Surface.bHasSelectedEntry =
SelectedEntry != nullptr && SelectedEntry->IsStructurallyValid();
Surface.bSelectedEntryIsLatest =
Surface.bHasSelectedEntry
&& SelectedGuidanceDecisionIndex == GuidanceDecisionHistoryEntries.Num() - 1;
Surface.bPreviousEnabled = bNavigationEnabled;
Surface.bNextEnabled = bNavigationEnabled;
Surface.bHasWeighting = Weighting.IsStructurallyValid();
Surface.WeightingStatusLine = Weighting.IsStructurallyValid()
? FString::Printf(
TEXT("Weighting: %d matching decisions | Base: %s | Weighted: %s | Last applied: %s"),
Weighting.MatchingDecisionCount,
*BuildGuidanceOutcomeActionLabel(Weighting.BaseAction),
*BuildGuidanceOutcomeActionLabel(Weighting.WeightedAction),
*BuildGuidanceOutcomeActionLabel(Weighting.LastAppliedAction))
: TEXT("Weighting: no matching authored decision history yet; the current outcome action is recap-derived only.");
if (Surface.bClosedLoopIdleActionability)
{
Surface.WeightingStatusLine =
TEXT("Weighting: retained outcome history remains inspectable, but no live guidance-outcome action is active.");
}
Surface.LastAppliedActionLabel = BuildDecisionActionLabel(Weighting.LastAppliedAction);
if (!Surface.bHasSelectedEntry)
{
Surface.SelectedEntryOrdinal = TEXT("none");
Surface.RecordedAtUtc = TEXT("n/a");
Surface.GuidanceLaneLabel = TEXT("none");
Surface.ScopeLabel = TEXT("n/a");
Surface.RecommendedActionLabel = TEXT("none");
Surface.AppliedActionLabel = TEXT("none");
Surface.StatusLine =
TEXT("Decision navigation: no applied guidance-outcome decisions recorded yet.");
Surface.DetailLine = FString::Printf(
TEXT("%s: %s | %s: %s | last applied: %s | selected latest: no"),
*Surface.PreviousLabel,
Surface.bPreviousEnabled ? TEXT("enabled") : TEXT("disabled"),
*Surface.NextLabel,
Surface.bNextEnabled ? TEXT("enabled") : TEXT("disabled"),
*Surface.LastAppliedActionLabel);
if (Surface.bClosedLoopIdleActionability)
{
Surface.StatusLine =
TEXT("Decision navigation: no applied guidance-outcome decisions are selected; retained decision history remains inspectable in the closed-loop idle state.");
Surface.DetailLine = FString::Printf(
TEXT("%s: %s | %s: %s | last applied: %s | selected latest: no"),
*Surface.PreviousLabel,
Surface.bPreviousEnabled ? TEXT("enabled") : TEXT("disabled"),
*Surface.NextLabel,
Surface.bNextEnabled ? TEXT("enabled") : TEXT("disabled"),
*Surface.LastAppliedActionLabel);
}
return Surface;
}
Surface.SelectedEntryOrdinal = FString::Printf(
TEXT("%d/%d"),
SelectedGuidanceDecisionIndex + 1,
GuidanceDecisionHistoryEntries.Num());
Surface.RecordedAtUtc = SelectedEntry->RecordedAtUtc.IsEmpty()
? TEXT("n/a")
: SelectedEntry->RecordedAtUtc;
Surface.GuidanceLaneLabel =
SelectedEntry->GuidanceLane == EHyperTwistCoachDashboardGuidancePreviewLane::Recommended
? TEXT("recommended")
: (SelectedEntry->GuidanceLane
== EHyperTwistCoachDashboardGuidancePreviewLane::FollowUp
? TEXT("follow-up")
: TEXT("none"));
Surface.ScopeLabel =
SelectedEntry->bSingleCaseScope ? TEXT("selected case") : TEXT("preview deck");
Surface.RecommendedActionLabel =
SelectedEntry->RecommendedActionLabel.IsEmpty()
? TEXT("none")
: SelectedEntry->RecommendedActionLabel;
Surface.AppliedActionLabel =
SelectedEntry->AppliedActionLabel.IsEmpty()
? TEXT("none")
: SelectedEntry->AppliedActionLabel;
Surface.StatusLine = FString::Printf(
TEXT("Decision navigation: selected %s | Lane: %s | Scope: %s | Recorded: %s"),
*Surface.SelectedEntryOrdinal,
*Surface.GuidanceLaneLabel,
*Surface.ScopeLabel,
*Surface.RecordedAtUtc);
Surface.DetailLine = FString::Printf(
TEXT("%s: %s | %s: %s | recommended: %s | applied: %s | last applied: %s | selected latest: %s"),
*Surface.PreviousLabel,
Surface.bPreviousEnabled ? TEXT("enabled") : TEXT("disabled"),
*Surface.NextLabel,
Surface.bNextEnabled ? TEXT("enabled") : TEXT("disabled"),
*Surface.RecommendedActionLabel,
*Surface.AppliedActionLabel,
*Surface.LastAppliedActionLabel,
Surface.bSelectedEntryIsLatest ? TEXT("yes") : TEXT("no"));
return Surface;
}
FHyperTwistTrainingCoachDashboardTemplateLaunchHistoryInspectSurface
UHyperTwistCoachDashboardWidget::GetSelectedTemplateLaunchHistoryInspectSurface() const
{
@ -24409,7 +24540,9 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
}
if (PreviousGuidanceDecisionButtonLabel != nullptr)
{
PreviousGuidanceDecisionButtonLabel->SetText(FText::FromString(TEXT("Prev Decision")));
DisplayedPreviousGuidanceDecisionNavigationLabel = TEXT("Prev Decision");
PreviousGuidanceDecisionButtonLabel->SetText(FText::FromString(
DisplayedPreviousGuidanceDecisionNavigationLabel));
}
if (PreviousGuidanceDecisionButton != nullptr)
{
@ -24417,7 +24550,9 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
}
if (NextGuidanceDecisionButtonLabel != nullptr)
{
NextGuidanceDecisionButtonLabel->SetText(FText::FromString(TEXT("Next Decision")));
DisplayedNextGuidanceDecisionNavigationLabel = TEXT("Next Decision");
NextGuidanceDecisionButtonLabel->SetText(FText::FromString(
DisplayedNextGuidanceDecisionNavigationLabel));
}
if (NextGuidanceDecisionButton != nullptr)
{

View file

@ -783,6 +783,10 @@ public:
FHyperTwistTrainingCoachDashboardGuidanceDecisionHistoryInspectSurface
GetSelectedGuidanceDecisionHistoryInspectSurface() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach|Dashboard")
FHyperTwistTrainingCoachDashboardGuidanceDecisionHistoryNavigationInspectSurface
GetDisplayedGuidanceDecisionHistoryNavigationInspectSurface() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach|Dashboard")
FHyperTwistTrainingCoachDashboardTemplateLaunchHistoryInspectSurface
GetSelectedTemplateLaunchHistoryInspectSurface() const;
@ -2026,6 +2030,8 @@ private:
FString DisplayedNextMethodDrillFollowUpNavigationLabel;
FString DisplayedPreviousHandoffHistoryNavigationLabel;
FString DisplayedNextHandoffHistoryNavigationLabel;
FString DisplayedPreviousGuidanceDecisionNavigationLabel;
FString DisplayedNextGuidanceDecisionNavigationLabel;
FString DisplayedPreviousAttemptNavigationLabel;
FString DisplayedNextAttemptNavigationLabel;
EHyperTwistCoachDashboardGuidancePreviewLane ActiveGuidancePreviewLane =

View file

@ -9847,6 +9847,84 @@ struct FHyperTwistTrainingCoachDashboardHandoffHistoryInspectSurface
}
};
USTRUCT(BlueprintType)
struct FHyperTwistTrainingCoachDashboardGuidanceDecisionHistoryNavigationInspectSurface
{
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 PreviousLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString NextLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString SelectedEntryOrdinal;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString RecordedAtUtc;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString GuidanceLaneLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString ScopeLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString RecommendedActionLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString AppliedActionLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString WeightingStatusLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString LastAppliedActionLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 SelectedEntryIndex = INDEX_NONE;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 HistoryEntryCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasSelectedEntry = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bSelectedEntryIsLatest = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bPreviousEnabled = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bNextEnabled = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasWeighting = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bClosedLoopIdleActionability = false;
bool IsStructurallyValid() const
{
return !StatusLine.IsEmpty()
|| !DetailLine.IsEmpty()
|| bHasSelectedEntry
|| bPreviousEnabled
|| bNextEnabled;
}
};
USTRUCT(BlueprintType)
struct FHyperTwistTrainingCoachDashboardGuidanceDecisionHistoryInspectSurface
{

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 retained-history navigation/control lane and adds the handoff history navigation control inspect surface over already-retained handoff-pattern shifts, so gameplay/Blueprint callers can inspect the live prev/next handoff labels, enablement posture, selected retained handoff ordinal, and recommendation/posture/readiness/latest-selection context without scraping button text or reopening retained-history derivation
- the next clean move is to continue that same retained-history navigation/control lane and expose the guidance decision history navigation control surface rather than drifting back into backend continuity or reopening the already-inspectable history detail seams
- the latest landed bounded `Phase 4` packet stays on that same retained-history navigation/control lane and adds the guidance decision history navigation control inspect surface over already-retained authored decision outcomes, so gameplay/Blueprint callers can inspect the live prev/next decision labels, enablement posture, selected retained decision ordinal, and lane/scope/action/latest-selection context without scraping button text or reopening retained-history derivation
- the next clean move is to continue that same retained-history navigation/control lane and expose the template launch history navigation control surface rather than drifting back into backend continuity or reopening the already-inspectable history detail seams
- 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
## Donor portfolio phase taxonomy

View file

@ -0,0 +1,52 @@
# HyperTwist Phase 4 dashboard guidance decision history navigation control inspect surface packet
- bounded Phase `4` dashboard-consumer slice
- date: `2026-05-10`
## Intent
Stay on the retained-history navigation/control lane inside `UHyperTwistCoachDashboardWidget` and expose the guidance decision history navigation controls directly to gameplay and Blueprint callers.
## Why this packet exists
The dashboard already derived and displayed:
- the previous decision-history button label
- the next decision-history button label
- the enablement posture for both controls
But that control state still only surfaced through button text and button enabled state. Callers still had to inspect widget controls directly to understand whether retained decision-history navigation is available and which retained authored outcome decision is currently selected.
## What changed
- added `FHyperTwistTrainingCoachDashboardGuidanceDecisionHistoryNavigationInspectSurface`
- added `GetDisplayedGuidanceDecisionHistoryNavigationInspectSurface()`
- retained the exact displayed previous/next decision-history labels inside the widget so the new inspect surface reports 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 live previous/next decision-history labels
- whether decision-history navigation is currently enabled
- the selected retained decision ordinal, recorded time, lane, scope, recommended action, and applied action
- the retained weighting status, last-applied action posture, and latest-selection posture
without scraping button text or reopening retained-history derivation.
## Validation
- full `Development Editor | Win64` MSBuild on `UnrealHyperTwist.sln`
- passed with `0 Warning(s), 0 Error(s)`
## Next clean slice
Stay on the retained-history navigation/control lane and expose the template launch history navigation control surface.