Expose gameplay primary coach CTA surface
This commit is contained in:
parent
2740fc75d6
commit
440cf06173
9 changed files with 333 additions and 2 deletions
|
|
@ -323,6 +323,81 @@ namespace HyperTwistTrainingCoachLibraryInternal
|
|||
}
|
||||
}
|
||||
|
||||
FString DescribePrimaryCoachActionLabel(
|
||||
const FHyperTwistTrainingCoachPanelState& PanelState,
|
||||
const EHyperTwistTrainingCoachHandoffKind ActionKind
|
||||
)
|
||||
{
|
||||
if (!PanelState.PrimaryActionLabel.IsEmpty())
|
||||
{
|
||||
return PanelState.PrimaryActionLabel;
|
||||
}
|
||||
|
||||
switch (ActionKind)
|
||||
{
|
||||
case EHyperTwistTrainingCoachHandoffKind::Queue:
|
||||
return TEXT("continue_queue");
|
||||
case EHyperTwistTrainingCoachHandoffKind::FollowUp:
|
||||
return TEXT("start_follow_up");
|
||||
case EHyperTwistTrainingCoachHandoffKind::Recommended:
|
||||
return TEXT("start_recommended");
|
||||
default:
|
||||
return TEXT("refresh");
|
||||
}
|
||||
}
|
||||
|
||||
FString DescribePrimaryCoachAvailabilityLine(
|
||||
const FHyperTwistTrainingCoachPanelState& PanelState,
|
||||
const EHyperTwistTrainingCoachHandoffKind ActionKind
|
||||
)
|
||||
{
|
||||
switch (ActionKind)
|
||||
{
|
||||
case EHyperTwistTrainingCoachHandoffKind::Queue:
|
||||
return !PanelState.QueueStatusLine.IsEmpty()
|
||||
? PanelState.QueueStatusLine
|
||||
: TEXT("Queued coach run ready");
|
||||
case EHyperTwistTrainingCoachHandoffKind::FollowUp:
|
||||
return !PanelState.SecondaryLine.IsEmpty()
|
||||
? PanelState.SecondaryLine
|
||||
: TEXT("Follow-up coach run ready");
|
||||
case EHyperTwistTrainingCoachHandoffKind::Recommended:
|
||||
return !PanelState.SecondaryLine.IsEmpty()
|
||||
? PanelState.SecondaryLine
|
||||
: TEXT("Recommended coach run ready");
|
||||
default:
|
||||
if (PanelState.bHasActiveRun)
|
||||
{
|
||||
return TEXT("A training run is already active");
|
||||
}
|
||||
if (!PanelState.QueueSuppressionLine.IsEmpty())
|
||||
{
|
||||
return PanelState.QueueSuppressionLine;
|
||||
}
|
||||
return !PanelState.SecondaryLine.IsEmpty()
|
||||
? PanelState.SecondaryLine
|
||||
: TEXT("No coach action ready");
|
||||
}
|
||||
}
|
||||
|
||||
FString DescribePrimaryCoachDetailLine(
|
||||
const FHyperTwistTrainingCoachPanelState& PanelState,
|
||||
const EHyperTwistTrainingCoachHandoffKind ActionKind
|
||||
)
|
||||
{
|
||||
if (!PanelState.LaunchBudgetReasonLine.IsEmpty())
|
||||
{
|
||||
return PanelState.LaunchBudgetReasonLine;
|
||||
}
|
||||
if (ActionKind == EHyperTwistTrainingCoachHandoffKind::Queue
|
||||
&& !PanelState.QueueSuppressionLine.IsEmpty())
|
||||
{
|
||||
return PanelState.QueueSuppressionLine;
|
||||
}
|
||||
|
||||
return PanelState.ReviewPolicyDetailLine;
|
||||
}
|
||||
|
||||
bool IsQueueRecoveryMemorySource(const FString& SourceLabel)
|
||||
{
|
||||
return SourceLabel.StartsWith(TEXT("coach-memory-queue-"))
|
||||
|
|
@ -5257,3 +5332,53 @@ FHyperTwistCoachSchedulePolicySummary UHyperTwistTrainingCoachLibrary::DeriveCoa
|
|||
|
||||
return Summary;
|
||||
}
|
||||
|
||||
EHyperTwistTrainingCoachHandoffKind UHyperTwistTrainingCoachLibrary::ResolvePrimaryCoachActionKind(
|
||||
const FHyperTwistTrainingCoachPanelState& PanelState
|
||||
)
|
||||
{
|
||||
if (PanelState.bCanStartQueuedRun)
|
||||
{
|
||||
return EHyperTwistTrainingCoachHandoffKind::Queue;
|
||||
}
|
||||
if (PanelState.bCanStartCoachFollowUpRun && PanelState.bHasFollowUpGuidance)
|
||||
{
|
||||
return EHyperTwistTrainingCoachHandoffKind::FollowUp;
|
||||
}
|
||||
if (PanelState.bCanStartCoachRecommendedRun)
|
||||
{
|
||||
return EHyperTwistTrainingCoachHandoffKind::Recommended;
|
||||
}
|
||||
|
||||
return EHyperTwistTrainingCoachHandoffKind::None;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachPrimaryActionSurface UHyperTwistTrainingCoachLibrary::DerivePrimaryCoachActionSurface(
|
||||
const FHyperTwistTrainingCoachPanelState& PanelState
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingCoachPrimaryActionSurface Surface;
|
||||
Surface.Headline = PanelState.Headline;
|
||||
Surface.FocusDeckId = PanelState.FocusDeckId;
|
||||
Surface.FocusMethodSegmentId = PanelState.FocusMethodSegmentId;
|
||||
Surface.ActionKind = ResolvePrimaryCoachActionKind(PanelState);
|
||||
Surface.PreferredGuidanceLane = PanelState.PreferredGuidanceLane;
|
||||
Surface.SuggestedMode = PanelState.SuggestedMode;
|
||||
Surface.SuggestedSelectionPolicy = PanelState.SuggestedSelectionPolicy;
|
||||
Surface.SuggestedLaunchCaseBudget = PanelState.SuggestedLaunchCaseBudget;
|
||||
Surface.bCanExecute = Surface.ActionKind != EHyperTwistTrainingCoachHandoffKind::None;
|
||||
Surface.ActionLabel = HyperTwistTrainingCoachLibraryInternal::DescribePrimaryCoachActionLabel(
|
||||
PanelState,
|
||||
Surface.ActionKind
|
||||
);
|
||||
Surface.AvailabilityLine =
|
||||
HyperTwistTrainingCoachLibraryInternal::DescribePrimaryCoachAvailabilityLine(
|
||||
PanelState,
|
||||
Surface.ActionKind
|
||||
);
|
||||
Surface.DetailLine = HyperTwistTrainingCoachLibraryInternal::DescribePrimaryCoachDetailLine(
|
||||
PanelState,
|
||||
Surface.ActionKind
|
||||
);
|
||||
return Surface;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -426,6 +426,17 @@ FString UHyperTwistTrainingPanelWidget::ResolvePrimaryCoachActionStartActionSour
|
|||
);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachPrimaryActionSurface
|
||||
UHyperTwistTrainingPanelWidget::GetDisplayedPrimaryCoachActionSurface() const
|
||||
{
|
||||
FHyperTwistTrainingCoachPrimaryActionSurface Surface =
|
||||
UHyperTwistTrainingCoachLibrary::DerivePrimaryCoachActionSurface(CachedCoachPanelState);
|
||||
Surface.ActionSourceLabel = Surface.bCanExecute
|
||||
? ResolvePrimaryCoachActionStartActionSourceLabel()
|
||||
: FString();
|
||||
return Surface;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRunState UHyperTwistTrainingPanelWidget::StartCoachRecommendedRun(
|
||||
const int32 MaxCases,
|
||||
const FString& StartActionSourceLabel
|
||||
|
|
|
|||
|
|
@ -407,6 +407,17 @@ FString AHyperTwistTrainingSessionActor::ResolvePrimaryCoachActionStartActionSou
|
|||
);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachPrimaryActionSurface
|
||||
AHyperTwistTrainingSessionActor::GetDisplayedPrimaryCoachActionSurface() const
|
||||
{
|
||||
FHyperTwistTrainingCoachPrimaryActionSurface Surface =
|
||||
UHyperTwistTrainingCoachLibrary::DerivePrimaryCoachActionSurface(CachedCoachPanelState);
|
||||
Surface.ActionSourceLabel = Surface.bCanExecute
|
||||
? ResolvePrimaryCoachActionStartActionSourceLabel()
|
||||
: FString();
|
||||
return Surface;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRunState AHyperTwistTrainingSessionActor::StartCoachRecommendedTrainingRun(
|
||||
const int32 MaxCases
|
||||
)
|
||||
|
|
|
|||
|
|
@ -584,4 +584,14 @@ public:
|
|||
const FHyperTwistTrainingCoachArchivePolicySummary& ArchivePolicySummary,
|
||||
const FString& ReferenceUtc
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
|
||||
static EHyperTwistTrainingCoachHandoffKind ResolvePrimaryCoachActionKind(
|
||||
const FHyperTwistTrainingCoachPanelState& PanelState
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
|
||||
static FHyperTwistTrainingCoachPrimaryActionSurface DerivePrimaryCoachActionSurface(
|
||||
const FHyperTwistTrainingCoachPanelState& PanelState
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -214,6 +214,9 @@ public:
|
|||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
|
||||
FString ResolvePrimaryCoachActionStartActionSourceLabel() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
|
||||
FHyperTwistTrainingCoachPrimaryActionSurface GetDisplayedPrimaryCoachActionSurface() const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach")
|
||||
FHyperTwistTrainingRunState StartCoachRecommendedRun(
|
||||
int32 MaxCases,
|
||||
|
|
|
|||
|
|
@ -195,6 +195,9 @@ public:
|
|||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
|
||||
FString ResolvePrimaryCoachActionStartActionSourceLabel() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
|
||||
FHyperTwistTrainingCoachPrimaryActionSurface GetDisplayedPrimaryCoachActionSurface() const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach")
|
||||
FHyperTwistTrainingRunState StartCoachRecommendedTrainingRun(int32 MaxCases);
|
||||
|
||||
|
|
|
|||
|
|
@ -7815,3 +7815,55 @@ struct FHyperTwistTrainingCoachPanelState
|
|||
|| bHasActiveRun;
|
||||
}
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistTrainingCoachPrimaryActionSurface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString Headline;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString ActionLabel;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString AvailabilityLine;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString DetailLine;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString ActionSourceLabel;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString FocusDeckId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString FocusMethodSegmentId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
EHyperTwistTrainingCoachHandoffKind ActionKind = EHyperTwistTrainingCoachHandoffKind::None;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
EHyperTwistTrainingCoachGuidanceLane PreferredGuidanceLane =
|
||||
EHyperTwistTrainingCoachGuidanceLane::None;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
EHyperTwistTrainingDeliveryMode SuggestedMode = EHyperTwistTrainingDeliveryMode::Timer;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
EHyperTwistTrainingSelectionPolicy SuggestedSelectionPolicy =
|
||||
EHyperTwistTrainingSelectionPolicy::Weighted;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 SuggestedLaunchCaseBudget = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bCanExecute = false;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
return bCanExecute || !ActionLabel.IsEmpty() || !Headline.IsEmpty() || !AvailabilityLine.IsEmpty();
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -68,8 +68,8 @@ Current correction call from the source-exposed audit and current execution refr
|
|||
- the imported generated-mode gap is closed; request/config/selection plumbing and the bounded clean-room executor are already landed in first-party code
|
||||
- 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 latest landed bounded `Phase 4` packet widens the first gameplay-facing coach consumer layer by giving the panel-widget and session-actor surfaces displayed coach-action helpers plus default source-label routing, so gameplay/Blueprint callers no longer have to fabricate coach provenance or manually chase active queue-entry ids just to launch the current action
|
||||
- the current next bounded packet is to stay on those same surfaces and expose one thin, truthful primary-action descriptor / availability layer for gameplay callers so they can bind a single coach CTA without reconstructing queue-vs-follow-up-vs-recommended state from the raw panel booleans
|
||||
- the latest landed bounded `Phase 4` packet stays on that same gameplay-facing consumer lane and adds a thin primary coach CTA descriptor / availability surface over the existing panel-widget and session-actor execution helpers, so gameplay/Blueprint callers can now bind one truthful coach action without replaying queue-vs-follow-up-vs-recommended branch logic from raw booleans
|
||||
- the current next bounded packet is to add the matching thin secondary queue-control descriptor / availability surface for defer and promote flows on those same two consumer surfaces, without widening into broader UI composition or backend policy work
|
||||
- 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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,116 @@
|
|||
# HyperTwist Phase 4 gameplay primary coach CTA surface packet
|
||||
|
||||
Created on `2026-05-07`
|
||||
|
||||
Status:
|
||||
|
||||
- first-party HyperTwist packet
|
||||
- bounded Phase `4` gameplay-facing consumer slice
|
||||
|
||||
## Purpose
|
||||
|
||||
This packet closes the next thin gameplay-facing consumer seam above the panel-widget and session-actor coach execution helpers by exposing one pure primary coach CTA descriptor / availability surface that follows the exact same branch order as the runtime primary launch path.
|
||||
|
||||
The open tasks were:
|
||||
|
||||
- stop forcing gameplay / Blueprint callers to reconstruct queue-vs-follow-up-vs-recommended launch readiness from raw `CachedCoachPanelState` booleans just to render one truthful coach CTA
|
||||
- expose the actual primary coach action kind, label, availability, and source label through a narrow pure surface instead of through dashboard-specific branching
|
||||
- keep that descriptor aligned with the existing `StartDisplayedPrimaryCoachAction()` / `StartPrimaryCoachTrainingRun()` execution path
|
||||
|
||||
It is not:
|
||||
|
||||
- a new backend continuity packet
|
||||
- a broader gameplay UI composition pass
|
||||
- a dashboard rewrite
|
||||
- a queue-policy or analytics-policy change
|
||||
|
||||
## Scope
|
||||
|
||||
Bounded lane:
|
||||
|
||||
- add a first-party primary coach CTA surface type in shared training types
|
||||
- add coach-library helpers that resolve the primary coach action kind and derive a narrow CTA surface from `FHyperTwistTrainingCoachPanelState`
|
||||
- expose that derived surface on `HyperTwistTrainingPanelWidget` and `HyperTwistTrainingSessionActor`
|
||||
- keep the descriptor aligned with the exact runtime branch order used by `StartPrimaryCoachAction()`
|
||||
|
||||
Out of scope:
|
||||
|
||||
- changing coach action selection policy
|
||||
- changing queue execution behavior
|
||||
- adding richer composed gameplay HUD widgets
|
||||
- widening into defer/promote secondary controls in the same packet
|
||||
|
||||
## Why this was the right next packet
|
||||
|
||||
Before this slice:
|
||||
|
||||
- gameplay-facing panel/session consumers already had displayed-action execution helpers
|
||||
- those surfaces already resolved default coach provenance for execution
|
||||
- callers could launch the current coach action without dashboard code
|
||||
|
||||
But one thin consumer seam was still open:
|
||||
|
||||
- callers still had to inspect `bCanStartQueuedRun`, `bCanStartCoachFollowUpRun`, `bHasFollowUpGuidance`, and `bCanStartCoachRecommendedRun` themselves to decide what the single primary CTA actually meant
|
||||
- that would have duplicated the same branch order that the runtime already uses in `StartPrimaryCoachAction()`
|
||||
|
||||
That meant:
|
||||
|
||||
- execution was thin
|
||||
- but truthful rendering of the primary CTA still required caller-side reconstruction
|
||||
|
||||
So the next bounded move was:
|
||||
|
||||
- keep the execution path unchanged
|
||||
- and expose one narrow pure descriptor derived from the same branch order
|
||||
|
||||
## What landed
|
||||
|
||||
Primary code changes:
|
||||
|
||||
- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h`
|
||||
- added `FHyperTwistTrainingCoachPrimaryActionSurface`
|
||||
- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h`
|
||||
- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp`
|
||||
- added `ResolvePrimaryCoachActionKind(...)`
|
||||
- added `DerivePrimaryCoachActionSurface(...)`
|
||||
- the branch order now matches `StartPrimaryCoachAction()` exactly: queue first, then follow-up-with-guidance, then recommended
|
||||
- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h`
|
||||
- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp`
|
||||
- added `GetDisplayedPrimaryCoachActionSurface()`
|
||||
- the returned surface is enriched with the same resolved start-action source label used by the execution helper
|
||||
- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h`
|
||||
- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp`
|
||||
- added `GetDisplayedPrimaryCoachActionSurface()`
|
||||
- the returned surface is enriched with the same resolved start-action source label used by the actor execution helper
|
||||
|
||||
## Product effect
|
||||
|
||||
The first gameplay-facing coach consumer layer is now more complete:
|
||||
|
||||
- gameplay / Blueprint callers can bind one primary coach CTA from a pure descriptor instead of replaying runtime branch logic
|
||||
- the descriptor and the execution verb now point at the same queue / follow-up / recommended action ordering
|
||||
- callers can render truthful CTA availability and provenance without reaching into dashboard code
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- shared training types expose a narrow primary coach CTA surface
|
||||
- coach-library derivation matches `StartPrimaryCoachAction()` branch order exactly
|
||||
- panel-widget callers can fetch the displayed primary coach CTA surface directly
|
||||
- session-actor callers can fetch the displayed primary coach CTA surface directly
|
||||
- full product build succeeds
|
||||
|
||||
## Validation checklist
|
||||
|
||||
1. build `UnrealHyperTwist.sln` / `UnrealHyperTwistEditor`
|
||||
2. confirm the new primary CTA surface compiles in shared types and coach library
|
||||
3. confirm panel-widget getters compile and return the derived surface
|
||||
4. confirm session-actor getters compile and return the derived surface
|
||||
|
||||
## Validation result
|
||||
|
||||
- full `Development Editor|Win64` build succeeded on `2026-05-07`
|
||||
- build completed with the same preexisting `UnrealMCP` / `PythonScriptPlugin` dependency warning and `0` errors
|
||||
|
||||
## Next bounded follow-on slice
|
||||
|
||||
- stay on the same gameplay-facing consumer lane and add the matching thin secondary queue-control descriptor / availability surface for defer and promote actions on `HyperTwistTrainingPanelWidget` and `HyperTwistTrainingSessionActor`, so callers can bind the common non-primary queue controls without reading raw queue-state booleans
|
||||
Loading…
Add table
Reference in a new issue