diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp index e739e7f..4c7f129 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp @@ -324,6 +324,29 @@ namespace HyperTwistTrainingCoachLibraryInternal } } + FString DescribeCoachEligibilityKind( + const EHyperTwistTrainingCoachEligibilityKind EligibilityKind + ) + { + switch (EligibilityKind) + { + case EHyperTwistTrainingCoachEligibilityKind::PrimaryAction: + return TEXT("primary coach continuation"); + case EHyperTwistTrainingCoachEligibilityKind::Promote: + return TEXT("deferred promotion"); + case EHyperTwistTrainingCoachEligibilityKind::Defer: + return TEXT("defer control"); + case EHyperTwistTrainingCoachEligibilityKind::ActiveRunBlocked: + return TEXT("active-run block"); + case EHyperTwistTrainingCoachEligibilityKind::QueueSuppressed: + return TEXT("queue suppression"); + case EHyperTwistTrainingCoachEligibilityKind::NoEligibleAction: + return TEXT("no eligible action"); + default: + return TEXT("none"); + } + } + FString DescribeCoachQueuePostureKind( const EHyperTwistTrainingCoachQueuePostureKind PostureKind) { @@ -6301,3 +6324,220 @@ UHyperTwistTrainingCoachLibrary::DeriveCoachWorkloadBudgetSurface( return Surface; } + +FHyperTwistTrainingCoachReadinessEligibilitySurface +UHyperTwistTrainingCoachLibrary::DeriveCoachReadinessEligibilitySurface( + const FHyperTwistTrainingCoachPanelState& PanelState, + const FHyperTwistTrainingCoachPrimaryActionSurface& PrimaryActionSurface, + const FHyperTwistTrainingCoachQueueControlSurface& QueueControlSurface, + const FHyperTwistTrainingCoachQueueRecoveryPostureSurface& QueueRecoveryPostureSurface, + const FHyperTwistTrainingCoachHandoffContinuationSurface& HandoffContinuationSurface, + const FHyperTwistTrainingCoachFocusProvenanceSurface& FocusProvenanceSurface, + const FHyperTwistTrainingCoachWorkloadBudgetSurface& WorkloadBudgetSurface +) +{ + auto ResolveFirstNonEmpty = []( + const FString& First, + const FString& Second, + const FString& Third + ) -> FString + { + if (!First.IsEmpty()) + { + return First; + } + if (!Second.IsEmpty()) + { + return Second; + } + + return Third; + }; + + FHyperTwistTrainingCoachReadinessEligibilitySurface Surface; + Surface.Headline = TEXT("Coach Readiness"); + Surface.ContinuationKind = HandoffContinuationSurface.ContinuationKind; + Surface.QueuePostureKind = QueueRecoveryPostureSurface.PostureKind; + Surface.bBlockedByActiveRun = PanelState.bHasActiveRun; + Surface.bBlockedByQueueSuppression = !PanelState.QueueSuppressionLine.IsEmpty(); + Surface.bNeedsVerificationAttention = PanelState.bRepositoryVerificationNeedsAttention; + + const bool bHasReadinessPressure = PanelState.bHasCoachGuidance + || PanelState.bHasFollowUpGuidance + || PanelState.bHasQueueWork + || PanelState.bHasDeferredQueueWork + || QueueRecoveryPostureSurface.bHasRecoveryPressure + || WorkloadBudgetSurface.bHasFocusPressure + || WorkloadBudgetSurface.bHasQueueLoad + || WorkloadBudgetSurface.bHasLaunchBudget; + + if (HandoffContinuationSurface.bHasActionableContinuation + && !HandoffContinuationSurface.bQueueRecoveryOverridesContinuation) + { + Surface.EligibilityKind = EHyperTwistTrainingCoachEligibilityKind::PrimaryAction; + Surface.bHasActionableControl = true; + Surface.ActionLabel = !HandoffContinuationSurface.ContinuationActionLabel.IsEmpty() + ? HandoffContinuationSurface.ContinuationActionLabel + : PrimaryActionSurface.ActionLabel; + Surface.ActionSourceLabel = ResolveFirstNonEmpty( + HandoffContinuationSurface.ContinuationSourceLabel, + PrimaryActionSurface.ActionSourceLabel, + PanelState.PreferredCoachHandoffSourceLabel + ); + Surface.StatusLine = TEXT("Readiness: primary coach continuation is actionable now."); + Surface.EligibilityLine = ResolveFirstNonEmpty( + HandoffContinuationSurface.StatusLine, + PrimaryActionSurface.AvailabilityLine, + TEXT("Coach continuation is actionable now.") + ); + } + else if (QueueRecoveryPostureSurface.PostureKind + == EHyperTwistTrainingCoachQueuePostureKind::Promote + && QueueControlSurface.bCanPromote) + { + Surface.EligibilityKind = EHyperTwistTrainingCoachEligibilityKind::Promote; + Surface.bHasActionableControl = true; + Surface.ActionLabel = QueueControlSurface.PromoteActionLabel; + Surface.ActionSourceLabel = ResolveFirstNonEmpty( + QueueControlSurface.PromoteActionSourceLabel, + QueueRecoveryPostureSurface.PreferredActionSourceLabel, + PanelState.PreferredQueueRecoveryActionSourceLabel + ); + Surface.StatusLine = + TEXT("Readiness: deferred promotion is the actionable next coach control."); + Surface.EligibilityLine = ResolveFirstNonEmpty( + QueueRecoveryPostureSurface.StatusLine, + QueueControlSurface.PromoteAvailabilityLine, + TEXT("Deferred promotion is actionable now.") + ); + } + else if (QueueRecoveryPostureSurface.PostureKind + == EHyperTwistTrainingCoachQueuePostureKind::Defer + && QueueControlSurface.bCanDefer) + { + Surface.EligibilityKind = EHyperTwistTrainingCoachEligibilityKind::Defer; + Surface.bHasActionableControl = true; + Surface.ActionLabel = QueueControlSurface.DeferActionLabel; + Surface.ActionSourceLabel = ResolveFirstNonEmpty( + QueueRecoveryPostureSurface.PreferredActionSourceLabel, + PanelState.PreferredQueueRecoveryActionSourceLabel, + PanelState.PreferredCoachHandoffSourceLabel + ); + Surface.StatusLine = + TEXT("Readiness: defer is the actionable queue-pressure release control."); + Surface.EligibilityLine = ResolveFirstNonEmpty( + QueueRecoveryPostureSurface.StatusLine, + QueueControlSurface.DeferAvailabilityLine, + TEXT("Defer is actionable now.") + ); + } + else if (PanelState.bHasActiveRun) + { + Surface.EligibilityKind = EHyperTwistTrainingCoachEligibilityKind::ActiveRunBlocked; + Surface.bHasBlockingCondition = true; + Surface.StatusLine = + TEXT("Readiness: coach continuation is blocked while a training run is already active."); + Surface.EligibilityLine = ResolveFirstNonEmpty( + HandoffContinuationSurface.StatusLine, + PrimaryActionSurface.AvailabilityLine, + TEXT("Finish or clear the active run before another coach action can start.") + ); + Surface.BlockedReasonLine = + TEXT("A training run is already active."); + } + else if (!PanelState.QueueSuppressionLine.IsEmpty()) + { + Surface.EligibilityKind = EHyperTwistTrainingCoachEligibilityKind::QueueSuppressed; + Surface.bHasBlockingCondition = true; + Surface.StatusLine = + TEXT("Readiness: no coach action is currently actionable because queue suppression is still active."); + Surface.EligibilityLine = PanelState.QueueSuppressionLine; + Surface.BlockedReasonLine = PanelState.QueueSuppressionLine; + } + else + { + Surface.EligibilityKind = EHyperTwistTrainingCoachEligibilityKind::NoEligibleAction; + Surface.bHasBlockingCondition = bHasReadinessPressure; + if (bHasReadinessPressure) + { + Surface.StatusLine = TEXT("Readiness: no coach control is actionable right now."); + Surface.EligibilityLine = ResolveFirstNonEmpty( + HandoffContinuationSurface.StatusLine, + QueueRecoveryPostureSurface.StatusLine, + ResolveFirstNonEmpty( + PrimaryActionSurface.AvailabilityLine, + PanelState.SecondaryLine, + TEXT("No coach control is currently actionable.") + ) + ); + Surface.BlockedReasonLine = ResolveFirstNonEmpty( + QueueRecoveryPostureSurface.StatusLine, + PrimaryActionSurface.AvailabilityLine, + Surface.EligibilityLine + ); + } + else + { + Surface.StatusLine = + TEXT("Readiness: coach lane is idle and no action is currently needed."); + Surface.EligibilityLine = ResolveFirstNonEmpty( + WorkloadBudgetSurface.StatusLine, + PanelState.SecondaryLine, + TEXT("No queued, follow-up, or recommended coach work is currently materialized.") + ); + } + } + + Surface.ActionSourceDescription = + HyperTwistTrainingCoachLibraryInternal::DescribeGameplayCoachSourceLabel( + Surface.ActionSourceLabel + ); + + const FString EligibilityKindLabel = + HyperTwistTrainingCoachLibraryInternal::DescribeCoachEligibilityKind( + Surface.EligibilityKind + ); + const FString QueuePostureLabel = + HyperTwistTrainingCoachLibraryInternal::DescribeCoachQueuePostureKind( + Surface.QueuePostureKind + ); + + Surface.DetailLine = FString::Printf( + TEXT("Eligibility: %s | Action: %s | Source: %s | Continuation: %s | Queue posture: %s | Workload: %s | Verification: %s | Recognition: %s | Review: %s"), + *EligibilityKindLabel, + Surface.ActionLabel.IsEmpty() ? TEXT("none") : *Surface.ActionLabel, + *Surface.ActionSourceDescription, + HandoffContinuationSurface.StatusLine.IsEmpty() + ? TEXT("n/a") + : *HandoffContinuationSurface.StatusLine, + *QueuePostureLabel, + WorkloadBudgetSurface.StatusLine.IsEmpty() + ? TEXT("n/a") + : *WorkloadBudgetSurface.StatusLine, + PanelState.VerificationStatusLine.IsEmpty() + ? TEXT("n/a") + : *PanelState.VerificationStatusLine, + PanelState.RecognitionStatusLine.IsEmpty() + ? TEXT("n/a") + : *PanelState.RecognitionStatusLine, + PanelState.ReviewPolicyStatusLine.IsEmpty() + ? TEXT("n/a") + : *PanelState.ReviewPolicyStatusLine + ); + if (!FocusProvenanceSurface.FocusSummaryLine.IsEmpty()) + { + Surface.DetailLine += FString::Printf( + TEXT(" | Focus: %s"), + *FocusProvenanceSurface.FocusSummaryLine + ); + } + if (!Surface.BlockedReasonLine.IsEmpty()) + { + Surface.DetailLine += FString::Printf( + TEXT(" | Blocked reason: %s"), + *Surface.BlockedReasonLine + ); + } + + return Surface; +} diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp index cff388c..eb3b970 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp @@ -559,6 +559,54 @@ UHyperTwistTrainingPanelWidget::GetDisplayedCoachWorkloadBudgetSurface() const ); } +FHyperTwistTrainingCoachReadinessEligibilitySurface +UHyperTwistTrainingPanelWidget::GetDisplayedCoachReadinessEligibilitySurface() const +{ + const FHyperTwistTrainingCoachPrimaryActionSurface PrimaryActionSurface = + GetDisplayedPrimaryCoachActionSurface(); + const FHyperTwistTrainingCoachQueueControlSurface QueueControlSurface = + GetDisplayedCoachQueueControlSurface(); + const FHyperTwistTrainingCoachQueueRecoveryPostureSurface QueueRecoveryPostureSurface = + UHyperTwistTrainingCoachLibrary::DeriveCoachQueueRecoveryPostureSurface( + CachedCoachSchedulePolicySummary, + CachedCoachQueueExecutionSummary, + CachedCoachPanelState, + PrimaryActionSurface, + QueueControlSurface + ); + const FHyperTwistTrainingCoachHandoffContinuationSurface HandoffContinuationSurface = + UHyperTwistTrainingCoachLibrary::DeriveCoachHandoffContinuationSurface( + CachedCoachPanelState, + PrimaryActionSurface, + QueueControlSurface, + QueueRecoveryPostureSurface + ); + const FHyperTwistTrainingCoachFocusProvenanceSurface FocusProvenanceSurface = + UHyperTwistTrainingCoachLibrary::DeriveCoachFocusProvenanceSurface( + CachedCoachPanelState, + PrimaryActionSurface, + QueueControlSurface, + QueueRecoveryPostureSurface, + HandoffContinuationSurface + ); + const FHyperTwistTrainingCoachWorkloadBudgetSurface WorkloadBudgetSurface = + UHyperTwistTrainingCoachLibrary::DeriveCoachWorkloadBudgetSurface( + CachedCoachPanelState, + QueueRecoveryPostureSurface, + HandoffContinuationSurface, + FocusProvenanceSurface + ); + return UHyperTwistTrainingCoachLibrary::DeriveCoachReadinessEligibilitySurface( + CachedCoachPanelState, + PrimaryActionSurface, + QueueControlSurface, + QueueRecoveryPostureSurface, + HandoffContinuationSurface, + FocusProvenanceSurface, + WorkloadBudgetSurface + ); +} + 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 399496e..7192777 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp @@ -540,6 +540,54 @@ AHyperTwistTrainingSessionActor::GetDisplayedCoachWorkloadBudgetSurface() const ); } +FHyperTwistTrainingCoachReadinessEligibilitySurface +AHyperTwistTrainingSessionActor::GetDisplayedCoachReadinessEligibilitySurface() const +{ + const FHyperTwistTrainingCoachPrimaryActionSurface PrimaryActionSurface = + GetDisplayedPrimaryCoachActionSurface(); + const FHyperTwistTrainingCoachQueueControlSurface QueueControlSurface = + GetDisplayedCoachQueueControlSurface(); + const FHyperTwistTrainingCoachQueueRecoveryPostureSurface QueueRecoveryPostureSurface = + UHyperTwistTrainingCoachLibrary::DeriveCoachQueueRecoveryPostureSurface( + CachedCoachSchedulePolicySummary, + CachedCoachQueueExecutionSummary, + CachedCoachPanelState, + PrimaryActionSurface, + QueueControlSurface + ); + const FHyperTwistTrainingCoachHandoffContinuationSurface HandoffContinuationSurface = + UHyperTwistTrainingCoachLibrary::DeriveCoachHandoffContinuationSurface( + CachedCoachPanelState, + PrimaryActionSurface, + QueueControlSurface, + QueueRecoveryPostureSurface + ); + const FHyperTwistTrainingCoachFocusProvenanceSurface FocusProvenanceSurface = + UHyperTwistTrainingCoachLibrary::DeriveCoachFocusProvenanceSurface( + CachedCoachPanelState, + PrimaryActionSurface, + QueueControlSurface, + QueueRecoveryPostureSurface, + HandoffContinuationSurface + ); + const FHyperTwistTrainingCoachWorkloadBudgetSurface WorkloadBudgetSurface = + UHyperTwistTrainingCoachLibrary::DeriveCoachWorkloadBudgetSurface( + CachedCoachPanelState, + QueueRecoveryPostureSurface, + HandoffContinuationSurface, + FocusProvenanceSurface + ); + return UHyperTwistTrainingCoachLibrary::DeriveCoachReadinessEligibilitySurface( + CachedCoachPanelState, + PrimaryActionSurface, + QueueControlSurface, + QueueRecoveryPostureSurface, + HandoffContinuationSurface, + FocusProvenanceSurface, + WorkloadBudgetSurface + ); +} + 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 cfba801..9cfeaba 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h @@ -638,4 +638,16 @@ public: const FHyperTwistTrainingCoachHandoffContinuationSurface& HandoffContinuationSurface, const FHyperTwistTrainingCoachFocusProvenanceSurface& FocusProvenanceSurface ); + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + static FHyperTwistTrainingCoachReadinessEligibilitySurface + DeriveCoachReadinessEligibilitySurface( + const FHyperTwistTrainingCoachPanelState& PanelState, + const FHyperTwistTrainingCoachPrimaryActionSurface& PrimaryActionSurface, + const FHyperTwistTrainingCoachQueueControlSurface& QueueControlSurface, + const FHyperTwistTrainingCoachQueueRecoveryPostureSurface& QueueRecoveryPostureSurface, + const FHyperTwistTrainingCoachHandoffContinuationSurface& HandoffContinuationSurface, + const FHyperTwistTrainingCoachFocusProvenanceSurface& FocusProvenanceSurface, + const FHyperTwistTrainingCoachWorkloadBudgetSurface& WorkloadBudgetSurface + ); }; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h index 64f966f..90c4828 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h @@ -236,6 +236,10 @@ public: FHyperTwistTrainingCoachWorkloadBudgetSurface GetDisplayedCoachWorkloadBudgetSurface() const; + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingCoachReadinessEligibilitySurface + GetDisplayedCoachReadinessEligibilitySurface() 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 fe1c5e6..4647602 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h @@ -217,6 +217,10 @@ public: FHyperTwistTrainingCoachWorkloadBudgetSurface GetDisplayedCoachWorkloadBudgetSurface() const; + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingCoachReadinessEligibilitySurface + GetDisplayedCoachReadinessEligibilitySurface() 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 3b0f421..566565e 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h @@ -59,6 +59,18 @@ enum class EHyperTwistTrainingCoachHandoffKind : uint8 Recommended UMETA(DisplayName = "Recommended") }; +UENUM(BlueprintType) +enum class EHyperTwistTrainingCoachEligibilityKind : uint8 +{ + None UMETA(DisplayName = "None"), + PrimaryAction UMETA(DisplayName = "Primary Action"), + Promote UMETA(DisplayName = "Promote"), + Defer UMETA(DisplayName = "Defer"), + ActiveRunBlocked UMETA(DisplayName = "Active Run Blocked"), + QueueSuppressed UMETA(DisplayName = "Queue Suppressed"), + NoEligibleAction UMETA(DisplayName = "No Eligible Action") +}; + UENUM(BlueprintType) enum class EHyperTwistTrainingPromptKind : uint8 { @@ -8170,3 +8182,68 @@ struct FHyperTwistTrainingCoachWorkloadBudgetSurface || bHasLaunchBudget; } }; + +USTRUCT(BlueprintType) +struct FHyperTwistTrainingCoachReadinessEligibilitySurface +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString Headline; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString StatusLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString EligibilityLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString BlockedReasonLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString DetailLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ActionLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ActionSourceLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ActionSourceDescription; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + EHyperTwistTrainingCoachEligibilityKind EligibilityKind = + EHyperTwistTrainingCoachEligibilityKind::None; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + EHyperTwistTrainingCoachHandoffKind ContinuationKind = + EHyperTwistTrainingCoachHandoffKind::None; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + EHyperTwistTrainingCoachQueuePostureKind QueuePostureKind = + EHyperTwistTrainingCoachQueuePostureKind::None; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasActionableControl = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasBlockingCondition = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bBlockedByActiveRun = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bBlockedByQueueSuppression = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bNeedsVerificationAttention = false; + + bool IsStructurallyValid() const + { + return !StatusLine.IsEmpty() + || !EligibilityLine.IsEmpty() + || bHasActionableControl + || bHasBlockingCondition; + } +}; diff --git a/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md b/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md index d39a0a8..24ae1a5 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 coach workload/budget summary surface over the already-landed primary CTA, secondary queue-control, queue-recovery posture, handoff-continuation, and focus/provenance surfaces, so gameplay/Blueprint callers can render launch case budget, queue load, and focus pressure without reading raw count and budget fields directly - - the current next bounded packet is to expose one thin coach readiness/eligibility summary surface on those same two consumer surfaces so gameplay/Blueprint callers can render what is actionable now, what is blocked, and why without reading raw booleans or suppression strings directly + - the latest landed bounded `Phase 4` packet stays on that same gameplay-facing consumer lane and adds one thin coach readiness/eligibility summary surface over the already-landed primary CTA, secondary queue-control, queue-recovery posture, handoff-continuation, focus/provenance, and workload/budget surfaces, so gameplay/Blueprint callers can render what is actionable now, what is blocked, and why without reading raw booleans or suppression strings directly + - the current next bounded packet is to expose one thin coach verification / recognition / review-policy context surface on those same two consumer surfaces so gameplay/Blueprint callers can render repository-verification, recognition-provider, and review-policy context without reading raw status/detail lines 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_READINESS_ELIGIBILITY_SURFACE_PACKET_2026-05-08.md b/docs/arch/HYPERTWIST_PHASE4_GAMEPLAY_READINESS_ELIGIBILITY_SURFACE_PACKET_2026-05-08.md new file mode 100644 index 0000000..9b1ec29 --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE4_GAMEPLAY_READINESS_ELIGIBILITY_SURFACE_PACKET_2026-05-08.md @@ -0,0 +1,115 @@ +# HyperTwist Phase 4 gameplay readiness/eligibility surface packet + +Created on `2026-05-08` + +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 readiness/eligibility summary surface over the already-landed primary coach CTA, queue-control, queue-recovery posture, handoff-continuation, focus/provenance, and workload/budget surfaces. + +The open tasks were: + +- stop forcing gameplay / Blueprint callers to read raw actionability booleans, active-run state, and queue-suppression strings just to explain whether coach work is actionable now +- expose one thin first-party readiness/eligibility surface that summarizes the live actionable control, the blocking condition when no control is live, and the direct reason for that blocked state +- keep that surface derived from already-cached gameplay consumer state instead of widening into dashboard-only orchestration or backend queue policy + +It is not: + +- a new coach launch policy packet +- a queue suppression or queue recovery policy packet +- a dashboard actionability migration +- a broader gameplay HUD composition pass + +## Scope + +Bounded lane: + +- add a first-party readiness/eligibility 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, the displayed queue-recovery posture surface, the displayed handoff-continuation surface, the displayed focus/provenance surface, and the displayed workload/budget surface +- expose that derived readiness/eligibility surface on `HyperTwistTrainingPanelWidget` and `HyperTwistTrainingSessionActor` +- keep the derivation presentation-only and rooted in already-cached gameplay consumer state + +Out of scope: + +- changing coach continuation branch order +- changing queue suppression or queue recovery policy +- importing dashboard-only orchestration or retained analytics into gameplay consumers +- 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 truthful primary CTA, queue-control, queue-recovery posture, handoff-continuation, focus/provenance, and workload/budget surfaces + +But one thin consumer seam was still open: + +- callers could bind actions, explain continuation, narrate provenance, and summarize workload +- but they still had to inspect raw booleans and queue-suppression text to explain whether coach work was actionable now or blocked + +That meant: + +- action binding was thin +- continuation, provenance, and workload explanation were thin +- but live readiness explanation still leaked raw panel-state gating into callers + +So the next bounded move was: + +- keep the already-landed action, posture, continuation, provenance, and workload surfaces unchanged +- and expose one narrow readiness/eligibility summary surface above them + +## What landed + +Primary code changes: + +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h` + - added `EHyperTwistTrainingCoachEligibilityKind` + - added `FHyperTwistTrainingCoachReadinessEligibilitySurface` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp` + - added `DeriveCoachReadinessEligibilitySurface(...)` + - readiness/eligibility is derived from existing panel-state, primary CTA, queue-control, queue-recovery posture, handoff-continuation, focus/provenance, and workload/budget inputs +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp` + - added `GetDisplayedCoachReadinessEligibilitySurface()` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp` + - added `GetDisplayedCoachReadinessEligibilitySurface()` + +## Product effect + +The gameplay-facing coach consumer layer is now more complete: + +- gameplay / Blueprint callers can read one pure surface to explain whether primary continuation, deferred promotion, or defer is actionable now +- callers can also read one direct blocked-reason line when active-run state or queue suppression prevents another coach action +- callers no longer need to inspect raw booleans or queue-suppression strings just to narrate live readiness +- the surface stays aligned with the already-landed primary CTA, queue-control, queue-recovery posture, handoff-continuation, focus/provenance, and workload/budget surfaces without importing dashboard-only orchestration + +## Acceptance criteria + +- shared training types expose a narrow readiness/eligibility surface +- coach-library derivation consumes already-cached gameplay consumer state rather than adding new runtime fetches +- panel-widget callers can fetch the displayed readiness/eligibility surface directly +- session-actor callers can fetch the displayed readiness/eligibility surface directly +- full product build succeeds + +## Validation checklist + +1. build `UnrealHyperTwist.sln` / `UnrealHyperTwistEditor` +2. confirm the new readiness/eligibility surface compiles in shared types and coach library +3. confirm panel-widget getters compile and return the derived readiness/eligibility surface +4. confirm session-actor getters compile and return the derived readiness/eligibility surface +5. confirm no backend continuity or dashboard-only orchestration work was pulled into the packet + +## Validation result + +- full `Development Editor|Win64` build succeeded on `2026-05-08` +- 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 verification / recognition / review-policy context surface on `HyperTwistTrainingPanelWidget` and `HyperTwistTrainingSessionActor`, so gameplay callers can render repository-verification, recognition-provider, and review-policy context without reading raw status/detail lines directly