Expose gameplay queue-recovery posture surface

This commit is contained in:
axiomlogicnexus 2026-05-07 23:20:22 +02:00
parent 8ffee266a4
commit 82b470b02b
9 changed files with 432 additions and 2 deletions

View file

@ -5571,3 +5571,209 @@ UHyperTwistTrainingCoachLibrary::DeriveCoachQueueControlSurface(
return Surface;
}
FHyperTwistTrainingCoachQueueRecoveryPostureSurface
UHyperTwistTrainingCoachLibrary::DeriveCoachQueueRecoveryPostureSurface(
const FHyperTwistCoachSchedulePolicySummary& SchedulePolicySummary,
const FHyperTwistTrainingCoachSessionQueueExecutionSummary& QueueExecutionSummary,
const FHyperTwistTrainingCoachPanelState& PanelState,
const FHyperTwistTrainingCoachPrimaryActionSurface& PrimaryActionSurface,
const FHyperTwistTrainingCoachQueueControlSurface& QueueControlSurface
)
{
auto DescribeGuidanceLane = [](const EHyperTwistTrainingCoachGuidanceLane Lane) -> FString
{
switch (Lane)
{
case EHyperTwistTrainingCoachGuidanceLane::FollowUp:
return TEXT("follow-up");
case EHyperTwistTrainingCoachGuidanceLane::Recommended:
return TEXT("recommended");
default:
return TEXT("none");
}
};
auto DescribeRecoveryAction = [](const EHyperTwistTrainingQueueRecoveryAction Action) -> FString
{
switch (Action)
{
case EHyperTwistTrainingQueueRecoveryAction::PromoteDeferred:
return TEXT("prefer promote-deferred recovery");
case EHyperTwistTrainingQueueRecoveryAction::LaunchRecovery:
return TEXT("prefer launch-recovery");
default:
return TEXT("no upstream recovery preference");
}
};
FHyperTwistTrainingCoachQueueRecoveryPostureSurface Surface;
Surface.Headline = TEXT("Queue Recovery");
Surface.PreferredRecoveryAction = PanelState.PreferredQueueRecoveryAction;
Surface.PreferredGuidanceLane = PanelState.PreferredGuidanceLane;
Surface.PressureScore = FMath::Max(
SchedulePolicySummary.ReschedulePressureScore,
QueueExecutionSummary.ExecutionPressureScore
);
Surface.bHasRecoveryPressure =
Surface.PressureScore > 0.0f
|| PanelState.bHasDeferredQueueWork
|| QueueExecutionSummary.bHasOutstandingDeferredWork
|| QueueExecutionSummary.bHasCompletionGap
|| SchedulePolicySummary.bNeedsManualRescheduleReview
|| SchedulePolicySummary.bHasExpiredDeferrals
|| SchedulePolicySummary.bHasArchiveAwareDeferrals
|| SchedulePolicySummary.bHasBlockedFollowUpDeferrals
|| PanelState.PreferredQueueRecoveryAction != EHyperTwistTrainingQueueRecoveryAction::None
|| HyperTwistTrainingCoachLibraryInternal::IsQueueRecoveryMemorySource(
PanelState.PreferredGuidanceSourceLabel
);
const bool bPrefersPromoteUpstream =
PanelState.PreferredQueueRecoveryAction
== EHyperTwistTrainingQueueRecoveryAction::PromoteDeferred;
const bool bPrefersLaunchUpstream =
PanelState.PreferredQueueRecoveryAction
== EHyperTwistTrainingQueueRecoveryAction::LaunchRecovery;
if (bPrefersPromoteUpstream && QueueControlSurface.bCanPromote)
{
Surface.PostureKind = EHyperTwistTrainingCoachQueuePostureKind::Promote;
}
else if (PrimaryActionSurface.bCanExecute
&& (bPrefersLaunchUpstream
|| !QueueControlSurface.bCanPromote
|| PanelState.PreferredQueueRecoveryAction
== EHyperTwistTrainingQueueRecoveryAction::None))
{
Surface.PostureKind = EHyperTwistTrainingCoachQueuePostureKind::Continue;
}
else if (QueueControlSurface.bCanPromote)
{
Surface.PostureKind = EHyperTwistTrainingCoachQueuePostureKind::Promote;
}
else if (PrimaryActionSurface.bCanExecute)
{
Surface.PostureKind = EHyperTwistTrainingCoachQueuePostureKind::Continue;
}
else if (QueueControlSurface.bCanDefer)
{
Surface.PostureKind = EHyperTwistTrainingCoachQueuePostureKind::Defer;
}
switch (Surface.PostureKind)
{
case EHyperTwistTrainingCoachQueuePostureKind::Continue:
Surface.PreferredActionLabel = PrimaryActionSurface.ActionLabel;
Surface.PreferredActionSourceLabel = PrimaryActionSurface.ActionSourceLabel;
Surface.bCanExecutePreferredAction = PrimaryActionSurface.bCanExecute;
if (bPrefersLaunchUpstream)
{
Surface.StatusLine = FString::Printf(
TEXT("Queue recovery keeps continue live as the preferred launch-recovery posture: %s"),
PrimaryActionSurface.AvailabilityLine.IsEmpty()
? TEXT("Continue is actionable")
: *PrimaryActionSurface.AvailabilityLine
);
}
else if (bPrefersPromoteUpstream && !QueueControlSurface.bCanPromote)
{
Surface.StatusLine = FString::Printf(
TEXT("Queue recovery falls back to continue because no deferred promote target is currently actionable: %s"),
PrimaryActionSurface.AvailabilityLine.IsEmpty()
? TEXT("Continue is actionable")
: *PrimaryActionSurface.AvailabilityLine
);
}
else if (Surface.bHasRecoveryPressure)
{
Surface.StatusLine = FString::Printf(
TEXT("Queue recovery keeps continue as the live actionable posture: %s"),
PrimaryActionSurface.AvailabilityLine.IsEmpty()
? TEXT("Continue is actionable")
: *PrimaryActionSurface.AvailabilityLine
);
}
else
{
Surface.StatusLine = FString::Printf(
TEXT("Queue posture is stable; continue remains the live coach action: %s"),
PrimaryActionSurface.AvailabilityLine.IsEmpty()
? TEXT("Continue is actionable")
: *PrimaryActionSurface.AvailabilityLine
);
}
break;
case EHyperTwistTrainingCoachQueuePostureKind::Promote:
Surface.PreferredActionLabel = QueueControlSurface.PromoteActionLabel;
Surface.PreferredActionSourceLabel = QueueControlSurface.PromoteActionSourceLabel;
Surface.bCanExecutePreferredAction = QueueControlSurface.bCanPromote;
Surface.StatusLine = bPrefersPromoteUpstream
? FString::Printf(
TEXT("Queue recovery prefers deferred promotion: %s"),
QueueControlSurface.PromoteAvailabilityLine.IsEmpty()
? TEXT("Promote is actionable")
: *QueueControlSurface.PromoteAvailabilityLine
)
: FString::Printf(
TEXT("Continue is not currently the preferred recovery path, so deferred promotion becomes the live posture: %s"),
QueueControlSurface.PromoteAvailabilityLine.IsEmpty()
? TEXT("Promote is actionable")
: *QueueControlSurface.PromoteAvailabilityLine
);
break;
case EHyperTwistTrainingCoachQueuePostureKind::Defer:
Surface.PreferredActionLabel = QueueControlSurface.DeferActionLabel;
Surface.bCanExecutePreferredAction = QueueControlSurface.bCanDefer;
Surface.StatusLine = FString::Printf(
TEXT("No continue or promote target is currently actionable; defer is the available queue-pressure release control: %s"),
QueueControlSurface.DeferAvailabilityLine.IsEmpty()
? TEXT("Defer is actionable")
: *QueueControlSurface.DeferAvailabilityLine
);
break;
default:
Surface.StatusLine = Surface.bHasRecoveryPressure
? TEXT("Queue recovery pressure is present, but no continue, defer, or promote control is currently actionable.")
: TEXT("Queue recovery pressure is not currently active.");
break;
}
const FString GuidanceLaneLabel = DescribeGuidanceLane(PanelState.PreferredGuidanceLane);
const FString GuidanceSourceLabel = PanelState.PreferredGuidanceSourceLabel.IsEmpty()
? TEXT("n/a")
: HyperTwistTrainingCoachLibraryInternal::DescribeCoachMemorySourceLabel(
PanelState.PreferredGuidanceSourceLabel
);
const FString RecoveryActionSourceLabel = PanelState.PreferredQueueRecoveryActionSourceLabel.IsEmpty()
? TEXT("n/a")
: HyperTwistTrainingCoachLibraryInternal::DescribeCoachMemorySourceLabel(
PanelState.PreferredQueueRecoveryActionSourceLabel
);
Surface.DetailLine = FString::Printf(
TEXT("Upstream: %s | Lane: %s | Guidance source: %s | Recovery action source: %s | Pressure %.2f | Runnable queue: %s | Deferred work: %s | Outstanding deferred: %s | Completion gap: %s | Manual review: %s | Blocked follow-up: %s | Archive-aware: %s | Dominant reason: %s"),
*DescribeRecoveryAction(PanelState.PreferredQueueRecoveryAction),
*GuidanceLaneLabel,
*GuidanceSourceLabel,
*RecoveryActionSourceLabel,
Surface.PressureScore,
PanelState.bHasRunnableQueueEntry ? TEXT("yes") : TEXT("no"),
PanelState.bHasDeferredQueueWork ? TEXT("yes") : TEXT("no"),
QueueExecutionSummary.bHasOutstandingDeferredWork ? TEXT("yes") : TEXT("no"),
QueueExecutionSummary.bHasCompletionGap ? TEXT("yes") : TEXT("no"),
SchedulePolicySummary.bNeedsManualRescheduleReview ? TEXT("yes") : TEXT("no"),
SchedulePolicySummary.bHasBlockedFollowUpDeferrals ? TEXT("yes") : TEXT("no"),
SchedulePolicySummary.bHasArchiveAwareDeferrals ? TEXT("yes") : TEXT("no"),
SchedulePolicySummary.DominantDeferralReasonLabel.IsEmpty()
? TEXT("n/a")
: *SchedulePolicySummary.DominantDeferralReasonLabel
);
if (!PanelState.QueueSuppressionLine.IsEmpty())
{
Surface.DetailLine += FString::Printf(
TEXT(" | Suppression: %s"),
*PanelState.QueueSuppressionLine
);
}
return Surface;
}

