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