diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp index ea13575..5275dc9 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp @@ -5571,3 +5571,209 @@ UHyperTwistTrainingCoachLibrary::DeriveCoachQueueControlSurface( return Surface; } + +FHyperTwistTrainingCoachQueueRecoveryPostureSurface +UHyperTwistTrainingCoachLibrary::DeriveCoachQueueRecoveryPostureSurface( + const FHyperTwistCoachSchedulePolicySummary& SchedulePolicySummary, + const FHyperTwistTrainingCoachSessionQueueExecutionSummary& QueueExecutionSummary, + const FHyperTwistTrainingCoachPanelState& PanelState, + const FHyperTwistTrainingCoachPrimaryActionSurface& PrimaryActionSurface, + const FHyperTwistTrainingCoachQueueControlSurface& QueueControlSurface +) +{ + auto DescribeGuidanceLane = [](const EHyperTwistTrainingCoachGuidanceLane Lane) -> FString + { + switch (Lane) + { + case EHyperTwistTrainingCoachGuidanceLane::FollowUp: + return TEXT("follow-up"); + case EHyperTwistTrainingCoachGuidanceLane::Recommended: + return TEXT("recommended"); + default: + return TEXT("none"); + } + }; + auto DescribeRecoveryAction = [](const EHyperTwistTrainingQueueRecoveryAction Action) -> FString + { + switch (Action) + { + case EHyperTwistTrainingQueueRecoveryAction::PromoteDeferred: + return TEXT("prefer promote-deferred recovery"); + case EHyperTwistTrainingQueueRecoveryAction::LaunchRecovery: + return TEXT("prefer launch-recovery"); + default: + return TEXT("no upstream recovery preference"); + } + }; + + FHyperTwistTrainingCoachQueueRecoveryPostureSurface Surface; + Surface.Headline = TEXT("Queue Recovery"); + Surface.PreferredRecoveryAction = PanelState.PreferredQueueRecoveryAction; + Surface.PreferredGuidanceLane = PanelState.PreferredGuidanceLane; + Surface.PressureScore = FMath::Max( + SchedulePolicySummary.ReschedulePressureScore, + QueueExecutionSummary.ExecutionPressureScore + ); + Surface.bHasRecoveryPressure = + Surface.PressureScore > 0.0f + || PanelState.bHasDeferredQueueWork + || QueueExecutionSummary.bHasOutstandingDeferredWork + || QueueExecutionSummary.bHasCompletionGap + || SchedulePolicySummary.bNeedsManualRescheduleReview + || SchedulePolicySummary.bHasExpiredDeferrals + || SchedulePolicySummary.bHasArchiveAwareDeferrals + || SchedulePolicySummary.bHasBlockedFollowUpDeferrals + || PanelState.PreferredQueueRecoveryAction != EHyperTwistTrainingQueueRecoveryAction::None + || HyperTwistTrainingCoachLibraryInternal::IsQueueRecoveryMemorySource( + PanelState.PreferredGuidanceSourceLabel + ); + + const bool bPrefersPromoteUpstream = + PanelState.PreferredQueueRecoveryAction + == EHyperTwistTrainingQueueRecoveryAction::PromoteDeferred; + const bool bPrefersLaunchUpstream = + PanelState.PreferredQueueRecoveryAction + == EHyperTwistTrainingQueueRecoveryAction::LaunchRecovery; + + if (bPrefersPromoteUpstream && QueueControlSurface.bCanPromote) + { + Surface.PostureKind = EHyperTwistTrainingCoachQueuePostureKind::Promote; + } + else if (PrimaryActionSurface.bCanExecute + && (bPrefersLaunchUpstream + || !QueueControlSurface.bCanPromote + || PanelState.PreferredQueueRecoveryAction + == EHyperTwistTrainingQueueRecoveryAction::None)) + { + Surface.PostureKind = EHyperTwistTrainingCoachQueuePostureKind::Continue; + } + else if (QueueControlSurface.bCanPromote) + { + Surface.PostureKind = EHyperTwistTrainingCoachQueuePostureKind::Promote; + } + else if (PrimaryActionSurface.bCanExecute) + { + Surface.PostureKind = EHyperTwistTrainingCoachQueuePostureKind::Continue; + } + else if (QueueControlSurface.bCanDefer) + { + Surface.PostureKind = EHyperTwistTrainingCoachQueuePostureKind::Defer; + } + + switch (Surface.PostureKind) + { + case EHyperTwistTrainingCoachQueuePostureKind::Continue: + Surface.PreferredActionLabel = PrimaryActionSurface.ActionLabel; + Surface.PreferredActionSourceLabel = PrimaryActionSurface.ActionSourceLabel; + Surface.bCanExecutePreferredAction = PrimaryActionSurface.bCanExecute; + if (bPrefersLaunchUpstream) + { + Surface.StatusLine = FString::Printf( + TEXT("Queue recovery keeps continue live as the preferred launch-recovery posture: %s"), + PrimaryActionSurface.AvailabilityLine.IsEmpty() + ? TEXT("Continue is actionable") + : *PrimaryActionSurface.AvailabilityLine + ); + } + else if (bPrefersPromoteUpstream && !QueueControlSurface.bCanPromote) + { + Surface.StatusLine = FString::Printf( + TEXT("Queue recovery falls back to continue because no deferred promote target is currently actionable: %s"), + PrimaryActionSurface.AvailabilityLine.IsEmpty() + ? TEXT("Continue is actionable") + : *PrimaryActionSurface.AvailabilityLine + ); + } + else if (Surface.bHasRecoveryPressure) + { + Surface.StatusLine = FString::Printf( + TEXT("Queue recovery keeps continue as the live actionable posture: %s"), + PrimaryActionSurface.AvailabilityLine.IsEmpty() + ? TEXT("Continue is actionable") + : *PrimaryActionSurface.AvailabilityLine + ); + } + else + { + Surface.StatusLine = FString::Printf( + TEXT("Queue posture is stable; continue remains the live coach action: %s"), + PrimaryActionSurface.AvailabilityLine.IsEmpty() + ? TEXT("Continue is actionable") + : *PrimaryActionSurface.AvailabilityLine + ); + } + break; + case EHyperTwistTrainingCoachQueuePostureKind::Promote: + Surface.PreferredActionLabel = QueueControlSurface.PromoteActionLabel; + Surface.PreferredActionSourceLabel = QueueControlSurface.PromoteActionSourceLabel; + Surface.bCanExecutePreferredAction = QueueControlSurface.bCanPromote; + Surface.StatusLine = bPrefersPromoteUpstream + ? FString::Printf( + TEXT("Queue recovery prefers deferred promotion: %s"), + QueueControlSurface.PromoteAvailabilityLine.IsEmpty() + ? TEXT("Promote is actionable") + : *QueueControlSurface.PromoteAvailabilityLine + ) + : FString::Printf( + TEXT("Continue is not currently the preferred recovery path, so deferred promotion becomes the live posture: %s"), + QueueControlSurface.PromoteAvailabilityLine.IsEmpty() + ? TEXT("Promote is actionable") + : *QueueControlSurface.PromoteAvailabilityLine + ); + break; + case EHyperTwistTrainingCoachQueuePostureKind::Defer: + Surface.PreferredActionLabel = QueueControlSurface.DeferActionLabel; + Surface.bCanExecutePreferredAction = QueueControlSurface.bCanDefer; + Surface.StatusLine = FString::Printf( + TEXT("No continue or promote target is currently actionable; defer is the available queue-pressure release control: %s"), + QueueControlSurface.DeferAvailabilityLine.IsEmpty() + ? TEXT("Defer is actionable") + : *QueueControlSurface.DeferAvailabilityLine + ); + break; + default: + Surface.StatusLine = Surface.bHasRecoveryPressure + ? TEXT("Queue recovery pressure is present, but no continue, defer, or promote control is currently actionable.") + : TEXT("Queue recovery pressure is not currently active."); + break; + } + + const FString GuidanceLaneLabel = DescribeGuidanceLane(PanelState.PreferredGuidanceLane); + const FString GuidanceSourceLabel = PanelState.PreferredGuidanceSourceLabel.IsEmpty() + ? TEXT("n/a") + : HyperTwistTrainingCoachLibraryInternal::DescribeCoachMemorySourceLabel( + PanelState.PreferredGuidanceSourceLabel + ); + const FString RecoveryActionSourceLabel = PanelState.PreferredQueueRecoveryActionSourceLabel.IsEmpty() + ? TEXT("n/a") + : HyperTwistTrainingCoachLibraryInternal::DescribeCoachMemorySourceLabel( + PanelState.PreferredQueueRecoveryActionSourceLabel + ); + Surface.DetailLine = FString::Printf( + TEXT("Upstream: %s | Lane: %s | Guidance source: %s | Recovery action source: %s | Pressure %.2f | Runnable queue: %s | Deferred work: %s | Outstanding deferred: %s | Completion gap: %s | Manual review: %s | Blocked follow-up: %s | Archive-aware: %s | Dominant reason: %s"), + *DescribeRecoveryAction(PanelState.PreferredQueueRecoveryAction), + *GuidanceLaneLabel, + *GuidanceSourceLabel, + *RecoveryActionSourceLabel, + Surface.PressureScore, + PanelState.bHasRunnableQueueEntry ? TEXT("yes") : TEXT("no"), + PanelState.bHasDeferredQueueWork ? TEXT("yes") : TEXT("no"), + QueueExecutionSummary.bHasOutstandingDeferredWork ? TEXT("yes") : TEXT("no"), + QueueExecutionSummary.bHasCompletionGap ? TEXT("yes") : TEXT("no"), + SchedulePolicySummary.bNeedsManualRescheduleReview ? TEXT("yes") : TEXT("no"), + SchedulePolicySummary.bHasBlockedFollowUpDeferrals ? TEXT("yes") : TEXT("no"), + SchedulePolicySummary.bHasArchiveAwareDeferrals ? TEXT("yes") : TEXT("no"), + SchedulePolicySummary.DominantDeferralReasonLabel.IsEmpty() + ? TEXT("n/a") + : *SchedulePolicySummary.DominantDeferralReasonLabel + ); + if (!PanelState.QueueSuppressionLine.IsEmpty()) + { + Surface.DetailLine += FString::Printf( + TEXT(" | Suppression: %s"), + *PanelState.QueueSuppressionLine + ); + } + + return Surface; +} diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp index 2aab5a1..3e6d95f 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp @@ -451,6 +451,22 @@ UHyperTwistTrainingPanelWidget::GetDisplayedCoachQueueControlSurface() const return Surface; } +FHyperTwistTrainingCoachQueueRecoveryPostureSurface +UHyperTwistTrainingPanelWidget::GetDisplayedCoachQueueRecoveryPostureSurface() const +{ + const FHyperTwistTrainingCoachPrimaryActionSurface PrimaryActionSurface = + GetDisplayedPrimaryCoachActionSurface(); + const FHyperTwistTrainingCoachQueueControlSurface QueueControlSurface = + GetDisplayedCoachQueueControlSurface(); + return UHyperTwistTrainingCoachLibrary::DeriveCoachQueueRecoveryPostureSurface( + CachedCoachSchedulePolicySummary, + CachedCoachQueueExecutionSummary, + CachedCoachPanelState, + PrimaryActionSurface, + QueueControlSurface + ); +} + FHyperTwistTrainingRunState UHyperTwistTrainingPanelWidget::StartCoachRecommendedRun( const int32 MaxCases, const FString& StartActionSourceLabel diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp index fd54b38..f535c08 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp @@ -432,6 +432,22 @@ AHyperTwistTrainingSessionActor::GetDisplayedCoachQueueControlSurface() const return Surface; } +FHyperTwistTrainingCoachQueueRecoveryPostureSurface +AHyperTwistTrainingSessionActor::GetDisplayedCoachQueueRecoveryPostureSurface() const +{ + const FHyperTwistTrainingCoachPrimaryActionSurface PrimaryActionSurface = + GetDisplayedPrimaryCoachActionSurface(); + const FHyperTwistTrainingCoachQueueControlSurface QueueControlSurface = + GetDisplayedCoachQueueControlSurface(); + return UHyperTwistTrainingCoachLibrary::DeriveCoachQueueRecoveryPostureSurface( + CachedCoachSchedulePolicySummary, + CachedCoachQueueExecutionSummary, + CachedCoachPanelState, + PrimaryActionSurface, + QueueControlSurface + ); +} + FHyperTwistTrainingRunState AHyperTwistTrainingSessionActor::StartCoachRecommendedTrainingRun( const int32 MaxCases ) diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h index 204e307..6c9097f 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h @@ -600,4 +600,14 @@ public: const FHyperTwistTrainingCoachSessionQueueState& QueueState, const FHyperTwistTrainingCoachPanelState& PanelState ); + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + static FHyperTwistTrainingCoachQueueRecoveryPostureSurface + DeriveCoachQueueRecoveryPostureSurface( + const FHyperTwistCoachSchedulePolicySummary& SchedulePolicySummary, + const FHyperTwistTrainingCoachSessionQueueExecutionSummary& QueueExecutionSummary, + const FHyperTwistTrainingCoachPanelState& PanelState, + const FHyperTwistTrainingCoachPrimaryActionSurface& PrimaryActionSurface, + const FHyperTwistTrainingCoachQueueControlSurface& QueueControlSurface + ); }; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h index 38fd954..2b8194f 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h @@ -220,6 +220,10 @@ public: UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") FHyperTwistTrainingCoachQueueControlSurface GetDisplayedCoachQueueControlSurface() const; + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingCoachQueueRecoveryPostureSurface + GetDisplayedCoachQueueRecoveryPostureSurface() const; + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach") FHyperTwistTrainingRunState StartCoachRecommendedRun( int32 MaxCases, diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h index 0216749..d09b96a 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h @@ -201,6 +201,10 @@ public: UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") FHyperTwistTrainingCoachQueueControlSurface GetDisplayedCoachQueueControlSurface() const; + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingCoachQueueRecoveryPostureSurface + GetDisplayedCoachQueueRecoveryPostureSurface() const; + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach") FHyperTwistTrainingRunState StartCoachRecommendedTrainingRun(int32 MaxCases); diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h index 1ab4ab5..c99261c 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h @@ -41,6 +41,15 @@ enum class EHyperTwistTrainingQueueRecoveryAction : uint8 LaunchRecovery UMETA(DisplayName = "Launch Recovery") }; +UENUM(BlueprintType) +enum class EHyperTwistTrainingCoachQueuePostureKind : uint8 +{ + None UMETA(DisplayName = "None"), + Continue UMETA(DisplayName = "Continue"), + Defer UMETA(DisplayName = "Defer"), + Promote UMETA(DisplayName = "Promote") +}; + UENUM(BlueprintType) enum class EHyperTwistTrainingCoachHandoffKind : uint8 { @@ -7908,3 +7917,53 @@ struct FHyperTwistTrainingCoachQueueControlSurface || !PromoteAvailabilityLine.IsEmpty(); } }; + +USTRUCT(BlueprintType) +struct FHyperTwistTrainingCoachQueueRecoveryPostureSurface +{ + 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 PreferredActionLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString PreferredActionSourceLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float PressureScore = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + EHyperTwistTrainingCoachQueuePostureKind PostureKind = + EHyperTwistTrainingCoachQueuePostureKind::None; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + EHyperTwistTrainingQueueRecoveryAction PreferredRecoveryAction = + EHyperTwistTrainingQueueRecoveryAction::None; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + EHyperTwistTrainingCoachGuidanceLane PreferredGuidanceLane = + EHyperTwistTrainingCoachGuidanceLane::None; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasRecoveryPressure = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCanExecutePreferredAction = false; + + bool IsStructurallyValid() const + { + return !StatusLine.IsEmpty() + || !DetailLine.IsEmpty() + || PostureKind != EHyperTwistTrainingCoachQueuePostureKind::None + || bHasRecoveryPressure; + } +}; diff --git a/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md b/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md index 8bb1271..c546ee6 100644 --- a/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md +++ b/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md @@ -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 stays on that same gameplay-facing consumer lane and adds the matching thin secondary queue-control descriptor / availability surface for defer and promote flows on those same two consumer surfaces, while also aligning their displayed promote path with the same deferred-entry recovery ranking already used by the dashboard - - the current next bounded packet is to expose one thin queue-recovery posture / status surface on those same two consumer surfaces so gameplay/Blueprint callers can explain why continue, defer, or promote is preferred without reading raw suppression/status strings or recovery enums directly + - the latest landed bounded `Phase 4` packet stays on that same gameplay-facing consumer lane and adds one thin queue-recovery posture / status surface over the already-landed primary CTA and secondary queue-control surfaces, so gameplay/Blueprint callers can explain why continue, defer, or promote is preferred without reading raw suppression/status strings or recovery enums directly + - the current next bounded packet is to expose one thin coach handoff recommendation / continuation status surface on those same two consumer surfaces so gameplay/Blueprint callers can render the queued-vs-follow-up-vs-recommended continuation posture without reaching into raw panel-state strings or handoff enums directly - 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 diff --git a/docs/arch/HYPERTWIST_PHASE4_GAMEPLAY_QUEUE_RECOVERY_POSTURE_SURFACE_PACKET_2026-05-07.md b/docs/arch/HYPERTWIST_PHASE4_GAMEPLAY_QUEUE_RECOVERY_POSTURE_SURFACE_PACKET_2026-05-07.md new file mode 100644 index 0000000..de0653a --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE4_GAMEPLAY_QUEUE_RECOVERY_POSTURE_SURFACE_PACKET_2026-05-07.md @@ -0,0 +1,115 @@ +# HyperTwist Phase 4 gameplay queue-recovery posture 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 helpers by exposing one pure queue-recovery posture / status surface over the already-landed primary coach CTA and secondary queue-control surfaces. + +The open tasks were: + +- stop forcing gameplay / Blueprint callers to read raw queue suppression strings, queue status strings, and queue-recovery enums just to explain why continue, defer, or promote is currently preferred +- expose one thin first-party posture surface that summarizes the live queue-recovery pressure, the currently preferred posture, and the upstream recovery provenance +- keep that posture surface derived from already-cached policy, execution, primary CTA, and queue-control state instead of widening into dashboard-only recovery analytics + +It is not: + +- a new recovery-policy packet +- a dashboard weighting migration +- a broader gameplay HUD composition pass +- a backend continuity or template-analytics packet + +## Scope + +Bounded lane: + +- add a first-party queue posture kind enum and queue-recovery posture/status surface in shared training types +- add a coach-library helper that derives the surface from schedule-policy summary, queue-execution summary, panel state, primary CTA surface, and queue-control surface +- expose that derived posture surface on `HyperTwistTrainingPanelWidget` and `HyperTwistTrainingSessionActor` +- keep the posture derivation presentation-only and rooted in already-cached gameplay consumer state + +Out of scope: + +- changing queue recovery selection policy +- importing the dashboard’s deeper learned recovery weighting into gameplay consumer surfaces +- adding new queue action verbs +- widening into handoff recommendation composition in the same packet + +## Why this was the right next packet + +Before this slice: + +- gameplay-facing panel/session consumers already exposed displayed coach-action helpers +- those surfaces already exposed a truthful primary CTA surface +- those surfaces already exposed a truthful secondary queue-control surface for defer and promote flows + +But one thin consumer seam was still open: + +- callers could bind the actions +- but they still had to inspect raw queue suppression/status text and recovery enums to explain why continue, defer, or promote was the current posture + +That meant: + +- action binding was thin +- but posture explanation still leaked raw backend-derived strings into callers + +So the next bounded move was: + +- keep the already-landed execution and control surfaces unchanged +- and expose one narrow posture/status surface above them + +## What landed + +Primary code changes: + +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h` + - added `EHyperTwistTrainingCoachQueuePostureKind` + - added `FHyperTwistTrainingCoachQueueRecoveryPostureSurface` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp` + - added `DeriveCoachQueueRecoveryPostureSurface(...)` + - posture is derived from existing schedule-policy, queue-execution, panel-state, primary CTA, and queue-control inputs +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp` + - added `GetDisplayedCoachQueueRecoveryPostureSurface()` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp` + - added `GetDisplayedCoachQueueRecoveryPostureSurface()` + +## Product effect + +The gameplay-facing coach consumer layer is now more complete: + +- gameplay / Blueprint callers can explain the live queue-recovery posture from one pure surface +- callers no longer need to read raw queue suppression/status strings or queue-recovery enums just to explain why continue, defer, or promote is preferred +- the posture surface stays aligned with the already-landed primary CTA and queue-control surfaces without importing dashboard-only recovery analytics + +## Acceptance criteria + +- shared training types expose a queue posture kind enum and a narrow queue-recovery posture surface +- coach-library derivation consumes the already-cached gameplay consumer state rather than adding new runtime fetches +- panel-widget callers can fetch the displayed queue-recovery posture surface directly +- session-actor callers can fetch the displayed queue-recovery posture surface directly +- full product build succeeds + +## Validation checklist + +1. build `UnrealHyperTwist.sln` / `UnrealHyperTwistEditor` +2. confirm the new posture 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 +5. confirm no backend continuity or dashboard-only recovery weighting work was pulled into the packet + +## Validation result + +- full `Development Editor|Win64` build succeeded on `2026-05-07` +- build completed with `0` warnings and `0` errors + +## Next bounded follow-on slice + +- stay on the same gameplay-facing consumer lane and expose one thin coach handoff recommendation / continuation status surface on `HyperTwistTrainingPanelWidget` and `HyperTwistTrainingSessionActor`, so gameplay callers can render the queued-vs-follow-up-vs-recommended continuation posture without reaching into raw panel-state strings or handoff enums directly