diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp index 5275dc9..1806c2d 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp @@ -323,6 +323,35 @@ namespace HyperTwistTrainingCoachLibraryInternal } } + FString DescribeCoachQueuePostureKind( + const EHyperTwistTrainingCoachQueuePostureKind PostureKind) + { + switch (PostureKind) + { + case EHyperTwistTrainingCoachQueuePostureKind::Continue: + return TEXT("continue"); + case EHyperTwistTrainingCoachQueuePostureKind::Defer: + return TEXT("defer"); + case EHyperTwistTrainingCoachQueuePostureKind::Promote: + return TEXT("promote"); + default: + return TEXT("none"); + } + } + + FString DescribeGuidanceLane(const EHyperTwistTrainingCoachGuidanceLane Lane) + { + switch (Lane) + { + case EHyperTwistTrainingCoachGuidanceLane::FollowUp: + return TEXT("follow-up"); + case EHyperTwistTrainingCoachGuidanceLane::Recommended: + return TEXT("recommended"); + default: + return TEXT("none"); + } + } + FString DescribePrimaryCoachActionLabel( const FHyperTwistTrainingCoachPanelState& PanelState, const EHyperTwistTrainingCoachHandoffKind ActionKind @@ -5581,18 +5610,6 @@ UHyperTwistTrainingCoachLibrary::DeriveCoachQueueRecoveryPostureSurface( 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) @@ -5738,7 +5755,9 @@ UHyperTwistTrainingCoachLibrary::DeriveCoachQueueRecoveryPostureSurface( break; } - const FString GuidanceLaneLabel = DescribeGuidanceLane(PanelState.PreferredGuidanceLane); + const FString GuidanceLaneLabel = HyperTwistTrainingCoachLibraryInternal::DescribeGuidanceLane( + PanelState.PreferredGuidanceLane + ); const FString GuidanceSourceLabel = PanelState.PreferredGuidanceSourceLabel.IsEmpty() ? TEXT("n/a") : HyperTwistTrainingCoachLibraryInternal::DescribeCoachMemorySourceLabel( @@ -5777,3 +5796,175 @@ UHyperTwistTrainingCoachLibrary::DeriveCoachQueueRecoveryPostureSurface( return Surface; } + +FHyperTwistTrainingCoachHandoffContinuationSurface +UHyperTwistTrainingCoachLibrary::DeriveCoachHandoffContinuationSurface( + const FHyperTwistTrainingCoachPanelState& PanelState, + const FHyperTwistTrainingCoachPrimaryActionSurface& PrimaryActionSurface, + const FHyperTwistTrainingCoachQueueControlSurface& QueueControlSurface, + const FHyperTwistTrainingCoachQueueRecoveryPostureSurface& QueueRecoveryPostureSurface +) +{ + FHyperTwistTrainingCoachHandoffContinuationSurface Surface; + Surface.Headline = TEXT("Coach Handoff"); + Surface.ContinuationKind = PrimaryActionSurface.ActionKind; + Surface.QueuePostureKind = QueueRecoveryPostureSurface.PostureKind; + Surface.bHasActionableContinuation = PrimaryActionSurface.bCanExecute; + Surface.ContinuationActionLabel = PrimaryActionSurface.bCanExecute + ? PrimaryActionSurface.ActionLabel + : FString(); + Surface.ContinuationAvailabilityLine = PrimaryActionSurface.AvailabilityLine; + Surface.ContinuationSourceLabel = PrimaryActionSurface.bCanExecute + ? PrimaryActionSurface.ActionSourceLabel + : FString(); + Surface.QueueRecoveryStatusLine = QueueRecoveryPostureSurface.StatusLine; + Surface.bQueueRecoveryOverridesContinuation = + PrimaryActionSurface.bCanExecute + && (QueueRecoveryPostureSurface.PostureKind + == EHyperTwistTrainingCoachQueuePostureKind::Promote + || QueueRecoveryPostureSurface.PostureKind + == EHyperTwistTrainingCoachQueuePostureKind::Defer); + + const FString ContinuationKindLabel = + HyperTwistTrainingCoachLibraryInternal::DescribeCoachHandoffKind( + PrimaryActionSurface.ActionKind + ); + const FString QueuePostureLabel = + HyperTwistTrainingCoachLibraryInternal::DescribeCoachQueuePostureKind( + QueueRecoveryPostureSurface.PostureKind + ); + const FString GuidanceLaneLabel = HyperTwistTrainingCoachLibraryInternal::DescribeGuidanceLane( + PanelState.PreferredGuidanceLane + ); + const FString ContinuationSourceDescription = Surface.ContinuationSourceLabel.IsEmpty() + ? TEXT("n/a") + : HyperTwistTrainingCoachLibraryInternal::DescribeCoachMemorySourceLabel( + Surface.ContinuationSourceLabel + ); + const FString PreferredHandoffSourceDescription = PanelState.PreferredCoachHandoffSourceLabel.IsEmpty() + ? TEXT("n/a") + : HyperTwistTrainingCoachLibraryInternal::DescribeCoachMemorySourceLabel( + PanelState.PreferredCoachHandoffSourceLabel + ); + + if (Surface.bQueueRecoveryOverridesContinuation) + { + Surface.StatusLine = + QueueRecoveryPostureSurface.PostureKind + == EHyperTwistTrainingCoachQueuePostureKind::Promote + ? FString::Printf( + TEXT("Continuation: %s coach work is ready, but queue recovery prefers deferred promotion before another run."), + *ContinuationKindLabel + ) + : FString::Printf( + TEXT("Continuation: %s coach work is ready, but queue recovery is currently holding the next step on defer control."), + *ContinuationKindLabel + ); + } + else + { + switch (PrimaryActionSurface.ActionKind) + { + case EHyperTwistTrainingCoachHandoffKind::Queue: + Surface.StatusLine = FString::Printf( + TEXT("Continuation: queued coach work is the live next handoff. Source: %s"), + *ContinuationSourceDescription + ); + break; + case EHyperTwistTrainingCoachHandoffKind::FollowUp: + Surface.StatusLine = FString::Printf( + TEXT("Continuation: follow-up coach work is the live next handoff. Source: %s"), + *ContinuationSourceDescription + ); + break; + case EHyperTwistTrainingCoachHandoffKind::Recommended: + Surface.StatusLine = FString::Printf( + TEXT("Continuation: recommended coach work is the live next handoff. Source: %s"), + *ContinuationSourceDescription + ); + break; + default: + if (QueueRecoveryPostureSurface.PostureKind + == EHyperTwistTrainingCoachQueuePostureKind::Promote) + { + Surface.StatusLine = TEXT( + "Continuation: no queued, follow-up, or recommended coach run is currently actionable; queue recovery prefers deferred promotion next." + ); + } + else if (QueueRecoveryPostureSurface.PostureKind + == EHyperTwistTrainingCoachQueuePostureKind::Defer) + { + Surface.StatusLine = TEXT( + "Continuation: no queued, follow-up, or recommended coach run is currently actionable; defer is the available queue-recovery control." + ); + } + else if (PanelState.bHasActiveRun) + { + Surface.StatusLine = TEXT( + "Continuation: finish or clear the active run before another coach handoff becomes actionable." + ); + } + else if (QueueRecoveryPostureSurface.bHasRecoveryPressure) + { + Surface.StatusLine = TEXT( + "Continuation: no queued, follow-up, or recommended coach run is currently actionable; queue recovery pressure remains active." + ); + } + else + { + Surface.StatusLine = TEXT( + "Continuation: no queued, follow-up, or recommended coach run is currently actionable." + ); + } + break; + } + } + + Surface.DetailLine = FString::Printf( + TEXT("Live continuation: %s | Primary CTA: %s | Availability: %s | Queue posture: %s | Queue recovery: %s | Preferred handoff: %s from %s | Guidance lane: %s"), + *ContinuationKindLabel, + PrimaryActionSurface.bCanExecute + && !PrimaryActionSurface.ActionLabel.IsEmpty() + ? *PrimaryActionSurface.ActionLabel + : TEXT("n/a"), + PrimaryActionSurface.AvailabilityLine.IsEmpty() + ? TEXT("n/a") + : *PrimaryActionSurface.AvailabilityLine, + *QueuePostureLabel, + QueueRecoveryPostureSurface.StatusLine.IsEmpty() + ? TEXT("n/a") + : *QueueRecoveryPostureSurface.StatusLine, + *HyperTwistTrainingCoachLibraryInternal::DescribeCoachHandoffKind( + PanelState.PreferredCoachHandoffKind + ), + *PreferredHandoffSourceDescription, + *GuidanceLaneLabel + ); + if (!PrimaryActionSurface.DetailLine.IsEmpty()) + { + Surface.DetailLine += FString::Printf( + TEXT(" | Primary detail: %s"), + *PrimaryActionSurface.DetailLine + ); + } + if (QueueRecoveryPostureSurface.PostureKind + == EHyperTwistTrainingCoachQueuePostureKind::Promote + && !QueueControlSurface.PromoteAvailabilityLine.IsEmpty()) + { + Surface.DetailLine += FString::Printf( + TEXT(" | Promote: %s"), + *QueueControlSurface.PromoteAvailabilityLine + ); + } + else if (QueueRecoveryPostureSurface.PostureKind + == EHyperTwistTrainingCoachQueuePostureKind::Defer + && !QueueControlSurface.DeferAvailabilityLine.IsEmpty()) + { + Surface.DetailLine += FString::Printf( + TEXT(" | Defer: %s"), + *QueueControlSurface.DeferAvailabilityLine + ); + } + + return Surface; +} diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp index 3e6d95f..53c7780 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp @@ -467,6 +467,29 @@ UHyperTwistTrainingPanelWidget::GetDisplayedCoachQueueRecoveryPostureSurface() c ); } +FHyperTwistTrainingCoachHandoffContinuationSurface +UHyperTwistTrainingPanelWidget::GetDisplayedCoachHandoffContinuationSurface() const +{ + const FHyperTwistTrainingCoachPrimaryActionSurface PrimaryActionSurface = + GetDisplayedPrimaryCoachActionSurface(); + const FHyperTwistTrainingCoachQueueControlSurface QueueControlSurface = + GetDisplayedCoachQueueControlSurface(); + const FHyperTwistTrainingCoachQueueRecoveryPostureSurface QueueRecoveryPostureSurface = + UHyperTwistTrainingCoachLibrary::DeriveCoachQueueRecoveryPostureSurface( + CachedCoachSchedulePolicySummary, + CachedCoachQueueExecutionSummary, + CachedCoachPanelState, + PrimaryActionSurface, + QueueControlSurface + ); + return UHyperTwistTrainingCoachLibrary::DeriveCoachHandoffContinuationSurface( + CachedCoachPanelState, + PrimaryActionSurface, + QueueControlSurface, + QueueRecoveryPostureSurface + ); +} + 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 f535c08..57e72ea 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp @@ -448,6 +448,29 @@ AHyperTwistTrainingSessionActor::GetDisplayedCoachQueueRecoveryPostureSurface() ); } +FHyperTwistTrainingCoachHandoffContinuationSurface +AHyperTwistTrainingSessionActor::GetDisplayedCoachHandoffContinuationSurface() const +{ + const FHyperTwistTrainingCoachPrimaryActionSurface PrimaryActionSurface = + GetDisplayedPrimaryCoachActionSurface(); + const FHyperTwistTrainingCoachQueueControlSurface QueueControlSurface = + GetDisplayedCoachQueueControlSurface(); + const FHyperTwistTrainingCoachQueueRecoveryPostureSurface QueueRecoveryPostureSurface = + UHyperTwistTrainingCoachLibrary::DeriveCoachQueueRecoveryPostureSurface( + CachedCoachSchedulePolicySummary, + CachedCoachQueueExecutionSummary, + CachedCoachPanelState, + PrimaryActionSurface, + QueueControlSurface + ); + return UHyperTwistTrainingCoachLibrary::DeriveCoachHandoffContinuationSurface( + CachedCoachPanelState, + PrimaryActionSurface, + QueueControlSurface, + QueueRecoveryPostureSurface + ); +} + 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 6c9097f..e72763c 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h @@ -610,4 +610,13 @@ public: const FHyperTwistTrainingCoachPrimaryActionSurface& PrimaryActionSurface, const FHyperTwistTrainingCoachQueueControlSurface& QueueControlSurface ); + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + static FHyperTwistTrainingCoachHandoffContinuationSurface + DeriveCoachHandoffContinuationSurface( + const FHyperTwistTrainingCoachPanelState& PanelState, + const FHyperTwistTrainingCoachPrimaryActionSurface& PrimaryActionSurface, + const FHyperTwistTrainingCoachQueueControlSurface& QueueControlSurface, + const FHyperTwistTrainingCoachQueueRecoveryPostureSurface& QueueRecoveryPostureSurface + ); }; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h index 2b8194f..3326a37 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h @@ -224,6 +224,10 @@ public: FHyperTwistTrainingCoachQueueRecoveryPostureSurface GetDisplayedCoachQueueRecoveryPostureSurface() const; + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingCoachHandoffContinuationSurface + GetDisplayedCoachHandoffContinuationSurface() 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 d09b96a..e277ed6 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h @@ -205,6 +205,10 @@ public: FHyperTwistTrainingCoachQueueRecoveryPostureSurface GetDisplayedCoachQueueRecoveryPostureSurface() const; + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingCoachHandoffContinuationSurface + GetDisplayedCoachHandoffContinuationSurface() 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 c99261c..3ea3e38 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h @@ -7967,3 +7967,52 @@ struct FHyperTwistTrainingCoachQueueRecoveryPostureSurface || bHasRecoveryPressure; } }; + +USTRUCT(BlueprintType) +struct FHyperTwistTrainingCoachHandoffContinuationSurface +{ + 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 ContinuationActionLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ContinuationAvailabilityLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ContinuationSourceLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString QueueRecoveryStatusLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + EHyperTwistTrainingCoachHandoffKind ContinuationKind = + EHyperTwistTrainingCoachHandoffKind::None; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + EHyperTwistTrainingCoachQueuePostureKind QueuePostureKind = + EHyperTwistTrainingCoachQueuePostureKind::None; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasActionableContinuation = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bQueueRecoveryOverridesContinuation = false; + + bool IsStructurallyValid() const + { + return !StatusLine.IsEmpty() + || !DetailLine.IsEmpty() + || bHasActionableContinuation + || !QueueRecoveryStatusLine.IsEmpty(); + } +}; diff --git a/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md b/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md index c546ee6..4366e82 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 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 latest landed bounded `Phase 4` packet stays on that same gameplay-facing consumer lane and adds one thin coach handoff recommendation / continuation status surface over the already-landed primary CTA, secondary queue-control, and queue-recovery posture 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 next bounded packet is to expose one thin coach focus/provenance summary surface on those same two consumer surfaces so gameplay/Blueprint callers can render the active coach target and upstream recommendation provenance without reading raw focus ids or source-label strings 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_HANDOFF_CONTINUATION_SURFACE_PACKET_2026-05-07.md b/docs/arch/HYPERTWIST_PHASE4_GAMEPLAY_HANDOFF_CONTINUATION_SURFACE_PACKET_2026-05-07.md new file mode 100644 index 0000000..89b60de --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE4_GAMEPLAY_HANDOFF_CONTINUATION_SURFACE_PACKET_2026-05-07.md @@ -0,0 +1,115 @@ +# HyperTwist Phase 4 gameplay handoff continuation 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 handoff recommendation / continuation status surface over the already-landed primary coach CTA, queue-control, and queue-recovery posture surfaces. + +The open tasks were: + +- stop forcing gameplay / Blueprint callers to read raw panel-state handoff enums and continuation strings just to explain whether queued, follow-up, or recommended coach work is live next +- expose one thin first-party continuation surface that summarizes the live coach handoff posture, the actionable continuation branch, and the queue-recovery override posture when recovery control takes precedence +- keep that continuation surface derived from already-cached gameplay consumer state instead of widening into the dashboard’s deeper retained recommendation-history analytics + +It is not: + +- a new dashboard handoff-weighting packet +- a queue recovery policy change +- a backend continuity or template-analytics packet +- a broader gameplay HUD composition pass + +## Scope + +Bounded lane: + +- add a first-party handoff continuation surface in shared training types +- add a coach-library helper that derives the surface from panel state, the displayed primary CTA surface, the displayed queue-control surface, and the displayed queue-recovery posture surface +- expose that derived continuation surface on `HyperTwistTrainingPanelWidget` and `HyperTwistTrainingSessionActor` +- keep the continuation derivation presentation-only and rooted in already-cached gameplay consumer state + +Out of scope: + +- importing the dashboard’s deeper retained handoff analytics into gameplay consumers +- changing coach handoff branch order +- changing queue recovery selection policy +- widening into higher-level gameplay UI composition in the same packet + +## Why this was the right next packet + +Before this slice: + +- gameplay-facing panel/session consumers already exposed a truthful primary CTA surface +- those surfaces already exposed truthful secondary queue-control and queue-recovery posture surfaces + +But one thin consumer seam was still open: + +- callers could bind actions and explain queue recovery +- but they still had to inspect raw handoff enums and panel-state continuation strings to explain whether queued, follow-up, or recommended coach work was actually live next + +That meant: + +- action binding was thin +- queue recovery posture was thin +- but continuation explanation still leaked raw panel-state state into callers + +So the next bounded move was: + +- keep the already-landed control and recovery surfaces unchanged +- and expose one narrow continuation status surface above them + +## What landed + +Primary code changes: + +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h` + - added `FHyperTwistTrainingCoachHandoffContinuationSurface` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp` + - added `DeriveCoachHandoffContinuationSurface(...)` + - continuation posture is derived from existing panel-state, primary CTA, queue-control, and queue-recovery posture inputs +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp` + - added `GetDisplayedCoachHandoffContinuationSurface()` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp` + - added `GetDisplayedCoachHandoffContinuationSurface()` + +## Product effect + +The gameplay-facing coach consumer layer is now more complete: + +- gameplay / Blueprint callers can read one pure surface to explain whether queued, follow-up, or recommended coach work is the live next continuation branch +- callers no longer need to inspect raw panel-state handoff enums or handoff status strings just to narrate continuation posture +- the surface also carries the queue-recovery override posture when deferred promotion or defer control takes precedence over immediately starting another run +- the continuation surface stays aligned with the already-landed primary CTA, queue-control, and queue-recovery posture surfaces without importing dashboard-only retained recommendation analytics + +## Acceptance criteria + +- shared training types expose a narrow handoff continuation surface +- coach-library derivation consumes the already-cached gameplay consumer state rather than adding new runtime fetches +- panel-widget callers can fetch the displayed handoff continuation surface directly +- session-actor callers can fetch the displayed handoff continuation surface directly +- full product build succeeds + +## Validation checklist + +1. build `UnrealHyperTwist.sln` / `UnrealHyperTwistEditor` +2. confirm the new continuation surface compiles in shared types and coach library +3. confirm panel-widget getters compile and return the derived continuation surface +4. confirm session-actor getters compile and return the derived continuation surface +5. confirm no backend continuity or dashboard-only handoff analytics 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 focus/provenance summary surface on `HyperTwistTrainingPanelWidget` and `HyperTwistTrainingSessionActor`, so gameplay callers can render the active coach target and upstream recommendation provenance without reading raw focus ids or source-label strings directly