Expose runtime coach displayed action and queue control helpers

This commit is contained in:
axiomlogicnexus 2026-05-09 03:18:17 +02:00
parent 792c5173ca
commit c60c61545d
4 changed files with 427 additions and 2 deletions

View file

@ -1,6 +1,7 @@
#include "HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.h"
#include "Engine/GameInstance.h"
#include "HyperTwistTraining/HyperTwistTrainingCoachLibrary.h"
#include "HyperTwistTraining/HyperTwistTrainingSubsystem.h"
namespace HyperTwistTrainingRuntimeLibraryInternal
@ -21,6 +22,100 @@ namespace HyperTwistTrainingRuntimeLibraryInternal
UGameInstance* GameInstance = World->GetGameInstance();
return GameInstance != nullptr ? GameInstance->GetSubsystem<UHyperTwistTrainingSubsystem>() : nullptr;
}
FString ResolveCoachHandoffStartActionSourceLabel(
const FHyperTwistTrainingCoachPanelState& PanelState,
const EHyperTwistTrainingCoachHandoffKind HandoffKind,
const FString& FallbackSourceLabel
)
{
if (HandoffKind == EHyperTwistTrainingCoachHandoffKind::Queue
&& !PanelState.PreferredQueueRecoveryActionSourceLabel.IsEmpty())
{
return PanelState.PreferredQueueRecoveryActionSourceLabel;
}
if (PanelState.PreferredCoachHandoffKind == HandoffKind
&& !PanelState.PreferredCoachHandoffSourceLabel.IsEmpty())
{
return PanelState.PreferredCoachHandoffSourceLabel;
}
if ((HandoffKind == EHyperTwistTrainingCoachHandoffKind::FollowUp
|| HandoffKind == EHyperTwistTrainingCoachHandoffKind::Recommended)
&& !PanelState.PreferredGuidanceSourceLabel.IsEmpty())
{
return PanelState.PreferredGuidanceSourceLabel;
}
if (!PanelState.PreferredCoachHandoffSourceLabel.IsEmpty())
{
return PanelState.PreferredCoachHandoffSourceLabel;
}
if (HandoffKind == EHyperTwistTrainingCoachHandoffKind::Queue
&& !PanelState.NextQueueSourceLabel.IsEmpty())
{
return PanelState.NextQueueSourceLabel;
}
if (!PanelState.PreferredLaunchBudgetSourceLabel.IsEmpty())
{
return PanelState.PreferredLaunchBudgetSourceLabel;
}
return FallbackSourceLabel;
}
FString ResolvePrimaryCoachActionStartActionSourceLabel(
const FHyperTwistTrainingCoachPanelState& PanelState,
const FString& FallbackSourceLabel
)
{
if (PanelState.PreferredQueueRecoveryAction != EHyperTwistTrainingQueueRecoveryAction::None)
{
return ResolveCoachHandoffStartActionSourceLabel(
PanelState,
EHyperTwistTrainingCoachHandoffKind::Queue,
FallbackSourceLabel
);
}
if (PanelState.PreferredCoachHandoffKind != EHyperTwistTrainingCoachHandoffKind::None)
{
return ResolveCoachHandoffStartActionSourceLabel(
PanelState,
PanelState.PreferredCoachHandoffKind,
FallbackSourceLabel
);
}
if (!PanelState.PreferredGuidanceSourceLabel.IsEmpty())
{
return PanelState.PreferredGuidanceSourceLabel;
}
if (!PanelState.PreferredLaunchBudgetSourceLabel.IsEmpty())
{
return PanelState.PreferredLaunchBudgetSourceLabel;
}
return FallbackSourceLabel;
}
FString BuildCoachHandoffFallbackSourceLabel(const EHyperTwistTrainingCoachHandoffKind HandoffKind)
{
return FString::Printf(
TEXT("coach-runtime-library-%s"),
HandoffKind == EHyperTwistTrainingCoachHandoffKind::Queue
? TEXT("queue")
: (HandoffKind == EHyperTwistTrainingCoachHandoffKind::FollowUp
? TEXT("follow-up")
: (HandoffKind == EHyperTwistTrainingCoachHandoffKind::Recommended
? TEXT("recommended")
: TEXT("handoff")))
);
}
}
UHyperTwistTrainingSubsystem* UHyperTwistTrainingRuntimeLibrary::GetTrainingSubsystem(UObject* WorldContextObject)
@ -726,6 +821,82 @@ FHyperTwistTrainingCoachSessionQueueExecutionSummary UHyperTwistTrainingRuntimeL
return FHyperTwistTrainingCoachSessionQueueExecutionSummary();
}
FString UHyperTwistTrainingRuntimeLibrary::ResolveActiveTrainingCoachHandoffStartActionSourceLabel(
UObject* WorldContextObject,
const EHyperTwistTrainingCoachHandoffKind HandoffKind
)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return HyperTwistTrainingRuntimeLibraryInternal::ResolveCoachHandoffStartActionSourceLabel(
TrainingSubsystem->GetActiveCoachPanelState(),
HandoffKind,
HyperTwistTrainingRuntimeLibraryInternal::BuildCoachHandoffFallbackSourceLabel(HandoffKind)
);
}
return HyperTwistTrainingRuntimeLibraryInternal::BuildCoachHandoffFallbackSourceLabel(HandoffKind);
}
FString UHyperTwistTrainingRuntimeLibrary::ResolveActiveTrainingPrimaryCoachActionStartActionSourceLabel(
UObject* WorldContextObject
)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return HyperTwistTrainingRuntimeLibraryInternal::ResolvePrimaryCoachActionStartActionSourceLabel(
TrainingSubsystem->GetActiveCoachPanelState(),
TEXT("coach-runtime-library-primary-action")
);
}
return TEXT("coach-runtime-library-primary-action");
}
FHyperTwistTrainingCoachPrimaryActionSurface
UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingCoachPrimaryActionSurface(
UObject* WorldContextObject
)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
FHyperTwistTrainingCoachPrimaryActionSurface Surface =
UHyperTwistTrainingCoachLibrary::DerivePrimaryCoachActionSurface(
TrainingSubsystem->GetActiveCoachPanelState()
);
Surface.ActionSourceLabel = Surface.bCanExecute
? ResolveActiveTrainingPrimaryCoachActionStartActionSourceLabel(WorldContextObject)
: FString();
return Surface;
}
return FHyperTwistTrainingCoachPrimaryActionSurface();
}
FHyperTwistTrainingCoachQueueControlSurface
UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingCoachQueueControlSurface(
UObject* WorldContextObject
)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
FHyperTwistTrainingCoachQueueControlSurface Surface =
UHyperTwistTrainingCoachLibrary::DeriveCoachQueueControlSurface(
TrainingSubsystem->GetActiveCoachSessionQueueState(),
TrainingSubsystem->GetActiveCoachPanelState()
);
Surface.PromoteActionSourceLabel = Surface.bCanPromote
? ResolveActiveTrainingCoachHandoffStartActionSourceLabel(
WorldContextObject,
EHyperTwistTrainingCoachHandoffKind::Queue
)
: FString();
return Surface;
}
return FHyperTwistTrainingCoachQueueControlSurface();
}
TArray<FHyperTwistTrainingMethodSegment> UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingLearnerMethodSegments(UObject* WorldContextObject)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
@ -1129,6 +1300,45 @@ bool UHyperTwistTrainingRuntimeLibrary::PromoteCoachSessionQueueEntry(
return false;
}
bool UHyperTwistTrainingRuntimeLibrary::DeferDisplayedCoachQueueEntry(
UObject* WorldContextObject,
const FString& DeferredUntilUtc,
const FString& DeferralReasonLabel,
const bool bManualReschedule
)
{
const FHyperTwistTrainingCoachQueueControlSurface Surface =
GetActiveTrainingCoachQueueControlSurface(WorldContextObject);
if (!Surface.bCanDefer || Surface.DeferTargetEntryId.IsEmpty())
{
return false;
}
return DeferCoachSessionQueueEntry(
WorldContextObject,
Surface.DeferTargetEntryId,
DeferredUntilUtc,
DeferralReasonLabel,
bManualReschedule
);
}
bool UHyperTwistTrainingRuntimeLibrary::PromoteDisplayedCoachQueueEntry(UObject* WorldContextObject)
{
const FHyperTwistTrainingCoachQueueControlSurface Surface =
GetActiveTrainingCoachQueueControlSurface(WorldContextObject);
if (!Surface.bCanPromote || Surface.PromoteTargetEntryId.IsEmpty())
{
return false;
}
return PromoteCoachSessionQueueEntry(
WorldContextObject,
Surface.PromoteTargetEntryId,
Surface.PromoteActionSourceLabel
);
}
bool UHyperTwistTrainingRuntimeLibrary::RemoveMethodDrillBatch(
UObject* WorldContextObject,
const FString& UserId,
@ -1450,6 +1660,96 @@ FHyperTwistTrainingRunState UHyperTwistTrainingRuntimeLibrary::StartCoachQueueEn
return FHyperTwistTrainingRunState();
}
FHyperTwistTrainingRunState UHyperTwistTrainingRuntimeLibrary::StartDisplayedCoachRecommendedRun(
UObject* WorldContextObject,
const FString& SessionId,
const int32 MaxCases
)
{
return StartCoachRecommendedRun(
WorldContextObject,
SessionId,
MaxCases,
ResolveActiveTrainingCoachHandoffStartActionSourceLabel(
WorldContextObject,
EHyperTwistTrainingCoachHandoffKind::Recommended
)
);
}
FHyperTwistTrainingRunState UHyperTwistTrainingRuntimeLibrary::StartDisplayedCoachFollowUpRun(
UObject* WorldContextObject,
const FString& SessionId,
const int32 MaxCases
)
{
return StartCoachFollowUpRun(
WorldContextObject,
SessionId,
MaxCases,
ResolveActiveTrainingCoachHandoffStartActionSourceLabel(
WorldContextObject,
EHyperTwistTrainingCoachHandoffKind::FollowUp
)
);
}
FHyperTwistTrainingRunState UHyperTwistTrainingRuntimeLibrary::StartDisplayedPrimaryCoachAction(
UObject* WorldContextObject,
const FString& SessionId,
const int32 MaxCases
)
{
return StartPrimaryCoachAction(
WorldContextObject,
SessionId,
MaxCases,
ResolveActiveTrainingPrimaryCoachActionStartActionSourceLabel(WorldContextObject)
);
}
FHyperTwistTrainingRunState UHyperTwistTrainingRuntimeLibrary::StartDisplayedNextQueuedCoachRun(
UObject* WorldContextObject,
const FString& SessionId,
const int32 MaxCases
)
{
return StartNextQueuedCoachRun(
WorldContextObject,
SessionId,
MaxCases,
ResolveActiveTrainingCoachHandoffStartActionSourceLabel(
WorldContextObject,
EHyperTwistTrainingCoachHandoffKind::Queue
)
);
}
FHyperTwistTrainingRunState UHyperTwistTrainingRuntimeLibrary::StartDisplayedCoachQueueEntry(
UObject* WorldContextObject,
const FString& SessionId,
const int32 MaxCases
)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
const FHyperTwistTrainingCoachPanelState PanelState = TrainingSubsystem->GetActiveCoachPanelState();
if (PanelState.ActiveQueueEntryId.IsEmpty())
{
return FHyperTwistTrainingRunState();
}
return StartCoachQueueEntry(
WorldContextObject,
PanelState.ActiveQueueEntryId,
SessionId,
MaxCases
);
}
return FHyperTwistTrainingRunState();
}
FHyperTwistTrainingRunState UHyperTwistTrainingRuntimeLibrary::StartSampleClassicTrainingRun(
UObject* WorldContextObject,
const FString& UserId,

View file

@ -264,6 +264,27 @@ public:
UObject* WorldContextObject
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject"))
static FString ResolveActiveTrainingCoachHandoffStartActionSourceLabel(
UObject* WorldContextObject,
EHyperTwistTrainingCoachHandoffKind HandoffKind
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject"))
static FString ResolveActiveTrainingPrimaryCoachActionStartActionSourceLabel(
UObject* WorldContextObject
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingCoachPrimaryActionSurface GetActiveTrainingCoachPrimaryActionSurface(
UObject* WorldContextObject
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingCoachQueueControlSurface GetActiveTrainingCoachQueueControlSurface(
UObject* WorldContextObject
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
static TArray<FHyperTwistTrainingMethodSegment> GetActiveTrainingLearnerMethodSegments(UObject* WorldContextObject);
@ -445,6 +466,17 @@ public:
const FString& StartActionSourceLabel
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject"))
static bool DeferDisplayedCoachQueueEntry(
UObject* WorldContextObject,
const FString& DeferredUntilUtc,
const FString& DeferralReasonLabel,
bool bManualReschedule
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject"))
static bool PromoteDisplayedCoachQueueEntry(UObject* WorldContextObject);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Drill", meta = (WorldContext = "WorldContextObject"))
static bool RemoveMethodDrillBatch(
UObject* WorldContextObject,
@ -598,6 +630,41 @@ public:
int32 MaxCases
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingRunState StartDisplayedCoachRecommendedRun(
UObject* WorldContextObject,
const FString& SessionId,
int32 MaxCases
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingRunState StartDisplayedCoachFollowUpRun(
UObject* WorldContextObject,
const FString& SessionId,
int32 MaxCases
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingRunState StartDisplayedPrimaryCoachAction(
UObject* WorldContextObject,
const FString& SessionId,
int32 MaxCases
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingRunState StartDisplayedNextQueuedCoachRun(
UObject* WorldContextObject,
const FString& SessionId,
int32 MaxCases
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingRunState StartDisplayedCoachQueueEntry(
UObject* WorldContextObject,
const FString& SessionId,
int32 MaxCases
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingRunState StartSampleClassicTrainingRun(
UObject* WorldContextObject,

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 exposes the already-cached imported generated-mode selection state, launch config, active launch request, latest retained deck launch request, and last execution blocked reason directly on those gameplay consumers, so gameplay/Blueprint callers can inspect imported selector/runtime/request state without reading raw cached properties off the widget or actor instances
- the current next bounded packet should stay on that same gameplay-facing consumer lane only if a real gameplay or Blueprint caller still needs one more thin display-contract getter or wrapper over already-cached imported or coach state; otherwise choose the next roadmap lane deliberately instead of extending consumer widening by drift
- the latest landed bounded `Phase 4` packet deliberately starts the follow-on `UHyperTwistTrainingRuntimeLibrary` world-context consumer lane and adds active coach handoff/primary source-label resolvers, active primary coach CTA and queue-control surfaces, and displayed recommended/follow-up/primary/next-queued/active-queue-entry/defer/promote helpers so gameplay/Blueprint callers can use truthful coach runtime convenience entry points without reconstructing provenance or queue-target routing from raw coach state
- the current next bounded packet should stay on that same runtime-library consumer lane only if a real world-context gameplay or Blueprint caller still needs one more thin derived coach/generated-mode display contract or displayed-action convenience helper over already-cached state; otherwise choose the next roadmap lane deliberately instead of extending runtime-consumer widening by drift
- 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,58 @@
# HyperTwist Phase 4 runtime-library coach displayed-action and queue-control helper packet
- bounded Phase `4` runtime-consumer slice
- date: `2026-05-09`
## Intent
Start the deliberate follow-on `UHyperTwistTrainingRuntimeLibrary` world-context consumer lane above the already-landed coach scheduling and recommendation state.
## Why this packet exists
The panel-widget and session-actor gameplay consumer lane is already far enough along that callers can read truthful displayed coach contracts and execute the common coach actions without stitching together raw cached state. The world-context runtime surface still lagged behind that convenience layer:
- it exposed the low-level coach launch and queue-mutation entry points
- it exposed the raw active coach panel state and queue state
- but it still forced world-context callers to reconstruct source-label routing and displayed queue-target selection for the common displayed-action path
That left runtime-library callers with the same provenance and queue-target glue that the panel/session consumer lane had already removed.
## What changed
- added active coach handoff and primary-action source-label resolvers on `UHyperTwistTrainingRuntimeLibrary`
- added active primary coach CTA and queue-control surfaces on `UHyperTwistTrainingRuntimeLibrary`
- added displayed world-context launch helpers for:
- recommended coach run
- follow-up coach run
- primary coach action
- next queued coach run
- active queue-entry coach run
- added displayed world-context queue helpers for:
- defer displayed queue entry
- promote displayed queue entry
- kept the routing aligned with the same active coach panel-state provenance order already used by `UHyperTwistTrainingPanelWidget` and `AHyperTwistTrainingSessionActor`
## Files
- `C:\HyperTwist\UnrealHyperTwist\Source\UnrealHyperTwist\Public\HyperTwistTraining\HyperTwistTrainingRuntimeLibrary.h`
- `C:\HyperTwist\UnrealHyperTwist\Source\UnrealHyperTwist\Private\HyperTwistTraining\HyperTwistTrainingRuntimeLibrary.cpp`
- `C:\HyperTwist\docs\HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md`
## Outcome
World-context gameplay and Blueprint callers can now use truthful runtime-library coach convenience entry points without reconstructing:
- preferred handoff provenance
- primary-action source-label routing
- active queue-entry selection
- displayed defer/promote queue targets
The backend continuity stays unchanged. This packet only widens the already-stabilized runtime consumer contract.
## Validation
- full `Development Editor | Win64` MSBuild on `UnrealHyperTwist.sln`
## Next clean slice
Stay on this runtime-library consumer lane only if a real world-context caller still needs one more thin derived coach/generated-mode display contract or displayed-action convenience helper over already-cached state. The next best bounded move inside that lane would be the runtime-library queue-recovery posture and handoff-continuation surfaces, because those are the next truthful display contracts above the newly-landed primary CTA and queue-control helpers.