Expose gameplay selector option execution-impact surface

This commit is contained in:
axiomlogicnexus 2026-05-08 03:27:04 +02:00
parent 3521d4ca4d
commit 6c57e4f0ab
9 changed files with 838 additions and 2 deletions

View file

@ -7815,3 +7815,512 @@ UHyperTwistTrainingCoachLibrary::DeriveCoachGeneratedModeSelectorOptionDispatchS
return Surface;
}
FHyperTwistTrainingCoachGeneratedModeSelectorOptionExecutionImpactSurface
UHyperTwistTrainingCoachLibrary::DeriveCoachGeneratedModeSelectorOptionExecutionImpactSurface(
const FHyperTwistTrainingRunState& RunState,
const FString& SelectorId,
const FHyperTwistTrainingCoachGeneratedModeLaunchExecutionContextSurface& LaunchExecutionContextSurface,
const FHyperTwistTrainingCoachGeneratedModeSelectorOptionMenuSurface& OptionMenuSurface,
const FHyperTwistTrainingCoachGeneratedModeSelectorOptionDispatchSurface& OptionDispatchSurface
)
{
FHyperTwistTrainingCoachGeneratedModeSelectorOptionExecutionImpactSurface Surface;
Surface.SelectorId = SelectorId;
Surface.SelectorLabel = OptionMenuSurface.SelectorLabel;
Surface.BindingLabel = OptionMenuSurface.BindingLabel;
Surface.Headline = TEXT("Generated Mode Selector Option Execution Impact");
Surface.bHasSelector = OptionMenuSurface.bHasSelector;
if (!SelectorId.IsEmpty())
{
Surface.Headline = OptionMenuSurface.SelectorLabel.IsEmpty()
? FString::Printf(
TEXT("Generated Mode Selector Option Execution Impact: %s"),
*SelectorId
)
: FString::Printf(
TEXT("Generated Mode Selector Option Execution Impact: %s"),
*OptionMenuSurface.SelectorLabel
);
}
if (SelectorId.IsEmpty())
{
Surface.StatusLine =
TEXT("Generated-mode selector option execution impact requires a selector id from the active selector roster.");
Surface.ExecutionImpactSummaryLine = TEXT("Execution impact: n/a");
Surface.CurrentSelectionExecutionImpactLine = TEXT("Current selection: n/a");
Surface.DetailLine = OptionDispatchSurface.StatusLine.IsEmpty()
? TEXT("Dispatch: n/a")
: FString::Printf(TEXT("Dispatch: %s"), *OptionDispatchSurface.StatusLine);
return Surface;
}
const FHyperTwistTrainingImportedRuntimeSurface& RuntimeSurface = RunState.ActiveDeck.ImportedRuntimeSurface;
const bool bHasGeneratedModeSurface = RuntimeSurface.IsStructurallyValid()
&& RuntimeSurface.SurfaceKind.Equals(TEXT("generated-mode"), ESearchCase::IgnoreCase);
const FHyperTwistTrainingImportedRuntimeSelector* MatchingSelector =
RuntimeSurface.Selectors.FindByPredicate(
[&SelectorId](const FHyperTwistTrainingImportedRuntimeSelector& Candidate)
{
return Candidate.SelectorId == SelectorId && Candidate.IsStructurallyValid();
}
);
const FString SelectorBinding = MatchingSelector != nullptr
? MatchingSelector->SelectorBinding
: OptionMenuSurface.BindingLabel;
const auto DescribeSelector = [](
const FHyperTwistTrainingImportedRuntimeSelector& Selector
) -> FString
{
if (!Selector.Label.IsEmpty())
{
return Selector.Label;
}
if (!Selector.SelectorBinding.IsEmpty())
{
return Selector.SelectorBinding;
}
return Selector.SelectorId;
};
const auto BuildUnresolvedSelectorBlockedReason = [&DescribeSelector, &RuntimeSurface](
const FHyperTwistTrainingImportedRuntimeSelectionState& SelectionState,
int32& OutUnresolvedSelectorCount
) -> FString
{
OutUnresolvedSelectorCount = 0;
FString FirstUnresolvedSelectorLabel;
for (const FHyperTwistTrainingImportedRuntimeSelector& Selector : RuntimeSurface.Selectors)
{
if (!Selector.IsStructurallyValid())
{
continue;
}
const FHyperTwistTrainingImportedRuntimeSelectorChoice* MatchingChoice =
SelectionState.SelectorChoices.FindByPredicate(
[&Selector](const FHyperTwistTrainingImportedRuntimeSelectorChoice& Candidate)
{
return Candidate.SelectorId == Selector.SelectorId
&& Candidate.IsStructurallyValid();
}
);
if (MatchingChoice != nullptr)
{
continue;
}
++OutUnresolvedSelectorCount;
if (FirstUnresolvedSelectorLabel.IsEmpty())
{
FirstUnresolvedSelectorLabel = DescribeSelector(Selector);
}
}
return OutUnresolvedSelectorCount > 0
? FString::Printf(
TEXT("Generated-mode execution is blocked because selector %s is unresolved."),
FirstUnresolvedSelectorLabel.IsEmpty() ? TEXT("n/a") : *FirstUnresolvedSelectorLabel
)
: FString();
};
const auto DoSelectorChoiceSetsMatch = [&RuntimeSurface](
const TArray<FHyperTwistTrainingImportedRuntimeSelectorChoice>& LeftChoices,
const TArray<FHyperTwistTrainingImportedRuntimeSelectorChoice>& RightChoices
) -> bool
{
for (const FHyperTwistTrainingImportedRuntimeSelector& Selector : RuntimeSurface.Selectors)
{
if (!Selector.IsStructurallyValid())
{
continue;
}
const FHyperTwistTrainingImportedRuntimeSelectorChoice* LeftChoice =
LeftChoices.FindByPredicate(
[&Selector](const FHyperTwistTrainingImportedRuntimeSelectorChoice& Candidate)
{
return Candidate.SelectorId == Selector.SelectorId
&& Candidate.IsStructurallyValid();
}
);
const FHyperTwistTrainingImportedRuntimeSelectorChoice* RightChoice =
RightChoices.FindByPredicate(
[&Selector](const FHyperTwistTrainingImportedRuntimeSelectorChoice& Candidate)
{
return Candidate.SelectorId == Selector.SelectorId
&& Candidate.IsStructurallyValid();
}
);
if (LeftChoice == nullptr || RightChoice == nullptr)
{
return false;
}
if (LeftChoice->SelectedValue != RightChoice->SelectedValue)
{
return false;
}
}
return true;
};
const auto DoesLaunchRequestMatchLaunchConfig = [&DoSelectorChoiceSetsMatch](
const FHyperTwistTrainingImportedGeneratedModeLaunchRequest& LaunchRequest,
const FHyperTwistTrainingImportedGeneratedModeLaunchConfig& ExpectedLaunchConfig
) -> bool
{
return LaunchRequest.IsStructurallyValid()
&& LaunchRequest.LaunchConfig.DeckId == ExpectedLaunchConfig.DeckId
&& LaunchRequest.LaunchConfig.SurfaceId == ExpectedLaunchConfig.SurfaceId
&& LaunchRequest.LaunchConfig.RuntimeModeId.Equals(
ExpectedLaunchConfig.RuntimeModeId,
ESearchCase::IgnoreCase
)
&& DoSelectorChoiceSetsMatch(
LaunchRequest.LaunchConfig.SelectorChoices,
ExpectedLaunchConfig.SelectorChoices
);
};
const auto DescribeUnchangedExecutionImpact = [&LaunchExecutionContextSurface]() -> FString
{
if (LaunchExecutionContextSurface.bHasExecutionTarget
&& LaunchExecutionContextSurface.bCanExecute)
{
return LaunchExecutionContextSurface.bWillRefreshLaunchRequestOnExecute
? TEXT("Execution impact: execute posture is unchanged and still refreshes the stored request.")
: TEXT("Execution impact: execute posture is unchanged and still uses the stored request.");
}
if (LaunchExecutionContextSurface.bHasExecutionTarget)
{
return TEXT("Execution impact: execute posture is unchanged and remains blocked.");
}
if (LaunchExecutionContextSurface.bHasLatestLaunchRequest)
{
return TEXT("Execution impact: execute posture is unchanged and no execution target is currently staged.");
}
return TEXT("Execution impact: execute posture is unchanged and no stored request or execution target is currently staged.");
};
if (!RunState.IsStructurallyValid() || !bHasGeneratedModeSurface)
{
Surface.StatusLine = OptionMenuSurface.StatusLine.IsEmpty()
? TEXT("Generated-mode selector option execution impact is not currently available.")
: OptionMenuSurface.StatusLine;
Surface.ExecutionImpactSummaryLine = TEXT("Execution impact: n/a");
Surface.CurrentSelectionExecutionImpactLine = OptionMenuSurface.bHasUnresolvedSelectionDetail
? OptionMenuSurface.UnresolvedSelectionLine
: (OptionMenuSurface.bHasSelection
? TEXT("Current selection remains cached, but no active generated-mode execute context is available.")
: TEXT("Current selection: unresolved"));
Surface.DetailLine = FString::Printf(
TEXT("Dispatch: %s | Execute: %s"),
OptionDispatchSurface.StatusLine.IsEmpty()
? TEXT("n/a")
: *OptionDispatchSurface.StatusLine,
LaunchExecutionContextSurface.StatusLine.IsEmpty()
? TEXT("n/a")
: *LaunchExecutionContextSurface.StatusLine
);
return Surface;
}
for (const FHyperTwistTrainingCoachGeneratedModeSelectorOptionDispatchEntrySurface& DispatchEntry :
OptionDispatchSurface.OptionEntries)
{
FHyperTwistTrainingCoachGeneratedModeSelectorOptionExecutionImpactEntrySurface Entry;
Entry.OptionId = DispatchEntry.OptionId;
Entry.Label = DispatchEntry.Label;
Entry.OptionValue = DispatchEntry.OptionValue;
Entry.bMatchesCurrentSelection = DispatchEntry.bMatchesCurrentSelection;
if (DispatchEntry.DispatchKind
== EHyperTwistTrainingCoachGeneratedModeOptionDispatchKind::DirectCaseLaunch
&& DispatchEntry.bCanDispatch)
{
Entry.ExecutionImpactKind =
EHyperTwistTrainingCoachGeneratedModeOptionExecutionImpactKind::ImmediateOwnedCaseStart;
Entry.ImpactLine = TEXT("Execution impact: starts a direct owned case immediately.");
Entry.DetailLine = DispatchEntry.DetailLine.IsEmpty()
? TEXT("Direct option launch bypasses generated-mode execute for this interaction.")
: DispatchEntry.DetailLine;
Entry.bStartsOwnedCaseImmediately = true;
Entry.bCanApplyImpact = true;
++Surface.ImmediateOwnedCaseStartOptionCount;
}
else if (DispatchEntry.DispatchKind
== EHyperTwistTrainingCoachGeneratedModeOptionDispatchKind::ApplyGeneratedChoice
&& DispatchEntry.bCanDispatch && MatchingSelector != nullptr)
{
FHyperTwistTrainingImportedRuntimeSelectionState HypotheticalSelectionState =
RunState.ImportedRuntimeSelectionState;
if (!HypotheticalSelectionState.IsStructurallyValid())
{
HypotheticalSelectionState.SurfaceId = RuntimeSurface.SurfaceId;
HypotheticalSelectionState.SurfaceKind = RuntimeSurface.SurfaceKind;
}
const FString SelectedValue = DispatchEntry.GeneratedChoiceSelectedValue.IsEmpty()
? (DispatchEntry.OptionValue.IsEmpty()
? DispatchEntry.OptionId
: DispatchEntry.OptionValue)
: DispatchEntry.GeneratedChoiceSelectedValue;
const FString DisplayValue = DispatchEntry.GeneratedChoiceDisplayValue.IsEmpty()
? (DispatchEntry.Label.IsEmpty() ? SelectedValue : DispatchEntry.Label)
: DispatchEntry.GeneratedChoiceDisplayValue;
FHyperTwistTrainingImportedRuntimeSelectorChoice* ExistingChoice =
HypotheticalSelectionState.SelectorChoices.FindByPredicate(
[&SelectorId](const FHyperTwistTrainingImportedRuntimeSelectorChoice& Candidate)
{
return Candidate.SelectorId == SelectorId;
}
);
if (ExistingChoice != nullptr)
{
ExistingChoice->SelectorBinding = SelectorBinding;
ExistingChoice->SelectedValue = SelectedValue;
ExistingChoice->DisplayValue = DisplayValue.IsEmpty() ? SelectedValue : DisplayValue;
}
else
{
FHyperTwistTrainingImportedRuntimeSelectorChoice Choice;
Choice.SelectorId = SelectorId;
Choice.SelectorBinding = SelectorBinding;
Choice.SelectedValue = SelectedValue;
Choice.DisplayValue = DisplayValue.IsEmpty() ? SelectedValue : DisplayValue;
HypotheticalSelectionState.SelectorChoices.Add(MoveTemp(Choice));
}
int32 UnresolvedSelectorCount = 0;
Entry.BlockedReasonLine = BuildUnresolvedSelectorBlockedReason(
HypotheticalSelectionState,
UnresolvedSelectorCount
);
if (!Entry.BlockedReasonLine.IsEmpty())
{
Entry.ExecutionImpactKind =
EHyperTwistTrainingCoachGeneratedModeOptionExecutionImpactKind::ExecuteBlocked;
Entry.ImpactLine =
TEXT("Execution impact: applies the selector choice, but execute remains blocked.");
Entry.DetailLine = FString::Printf(
TEXT("Choice apply: %s | %d selector%s still need resolution before generated-mode execute."),
DispatchEntry.DetailLine.IsEmpty() ? TEXT("n/a") : *DispatchEntry.DetailLine,
UnresolvedSelectorCount,
UnresolvedSelectorCount == 1 ? TEXT("") : TEXT("s")
);
Entry.bLeavesExecuteBlocked = true;
Entry.bCanApplyImpact = true;
++Surface.BlockedExecuteOptionCount;
}
else
{
FHyperTwistTrainingImportedGeneratedModeLaunchConfig HypotheticalLaunchConfig;
if (!UHyperTwistTrainingCatalogLibrary::TryBuildImportedGeneratedModeLaunchConfig(
RunState.ActiveDeck,
HypotheticalSelectionState,
RunState.Session,
HypotheticalLaunchConfig))
{
Entry.ExecutionImpactKind =
EHyperTwistTrainingCoachGeneratedModeOptionExecutionImpactKind::ExecuteBlocked;
Entry.ImpactLine =
TEXT("Execution impact: applies the selector choice, but execute remains blocked.");
Entry.BlockedReasonLine =
TEXT("Generated-mode execution could not derive a structurally valid launch request from the resulting selector state.");
Entry.DetailLine = FString::Printf(
TEXT("Choice apply: %s | The resulting selector state is not executable yet."),
DispatchEntry.DetailLine.IsEmpty() ? TEXT("n/a") : *DispatchEntry.DetailLine
);
Entry.bLeavesExecuteBlocked = true;
Entry.bCanApplyImpact = true;
++Surface.BlockedExecuteOptionCount;
}
else
{
const bool bWouldUseStoredRequest = DoesLaunchRequestMatchLaunchConfig(
RunState.ImportedGeneratedModeLaunchRequest,
HypotheticalLaunchConfig
);
if (DispatchEntry.bMatchesCurrentSelection)
{
Entry.ExecutionImpactKind =
EHyperTwistTrainingCoachGeneratedModeOptionExecutionImpactKind::ExecutePostureUnchanged;
Entry.ImpactLine = DescribeUnchangedExecutionImpact();
Entry.BlockedReasonLine = LaunchExecutionContextSurface.bHasBlockedExecution
? LaunchExecutionContextSurface.BlockedReasonLine
: FString();
Entry.DetailLine = LaunchExecutionContextSurface.StatusLine.IsEmpty()
? TEXT("Current generated-mode execute posture remains unchanged.")
: FString::Printf(
TEXT("Current generated-mode execute posture: %s"),
*LaunchExecutionContextSurface.StatusLine
);
Entry.bLeavesExecutePostureUnchanged = true;
Entry.bExecutesFromStoredRequest =
LaunchExecutionContextSurface.bHasExecutionTarget
&& LaunchExecutionContextSurface.bCanExecute
&& !LaunchExecutionContextSurface.bWillRefreshLaunchRequestOnExecute;
Entry.bRequiresLaunchRequestRefreshBeforeExecute =
LaunchExecutionContextSurface.bWillRefreshLaunchRequestOnExecute;
Entry.bLeavesExecuteBlocked =
LaunchExecutionContextSurface.bHasExecutionTarget
&& !LaunchExecutionContextSurface.bCanExecute;
Entry.bCanApplyImpact = true;
++Surface.UnchangedExecutePostureOptionCount;
}
else if (bWouldUseStoredRequest)
{
Entry.ExecutionImpactKind =
EHyperTwistTrainingCoachGeneratedModeOptionExecutionImpactKind::ExecuteFromStoredRequest;
Entry.ImpactLine = TEXT("Execution impact: generated-mode execute can use the stored request after this choice.");
Entry.DetailLine = FString::Printf(
TEXT("Choice apply: %s | The resulting selector state realigns with the current stored request."),
DispatchEntry.DetailLine.IsEmpty() ? TEXT("n/a") : *DispatchEntry.DetailLine
);
Entry.bExecutesFromStoredRequest = true;
Entry.bCanApplyImpact = true;
++Surface.ExecuteFromStoredRequestOptionCount;
}
else
{
Entry.ExecutionImpactKind =
EHyperTwistTrainingCoachGeneratedModeOptionExecutionImpactKind::LaunchRequestRefreshRequired;
Entry.ImpactLine = TEXT("Execution impact: generated-mode execute will require launch-request refresh after this choice.");
Entry.DetailLine = FString::Printf(
TEXT("Choice apply: %s | The resulting selector state no longer matches the current stored request."),
DispatchEntry.DetailLine.IsEmpty() ? TEXT("n/a") : *DispatchEntry.DetailLine
);
Entry.bRequiresLaunchRequestRefreshBeforeExecute = true;
Entry.bCanApplyImpact = true;
++Surface.RefreshRequiredOptionCount;
}
}
}
}
else
{
Entry.ExecutionImpactKind =
EHyperTwistTrainingCoachGeneratedModeOptionExecutionImpactKind::Unavailable;
Entry.ImpactLine = TEXT("Execution impact: unavailable.");
Entry.DetailLine = TEXT("Execution-impact preview is unavailable because this selector option cannot dispatch from the active generated-mode surface.");
Entry.bCanApplyImpact = false;
++Surface.UnavailableOptionCount;
}
if (Entry.bMatchesCurrentSelection)
{
Surface.bCurrentSelectionStartsOwnedCaseImmediately = Entry.bStartsOwnedCaseImmediately;
Surface.bCurrentSelectionLeavesExecutePostureUnchanged =
Entry.bLeavesExecutePostureUnchanged;
Surface.bCurrentSelectionRequiresLaunchRequestRefreshBeforeExecute =
Entry.bRequiresLaunchRequestRefreshBeforeExecute;
Surface.bCurrentSelectionLeavesExecuteBlocked = Entry.bLeavesExecuteBlocked;
Surface.CurrentSelectionExecutionImpactLine = Entry.bLeavesExecuteBlocked
&& !Entry.BlockedReasonLine.IsEmpty()
? FString::Printf(TEXT("%s %s"), *Entry.ImpactLine, *Entry.BlockedReasonLine)
: Entry.ImpactLine;
}
Surface.OptionEntries.Add(MoveTemp(Entry));
}
Surface.bHasExecutionImpactOptions = Surface.OptionEntries.Num() > 0;
Surface.bHasImmediateOwnedCaseStartOptions =
Surface.ImmediateOwnedCaseStartOptionCount > 0;
Surface.bHasStoredRequestExecutionOptions =
Surface.ExecuteFromStoredRequestOptionCount > 0;
Surface.bHasRefreshRequiredOptions = Surface.RefreshRequiredOptionCount > 0;
Surface.bHasBlockedExecuteOptions = Surface.BlockedExecuteOptionCount > 0;
Surface.ExecutionImpactSummaryLine = Surface.bHasExecutionImpactOptions
? FString::Printf(
TEXT("Execution impact: %d direct-start | %d stored-request | %d unchanged | %d refresh-required | %d blocked | %d unavailable"),
Surface.ImmediateOwnedCaseStartOptionCount,
Surface.ExecuteFromStoredRequestOptionCount,
Surface.UnchangedExecutePostureOptionCount,
Surface.RefreshRequiredOptionCount,
Surface.BlockedExecuteOptionCount,
Surface.UnavailableOptionCount
)
: TEXT("Execution impact: no imported options");
if (Surface.CurrentSelectionExecutionImpactLine.IsEmpty())
{
if (OptionMenuSurface.bHasUnresolvedSelectionDetail)
{
Surface.CurrentSelectionExecutionImpactLine = OptionMenuSurface.UnresolvedSelectionLine;
}
else if (OptionMenuSurface.bHasSelection)
{
Surface.CurrentSelectionExecutionImpactLine =
TEXT("Current selection has no additional execution-impact detail.");
}
else if (OptionMenuSurface.bHasSelector)
{
Surface.CurrentSelectionExecutionImpactLine = TEXT("Current selection: unresolved");
}
else
{
Surface.CurrentSelectionExecutionImpactLine = TEXT("Current selection: n/a");
}
}
if (OptionMenuSurface.bHasSelector && Surface.bHasExecutionImpactOptions)
{
Surface.StatusLine = FString::Printf(
TEXT("Generated-mode selector option execution impact exposes %d direct-start option%s, %d stored-request option%s, %d unchanged execute option%s, %d refresh-required option%s, %d blocked option%s, and %d unavailable option%s."),
Surface.ImmediateOwnedCaseStartOptionCount,
Surface.ImmediateOwnedCaseStartOptionCount == 1 ? TEXT("") : TEXT("s"),
Surface.ExecuteFromStoredRequestOptionCount,
Surface.ExecuteFromStoredRequestOptionCount == 1 ? TEXT("") : TEXT("s"),
Surface.UnchangedExecutePostureOptionCount,
Surface.UnchangedExecutePostureOptionCount == 1 ? TEXT("") : TEXT("s"),
Surface.RefreshRequiredOptionCount,
Surface.RefreshRequiredOptionCount == 1 ? TEXT("") : TEXT("s"),
Surface.BlockedExecuteOptionCount,
Surface.BlockedExecuteOptionCount == 1 ? TEXT("") : TEXT("s"),
Surface.UnavailableOptionCount,
Surface.UnavailableOptionCount == 1 ? TEXT("") : TEXT("s")
);
}
else if (OptionMenuSurface.bHasSelector)
{
Surface.StatusLine =
TEXT("Generated-mode selector option execution impact is active, but this selector does not expose imported options.");
}
else
{
Surface.StatusLine = OptionMenuSurface.StatusLine.IsEmpty()
? TEXT("Generated-mode selector option execution impact is not currently available.")
: OptionMenuSurface.StatusLine;
}
const FString SelectorSummary = Surface.SelectorLabel.IsEmpty()
? (Surface.SelectorId.IsEmpty() ? FString(TEXT("n/a")) : Surface.SelectorId)
: Surface.SelectorLabel;
const FString BindingSummary = Surface.BindingLabel.IsEmpty()
? FString(TEXT("n/a"))
: Surface.BindingLabel;
Surface.DetailLine = FString::Printf(
TEXT("Dispatch: %s | Execute: %s | Selector: %s | Binding: %s"),
OptionDispatchSurface.StatusLine.IsEmpty()
? TEXT("n/a")
: *OptionDispatchSurface.StatusLine,
LaunchExecutionContextSurface.StatusLine.IsEmpty()
? TEXT("n/a")
: *LaunchExecutionContextSurface.StatusLine,
*SelectorSummary,
*BindingSummary
);
return Surface;
}