View file

@ -451,6 +451,22 @@ UHyperTwistTrainingPanelWidget::GetDisplayedCoachQueueControlSurface() const
return Surface;
}
FHyperTwistTrainingCoachQueueRecoveryPostureSurface
UHyperTwistTrainingPanelWidget::GetDisplayedCoachQueueRecoveryPostureSurface() const
{
const FHyperTwistTrainingCoachPrimaryActionSurface PrimaryActionSurface =
GetDisplayedPrimaryCoachActionSurface();
const FHyperTwistTrainingCoachQueueControlSurface QueueControlSurface =
GetDisplayedCoachQueueControlSurface();
return UHyperTwistTrainingCoachLibrary::DeriveCoachQueueRecoveryPostureSurface(
CachedCoachSchedulePolicySummary,
CachedCoachQueueExecutionSummary,
CachedCoachPanelState,
PrimaryActionSurface,
QueueControlSurface
);
}
FHyperTwistTrainingRunState UHyperTwistTrainingPanelWidget::StartCoachRecommendedRun(
const int32 MaxCases,
const FString& StartActionSourceLabel

View file

@ -432,6 +432,22 @@ AHyperTwistTrainingSessionActor::GetDisplayedCoachQueueControlSurface() const
return Surface;
}
FHyperTwistTrainingCoachQueueRecoveryPostureSurface
AHyperTwistTrainingSessionActor::GetDisplayedCoachQueueRecoveryPostureSurface() const
{
const FHyperTwistTrainingCoachPrimaryActionSurface PrimaryActionSurface =
GetDisplayedPrimaryCoachActionSurface();
const FHyperTwistTrainingCoachQueueControlSurface QueueControlSurface =
GetDisplayedCoachQueueControlSurface();
return UHyperTwistTrainingCoachLibrary::DeriveCoachQueueRecoveryPostureSurface(
CachedCoachSchedulePolicySummary,
CachedCoachQueueExecutionSummary,
CachedCoachPanelState,
PrimaryActionSurface,
QueueControlSurface
);
}
FHyperTwistTrainingRunState AHyperTwistTrainingSessionActor::StartCoachRecommendedTrainingRun(
const int32 MaxCases
)

