From 023536695edb3e00343fb02cabfde51dd8bf39e4 Mon Sep 17 00:00:00 2001 From: axiomlogicnexus Date: Thu, 7 May 2026 22:19:12 +0200 Subject: [PATCH] Widen gameplay coach surface orchestration --- .../HyperTwistTrainingPanelWidget.cpp | 173 ++++++++++++++++++ .../HyperTwistTrainingSessionActor.cpp | 171 ++++++++++++++++- .../HyperTwistTrainingPanelWidget.h | 26 +++ .../HyperTwistTrainingSessionActor.h | 26 +++ ...RTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md | 3 +- ...SURFACE_ORCHESTRATION_PACKET_2026-05-07.md | 112 ++++++++++++ 6 files changed, 505 insertions(+), 6 deletions(-) create mode 100644 docs/arch/HYPERTWIST_PHASE4_GAMEPLAY_COACH_SURFACE_ORCHESTRATION_PACKET_2026-05-07.md diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp index d9b85df..e221840 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp @@ -2,6 +2,89 @@ #include "HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.h" +namespace HyperTwistTrainingPanelWidgetInternal +{ + FString ResolveCoachHandoffStartActionSourceLabel( + const FHyperTwistTrainingCoachPanelState& PanelState, + const EHyperTwistTrainingCoachHandoffKind HandoffKind, + const FString& FallbackSourceLabel + ) + { + if (HandoffKind == EHyperTwistTrainingCoachHandoffKind::Queue + && !PanelState.PreferredQueueRecoveryActionSourceLabel.IsEmpty()) + { + return PanelState.PreferredQueueRecoveryActionSourceLabel; + } + + if (PanelState.PreferredCoachHandoffKind == HandoffKind + && !PanelState.PreferredCoachHandoffSourceLabel.IsEmpty()) + { + return PanelState.PreferredCoachHandoffSourceLabel; + } + + if ((HandoffKind == EHyperTwistTrainingCoachHandoffKind::FollowUp + || HandoffKind == EHyperTwistTrainingCoachHandoffKind::Recommended) + && !PanelState.PreferredGuidanceSourceLabel.IsEmpty()) + { + return PanelState.PreferredGuidanceSourceLabel; + } + + if (!PanelState.PreferredCoachHandoffSourceLabel.IsEmpty()) + { + return PanelState.PreferredCoachHandoffSourceLabel; + } + + if (HandoffKind == EHyperTwistTrainingCoachHandoffKind::Queue + && !PanelState.NextQueueSourceLabel.IsEmpty()) + { + return PanelState.NextQueueSourceLabel; + } + + if (!PanelState.PreferredLaunchBudgetSourceLabel.IsEmpty()) + { + return PanelState.PreferredLaunchBudgetSourceLabel; + } + + return FallbackSourceLabel; + } + + FString ResolvePrimaryCoachActionStartActionSourceLabel( + const FHyperTwistTrainingCoachPanelState& PanelState, + const FString& FallbackSourceLabel + ) + { + if (PanelState.PreferredQueueRecoveryAction != EHyperTwistTrainingQueueRecoveryAction::None) + { + return ResolveCoachHandoffStartActionSourceLabel( + PanelState, + EHyperTwistTrainingCoachHandoffKind::Queue, + FallbackSourceLabel + ); + } + + if (PanelState.PreferredCoachHandoffKind != EHyperTwistTrainingCoachHandoffKind::None) + { + return ResolveCoachHandoffStartActionSourceLabel( + PanelState, + PanelState.PreferredCoachHandoffKind, + FallbackSourceLabel + ); + } + + if (!PanelState.PreferredGuidanceSourceLabel.IsEmpty()) + { + return PanelState.PreferredGuidanceSourceLabel; + } + + if (!PanelState.PreferredLaunchBudgetSourceLabel.IsEmpty()) + { + return PanelState.PreferredLaunchBudgetSourceLabel; + } + + return FallbackSourceLabel; + } +} + void UHyperTwistTrainingPanelWidget::NativeConstruct() { Super::NativeConstruct(); @@ -315,6 +398,34 @@ FHyperTwistTrainingDeck UHyperTwistTrainingPanelWidget::BuildActiveMethodDrillFo return CachedMethodDrillFollowUpDeck; } +FString UHyperTwistTrainingPanelWidget::ResolveCoachHandoffStartActionSourceLabel( + const EHyperTwistTrainingCoachHandoffKind HandoffKind +) const +{ + return HyperTwistTrainingPanelWidgetInternal::ResolveCoachHandoffStartActionSourceLabel( + CachedCoachPanelState, + HandoffKind, + FString::Printf( + TEXT("coach-panel-%s"), + HandoffKind == EHyperTwistTrainingCoachHandoffKind::Queue + ? TEXT("queue") + : (HandoffKind == EHyperTwistTrainingCoachHandoffKind::FollowUp + ? TEXT("follow-up") + : (HandoffKind == EHyperTwistTrainingCoachHandoffKind::Recommended + ? TEXT("recommended") + : TEXT("handoff"))) + ) + ); +} + +FString UHyperTwistTrainingPanelWidget::ResolvePrimaryCoachActionStartActionSourceLabel() const +{ + return HyperTwistTrainingPanelWidgetInternal::ResolvePrimaryCoachActionStartActionSourceLabel( + CachedCoachPanelState, + TEXT("coach-panel-primary-action") + ); +} + FHyperTwistTrainingRunState UHyperTwistTrainingPanelWidget::StartCoachRecommendedRun( const int32 MaxCases, const FString& StartActionSourceLabel @@ -395,6 +506,55 @@ FHyperTwistTrainingRunState UHyperTwistTrainingPanelWidget::StartCoachQueueEntry return CachedRunState; } +FHyperTwistTrainingRunState UHyperTwistTrainingPanelWidget::StartDisplayedCoachRecommendedRun( + const int32 MaxCases +) +{ + return StartCoachRecommendedRun( + MaxCases, + ResolveCoachHandoffStartActionSourceLabel(EHyperTwistTrainingCoachHandoffKind::Recommended) + ); +} + +FHyperTwistTrainingRunState UHyperTwistTrainingPanelWidget::StartDisplayedCoachFollowUpRun( + const int32 MaxCases +) +{ + return StartCoachFollowUpRun( + MaxCases, + ResolveCoachHandoffStartActionSourceLabel(EHyperTwistTrainingCoachHandoffKind::FollowUp) + ); +} + +FHyperTwistTrainingRunState UHyperTwistTrainingPanelWidget::StartDisplayedPrimaryCoachAction( + const int32 MaxCases +) +{ + return StartPrimaryCoachAction(MaxCases, ResolvePrimaryCoachActionStartActionSourceLabel()); +} + +FHyperTwistTrainingRunState UHyperTwistTrainingPanelWidget::StartDisplayedNextQueuedCoachRun( + const int32 MaxCases +) +{ + return StartNextQueuedCoachRun( + MaxCases, + ResolveCoachHandoffStartActionSourceLabel(EHyperTwistTrainingCoachHandoffKind::Queue) + ); +} + +FHyperTwistTrainingRunState UHyperTwistTrainingPanelWidget::StartDisplayedCoachQueueEntry( + const int32 MaxCases +) +{ + if (CachedCoachPanelState.ActiveQueueEntryId.IsEmpty()) + { + return FHyperTwistTrainingRunState(); + } + + return StartCoachQueueEntry(CachedCoachPanelState.ActiveQueueEntryId, MaxCases); +} + bool UHyperTwistTrainingPanelWidget::DeferCoachQueueEntry( const FString& EntryId, const FString& DeferredUntilUtc, @@ -446,6 +606,19 @@ bool UHyperTwistTrainingPanelWidget::PromoteCoachQueueEntry( return bSucceeded; } +bool UHyperTwistTrainingPanelWidget::PromoteDisplayedCoachQueueEntry() +{ + if (CachedCoachPanelState.ActiveQueueEntryId.IsEmpty()) + { + return false; + } + + return PromoteCoachQueueEntry( + CachedCoachPanelState.ActiveQueueEntryId, + ResolveCoachHandoffStartActionSourceLabel(EHyperTwistTrainingCoachHandoffKind::Queue) + ); +} + FHyperTwistTrainingRunState UHyperTwistTrainingPanelWidget::StartActiveMethodDrillFollowUpRun( const FString& SessionId ) diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp index 7093cf3..3d7e938 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp @@ -2,6 +2,89 @@ #include "HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.h" +namespace HyperTwistTrainingSessionActorInternal +{ + FString ResolveCoachHandoffStartActionSourceLabel( + const FHyperTwistTrainingCoachPanelState& PanelState, + const EHyperTwistTrainingCoachHandoffKind HandoffKind, + const FString& FallbackSourceLabel + ) + { + if (HandoffKind == EHyperTwistTrainingCoachHandoffKind::Queue + && !PanelState.PreferredQueueRecoveryActionSourceLabel.IsEmpty()) + { + return PanelState.PreferredQueueRecoveryActionSourceLabel; + } + + if (PanelState.PreferredCoachHandoffKind == HandoffKind + && !PanelState.PreferredCoachHandoffSourceLabel.IsEmpty()) + { + return PanelState.PreferredCoachHandoffSourceLabel; + } + + if ((HandoffKind == EHyperTwistTrainingCoachHandoffKind::FollowUp + || HandoffKind == EHyperTwistTrainingCoachHandoffKind::Recommended) + && !PanelState.PreferredGuidanceSourceLabel.IsEmpty()) + { + return PanelState.PreferredGuidanceSourceLabel; + } + + if (!PanelState.PreferredCoachHandoffSourceLabel.IsEmpty()) + { + return PanelState.PreferredCoachHandoffSourceLabel; + } + + if (HandoffKind == EHyperTwistTrainingCoachHandoffKind::Queue + && !PanelState.NextQueueSourceLabel.IsEmpty()) + { + return PanelState.NextQueueSourceLabel; + } + + if (!PanelState.PreferredLaunchBudgetSourceLabel.IsEmpty()) + { + return PanelState.PreferredLaunchBudgetSourceLabel; + } + + return FallbackSourceLabel; + } + + FString ResolvePrimaryCoachActionStartActionSourceLabel( + const FHyperTwistTrainingCoachPanelState& PanelState, + const FString& FallbackSourceLabel + ) + { + if (PanelState.PreferredQueueRecoveryAction != EHyperTwistTrainingQueueRecoveryAction::None) + { + return ResolveCoachHandoffStartActionSourceLabel( + PanelState, + EHyperTwistTrainingCoachHandoffKind::Queue, + FallbackSourceLabel + ); + } + + if (PanelState.PreferredCoachHandoffKind != EHyperTwistTrainingCoachHandoffKind::None) + { + return ResolveCoachHandoffStartActionSourceLabel( + PanelState, + PanelState.PreferredCoachHandoffKind, + FallbackSourceLabel + ); + } + + if (!PanelState.PreferredGuidanceSourceLabel.IsEmpty()) + { + return PanelState.PreferredGuidanceSourceLabel; + } + + if (!PanelState.PreferredLaunchBudgetSourceLabel.IsEmpty()) + { + return PanelState.PreferredLaunchBudgetSourceLabel; + } + + return FallbackSourceLabel; + } +} + AHyperTwistTrainingSessionActor::AHyperTwistTrainingSessionActor() { PrimaryActorTick.bCanEverTick = false; @@ -296,6 +379,34 @@ FHyperTwistTrainingRunState AHyperTwistTrainingSessionActor::StartRunFromTemplat return CachedRunState; } +FString AHyperTwistTrainingSessionActor::ResolveCoachHandoffStartActionSourceLabel( + const EHyperTwistTrainingCoachHandoffKind HandoffKind +) const +{ + return HyperTwistTrainingSessionActorInternal::ResolveCoachHandoffStartActionSourceLabel( + CachedCoachPanelState, + HandoffKind, + FString::Printf( + TEXT("coach-session-actor-%s"), + HandoffKind == EHyperTwistTrainingCoachHandoffKind::Queue + ? TEXT("queue") + : (HandoffKind == EHyperTwistTrainingCoachHandoffKind::FollowUp + ? TEXT("follow-up") + : (HandoffKind == EHyperTwistTrainingCoachHandoffKind::Recommended + ? TEXT("recommended") + : TEXT("handoff"))) + ) + ); +} + +FString AHyperTwistTrainingSessionActor::ResolvePrimaryCoachActionStartActionSourceLabel() const +{ + return HyperTwistTrainingSessionActorInternal::ResolvePrimaryCoachActionStartActionSourceLabel( + CachedCoachPanelState, + TEXT("coach-session-actor-primary-action") + ); +} + FHyperTwistTrainingRunState AHyperTwistTrainingSessionActor::StartCoachRecommendedTrainingRun( const int32 MaxCases ) @@ -304,7 +415,7 @@ FHyperTwistTrainingRunState AHyperTwistTrainingSessionActor::StartCoachRecommend this, AutoStartSessionId + TEXT("_coach"), MaxCases > 0 ? MaxCases : DefaultCoachMaxCases, - FString() + ResolveCoachHandoffStartActionSourceLabel(EHyperTwistTrainingCoachHandoffKind::Recommended) ); RefreshCachedTrainingState(); return CachedRunState; @@ -318,7 +429,7 @@ FHyperTwistTrainingRunState AHyperTwistTrainingSessionActor::StartCoachFollowUpT this, AutoStartSessionId + TEXT("_follow_up"), MaxCases > 0 ? MaxCases : DefaultCoachMaxCases, - FString() + ResolveCoachHandoffStartActionSourceLabel(EHyperTwistTrainingCoachHandoffKind::FollowUp) ); RefreshCachedTrainingState(); return CachedRunState; @@ -332,7 +443,7 @@ FHyperTwistTrainingRunState AHyperTwistTrainingSessionActor::StartPrimaryCoachTr this, AutoStartSessionId + TEXT("_primary"), MaxCases > 0 ? MaxCases : DefaultCoachMaxCases, - FString() + ResolvePrimaryCoachActionStartActionSourceLabel() ); RefreshCachedTrainingState(); return CachedRunState; @@ -346,7 +457,7 @@ FHyperTwistTrainingRunState AHyperTwistTrainingSessionActor::StartNextQueuedCoac this, AutoStartSessionId + TEXT("_queue"), MaxCases > 0 ? MaxCases : DefaultCoachMaxCases, - FString() + ResolveCoachHandoffStartActionSourceLabel(EHyperTwistTrainingCoachHandoffKind::Queue) ); RefreshCachedTrainingState(); return CachedRunState; @@ -367,6 +478,46 @@ FHyperTwistTrainingRunState AHyperTwistTrainingSessionActor::StartCoachQueueEntr return CachedRunState; } +FHyperTwistTrainingRunState AHyperTwistTrainingSessionActor::StartDisplayedCoachRecommendedTrainingRun( + const int32 MaxCases +) +{ + return StartCoachRecommendedTrainingRun(MaxCases); +} + +FHyperTwistTrainingRunState AHyperTwistTrainingSessionActor::StartDisplayedCoachFollowUpTrainingRun( + const int32 MaxCases +) +{ + return StartCoachFollowUpTrainingRun(MaxCases); +} + +FHyperTwistTrainingRunState AHyperTwistTrainingSessionActor::StartDisplayedPrimaryCoachTrainingRun( + const int32 MaxCases +) +{ + return StartPrimaryCoachTrainingRun(MaxCases); +} + +FHyperTwistTrainingRunState AHyperTwistTrainingSessionActor::StartDisplayedNextQueuedCoachTrainingRun( + const int32 MaxCases +) +{ + return StartNextQueuedCoachTrainingRun(MaxCases); +} + +FHyperTwistTrainingRunState AHyperTwistTrainingSessionActor::StartDisplayedCoachQueueEntryTrainingRun( + const int32 MaxCases +) +{ + if (CachedCoachPanelState.ActiveQueueEntryId.IsEmpty()) + { + return FHyperTwistTrainingRunState(); + } + + return StartCoachQueueEntryTrainingRun(CachedCoachPanelState.ActiveQueueEntryId, MaxCases); +} + bool AHyperTwistTrainingSessionActor::DeferCoachQueueEntry( const FString& EntryId, const FString& DeferredUntilUtc, @@ -409,12 +560,22 @@ bool AHyperTwistTrainingSessionActor::PromoteCoachQueueEntry(const FString& Entr const bool bSucceeded = UHyperTwistTrainingRuntimeLibrary::PromoteCoachSessionQueueEntry( this, EntryId, - FString() + ResolveCoachHandoffStartActionSourceLabel(EHyperTwistTrainingCoachHandoffKind::Queue) ); RefreshCachedTrainingState(); return bSucceeded; } +bool AHyperTwistTrainingSessionActor::PromoteDisplayedCoachQueueEntry() +{ + if (CachedCoachPanelState.ActiveQueueEntryId.IsEmpty()) + { + return false; + } + + return PromoteCoachQueueEntry(CachedCoachPanelState.ActiveQueueEntryId); +} + FHyperTwistTrainingRepositoryRoundTripVerification AHyperTwistTrainingSessionActor::VerifyRepositoryRoundTrip() { diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h index c91c7c2..3668298 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h @@ -206,6 +206,14 @@ public: UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Drill") FHyperTwistTrainingDeck BuildActiveMethodDrillFollowUpDeck(); + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + FString ResolveCoachHandoffStartActionSourceLabel( + EHyperTwistTrainingCoachHandoffKind HandoffKind + ) const; + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + FString ResolvePrimaryCoachActionStartActionSourceLabel() const; + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach") FHyperTwistTrainingRunState StartCoachRecommendedRun( int32 MaxCases, @@ -236,6 +244,21 @@ public: int32 MaxCases ); + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingRunState StartDisplayedCoachRecommendedRun(int32 MaxCases); + + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingRunState StartDisplayedCoachFollowUpRun(int32 MaxCases); + + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingRunState StartDisplayedPrimaryCoachAction(int32 MaxCases); + + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingRunState StartDisplayedNextQueuedCoachRun(int32 MaxCases); + + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingRunState StartDisplayedCoachQueueEntry(int32 MaxCases); + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach") bool DeferCoachQueueEntry( const FString& EntryId, @@ -257,6 +280,9 @@ public: const FString& StartActionSourceLabel ); + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach") + bool PromoteDisplayedCoachQueueEntry(); + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Drill") FHyperTwistTrainingRunState StartActiveMethodDrillFollowUpRun(const FString& SessionId); diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h index 59b4ab1..4925c35 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h @@ -187,6 +187,14 @@ public: const FString& SessionId ); + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + FString ResolveCoachHandoffStartActionSourceLabel( + EHyperTwistTrainingCoachHandoffKind HandoffKind + ) const; + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + FString ResolvePrimaryCoachActionStartActionSourceLabel() const; + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach") FHyperTwistTrainingRunState StartCoachRecommendedTrainingRun(int32 MaxCases); @@ -205,6 +213,21 @@ public: int32 MaxCases ); + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingRunState StartDisplayedCoachRecommendedTrainingRun(int32 MaxCases); + + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingRunState StartDisplayedCoachFollowUpTrainingRun(int32 MaxCases); + + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingRunState StartDisplayedPrimaryCoachTrainingRun(int32 MaxCases); + + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingRunState StartDisplayedNextQueuedCoachTrainingRun(int32 MaxCases); + + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingRunState StartDisplayedCoachQueueEntryTrainingRun(int32 MaxCases); + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach") bool DeferCoachQueueEntry( const FString& EntryId, @@ -223,6 +246,9 @@ public: UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach") bool PromoteCoachQueueEntry(const FString& EntryId); + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach") + bool PromoteDisplayedCoachQueueEntry(); + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training") FHyperTwistTrainingRepositoryRoundTripVerification VerifyRepositoryRoundTrip(); }; diff --git a/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md b/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md index b3ce807..8a814bf 100644 --- a/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md +++ b/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md @@ -68,7 +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 current next bounded packet is to widen into the first thin gameplay-facing consumers of coach scheduling and recommendations, starting from the already-exposed panel-widget and session-actor coach state/action surfaces without reopening backend continuity or analytics policy work + - 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 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_COACH_SURFACE_ORCHESTRATION_PACKET_2026-05-07.md b/docs/arch/HYPERTWIST_PHASE4_GAMEPLAY_COACH_SURFACE_ORCHESTRATION_PACKET_2026-05-07.md new file mode 100644 index 0000000..5ff5b02 --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE4_GAMEPLAY_COACH_SURFACE_ORCHESTRATION_PACKET_2026-05-07.md @@ -0,0 +1,112 @@ +# HyperTwist Phase 4 gameplay coach surface orchestration packet + +Created on `2026-05-07` + +Status: + +- first-party HyperTwist packet +- bounded Phase `4` gameplay-facing consumer slice + +## Purpose + +This packet starts the first thin gameplay-facing consumer widening above the now-stable coach scheduling and recommendation backend by making the panel-widget and session-actor surfaces executable without extra caller-side provenance glue. + +The open tasks were: + +- stop forcing gameplay / Blueprint callers to invent their own coach start-action source labels before they can launch the currently displayed recommended, follow-up, queue, or primary coach action +- stop dropping coach provenance entirely on the session-actor path when those actions are launched or deferred work is promoted +- expose one thin displayed-action helper layer so callers can act on the retained panel state directly instead of manually wiring active queue-entry ids every time + +It is not: + +- a new backend continuity packet +- a new analytics-selection packet +- a dashboard rewrite +- a broader gameplay UI integration pass + +## Scope + +Bounded lane: + +- add panel-widget helpers that resolve default coach handoff / primary-action source labels from the cached coach panel state +- add panel-widget helpers that launch the currently displayed recommended, follow-up, primary, next-queued, and active queue-entry actions without extra caller-side routing +- add panel-widget helper coverage for promoting the currently displayed queue entry +- update the session-actor launch and promotion path to stop recording empty source labels +- mirror the same displayed-action helper layer on the session actor for gameplay-facing Blueprint consumers + +Out of scope: + +- changing coach recommendation policy +- changing queue execution policy +- changing retained dashboard continuity behavior +- widening into a richer gameplay HUD or CTA presentation pass in the same packet + +## Why this was the right next packet + +Before this slice: + +- the recognition-assisted coach-to-review closure lane was already documented as complete +- the repo already exposed rich cached coach state on `HyperTwistTrainingPanelWidget` and `HyperTwistTrainingSessionActor` +- those surfaces already had the raw launch verbs for recommended, follow-up, queue, and queue-entry work + +But the first gameplay-facing consumer seam was still obvious: + +- the widget surface still required callers to supply coach start-action source labels themselves +- the actor surface launched and promoted coach work with empty source labels, which threw away provenance and downstream analytics context +- callers still had to manually thread the active queue-entry id out of cached panel state for common displayed-action cases + +That meant: + +- the backend was ready +- but the first gameplay-facing consumer layer still imposed avoidable orchestration work and provenance loss on the caller + +So the next bounded move was: + +- keep the backend untouched +- and make the existing panel/actor surfaces honest thin consumers of the current coach panel state + +## What landed + +Primary code changes: + +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp` + - added coach handoff and primary-action source-label resolvers driven by `CachedCoachPanelState` + - added displayed-action helpers for recommended, follow-up, primary, next-queued, and active queue-entry coach launches + - added a displayed queue-entry promotion helper +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp` + - added the same source-label resolvers and displayed-action helpers for gameplay-facing actor callers + - updated existing actor launch and queue-promotion methods so they no longer pass empty start-action source labels into the runtime path + +## Product effect + +The first gameplay-facing coach consumers are now thinner and more truthful: + +- gameplay / Blueprint callers can execute the displayed coach action surface without manufacturing source labels first +- session-actor launches and queue promotions now preserve coach provenance instead of silently dropping it +- callers no longer need to manually copy `ActiveQueueEntryId` out of cached panel state for the common displayed queue-entry path + +## Acceptance criteria + +- panel-widget callers can resolve default coach source labels from the cached coach panel state +- panel-widget callers can launch the displayed recommended, follow-up, primary, next-queued, and active queue-entry actions through dedicated helpers +- session-actor launches and queue promotions no longer record empty source labels +- session-actor callers get the same displayed-action helper layer +- full product build succeeds + +## Validation checklist + +1. build `UnrealHyperTwist.sln` / `UnrealHyperTwistEditor` +2. confirm the panel widget compiles with the new displayed-action and source-label helper surface +3. confirm the session actor compiles with the new displayed-action helper surface +4. confirm actor-side coach launches and queue promotions now route non-empty source labels + +## Validation result + +- full `Development Editor|Win64` build succeeded on `2026-05-07` +- build completed clean with `0` warnings and `0` errors + +## Next bounded follow-on slice + +- stay on the same gameplay-facing consumer lane and expose one thin primary coach CTA descriptor / availability surface on the panel-widget and session-actor layer so gameplay callers can bind a single truthful coach action without reconstructing queue-vs-follow-up-vs-recommended state from raw booleans