View file

@ -750,6 +750,27 @@ UHyperTwistTrainingPanelWidget::GetDisplayedCoachGeneratedModeSelectorOptionDisp
);
}
FHyperTwistTrainingCoachGeneratedModeSelectorOptionExecutionImpactSurface
UHyperTwistTrainingPanelWidget::GetDisplayedCoachGeneratedModeSelectorOptionExecutionImpactSurface(
const FString& SelectorId
) const
{
const FHyperTwistTrainingCoachGeneratedModeLaunchExecutionContextSurface
LaunchExecutionContextSurface =
GetDisplayedCoachGeneratedModeLaunchExecutionContextSurface();
const FHyperTwistTrainingCoachGeneratedModeSelectorOptionMenuSurface OptionMenuSurface =
GetDisplayedCoachGeneratedModeSelectorOptionMenuSurface(SelectorId);
const FHyperTwistTrainingCoachGeneratedModeSelectorOptionDispatchSurface OptionDispatchSurface =
GetDisplayedCoachGeneratedModeSelectorOptionDispatchSurface(SelectorId);
return UHyperTwistTrainingCoachLibrary::DeriveCoachGeneratedModeSelectorOptionExecutionImpactSurface(
CachedRunState,
SelectorId,
LaunchExecutionContextSurface,
OptionMenuSurface,
OptionDispatchSurface
);
}
FHyperTwistTrainingRunState UHyperTwistTrainingPanelWidget::StartCoachRecommendedRun(
const int32 MaxCases,
const FString& StartActionSourceLabel

View file

@ -731,6 +731,27 @@ AHyperTwistTrainingSessionActor::GetDisplayedCoachGeneratedModeSelectorOptionDis
);
}
FHyperTwistTrainingCoachGeneratedModeSelectorOptionExecutionImpactSurface
AHyperTwistTrainingSessionActor::GetDisplayedCoachGeneratedModeSelectorOptionExecutionImpactSurface(
const FString& SelectorId
) const
{
const FHyperTwistTrainingCoachGeneratedModeLaunchExecutionContextSurface
LaunchExecutionContextSurface =
GetDisplayedCoachGeneratedModeLaunchExecutionContextSurface();
const FHyperTwistTrainingCoachGeneratedModeSelectorOptionMenuSurface OptionMenuSurface =
GetDisplayedCoachGeneratedModeSelectorOptionMenuSurface(SelectorId);
const FHyperTwistTrainingCoachGeneratedModeSelectorOptionDispatchSurface OptionDispatchSurface =
GetDisplayedCoachGeneratedModeSelectorOptionDispatchSurface(SelectorId);
return UHyperTwistTrainingCoachLibrary::DeriveCoachGeneratedModeSelectorOptionExecutionImpactSurface(
CachedRunState,
SelectorId,
LaunchExecutionContextSurface,
OptionMenuSurface,
OptionDispatchSurface
);
}
FHyperTwistTrainingRunState AHyperTwistTrainingSessionActor::StartCoachRecommendedTrainingRun(
const int32 MaxCases
)

