diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp index 0e78fc4..7d78c55 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp @@ -2768,19 +2768,7 @@ void UHyperTwistCoachDashboardWidget::RefreshCoachDashboardView() FHyperTwistTrainingRunState UHyperTwistCoachDashboardWidget::ExecutePrimaryCoachAction() { - FHyperTwistTrainingRunState RunState; - if (CachedCoachPanelState.bCanStartQueuedRun) - { - RunState = StartNextQueuedCoachRun(DefaultCoachMaxCases, FString()); - } - else if (CachedCoachPanelState.bCanStartCoachFollowUpRun && CachedCoachPanelState.bHasFollowUpGuidance) - { - RunState = StartCoachFollowUpRun(DefaultCoachMaxCases, FString()); - } - else if (CachedCoachPanelState.bCanStartCoachRecommendedRun) - { - RunState = StartCoachRecommendedRun(DefaultCoachMaxCases, FString()); - } + const FHyperTwistTrainingRunState RunState = StartPrimaryCoachAction(DefaultCoachMaxCases, FString()); SyncRetainedRunRecap(); SyncSelectedQueueEntry(); diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp index a646d7d..e3e3ab7 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp @@ -252,6 +252,22 @@ FHyperTwistTrainingRunState UHyperTwistTrainingPanelWidget::StartCoachFollowUpRu return CachedRunState; } +FHyperTwistTrainingRunState UHyperTwistTrainingPanelWidget::StartPrimaryCoachAction( + const int32 MaxCases, + const FString& StartActionSourceLabel +) +{ + LastStepResult = FHyperTwistTrainingRunStepResult(); + CachedRunState = UHyperTwistTrainingRuntimeLibrary::StartPrimaryCoachAction( + this, + DefaultSessionId + TEXT("_primary"), + MaxCases > 0 ? MaxCases : DefaultCoachMaxCases, + StartActionSourceLabel + ); + RefreshTrainingState(); + return CachedRunState; +} + FHyperTwistTrainingRunState UHyperTwistTrainingPanelWidget::StartNextQueuedCoachRun( const int32 MaxCases, const FString& StartActionSourceLabel diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.cpp index da6ab85..c88d8cb 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.cpp @@ -1328,6 +1328,21 @@ FHyperTwistTrainingRunState UHyperTwistTrainingRuntimeLibrary::StartCoachFollowU return FHyperTwistTrainingRunState(); } +FHyperTwistTrainingRunState UHyperTwistTrainingRuntimeLibrary::StartPrimaryCoachAction( + UObject* WorldContextObject, + const FString& SessionId, + int32 MaxCases, + const FString& StartActionSourceLabel +) +{ + if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject)) + { + return TrainingSubsystem->StartPrimaryCoachAction(SessionId, MaxCases, StartActionSourceLabel); + } + + return FHyperTwistTrainingRunState(); +} + FHyperTwistTrainingRunState UHyperTwistTrainingRuntimeLibrary::StartNextQueuedCoachRun( UObject* WorldContextObject, const FString& SessionId, diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp index 0fd98a2..62b553b 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp @@ -230,6 +230,20 @@ FHyperTwistTrainingRunState AHyperTwistTrainingSessionActor::StartCoachFollowUpT return CachedRunState; } +FHyperTwistTrainingRunState AHyperTwistTrainingSessionActor::StartPrimaryCoachTrainingRun( + const int32 MaxCases +) +{ + CachedRunState = UHyperTwistTrainingRuntimeLibrary::StartPrimaryCoachAction( + this, + AutoStartSessionId + TEXT("_primary"), + MaxCases > 0 ? MaxCases : DefaultCoachMaxCases, + FString() + ); + RefreshCachedTrainingState(); + return CachedRunState; +} + FHyperTwistTrainingRunState AHyperTwistTrainingSessionActor::StartNextQueuedCoachTrainingRun( const int32 MaxCases ) diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp index 5f84650..c1fe54a 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp @@ -3193,6 +3193,29 @@ FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartCoachFollowUpRun( return RunState; } +FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartPrimaryCoachAction( + const FString& SessionId, + int32 MaxCases, + const FString& StartActionSourceLabel +) +{ + const FHyperTwistTrainingCoachPanelState PanelState = GetActiveCoachPanelState(); + if (PanelState.bCanStartQueuedRun) + { + return StartNextQueuedCoachRun(SessionId, MaxCases, StartActionSourceLabel); + } + if (PanelState.bCanStartCoachFollowUpRun && PanelState.bHasFollowUpGuidance) + { + return StartCoachFollowUpRun(SessionId, MaxCases, StartActionSourceLabel); + } + if (PanelState.bCanStartCoachRecommendedRun) + { + return StartCoachRecommendedRun(SessionId, MaxCases, StartActionSourceLabel); + } + + return FHyperTwistTrainingRunState(); +} + FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartNextQueuedCoachRun( const FString& SessionId, int32 MaxCases, diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h index 3ac983b..c0e68aa 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h @@ -181,6 +181,12 @@ public: const FString& StartActionSourceLabel ); + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingRunState StartPrimaryCoachAction( + int32 MaxCases, + const FString& StartActionSourceLabel + ); + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach") FHyperTwistTrainingRunState StartNextQueuedCoachRun( int32 MaxCases, diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.h index 1e21f57..2afcfa0 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.h @@ -548,6 +548,14 @@ public: const FString& StartActionSourceLabel ); + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject")) + static FHyperTwistTrainingRunState StartPrimaryCoachAction( + UObject* WorldContextObject, + const FString& SessionId, + int32 MaxCases, + const FString& StartActionSourceLabel + ); + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject")) static FHyperTwistTrainingRunState StartNextQueuedCoachRun( UObject* WorldContextObject, diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h index 8da5def..da9314a 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h @@ -156,6 +156,9 @@ public: UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach") FHyperTwistTrainingRunState StartCoachFollowUpTrainingRun(int32 MaxCases); + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingRunState StartPrimaryCoachTrainingRun(int32 MaxCases); + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach") FHyperTwistTrainingRunState StartNextQueuedCoachTrainingRun(int32 MaxCases); diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSubsystem.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSubsystem.h index 3517058..b84619d 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSubsystem.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSubsystem.h @@ -444,6 +444,13 @@ public: const FString& StartActionSourceLabel ); + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingRunState StartPrimaryCoachAction( + const FString& SessionId, + int32 MaxCases, + const FString& StartActionSourceLabel + ); + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach") FHyperTwistTrainingRunState StartNextQueuedCoachRun( const FString& SessionId, diff --git a/docs/arch/HYPERTWIST_PHASE4_ORCHESTRATION_ENTRY_PACKET_2026-05-06.md b/docs/arch/HYPERTWIST_PHASE4_ORCHESTRATION_ENTRY_PACKET_2026-05-06.md new file mode 100644 index 0000000..bc274b7 --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE4_ORCHESTRATION_ENTRY_PACKET_2026-05-06.md @@ -0,0 +1,105 @@ +# HyperTwist Phase 4 orchestration entry packet + +Created on `2026-05-06` + +Status: + +- first-party HyperTwist packet +- bounded post-Phase-`3` implementation slice + +## Purpose + +This packet starts Phase `4` with an orchestration-first slice over the already-closed training/coaching backend. + +The open task is: + +- expose one production-facing primary coach orchestration action above the existing low-level coach-run launch methods + +It is not: + +- a recognition-provider packet +- a generated-mode backend packet +- a dashboard redesign +- a broad runtime refactor + +## Scope + +Bounded lane: + +- create one first-party primary coach orchestration entry action in the subsystem and runtime library +- surface that action through the generic panel widget and session actor consumers +- make the dashboard use that orchestration action instead of owning the launch-choice branching itself + +Required result: + +- the repo has a stable first-class action for: + - launch next queued coach work when runnable + - otherwise launch follow-up work when available + - otherwise launch recommended work when available +- dashboard, panel widget, and session actor can all consume that same orchestration action + +Out of scope: + +- timer-phase orchestration +- provider-backed recognition integration +- new queue heuristics +- changes to generated-mode request/execution behavior + +## Current owned product truth + +These pieces are already live: + +- low-level coach launch primitives: + - recommended + - follow-up + - next queued + - selected queue entry +- consumer surfaces over those primitives in: + - runtime library + - panel widget + - session actor + - dashboard widget + +What was still missing: + +- one explicit orchestration action above those low-level primitives +- removal of dashboard-local branching as the only place where the primary coach launch order existed + +## Implementation target + +The slice should do the following: + +1. add `StartPrimaryCoachAction(...)` to the training subsystem +2. add the matching runtime-library wrapper +3. add matching thin wrappers to the panel widget and session actor +4. update the dashboard widget to call that action instead of hand-owning the queued / follow-up / recommended branching + +## Suggested file ownership + +Primary edit surfaces: + +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSubsystem.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.cpp` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp` +- `docs/arch/HYPERTWIST_PHASE4_ORCHESTRATION_ENTRY_PACKET_2026-05-06.md` + +## Acceptance criteria + +- one first-party primary coach orchestration action exists above the low-level launch primitives +- dashboard launch branching uses that action +- panel widget and session actor surface that action too +- full UE build still succeeds + +## Validation checklist + +1. build `UnrealHyperTwist.sln` +2. confirm dashboard launch branching now routes through the orchestration action +3. confirm panel widget and session actor expose the same action + +That is the packet.