View file

@ -600,4 +600,14 @@ public:
const FHyperTwistTrainingCoachSessionQueueState& QueueState,
const FHyperTwistTrainingCoachPanelState& PanelState
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
static FHyperTwistTrainingCoachQueueRecoveryPostureSurface
DeriveCoachQueueRecoveryPostureSurface(
const FHyperTwistCoachSchedulePolicySummary& SchedulePolicySummary,
const FHyperTwistTrainingCoachSessionQueueExecutionSummary& QueueExecutionSummary,
const FHyperTwistTrainingCoachPanelState& PanelState,
const FHyperTwistTrainingCoachPrimaryActionSurface& PrimaryActionSurface,
const FHyperTwistTrainingCoachQueueControlSurface& QueueControlSurface
);
};

View file

@ -220,6 +220,10 @@ public:
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
FHyperTwistTrainingCoachQueueControlSurface GetDisplayedCoachQueueControlSurface() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
FHyperTwistTrainingCoachQueueRecoveryPostureSurface
GetDisplayedCoachQueueRecoveryPostureSurface() const;
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach")
FHyperTwistTrainingRunState StartCoachRecommendedRun(
int32 MaxCases,

View file

@ -201,6 +201,10 @@ public:
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
FHyperTwistTrainingCoachQueueControlSurface GetDisplayedCoachQueueControlSurface() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
FHyperTwistTrainingCoachQueueRecoveryPostureSurface
GetDisplayedCoachQueueRecoveryPostureSurface() const;
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach")
FHyperTwistTrainingRunState StartCoachRecommendedTrainingRun(int32 MaxCases);

View file

@ -41,6 +41,15 @@ enum class EHyperTwistTrainingQueueRecoveryAction : uint8
LaunchRecovery UMETA(DisplayName = "Launch Recovery")
};
UENUM(BlueprintType)
enum class EHyperTwistTrainingCoachQueuePostureKind : uint8
{
None UMETA(DisplayName = "None"),
Continue UMETA(DisplayName = "Continue"),
Defer UMETA(DisplayName = "Defer"),
Promote UMETA(DisplayName = "Promote")
};
UENUM(BlueprintType)
enum class EHyperTwistTrainingCoachHandoffKind : uint8
{
@ -7908,3 +7917,53 @@ struct FHyperTwistTrainingCoachQueueControlSurface
|| !PromoteAvailabilityLine.IsEmpty();
}
};
USTRUCT(BlueprintType)
struct FHyperTwistTrainingCoachQueueRecoveryPostureSurface
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString Headline;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString StatusLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString DetailLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString PreferredActionLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString PreferredActionSourceLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
float PressureScore = 0.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
EHyperTwistTrainingCoachQueuePostureKind PostureKind =
EHyperTwistTrainingCoachQueuePostureKind::None;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
EHyperTwistTrainingQueueRecoveryAction PreferredRecoveryAction =
EHyperTwistTrainingQueueRecoveryAction::None;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
EHyperTwistTrainingCoachGuidanceLane PreferredGuidanceLane =
EHyperTwistTrainingCoachGuidanceLane::None;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasRecoveryPressure = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bCanExecutePreferredAction = false;
bool IsStructurallyValid() const
{
return !StatusLine.IsEmpty()
|| !DetailLine.IsEmpty()
|| PostureKind != EHyperTwistTrainingCoachQueuePostureKind::None
|| bHasRecoveryPressure;
}
};

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 the matching thin secondary queue-control descriptor / availability surface for defer and promote flows on those same two consumer surfaces, while also aligning their displayed promote path with the same deferred-entry recovery ranking already used by the dashboard
- the current next bounded packet is to expose one thin queue-recovery posture / status surface on those same two consumer surfaces so gameplay/Blueprint callers can explain why continue, defer, or promote is preferred without reading raw suppression/status strings or recovery enums directly
- the latest landed bounded `Phase 4` packet stays on that same gameplay-facing consumer lane and adds one thin queue-recovery posture / status surface over the already-landed primary CTA and secondary queue-control surfaces, so gameplay/Blueprint callers can explain why continue, defer, or promote is preferred without reading raw suppression/status strings or recovery enums directly
- the current next bounded packet is to expose one thin coach handoff recommendation / continuation status surface on those same two consumer surfaces so gameplay/Blueprint callers can render the queued-vs-follow-up-vs-recommended continuation posture without reaching into raw panel-state strings or handoff enums 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 queue-recovery posture 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 helpers by exposing one pure queue-recovery posture / status surface over the already-landed primary coach CTA and secondary queue-control surfaces.
The open tasks were:
- stop forcing gameplay / Blueprint callers to read raw queue suppression strings, queue status strings, and queue-recovery enums just to explain why continue, defer, or promote is currently preferred
- expose one thin first-party posture surface that summarizes the live queue-recovery pressure, the currently preferred posture, and the upstream recovery provenance
- keep that posture surface derived from already-cached policy, execution, primary CTA, and queue-control state instead of widening into dashboard-only recovery analytics
It is not:
- a new recovery-policy packet
- a dashboard weighting migration
- a broader gameplay HUD composition pass
- a backend continuity or template-analytics packet
## Scope
Bounded lane:
- add a first-party queue posture kind enum and queue-recovery posture/status surface in shared training types
- add a coach-library helper that derives the surface from schedule-policy summary, queue-execution summary, panel state, primary CTA surface, and queue-control surface
- expose that derived posture surface on `HyperTwistTrainingPanelWidget` and `HyperTwistTrainingSessionActor`
- keep the posture derivation presentation-only and rooted in already-cached gameplay consumer state
Out of scope:
- changing queue recovery selection policy
- importing the dashboards deeper learned recovery weighting into gameplay consumer surfaces
- adding new queue action verbs
- widening into handoff recommendation composition in the same packet
## Why this was the right next packet
Before this slice:
- gameplay-facing panel/session consumers already exposed displayed coach-action helpers
- those surfaces already exposed a truthful primary CTA surface
- those surfaces already exposed a truthful secondary queue-control surface for defer and promote flows
But one thin consumer seam was still open:
- callers could bind the actions
- but they still had to inspect raw queue suppression/status text and recovery enums to explain why continue, defer, or promote was the current posture
That meant:
- action binding was thin
- but posture explanation still leaked raw backend-derived strings into callers
So the next bounded move was:
- keep the already-landed execution and control surfaces unchanged
- and expose one narrow posture/status surface above them
## What landed
Primary code changes:
- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h`
- added `EHyperTwistTrainingCoachQueuePostureKind`
- added `FHyperTwistTrainingCoachQueueRecoveryPostureSurface`
- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h`
- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp`
- added `DeriveCoachQueueRecoveryPostureSurface(...)`
- posture is derived from existing schedule-policy, queue-execution, panel-state, primary CTA, and queue-control inputs
- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h`
- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp`
- added `GetDisplayedCoachQueueRecoveryPostureSurface()`
- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h`
- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp`
- added `GetDisplayedCoachQueueRecoveryPostureSurface()`
## Product effect
The gameplay-facing coach consumer layer is now more complete:
- gameplay / Blueprint callers can explain the live queue-recovery posture from one pure surface
- callers no longer need to read raw queue suppression/status strings or queue-recovery enums just to explain why continue, defer, or promote is preferred
- the posture surface stays aligned with the already-landed primary CTA and queue-control surfaces without importing dashboard-only recovery analytics
## Acceptance criteria
- shared training types expose a queue posture kind enum and a narrow queue-recovery posture surface
- coach-library derivation consumes the already-cached gameplay consumer state rather than adding new runtime fetches
- panel-widget callers can fetch the displayed queue-recovery posture surface directly
- session-actor callers can fetch the displayed queue-recovery posture surface directly
- full product build succeeds
## Validation checklist
1. build `UnrealHyperTwist.sln` / `UnrealHyperTwistEditor`
2. confirm the new posture 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
5. confirm no backend continuity or dashboard-only recovery weighting work was pulled into the packet
## Validation result
- full `Development Editor|Win64` build succeeded on `2026-05-07`
- 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 handoff recommendation / continuation status surface on `HyperTwistTrainingPanelWidget` and `HyperTwistTrainingSessionActor`, so gameplay callers can render the queued-vs-follow-up-vs-recommended continuation posture without reaching into raw panel-state strings or handoff enums directly