View file

@ -713,4 +713,15 @@ public:
const FHyperTwistTrainingCoachGeneratedModeSelectorOptionActionabilitySurface&
OptionActionabilitySurface
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
static FHyperTwistTrainingCoachGeneratedModeSelectorOptionExecutionImpactSurface
DeriveCoachGeneratedModeSelectorOptionExecutionImpactSurface(
const FHyperTwistTrainingRunState& RunState,
const FString& SelectorId,
const FHyperTwistTrainingCoachGeneratedModeLaunchExecutionContextSurface&
LaunchExecutionContextSurface,
const FHyperTwistTrainingCoachGeneratedModeSelectorOptionMenuSurface& OptionMenuSurface,
const FHyperTwistTrainingCoachGeneratedModeSelectorOptionDispatchSurface& OptionDispatchSurface
);
};

View file

@ -268,6 +268,10 @@ public:
FHyperTwistTrainingCoachGeneratedModeSelectorOptionDispatchSurface
GetDisplayedCoachGeneratedModeSelectorOptionDispatchSurface(const FString& SelectorId) const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
FHyperTwistTrainingCoachGeneratedModeSelectorOptionExecutionImpactSurface
GetDisplayedCoachGeneratedModeSelectorOptionExecutionImpactSurface(const FString& SelectorId) const;
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach")
FHyperTwistTrainingRunState StartCoachRecommendedRun(
int32 MaxCases,

View file

@ -249,6 +249,10 @@ public:
FHyperTwistTrainingCoachGeneratedModeSelectorOptionDispatchSurface
GetDisplayedCoachGeneratedModeSelectorOptionDispatchSurface(const FString& SelectorId) const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
FHyperTwistTrainingCoachGeneratedModeSelectorOptionExecutionImpactSurface
GetDisplayedCoachGeneratedModeSelectorOptionExecutionImpactSurface(const FString& SelectorId) const;
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach")
FHyperTwistTrainingRunState StartCoachRecommendedTrainingRun(int32 MaxCases);

View file

@ -80,6 +80,18 @@ enum class EHyperTwistTrainingCoachGeneratedModeOptionDispatchKind : uint8
Unavailable UMETA(DisplayName = "Unavailable")
};
UENUM(BlueprintType)
enum class EHyperTwistTrainingCoachGeneratedModeOptionExecutionImpactKind : uint8
{
None UMETA(DisplayName = "None"),
ImmediateOwnedCaseStart UMETA(DisplayName = "Immediate Owned Case Start"),
ExecuteFromStoredRequest UMETA(DisplayName = "Execute From Stored Request"),
ExecutePostureUnchanged UMETA(DisplayName = "Execute Posture Unchanged"),
LaunchRequestRefreshRequired UMETA(DisplayName = "Launch-Request Refresh Required"),
ExecuteBlocked UMETA(DisplayName = "Execute Blocked"),
Unavailable UMETA(DisplayName = "Unavailable")
};
UENUM(BlueprintType)
enum class EHyperTwistTrainingPromptKind : uint8
{
@ -8903,3 +8915,153 @@ struct FHyperTwistTrainingCoachGeneratedModeSelectorOptionDispatchSurface
|| bHasDispatchOptions;
}
};
USTRUCT(BlueprintType)
struct FHyperTwistTrainingCoachGeneratedModeSelectorOptionExecutionImpactEntrySurface
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString OptionId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString Label;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString OptionValue;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
EHyperTwistTrainingCoachGeneratedModeOptionExecutionImpactKind ExecutionImpactKind =
EHyperTwistTrainingCoachGeneratedModeOptionExecutionImpactKind::None;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString ImpactLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString BlockedReasonLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString DetailLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bMatchesCurrentSelection = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bStartsOwnedCaseImmediately = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bExecutesFromStoredRequest = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bLeavesExecutePostureUnchanged = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bRequiresLaunchRequestRefreshBeforeExecute = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bLeavesExecuteBlocked = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bCanApplyImpact = false;
bool IsStructurallyValid() const
{
return !OptionId.IsEmpty()
|| !Label.IsEmpty()
|| !ImpactLine.IsEmpty()
|| ExecutionImpactKind
!= EHyperTwistTrainingCoachGeneratedModeOptionExecutionImpactKind::None
|| bCanApplyImpact;
}
};
USTRUCT(BlueprintType)
struct FHyperTwistTrainingCoachGeneratedModeSelectorOptionExecutionImpactSurface
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString SelectorId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString SelectorLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString BindingLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString Headline;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString StatusLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString ExecutionImpactSummaryLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString CurrentSelectionExecutionImpactLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString DetailLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FHyperTwistTrainingCoachGeneratedModeSelectorOptionExecutionImpactEntrySurface>
OptionEntries;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 ImmediateOwnedCaseStartOptionCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 ExecuteFromStoredRequestOptionCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 UnchangedExecutePostureOptionCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 RefreshRequiredOptionCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 BlockedExecuteOptionCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 UnavailableOptionCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasSelector = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasExecutionImpactOptions = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasImmediateOwnedCaseStartOptions = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasStoredRequestExecutionOptions = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasRefreshRequiredOptions = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasBlockedExecuteOptions = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bCurrentSelectionStartsOwnedCaseImmediately = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bCurrentSelectionLeavesExecutePostureUnchanged = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bCurrentSelectionRequiresLaunchRequestRefreshBeforeExecute = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bCurrentSelectionLeavesExecuteBlocked = false;
bool IsStructurallyValid() const
{
return !StatusLine.IsEmpty()
|| !SelectorId.IsEmpty()
|| OptionEntries.Num() > 0
|| bHasSelector
|| bHasExecutionImpactOptions;
}
};

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 generated-mode selector option interaction / dispatch surface over the already-cached selector/runtime state, so gameplay/Blueprint callers can render whether choosing an option should launch a direct owned case, apply a generated-mode selector choice, or stay unavailable without reaching into raw runtime action-path rules directly
- the current next bounded packet is to expose one thin generated-mode selector option execution-impact / launch-request-refresh surface on those same two consumer surfaces so gameplay/Blueprint callers can render whether a choice would start an owned case immediately, leave generated-mode execute posture unchanged, or require launch-request refresh before execute without reading raw launch-request freshness or blocked-reason rules directly
- the latest landed bounded `Phase 4` packet stays on that same gameplay-facing consumer lane and adds one thin generated-mode selector option execution-impact / launch-request-refresh surface over the already-cached selector/runtime state, so gameplay/Blueprint callers can render whether a choice would start a direct owned case immediately, leave generated-mode execute posture unchanged, realign with the stored request, require launch-request refresh before execute, or still leave execute blocked without reading raw launch-request freshness or blocked-reason rules directly
- the current next bounded packet is to expose one thin generated-mode selector option request-alignment / execute-preview surface on those same two consumer surfaces so gameplay/Blueprint callers can render hypothetical selector summary, stored-request alignment, and unresolved-selector preview for a chosen option without diffing cached selector choices or launch-request payloads 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,104 @@
# HyperTwist Phase 4 gameplay generated-mode selector option execution-impact / launch-request-refresh 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 generated-mode selector option interaction / dispatch surface by exposing one parameterized option execution-impact surface for a chosen selector id.
The open tasks were:
- stop forcing gameplay / Blueprint callers to reconstruct launch-request freshness and blocked-execute rules just to explain what choosing an imported option would do to generated-mode execute posture
- expose whether an option would start a direct owned case immediately, keep generated-mode execute posture unchanged, realign with the stored request, require a fresh launch request before execute, or still leave execute blocked
- keep the surface derived from already-cached gameplay consumer run, selector, and request state plus the already-landed launch-execution, option-detail, and option-dispatch surfaces instead of widening into backend logic
It is not:
- a new selector-dispatch packet
- a new launch-request creation packet
- a generated-mode backend behavior change
- a broader gameplay UI composition pass
## Scope
Bounded lane:
- add first-party generated-mode selector option execution-impact surfaces in shared training types
- add a coach-library helper that derives execution-impact posture from the cached run state plus the already-landed launch-execution, option-detail, and option-dispatch surfaces
- expose that parameterized execution-impact surface on `HyperTwistTrainingPanelWidget` and `HyperTwistTrainingSessionActor`
- keep the derivation presentation-only and rooted in the already-owned selector-choice / launch-request / execute rules
Out of scope:
- changing selector-choice apply behavior
- changing direct owned-case option-launch behavior
- changing generated-mode execute or launch-request persistence behavior
- widening into higher-level UI composition or dashboard-only work
## Why this was the right next packet
Before this slice:
- gameplay-facing panel/session consumers already exposed truthful generated-mode launch / execution context
- those same consumers already exposed truthful chosen-selector option detail
- those same consumers already exposed truthful chosen-selector option dispatch posture
But one thin consumer seam was still open:
- callers could tell how an option dispatches
- but they still had to know raw launch-request freshness and blocked-execute rules to explain what that option would do to generated-mode execute posture
That meant:
- dispatch posture was thin and reusable
- but execute-impact posture still leaked runtime freshness/blocking rules into callers
So the next bounded move was:
- keep the already-landed generated-mode execute context, option-detail, and option-dispatch surfaces unchanged
- and expose one narrow option execution-impact / launch-request-refresh surface above those already-cached surfaces
## What landed
Primary code changes:
- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h`
- added `EHyperTwistTrainingCoachGeneratedModeOptionExecutionImpactKind`
- added `FHyperTwistTrainingCoachGeneratedModeSelectorOptionExecutionImpactEntrySurface`
- added `FHyperTwistTrainingCoachGeneratedModeSelectorOptionExecutionImpactSurface`
- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h`
- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp`
- added `DeriveCoachGeneratedModeSelectorOptionExecutionImpactSurface(...)`
- execution-impact posture is derived from the cached run state plus the already-landed launch-execution, option-detail, and option-dispatch surfaces
- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h`
- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp`
- added `GetDisplayedCoachGeneratedModeSelectorOptionExecutionImpactSurface(const FString& SelectorId)`
- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h`
- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp`
- added `GetDisplayedCoachGeneratedModeSelectorOptionExecutionImpactSurface(const FString& SelectorId)`
Surface behavior:
- reports whether each imported option starts a direct owned case immediately, realigns with the stored request, leaves current execute posture unchanged, requires launch-request refresh before execute, or still leaves execute blocked
- exposes blocked-reason preview for options that still leave generated-mode execute unresolved
- reports how the current cached selection would affect generated-mode execute right now
- keeps the surface presentation-only and compatible with the already-landed generated-mode launch / execution, option-detail, and option-dispatch surfaces
## Validation
Validation target:
- full `Development Editor|Win64` build of `C:\HyperTwist\UnrealHyperTwist\UnrealHyperTwist.sln`
Result:
- passed with `0` warnings and `0` errors
## Follow-on
The next bounded packet on this same gameplay-facing consumer lane should stay thin and expose one generated-mode selector option request-alignment / execute-preview surface so callers can render hypothetical selector summary, stored-request alignment, and unresolved-selector preview for a chosen option without diffing cached selector choices or launch-request payloads directly.