diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp index c00f7b8..e739e7f 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp @@ -6169,3 +6169,135 @@ UHyperTwistTrainingCoachLibrary::DeriveCoachFocusProvenanceSurface( return Surface; } + +FHyperTwistTrainingCoachWorkloadBudgetSurface +UHyperTwistTrainingCoachLibrary::DeriveCoachWorkloadBudgetSurface( + const FHyperTwistTrainingCoachPanelState& PanelState, + const FHyperTwistTrainingCoachQueueRecoveryPostureSurface& QueueRecoveryPostureSurface, + const FHyperTwistTrainingCoachHandoffContinuationSurface& HandoffContinuationSurface, + const FHyperTwistTrainingCoachFocusProvenanceSurface& FocusProvenanceSurface +) +{ + FHyperTwistTrainingCoachWorkloadBudgetSurface Surface; + Surface.Headline = TEXT("Coach Workload"); + Surface.SignalCount = FMath::Max(PanelState.SignalCount, 0); + Surface.FocusCaseCount = FMath::Max(PanelState.FocusCaseCount, 0); + Surface.SuggestedLaunchCaseBudget = FMath::Max(PanelState.SuggestedLaunchCaseBudget, 0); + Surface.TotalQueueEntryCount = FMath::Max(PanelState.TotalQueueEntryCount, 0); + Surface.ReadyQueueEntryCount = FMath::Max(PanelState.ReadyQueueEntryCount, 0); + Surface.DeferredQueueEntryCount = FMath::Max(PanelState.DeferredQueueEntryCount, 0); + Surface.FollowUpQueueEntryCount = FMath::Max(PanelState.FollowUpQueueEntryCount, 0); + Surface.PressureScore = FMath::Max(QueueRecoveryPostureSurface.PressureScore, 0.0f); + Surface.ContinuationKind = HandoffContinuationSurface.ContinuationKind; + Surface.QueuePostureKind = QueueRecoveryPostureSurface.PostureKind; + Surface.bHasFocusPressure = Surface.SignalCount > 0 || Surface.FocusCaseCount > 0; + Surface.bHasQueueLoad = Surface.TotalQueueEntryCount > 0 + || Surface.ReadyQueueEntryCount > 0 + || Surface.DeferredQueueEntryCount > 0 + || Surface.FollowUpQueueEntryCount > 0; + Surface.bHasLaunchBudget = Surface.SuggestedLaunchCaseBudget > 0 + || !PanelState.LaunchBudgetReasonLine.IsEmpty(); + Surface.bHasQueuePressure = QueueRecoveryPostureSurface.bHasRecoveryPressure + || QueueRecoveryPostureSurface.PostureKind + != EHyperTwistTrainingCoachQueuePostureKind::None + || Surface.PressureScore > 0.0f; + + const FString ContinuationKindLabel = + HyperTwistTrainingCoachLibraryInternal::DescribeCoachHandoffKind( + Surface.ContinuationKind + ); + const FString QueuePostureLabel = + HyperTwistTrainingCoachLibraryInternal::DescribeCoachQueuePostureKind( + Surface.QueuePostureKind + ); + + Surface.StatusLine = FString::Printf( + TEXT("Workload: %d signals across %d focus cases | Queue: %d total (%d ready, %d deferred, %d follow-up) | Continuation: %s"), + Surface.SignalCount, + Surface.FocusCaseCount, + Surface.TotalQueueEntryCount, + Surface.ReadyQueueEntryCount, + Surface.DeferredQueueEntryCount, + Surface.FollowUpQueueEntryCount, + *ContinuationKindLabel + ); + if (Surface.SuggestedLaunchCaseBudget > 0) + { + Surface.StatusLine += FString::Printf( + TEXT(" | Launch cases: %d"), + Surface.SuggestedLaunchCaseBudget + ); + } + if (Surface.bHasQueuePressure) + { + Surface.StatusLine += FString::Printf( + TEXT(" | Pressure: %.2f"), + Surface.PressureScore + ); + } + + Surface.FocusPressureLine = FString::Printf( + TEXT("Focus: %d signals | Cases: %d%s"), + Surface.SignalCount, + Surface.FocusCaseCount, + Surface.SuggestedLaunchCaseBudget > 0 + ? *FString::Printf( + TEXT(" | Launch cases: %d"), + Surface.SuggestedLaunchCaseBudget) + : TEXT("") + ); + + Surface.QueueLoadLine = !PanelState.QueueStatusLine.IsEmpty() + ? PanelState.QueueStatusLine + : FString::Printf( + TEXT("Queue: %d total, %d ready, %d deferred"), + Surface.TotalQueueEntryCount, + Surface.ReadyQueueEntryCount, + Surface.DeferredQueueEntryCount + ); + Surface.QueueLoadLine += FString::Printf( + TEXT(" | Follow-up entries: %d"), + Surface.FollowUpQueueEntryCount + ); + + Surface.BudgetLine = Surface.SuggestedLaunchCaseBudget > 0 + ? FString::Printf( + TEXT("Launch budget: %d cases"), + Surface.SuggestedLaunchCaseBudget + ) + : TEXT("Launch budget: no explicit case budget"); + if (!FocusProvenanceSurface.LaunchBudgetSourceDescription.IsEmpty() + && FocusProvenanceSurface.LaunchBudgetSourceDescription != TEXT("n/a")) + { + Surface.BudgetLine += FString::Printf( + TEXT(" | Source: %s"), + *FocusProvenanceSurface.LaunchBudgetSourceDescription + ); + } + if (!PanelState.LaunchBudgetReasonLine.IsEmpty()) + { + Surface.BudgetLine += FString::Printf( + TEXT(" | Reason: %s"), + *PanelState.LaunchBudgetReasonLine + ); + } + + Surface.DetailLine = FString::Printf( + TEXT("Queue posture: %s | Focus summary: %s | Queue status: %s | Continuation status: %s | Queue recovery: %s"), + *QueuePostureLabel, + FocusProvenanceSurface.FocusSummaryLine.IsEmpty() + ? TEXT("n/a") + : *FocusProvenanceSurface.FocusSummaryLine, + PanelState.QueueStatusLine.IsEmpty() + ? TEXT("n/a") + : *PanelState.QueueStatusLine, + HandoffContinuationSurface.StatusLine.IsEmpty() + ? TEXT("n/a") + : *HandoffContinuationSurface.StatusLine, + QueueRecoveryPostureSurface.StatusLine.IsEmpty() + ? TEXT("n/a") + : *QueueRecoveryPostureSurface.StatusLine + ); + + return Surface; +} diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp index a6c34ae..cff388c 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp @@ -521,6 +521,44 @@ UHyperTwistTrainingPanelWidget::GetDisplayedCoachFocusProvenanceSurface() const ); } +FHyperTwistTrainingCoachWorkloadBudgetSurface +UHyperTwistTrainingPanelWidget::GetDisplayedCoachWorkloadBudgetSurface() 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 + ); + return UHyperTwistTrainingCoachLibrary::DeriveCoachWorkloadBudgetSurface( + CachedCoachPanelState, + QueueRecoveryPostureSurface, + HandoffContinuationSurface, + FocusProvenanceSurface + ); +} + 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 61e1869..399496e 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp @@ -502,6 +502,44 @@ AHyperTwistTrainingSessionActor::GetDisplayedCoachFocusProvenanceSurface() const ); } +FHyperTwistTrainingCoachWorkloadBudgetSurface +AHyperTwistTrainingSessionActor::GetDisplayedCoachWorkloadBudgetSurface() 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 + ); + return UHyperTwistTrainingCoachLibrary::DeriveCoachWorkloadBudgetSurface( + CachedCoachPanelState, + QueueRecoveryPostureSurface, + HandoffContinuationSurface, + FocusProvenanceSurface + ); +} + 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 fc3e7a6..cfba801 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h @@ -629,4 +629,13 @@ public: const FHyperTwistTrainingCoachQueueRecoveryPostureSurface& QueueRecoveryPostureSurface, const FHyperTwistTrainingCoachHandoffContinuationSurface& HandoffContinuationSurface ); + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + static FHyperTwistTrainingCoachWorkloadBudgetSurface + DeriveCoachWorkloadBudgetSurface( + const FHyperTwistTrainingCoachPanelState& PanelState, + const FHyperTwistTrainingCoachQueueRecoveryPostureSurface& QueueRecoveryPostureSurface, + const FHyperTwistTrainingCoachHandoffContinuationSurface& HandoffContinuationSurface, + const FHyperTwistTrainingCoachFocusProvenanceSurface& FocusProvenanceSurface + ); }; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h index 3005f9a..64f966f 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h @@ -232,6 +232,10 @@ public: FHyperTwistTrainingCoachFocusProvenanceSurface GetDisplayedCoachFocusProvenanceSurface() const; + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingCoachWorkloadBudgetSurface + GetDisplayedCoachWorkloadBudgetSurface() 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 c13ba33..fe1c5e6 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h @@ -213,6 +213,10 @@ public: FHyperTwistTrainingCoachFocusProvenanceSurface GetDisplayedCoachFocusProvenanceSurface() const; + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingCoachWorkloadBudgetSurface + GetDisplayedCoachWorkloadBudgetSurface() 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 439e152..3b0f421 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h @@ -8093,3 +8093,80 @@ struct FHyperTwistTrainingCoachFocusProvenanceSurface || bHasUpstreamProvenance; } }; + +USTRUCT(BlueprintType) +struct FHyperTwistTrainingCoachWorkloadBudgetSurface +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString Headline; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString StatusLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString FocusPressureLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString QueueLoadLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString BudgetLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString DetailLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 SignalCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 FocusCaseCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 SuggestedLaunchCaseBudget = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 TotalQueueEntryCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 ReadyQueueEntryCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 DeferredQueueEntryCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 FollowUpQueueEntryCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float PressureScore = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + EHyperTwistTrainingCoachHandoffKind ContinuationKind = + EHyperTwistTrainingCoachHandoffKind::None; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + EHyperTwistTrainingCoachQueuePostureKind QueuePostureKind = + EHyperTwistTrainingCoachQueuePostureKind::None; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasFocusPressure = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasQueueLoad = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasLaunchBudget = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasQueuePressure = false; + + bool IsStructurallyValid() const + { + return !StatusLine.IsEmpty() + || !BudgetLine.IsEmpty() + || bHasFocusPressure + || bHasQueueLoad + || bHasLaunchBudget; + } +}; diff --git a/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md b/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md index 4633b51..d39a0a8 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 focus/provenance summary surface over the already-landed primary CTA, secondary queue-control, queue-recovery posture, and handoff-continuation surfaces, so gameplay/Blueprint callers can render the active coach target and upstream recommendation provenance without reading raw focus ids or source-label strings directly - - the current next bounded packet is to expose one thin coach workload/budget summary surface on those same two consumer surfaces so gameplay/Blueprint callers can render launch case budget, queue load, and focus pressure without reading raw count and budget fields directly + - 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 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_WORKLOAD_BUDGET_SURFACE_PACKET_2026-05-08.md b/docs/arch/HYPERTWIST_PHASE4_GAMEPLAY_WORKLOAD_BUDGET_SURFACE_PACKET_2026-05-08.md new file mode 100644 index 0000000..2bf6ed2 --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE4_GAMEPLAY_WORKLOAD_BUDGET_SURFACE_PACKET_2026-05-08.md @@ -0,0 +1,113 @@ +# HyperTwist Phase 4 gameplay workload/budget 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 workload/budget summary surface over the already-landed primary coach CTA, queue-control, queue-recovery posture, handoff-continuation, and focus/provenance surfaces. + +The open tasks were: + +- stop forcing gameplay / Blueprint callers to read raw focus-case counts, signal counts, launch-budget counts, queue-entry counts, and pressure scores just to explain current coach workload +- expose one thin first-party workload/budget surface that summarizes focus pressure, queue load, launch-case budget, and live continuation / queue-pressure posture +- keep that surface derived from already-cached gameplay consumer state instead of widening into dashboard-only retained analytics or backend scheduling policy + +It is not: + +- a new coach scheduling or launch-budget policy packet +- a queue recovery selection-policy packet +- a dashboard workload migration +- a broader gameplay HUD composition pass + +## Scope + +Bounded lane: + +- add a first-party workload/budget surface in shared training types +- add a coach-library helper that derives the surface from panel state, the displayed queue-recovery posture surface, the displayed handoff-continuation surface, and the displayed focus/provenance surface +- expose that derived workload/budget 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 recovery or launch-budget policy +- importing dashboard-only retained workload 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, and focus/provenance surfaces + +But one thin consumer seam was still open: + +- callers could bind actions, explain continuation, and narrate focus/provenance +- but they still had to inspect raw counts and budget fields to explain how much work was live, how much queue load remained, and why a given launch-case budget existed + +That meant: + +- action binding was thin +- continuation and provenance explanation were thin +- but workload and budget explanation still leaked raw panel-state values into callers + +So the next bounded move was: + +- keep the already-landed action, posture, continuation, and provenance surfaces unchanged +- and expose one narrow workload/budget summary surface above them + +## What landed + +Primary code changes: + +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h` + - added `FHyperTwistTrainingCoachWorkloadBudgetSurface` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp` + - added `DeriveCoachWorkloadBudgetSurface(...)` + - workload/budget is derived from existing panel-state, queue-recovery posture, handoff-continuation, and focus/provenance inputs +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp` + - added `GetDisplayedCoachWorkloadBudgetSurface()` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp` + - added `GetDisplayedCoachWorkloadBudgetSurface()` + +## Product effect + +The gameplay-facing coach consumer layer is now more complete: + +- gameplay / Blueprint callers can read one pure surface to describe focus pressure, queue load, launch-case budget, and live queue-pressure posture +- callers no longer need to inspect raw count fields, launch-budget fields, or queue-status fragments just to render workload narration +- the surface stays aligned with the already-landed primary CTA, queue-control, queue-recovery posture, handoff-continuation, and focus/provenance surfaces without importing dashboard-only retained workload analytics + +## Acceptance criteria + +- shared training types expose a narrow workload/budget surface +- coach-library derivation consumes already-cached gameplay consumer state rather than adding new runtime fetches +- panel-widget callers can fetch the displayed workload/budget surface directly +- session-actor callers can fetch the displayed workload/budget surface directly +- full product build succeeds + +## Validation checklist + +1. build `UnrealHyperTwist.sln` / `UnrealHyperTwistEditor` +2. confirm the new workload/budget surface compiles in shared types and coach library +3. confirm panel-widget getters compile and return the derived workload/budget surface +4. confirm session-actor getters compile and return the derived workload/budget surface +5. confirm no backend continuity or dashboard-only retained analytics 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 readiness/eligibility summary surface on `HyperTwistTrainingPanelWidget` and `HyperTwistTrainingSessionActor`, so gameplay callers can render what is actionable now, what is blocked, and why without reading raw booleans or suppression strings directly