Expose gameplay readiness eligibility surface

This commit is contained in:
axiomlogicnexus 2026-05-08 00:37:22 +02:00
parent 6db9557751
commit eec693883d
9 changed files with 550 additions and 2 deletions

View file

@ -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;
}

View file

@ -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

View file

@ -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
)

View file

@ -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
);
};

View file

@ -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,

View file

@ -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);

View file

@ -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;
}
};

View file

@ -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

View file

@ -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