Expose gameplay selector launch-request preview surface

This commit is contained in:
axiomlogicnexus 2026-05-08 04:48:06 +02:00
parent 71d29c331c
commit 8fff384597
9 changed files with 1292 additions and 2 deletions

View file

@ -9591,3 +9591,875 @@ UHyperTwistTrainingCoachLibrary::DeriveCoachGeneratedModeSelectorOptionStoredReq
return Surface;
}
FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface
UHyperTwistTrainingCoachLibrary::DeriveCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface(
const FHyperTwistTrainingRunState& RunState,
const FString& SelectorId,
const FHyperTwistTrainingCoachGeneratedModeSelectorOptionMenuSurface& OptionMenuSurface,
const FHyperTwistTrainingCoachGeneratedModeSelectorOptionDispatchSurface& OptionDispatchSurface,
const FHyperTwistTrainingCoachGeneratedModeSelectorOptionRequestAlignmentSurface&
OptionRequestAlignmentSurface
)
{
FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface Surface;
Surface.SelectorId = SelectorId;
Surface.SelectorLabel = OptionMenuSurface.SelectorLabel;
Surface.BindingLabel = OptionMenuSurface.BindingLabel;
Surface.Headline = TEXT("Generated Mode Selector Option Launch-Request Preview");
Surface.bHasSelector = OptionMenuSurface.bHasSelector;
if (!SelectorId.IsEmpty())
{
Surface.Headline = OptionMenuSurface.SelectorLabel.IsEmpty()
? FString::Printf(
TEXT("Generated Mode Selector Option Launch-Request Preview: %s"),
*SelectorId
)
: FString::Printf(
TEXT("Generated Mode Selector Option Launch-Request Preview: %s"),
*OptionMenuSurface.SelectorLabel
);
}
if (SelectorId.IsEmpty())
{
Surface.StatusLine =
TEXT("Generated-mode selector option launch-request preview requires a selector id from the active selector roster.");
Surface.PayloadSummaryLine = TEXT("Payload preview: n/a");
Surface.StoredRequestReuseSummaryLine = TEXT("Stored request reuse: n/a");
Surface.RefreshPreviewSummaryLine = TEXT("Refresh preview: n/a");
Surface.CurrentSelectionPayloadLine = TEXT("Current selection: n/a");
Surface.DetailLine = OptionRequestAlignmentSurface.StatusLine.IsEmpty()
? TEXT("Request alignment: n/a")
: FString::Printf(
TEXT("Request alignment: %s"),
*OptionRequestAlignmentSurface.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 FHyperTwistTrainingImportedGeneratedModeLaunchRequest& LaunchRequest =
RunState.ImportedGeneratedModeLaunchRequest;
const bool bHasComparableStoredRequest = LaunchRequest.IsStructurallyValid()
&& LaunchRequest.LaunchConfig.DeckId == RunState.ActiveDeck.DeckId
&& LaunchRequest.LaunchConfig.SurfaceId == RuntimeSurface.SurfaceId
&& LaunchRequest.LaunchConfig.RuntimeModeId.Equals(
RuntimeSurface.RuntimeModeId,
ESearchCase::IgnoreCase
);
const FHyperTwistTrainingCase* SeedCase = RunState.ActiveDeck.Cases.FindByPredicate(
[](const FHyperTwistTrainingCase& Candidate)
{
return Candidate.IsStructurallyValid();
}
);
const FString FallbackPuzzleId = SeedCase != nullptr ? SeedCase->PuzzleId : FString();
const auto DescribeDeliveryMode = [](
const EHyperTwistTrainingDeliveryMode DeliveryMode
) -> FString
{
switch (DeliveryMode)
{
case EHyperTwistTrainingDeliveryMode::Timer:
return TEXT("Timer");
case EHyperTwistTrainingDeliveryMode::VirtualCube:
return TEXT("Virtual Cube");
case EHyperTwistTrainingDeliveryMode::SmartDevice:
return TEXT("Smart Device");
case EHyperTwistTrainingDeliveryMode::RecognitionAssisted:
return TEXT("Recognition Assisted");
case EHyperTwistTrainingDeliveryMode::CoachReviewed:
return TEXT("Coach Reviewed");
default:
return TEXT("Unknown");
}
};
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 FindSelectorChoice = [](
const TArray<FHyperTwistTrainingImportedRuntimeSelectorChoice>& Choices,
const FString& InSelectorId
) -> const FHyperTwistTrainingImportedRuntimeSelectorChoice*
{
return Choices.FindByPredicate(
[&InSelectorId](const FHyperTwistTrainingImportedRuntimeSelectorChoice& Candidate)
{
return Candidate.SelectorId == InSelectorId && Candidate.IsStructurallyValid();
}
);
};
const auto DoChoicesMatch = [](
const FHyperTwistTrainingImportedRuntimeSelectorChoice* LeftChoice,
const FHyperTwistTrainingImportedRuntimeSelectorChoice* RightChoice
) -> bool
{
if (LeftChoice == nullptr || RightChoice == nullptr)
{
return LeftChoice == nullptr && RightChoice == nullptr;
}
return LeftChoice->SelectedValue == RightChoice->SelectedValue;
};
const auto DoSelectorChoiceSetsMatch = [&RuntimeSurface, &FindSelectorChoice](
const TArray<FHyperTwistTrainingImportedRuntimeSelectorChoice>& LeftChoices,
const TArray<FHyperTwistTrainingImportedRuntimeSelectorChoice>& RightChoices
) -> bool
{
for (const FHyperTwistTrainingImportedRuntimeSelector& Selector : RuntimeSurface.Selectors)
{
if (!Selector.IsStructurallyValid())
{
continue;
}
const FHyperTwistTrainingImportedRuntimeSelectorChoice* LeftChoice =
FindSelectorChoice(LeftChoices, Selector.SelectorId);
const FHyperTwistTrainingImportedRuntimeSelectorChoice* RightChoice =
FindSelectorChoice(RightChoices, Selector.SelectorId);
if (LeftChoice == nullptr || RightChoice == nullptr)
{
return false;
}
if (LeftChoice->SelectedValue != RightChoice->SelectedValue)
{
return false;
}
}
return true;
};
const auto DoesLaunchRequestMatchLaunchConfig = [&DoSelectorChoiceSetsMatch](
const FHyperTwistTrainingImportedGeneratedModeLaunchRequest& InLaunchRequest,
const FHyperTwistTrainingImportedGeneratedModeLaunchConfig& ExpectedLaunchConfig
) -> bool
{
return InLaunchRequest.IsStructurallyValid()
&& InLaunchRequest.LaunchConfig.DeckId == ExpectedLaunchConfig.DeckId
&& InLaunchRequest.LaunchConfig.SurfaceId == ExpectedLaunchConfig.SurfaceId
&& InLaunchRequest.LaunchConfig.RuntimeModeId.Equals(
ExpectedLaunchConfig.RuntimeModeId,
ESearchCase::IgnoreCase
)
&& DoSelectorChoiceSetsMatch(
InLaunchRequest.LaunchConfig.SelectorChoices,
ExpectedLaunchConfig.SelectorChoices
);
};
const auto DescribeChoiceValue = [](
const FHyperTwistTrainingImportedRuntimeSelectorChoice* Choice,
const TCHAR* MissingText
) -> FString
{
if (Choice == nullptr)
{
return MissingText;
}
if (Choice->DisplayValue.IsEmpty() || Choice->DisplayValue == Choice->SelectedValue)
{
return Choice->SelectedValue.IsEmpty() ? FString(MissingText) : Choice->SelectedValue;
}
return FString::Printf(
TEXT("%s [%s]"),
*Choice->DisplayValue,
Choice->SelectedValue.IsEmpty() ? TEXT("n/a") : *Choice->SelectedValue
);
};
const auto BuildPayloadIdentityLine = [&DescribeDeliveryMode](
const FString& DeckId,
const FString& TrainingSessionId,
const EHyperTwistTrainingDeliveryMode DeliveryMode,
const FString& SurfaceId,
const FString& SurfaceLabel,
const FString& RuntimeModeId,
const FString& GeneratorType,
const FString& PuzzleId
) -> FString
{
const FString DeckSummary = DeckId.IsEmpty() ? TEXT("n/a") : DeckId;
const FString SessionSummary = TrainingSessionId.IsEmpty() ? TEXT("n/a") : TrainingSessionId;
const FString SurfaceSummary = SurfaceLabel.IsEmpty()
? (SurfaceId.IsEmpty() ? FString(TEXT("n/a")) : SurfaceId)
: FString::Printf(
TEXT("%s [%s]"),
*SurfaceLabel,
SurfaceId.IsEmpty() ? TEXT("n/a") : *SurfaceId
);
const FString RuntimeModeSummary = RuntimeModeId.IsEmpty() ? TEXT("n/a") : RuntimeModeId;
const FString GeneratorSummary = GeneratorType.IsEmpty() ? TEXT("n/a") : GeneratorType;
const FString PuzzleSummary = PuzzleId.IsEmpty() ? TEXT("n/a") : PuzzleId;
return FString::Printf(
TEXT("Payload: deck=%s | session=%s | delivery=%s | surface=%s | mode=%s | generator=%s | puzzle=%s"),
*DeckSummary,
*SessionSummary,
*DescribeDeliveryMode(DeliveryMode),
*SurfaceSummary,
*RuntimeModeSummary,
*GeneratorSummary,
*PuzzleSummary
);
};
const auto BuildSelectorChoiceEntriesAndSummary =
[&RuntimeSurface,
&SelectorId,
&RunState,
&LaunchRequest,
&bHasComparableStoredRequest,
&DescribeSelector,
&FindSelectorChoice,
&DoChoicesMatch,
&DescribeChoiceValue](
const TArray<FHyperTwistTrainingImportedRuntimeSelectorChoice>& PreviewChoices,
TArray<FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestSelectorChoiceEntrySurface>&
OutEntries,
int32& OutResolvedChoiceCount
) -> FString
{
OutEntries.Reset();
OutResolvedChoiceCount = 0;
TSet<FString> AddedSelectorIds;
TArray<FString> SelectorParts;
for (const FHyperTwistTrainingImportedRuntimeSelector& Selector : RuntimeSurface.Selectors)
{
if (!Selector.IsStructurallyValid())
{
continue;
}
const FHyperTwistTrainingImportedRuntimeSelectorChoice* PreviewChoice =
FindSelectorChoice(PreviewChoices, Selector.SelectorId);
const FHyperTwistTrainingImportedRuntimeSelectorChoice* CurrentChoice =
FindSelectorChoice(
RunState.ImportedRuntimeSelectionState.SelectorChoices,
Selector.SelectorId
);
const FHyperTwistTrainingImportedRuntimeSelectorChoice* StoredRequestChoice =
bHasComparableStoredRequest
? FindSelectorChoice(LaunchRequest.LaunchConfig.SelectorChoices, Selector.SelectorId)
: nullptr;
const FString ChoiceValueSummary = DescribeChoiceValue(PreviewChoice, TEXT("unresolved"));
FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestSelectorChoiceEntrySurface
ChoiceEntry;
ChoiceEntry.SelectorId = Selector.SelectorId;
ChoiceEntry.SelectorLabel = DescribeSelector(Selector);
ChoiceEntry.BindingLabel = Selector.SelectorBinding;
ChoiceEntry.SelectedValue = PreviewChoice == nullptr ? FString() : PreviewChoice->SelectedValue;
ChoiceEntry.DisplayValue = ChoiceValueSummary;
ChoiceEntry.PayloadLine = FString::Printf(
TEXT("Payload choice: %s=%s"),
*ChoiceEntry.SelectorLabel,
*ChoiceValueSummary
);
ChoiceEntry.bIsTargetSelector = Selector.SelectorId == SelectorId;
ChoiceEntry.bHasChoice = PreviewChoice != nullptr;
ChoiceEntry.bMatchesCurrentSelectionChoice = DoChoicesMatch(CurrentChoice, PreviewChoice);
ChoiceEntry.bMatchesStoredRequestChoice =
bHasComparableStoredRequest && DoChoicesMatch(StoredRequestChoice, PreviewChoice);
if (PreviewChoice != nullptr)
{
++OutResolvedChoiceCount;
}
SelectorParts.Add(FString::Printf(
TEXT("%s=%s"),
*ChoiceEntry.SelectorLabel,
*ChoiceValueSummary
));
AddedSelectorIds.Add(Selector.SelectorId);
OutEntries.Add(MoveTemp(ChoiceEntry));
}
for (const FHyperTwistTrainingImportedRuntimeSelectorChoice& PreviewChoice : PreviewChoices)
{
if (!PreviewChoice.IsStructurallyValid()
|| AddedSelectorIds.Contains(PreviewChoice.SelectorId))
{
continue;
}
const FHyperTwistTrainingImportedRuntimeSelectorChoice* CurrentChoice =
FindSelectorChoice(
RunState.ImportedRuntimeSelectionState.SelectorChoices,
PreviewChoice.SelectorId
);
const FHyperTwistTrainingImportedRuntimeSelectorChoice* StoredRequestChoice =
bHasComparableStoredRequest
? FindSelectorChoice(
LaunchRequest.LaunchConfig.SelectorChoices,
PreviewChoice.SelectorId
)
: nullptr;
const FString ChoiceValueSummary = DescribeChoiceValue(&PreviewChoice, TEXT("unresolved"));
FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestSelectorChoiceEntrySurface
ChoiceEntry;
ChoiceEntry.SelectorId = PreviewChoice.SelectorId;
ChoiceEntry.SelectorLabel = PreviewChoice.SelectorBinding.IsEmpty()
? PreviewChoice.SelectorId
: PreviewChoice.SelectorBinding;
ChoiceEntry.BindingLabel = PreviewChoice.SelectorBinding;
ChoiceEntry.SelectedValue = PreviewChoice.SelectedValue;
ChoiceEntry.DisplayValue = ChoiceValueSummary;
ChoiceEntry.PayloadLine = FString::Printf(
TEXT("Payload choice: %s=%s"),
*ChoiceEntry.SelectorLabel,
*ChoiceValueSummary
);
ChoiceEntry.bIsTargetSelector = PreviewChoice.SelectorId == SelectorId;
ChoiceEntry.bHasChoice = true;
ChoiceEntry.bMatchesCurrentSelectionChoice = DoChoicesMatch(CurrentChoice, &PreviewChoice);
ChoiceEntry.bMatchesStoredRequestChoice =
bHasComparableStoredRequest && DoChoicesMatch(StoredRequestChoice, &PreviewChoice);
++OutResolvedChoiceCount;
SelectorParts.Add(FString::Printf(
TEXT("%s=%s"),
*ChoiceEntry.SelectorLabel,
*ChoiceValueSummary
));
OutEntries.Add(MoveTemp(ChoiceEntry));
}
return SelectorParts.Num() > 0
? FString::Printf(
TEXT("Selector payload: %s"),
*FString::Join(SelectorParts, TEXT(" | "))
)
: TEXT("Selector payload: none");
};
const auto BuildFallbackUnresolvedSelectorLine = [&RuntimeSurface, &DescribeSelector, &FindSelectorChoice](
const FHyperTwistTrainingImportedRuntimeSelectionState& SelectionState,
int32& OutUnresolvedSelectorCount
) -> FString
{
OutUnresolvedSelectorCount = 0;
TArray<FString> UnresolvedLabels;
for (const FHyperTwistTrainingImportedRuntimeSelector& Selector : RuntimeSurface.Selectors)
{
if (!Selector.IsStructurallyValid())
{
continue;
}
if (FindSelectorChoice(SelectionState.SelectorChoices, Selector.SelectorId) != nullptr)
{
continue;
}
++OutUnresolvedSelectorCount;
UnresolvedLabels.Add(DescribeSelector(Selector));
}
if (OutUnresolvedSelectorCount == 0)
{
return TEXT("Unresolved after choice: none");
}
const int32 PreviewCount = FMath::Min(3, UnresolvedLabels.Num());
TArray<FString> PreviewLabels;
for (int32 Index = 0; Index < PreviewCount; ++Index)
{
PreviewLabels.Add(UnresolvedLabels[Index]);
}
return FString::Printf(
TEXT("Unresolved after choice: %s%s"),
*FString::Join(PreviewLabels, TEXT(", ")),
UnresolvedLabels.Num() > PreviewCount
? *FString::Printf(TEXT(" (+%d more)"), UnresolvedLabels.Num() - PreviewCount)
: TEXT("")
);
};
if (!RunState.IsStructurallyValid() || !bHasGeneratedModeSurface)
{
Surface.StatusLine = OptionMenuSurface.StatusLine.IsEmpty()
? TEXT("Generated-mode selector option launch-request preview is not currently available.")
: OptionMenuSurface.StatusLine;
Surface.PayloadSummaryLine = TEXT("Payload preview: n/a");
Surface.StoredRequestReuseSummaryLine = TEXT("Stored request reuse: n/a");
Surface.RefreshPreviewSummaryLine = TEXT("Refresh preview: n/a");
Surface.CurrentSelectionPayloadLine = OptionMenuSurface.bHasUnresolvedSelectionDetail
? OptionMenuSurface.UnresolvedSelectionLine
: (OptionMenuSurface.bHasSelection
? TEXT("Current selection remains cached, but no active launch-request preview is available.")
: TEXT("Current selection: unresolved"));
Surface.DetailLine = FString::Printf(
TEXT("Dispatch: %s | Request alignment: %s"),
OptionDispatchSurface.StatusLine.IsEmpty()
? TEXT("n/a")
: *OptionDispatchSurface.StatusLine,
OptionRequestAlignmentSurface.StatusLine.IsEmpty()
? TEXT("n/a")
: *OptionRequestAlignmentSurface.StatusLine
);
return Surface;
}
const FString FallbackPayloadIdentityLine = BuildPayloadIdentityLine(
RunState.ActiveDeck.DeckId,
RunState.Session.TrainingSessionId,
RunState.Session.Mode,
RuntimeSurface.SurfaceId,
RuntimeSurface.SurfaceLabel,
RuntimeSurface.RuntimeModeId,
RuntimeSurface.GeneratorType,
FallbackPuzzleId
);
int32 PreviewableGeneratedOptionCount = 0;
int32 PreviewablePayloadOptionCount = 0;
for (const FHyperTwistTrainingCoachGeneratedModeSelectorOptionDispatchEntrySurface& DispatchEntry :
OptionDispatchSurface.OptionEntries)
{
FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestPreviewEntrySurface Entry;
Entry.OptionId = DispatchEntry.OptionId;
Entry.Label = DispatchEntry.Label;
Entry.OptionValue = DispatchEntry.OptionValue;
Entry.bMatchesCurrentSelection = DispatchEntry.bMatchesCurrentSelection;
Entry.HypotheticalDeckId = RunState.ActiveDeck.DeckId;
Entry.HypotheticalTrainingSessionId = RunState.Session.TrainingSessionId;
Entry.HypotheticalSurfaceId = RuntimeSurface.SurfaceId;
Entry.HypotheticalSurfaceLabel = RuntimeSurface.SurfaceLabel;
Entry.HypotheticalRuntimeModeId = RuntimeSurface.RuntimeModeId;
Entry.HypotheticalGeneratorType = RuntimeSurface.GeneratorType;
Entry.HypotheticalPuzzleId = FallbackPuzzleId;
Entry.HypotheticalDeliveryMode = RunState.Session.Mode;
const FHyperTwistTrainingCoachGeneratedModeSelectorOptionRequestAlignmentEntrySurface*
MatchingRequestAlignmentEntry =
OptionRequestAlignmentSurface.OptionEntries.FindByPredicate(
[&DispatchEntry](
const FHyperTwistTrainingCoachGeneratedModeSelectorOptionRequestAlignmentEntrySurface&
Candidate
)
{
return Candidate.OptionId == DispatchEntry.OptionId;
}
);
const FString RequestAlignmentDetail =
MatchingRequestAlignmentEntry == nullptr || MatchingRequestAlignmentEntry->DetailLine.IsEmpty()
? TEXT("Request alignment detail: n/a")
: MatchingRequestAlignmentEntry->DetailLine;
if (DispatchEntry.DispatchKind
== EHyperTwistTrainingCoachGeneratedModeOptionDispatchKind::DirectCaseLaunch
&& DispatchEntry.bCanDispatch)
{
Entry.LaunchRequestPreviewKind =
EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind::
DirectOwnedCaseStart;
Entry.PayloadIdentityLine =
TEXT("Payload: direct owned-case launch bypasses generated-mode launch-request staging.");
Entry.SelectorPayloadLine =
TEXT("Selector payload: direct owned-case launch bypasses generated-mode selector payload staging.");
Entry.StoredRequestReuseLine =
TEXT("Stored request reuse: direct owned-case launch bypasses stored launch-request reuse.");
Entry.RefreshPreviewLine =
TEXT("Refresh preview: direct owned-case launch bypasses generated-mode request refresh.");
Entry.ExecutePreviewLine =
MatchingRequestAlignmentEntry == nullptr
|| MatchingRequestAlignmentEntry->ExecutePreviewLine.IsEmpty()
? TEXT("Execute preview: direct owned-case launch starts immediately.")
: MatchingRequestAlignmentEntry->ExecutePreviewLine;
Entry.DetailLine = MatchingRequestAlignmentEntry == nullptr
? DispatchEntry.DetailLine
: FString::Printf(TEXT("%s | %s"), *Entry.PayloadIdentityLine, *RequestAlignmentDetail);
Entry.bStartsOwnedCaseImmediately = true;
Entry.bCanPreview = true;
++Surface.DirectOwnedCaseStartOptionCount;
}
else if (DispatchEntry.DispatchKind
== EHyperTwistTrainingCoachGeneratedModeOptionDispatchKind::ApplyGeneratedChoice
&& DispatchEntry.bCanDispatch && MatchingSelector != nullptr)
{
++PreviewableGeneratedOptionCount;
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));
}
Entry.ExecutePreviewLine =
MatchingRequestAlignmentEntry == nullptr
|| MatchingRequestAlignmentEntry->ExecutePreviewLine.IsEmpty()
? TEXT("Execute preview: unavailable.")
: MatchingRequestAlignmentEntry->ExecutePreviewLine;
if (MatchingRequestAlignmentEntry != nullptr)
{
Entry.bWouldLeaveSelectorsUnresolved =
MatchingRequestAlignmentEntry->bWouldLeaveSelectorsUnresolved;
}
int32 UnresolvedSelectorCount = 0;
FString UnresolvedSelectorsLine = MatchingRequestAlignmentEntry == nullptr
? BuildFallbackUnresolvedSelectorLine(
HypotheticalSelectionState,
UnresolvedSelectorCount
)
: MatchingRequestAlignmentEntry->UnresolvedSelectorsLine;
if (MatchingRequestAlignmentEntry != nullptr)
{
UnresolvedSelectorCount = MatchingRequestAlignmentEntry->UnresolvedSelectorCount;
}
Entry.PayloadIdentityLine = FallbackPayloadIdentityLine;
Entry.SelectorPayloadLine = BuildSelectorChoiceEntriesAndSummary(
HypotheticalSelectionState.SelectorChoices,
Entry.SelectorChoiceEntries,
Entry.SelectorChoiceCount
);
if (Entry.bWouldLeaveSelectorsUnresolved)
{
Entry.LaunchRequestPreviewKind =
EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind::
UnresolvedAfterChoice;
Entry.StoredRequestReuseLine =
TEXT("Stored request reuse: no structurally valid launch payload is available while selector coverage remains incomplete.");
Entry.RefreshPreviewLine =
TEXT("Refresh preview: execute cannot stage a launch request until every required selector is resolved.");
Entry.DetailLine = FString::Printf(
TEXT("%s | %s | %s"),
*Entry.SelectorPayloadLine,
*UnresolvedSelectorsLine,
*RequestAlignmentDetail
);
Entry.bCanPreview = true;
++Surface.UnresolvedAfterChoiceOptionCount;
}
else
{
FHyperTwistTrainingImportedGeneratedModeLaunchConfig HypotheticalLaunchConfig;
if (!UHyperTwistTrainingCatalogLibrary::TryBuildImportedGeneratedModeLaunchConfig(
RunState.ActiveDeck,
HypotheticalSelectionState,
RunState.Session,
HypotheticalLaunchConfig))
{
Entry.LaunchRequestPreviewKind =
EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind::
PayloadUnavailable;
Entry.StoredRequestReuseLine =
TEXT("Stored request reuse: no structurally valid launch payload could be derived from the resulting selector state.");
Entry.RefreshPreviewLine =
TEXT("Refresh preview: execute cannot preview request refresh because the resulting selector state is not structurally valid.");
Entry.DetailLine = FString::Printf(
TEXT("%s | %s"),
*Entry.SelectorPayloadLine,
*RequestAlignmentDetail
);
Entry.bCanPreview = true;
++Surface.PayloadUnavailableOptionCount;
}
else
{
const bool bWouldReuseStoredRequest =
MatchingRequestAlignmentEntry != nullptr
? MatchingRequestAlignmentEntry->bWouldUseStoredRequestOnExecute
: DoesLaunchRequestMatchLaunchConfig(
LaunchRequest,
HypotheticalLaunchConfig
);
const bool bWouldRequireLaunchRequestRefresh =
MatchingRequestAlignmentEntry != nullptr
? MatchingRequestAlignmentEntry->bWouldRequireLaunchRequestRefreshBeforeExecute
: !bWouldReuseStoredRequest;
Entry.HypotheticalDeckId = HypotheticalLaunchConfig.DeckId;
Entry.HypotheticalTrainingSessionId =
HypotheticalLaunchConfig.TrainingSessionId;
Entry.HypotheticalSurfaceId = HypotheticalLaunchConfig.SurfaceId;
Entry.HypotheticalSurfaceLabel = HypotheticalLaunchConfig.SurfaceLabel;
Entry.HypotheticalRuntimeModeId = HypotheticalLaunchConfig.RuntimeModeId;
Entry.HypotheticalGeneratorType = HypotheticalLaunchConfig.GeneratorType;
Entry.HypotheticalPuzzleId = HypotheticalLaunchConfig.PuzzleId;
Entry.HypotheticalDeliveryMode = HypotheticalLaunchConfig.DeliveryMode;
Entry.PayloadIdentityLine = BuildPayloadIdentityLine(
HypotheticalLaunchConfig.DeckId,
HypotheticalLaunchConfig.TrainingSessionId,
HypotheticalLaunchConfig.DeliveryMode,
HypotheticalLaunchConfig.SurfaceId,
HypotheticalLaunchConfig.SurfaceLabel,
HypotheticalLaunchConfig.RuntimeModeId,
HypotheticalLaunchConfig.GeneratorType,
HypotheticalLaunchConfig.PuzzleId
);
Entry.SelectorPayloadLine = BuildSelectorChoiceEntriesAndSummary(
HypotheticalLaunchConfig.SelectorChoices,
Entry.SelectorChoiceEntries,
Entry.SelectorChoiceCount
);
Entry.bWouldReuseStoredRequest = bWouldReuseStoredRequest;
Entry.bWouldRequireLaunchRequestRefresh =
bWouldRequireLaunchRequestRefresh;
Entry.bHasStructurallyValidLaunchPayload = true;
Entry.bCanPreview = true;
if (bWouldReuseStoredRequest)
{
Entry.LaunchRequestPreviewKind =
EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind::
StoredRequestReusable;
Entry.StoredRequestReuseLine =
TEXT("Stored request reuse: the current stored request already matches this hypothetical launch payload.");
Entry.RefreshPreviewLine =
TEXT("Refresh preview: execute can use the stored request without rebuilding it.");
++Surface.StoredRequestReusableOptionCount;
}
else
{
Entry.LaunchRequestPreviewKind =
EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind::
RefreshRequired;
Entry.StoredRequestReuseLine =
!LaunchRequest.IsStructurallyValid()
? TEXT("Stored request reuse: no stored request is currently persisted for this hypothetical launch payload.")
: (bHasComparableStoredRequest
? TEXT("Stored request reuse: this hypothetical launch payload diverges from the current stored request.")
: TEXT("Stored request reuse: a stored request exists, but it does not match this generated-mode surface or runtime mode."));
Entry.RefreshPreviewLine =
!LaunchRequest.IsStructurallyValid()
? TEXT("Refresh preview: execute would persist a first stored request from this hypothetical launch payload.")
: (bHasComparableStoredRequest
? TEXT("Refresh preview: execute would refresh the stored request from this hypothetical launch payload.")
: TEXT("Refresh preview: execute would replace the currently persisted stored request with this hypothetical launch payload."));
++Surface.RefreshRequiredOptionCount;
}
++PreviewablePayloadOptionCount;
Entry.DetailLine = FString::Printf(
TEXT("%s | %s"),
*Entry.PayloadIdentityLine,
*RequestAlignmentDetail
);
}
}
}
else
{
Entry.LaunchRequestPreviewKind =
EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind::Unavailable;
Entry.PayloadIdentityLine = TEXT("Payload: unavailable");
Entry.SelectorPayloadLine = TEXT("Selector payload: unavailable");
Entry.StoredRequestReuseLine = TEXT("Stored request reuse: unavailable");
Entry.RefreshPreviewLine = TEXT("Refresh preview: unavailable");
Entry.ExecutePreviewLine = TEXT("Execute preview: unavailable.");
Entry.DetailLine =
TEXT("Launch-request preview is unavailable because this selector option cannot dispatch from the active generated-mode surface.");
Entry.bCanPreview = false;
++Surface.UnavailableOptionCount;
}
if (Entry.bMatchesCurrentSelection)
{
Surface.bCurrentSelectionWouldReuseStoredRequest = Entry.bWouldReuseStoredRequest;
Surface.bCurrentSelectionWouldRequireLaunchRequestRefresh =
Entry.bWouldRequireLaunchRequestRefresh;
Surface.bCurrentSelectionWouldLeaveSelectorsUnresolved =
Entry.bWouldLeaveSelectorsUnresolved;
Surface.bCurrentSelectionHasStructurallyValidLaunchPayload =
Entry.bHasStructurallyValidLaunchPayload;
Surface.CurrentSelectionPayloadLine = Entry.bStartsOwnedCaseImmediately
? FString::Printf(
TEXT("%s | %s"),
*Entry.PayloadIdentityLine,
*Entry.ExecutePreviewLine
)
: FString::Printf(
TEXT("%s | %s | %s"),
*Entry.PayloadIdentityLine,
*Entry.StoredRequestReuseLine,
*Entry.RefreshPreviewLine
);
}
Surface.OptionEntries.Add(MoveTemp(Entry));
}
Surface.bHasLaunchRequestPreviewOptions = Surface.OptionEntries.Num() > 0;
Surface.bHasStoredRequestReusableOptions = Surface.StoredRequestReusableOptionCount > 0;
Surface.bHasRefreshRequiredOptions = Surface.RefreshRequiredOptionCount > 0;
Surface.bHasUnresolvedAfterChoiceOptions = Surface.UnresolvedAfterChoiceOptionCount > 0;
Surface.bHasPayloadUnavailableOptions = Surface.PayloadUnavailableOptionCount > 0;
Surface.PayloadSummaryLine = Surface.OptionEntries.Num() > 0
? FString::Printf(
TEXT("Payload preview: %d reusable | %d refresh-required | %d unresolved | %d payload-unavailable | %d direct-launch | %d unavailable"),
Surface.StoredRequestReusableOptionCount,
Surface.RefreshRequiredOptionCount,
Surface.UnresolvedAfterChoiceOptionCount,
Surface.PayloadUnavailableOptionCount,
Surface.DirectOwnedCaseStartOptionCount,
Surface.UnavailableOptionCount
)
: TEXT("Payload preview: no imported options");
Surface.StoredRequestReuseSummaryLine = PreviewableGeneratedOptionCount > 0
? FString::Printf(
TEXT("Stored request reuse: %d option%s can reuse the current stored request; %d would need refresh; %d still have no valid launch payload."),
Surface.StoredRequestReusableOptionCount,
Surface.StoredRequestReusableOptionCount == 1 ? TEXT("") : TEXT("s"),
Surface.RefreshRequiredOptionCount,
Surface.UnresolvedAfterChoiceOptionCount + Surface.PayloadUnavailableOptionCount
)
: TEXT("Stored request reuse: no previewable generated-choice options");
Surface.RefreshPreviewSummaryLine = PreviewableGeneratedOptionCount > 0
? FString::Printf(
TEXT("Refresh preview: %d option%s would refresh the stored request; %d stay reusable without refresh; %d still cannot stage a complete payload."),
Surface.RefreshRequiredOptionCount,
Surface.RefreshRequiredOptionCount == 1 ? TEXT("") : TEXT("s"),
Surface.StoredRequestReusableOptionCount,
Surface.UnresolvedAfterChoiceOptionCount + Surface.PayloadUnavailableOptionCount
)
: TEXT("Refresh preview: no previewable generated-choice options");
if (Surface.CurrentSelectionPayloadLine.IsEmpty())
{
if (OptionMenuSurface.bHasUnresolvedSelectionDetail)
{
Surface.CurrentSelectionPayloadLine = OptionMenuSurface.UnresolvedSelectionLine;
}
else if (OptionMenuSurface.bHasSelection)
{
Surface.CurrentSelectionPayloadLine =
TEXT("Current selection has no additional launch-request payload preview.");
}
else if (OptionMenuSurface.bHasSelector)
{
Surface.CurrentSelectionPayloadLine = TEXT("Current selection: unresolved");
}
else
{
Surface.CurrentSelectionPayloadLine = TEXT("Current selection: n/a");
}
}
if (OptionMenuSurface.bHasSelector && Surface.OptionEntries.Num() > 0)
{
Surface.StatusLine = FString::Printf(
TEXT("Generated-mode selector option launch-request preview exposes %d stored-request-reusable option%s, %d refresh-required option%s, %d unresolved option%s, %d payload-unavailable option%s, %d direct-launch option%s, and %d unavailable option%s."),
Surface.StoredRequestReusableOptionCount,
Surface.StoredRequestReusableOptionCount == 1 ? TEXT("") : TEXT("s"),
Surface.RefreshRequiredOptionCount,
Surface.RefreshRequiredOptionCount == 1 ? TEXT("") : TEXT("s"),
Surface.UnresolvedAfterChoiceOptionCount,
Surface.UnresolvedAfterChoiceOptionCount == 1 ? TEXT("") : TEXT("s"),
Surface.PayloadUnavailableOptionCount,
Surface.PayloadUnavailableOptionCount == 1 ? TEXT("") : TEXT("s"),
Surface.DirectOwnedCaseStartOptionCount,
Surface.DirectOwnedCaseStartOptionCount == 1 ? TEXT("") : TEXT("s"),
Surface.UnavailableOptionCount,
Surface.UnavailableOptionCount == 1 ? TEXT("") : TEXT("s")
);
}
else if (OptionMenuSurface.bHasSelector)
{
Surface.StatusLine =
TEXT("Generated-mode selector option launch-request preview is active, but this selector does not expose imported options.");
}
else
{
Surface.StatusLine = OptionMenuSurface.StatusLine.IsEmpty()
? TEXT("Generated-mode selector option launch-request preview 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 | Request alignment: %s | Payload-ready: %d | Selector: %s | Binding: %s"),
OptionDispatchSurface.StatusLine.IsEmpty()
? TEXT("n/a")
: *OptionDispatchSurface.StatusLine,
OptionRequestAlignmentSurface.StatusLine.IsEmpty()
? TEXT("n/a")
: *OptionRequestAlignmentSurface.StatusLine,
PreviewablePayloadOptionCount,
*SelectorSummary,
*BindingSummary
);
return Surface;
}

View file

@ -813,6 +813,27 @@ UHyperTwistTrainingPanelWidget::GetDisplayedCoachGeneratedModeSelectorOptionStor
);
}
FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface
UHyperTwistTrainingPanelWidget::GetDisplayedCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface(
const FString& SelectorId
) const
{
const FHyperTwistTrainingCoachGeneratedModeSelectorOptionMenuSurface OptionMenuSurface =
GetDisplayedCoachGeneratedModeSelectorOptionMenuSurface(SelectorId);
const FHyperTwistTrainingCoachGeneratedModeSelectorOptionDispatchSurface OptionDispatchSurface =
GetDisplayedCoachGeneratedModeSelectorOptionDispatchSurface(SelectorId);
const FHyperTwistTrainingCoachGeneratedModeSelectorOptionRequestAlignmentSurface
OptionRequestAlignmentSurface =
GetDisplayedCoachGeneratedModeSelectorOptionRequestAlignmentSurface(SelectorId);
return UHyperTwistTrainingCoachLibrary::DeriveCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface(
CachedRunState,
SelectorId,
OptionMenuSurface,
OptionDispatchSurface,
OptionRequestAlignmentSurface
);
}
FHyperTwistTrainingRunState UHyperTwistTrainingPanelWidget::StartCoachRecommendedRun(
const int32 MaxCases,
const FString& StartActionSourceLabel

View file

@ -794,6 +794,27 @@ AHyperTwistTrainingSessionActor::GetDisplayedCoachGeneratedModeSelectorOptionSto
);
}
FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface
AHyperTwistTrainingSessionActor::GetDisplayedCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface(
const FString& SelectorId
) const
{
const FHyperTwistTrainingCoachGeneratedModeSelectorOptionMenuSurface OptionMenuSurface =
GetDisplayedCoachGeneratedModeSelectorOptionMenuSurface(SelectorId);
const FHyperTwistTrainingCoachGeneratedModeSelectorOptionDispatchSurface OptionDispatchSurface =
GetDisplayedCoachGeneratedModeSelectorOptionDispatchSurface(SelectorId);
const FHyperTwistTrainingCoachGeneratedModeSelectorOptionRequestAlignmentSurface
OptionRequestAlignmentSurface =
GetDisplayedCoachGeneratedModeSelectorOptionRequestAlignmentSurface(SelectorId);
return UHyperTwistTrainingCoachLibrary::DeriveCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface(
CachedRunState,
SelectorId,
OptionMenuSurface,
OptionDispatchSurface,
OptionRequestAlignmentSurface
);
}
FHyperTwistTrainingRunState AHyperTwistTrainingSessionActor::StartCoachRecommendedTrainingRun(
const int32 MaxCases
)

View file

@ -746,4 +746,15 @@ public:
const FHyperTwistTrainingCoachGeneratedModeSelectorOptionRequestAlignmentSurface&
OptionRequestAlignmentSurface
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
static FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface
DeriveCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface(
const FHyperTwistTrainingRunState& RunState,
const FString& SelectorId,
const FHyperTwistTrainingCoachGeneratedModeSelectorOptionMenuSurface& OptionMenuSurface,
const FHyperTwistTrainingCoachGeneratedModeSelectorOptionDispatchSurface& OptionDispatchSurface,
const FHyperTwistTrainingCoachGeneratedModeSelectorOptionRequestAlignmentSurface&
OptionRequestAlignmentSurface
);
};

View file

@ -280,6 +280,10 @@ public:
FHyperTwistTrainingCoachGeneratedModeSelectorOptionStoredRequestDeltaSurface
GetDisplayedCoachGeneratedModeSelectorOptionStoredRequestDeltaSurface(const FString& SelectorId) const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface
GetDisplayedCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface(const FString& SelectorId) const;
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach")
FHyperTwistTrainingRunState StartCoachRecommendedRun(
int32 MaxCases,

View file

@ -261,6 +261,10 @@ public:
FHyperTwistTrainingCoachGeneratedModeSelectorOptionStoredRequestDeltaSurface
GetDisplayedCoachGeneratedModeSelectorOptionStoredRequestDeltaSurface(const FString& SelectorId) const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface
GetDisplayedCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface(const FString& SelectorId) const;
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach")
FHyperTwistTrainingRunState StartCoachRecommendedTrainingRun(int32 MaxCases);

View file

@ -116,6 +116,18 @@ enum class EHyperTwistTrainingCoachGeneratedModeOptionStoredRequestDeltaKind : u
Unavailable UMETA(DisplayName = "Unavailable")
};
UENUM(BlueprintType)
enum class EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind : uint8
{
None UMETA(DisplayName = "None"),
DirectOwnedCaseStart UMETA(DisplayName = "Direct Owned Case Start"),
StoredRequestReusable UMETA(DisplayName = "Stored Request Reusable"),
RefreshRequired UMETA(DisplayName = "Refresh Required"),
UnresolvedAfterChoice UMETA(DisplayName = "Unresolved After Choice"),
PayloadUnavailable UMETA(DisplayName = "Payload Unavailable"),
Unavailable UMETA(DisplayName = "Unavailable")
};
UENUM(BlueprintType)
enum class EHyperTwistTrainingPromptKind : uint8
{
@ -9496,3 +9508,243 @@ struct FHyperTwistTrainingCoachGeneratedModeSelectorOptionStoredRequestDeltaSurf
|| bHasStoredRequestDeltaOptions;
}
};
USTRUCT(BlueprintType)
struct FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestSelectorChoiceEntrySurface
{
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 SelectedValue;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString DisplayValue;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString PayloadLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bIsTargetSelector = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasChoice = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bMatchesCurrentSelectionChoice = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bMatchesStoredRequestChoice = false;
bool IsStructurallyValid() const
{
return !SelectorId.IsEmpty()
|| !SelectorLabel.IsEmpty()
|| !PayloadLine.IsEmpty()
|| bHasChoice;
}
};
USTRUCT(BlueprintType)
struct FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestPreviewEntrySurface
{
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")
EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind LaunchRequestPreviewKind =
EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind::None;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString PayloadIdentityLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString SelectorPayloadLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString StoredRequestReuseLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString RefreshPreviewLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString ExecutePreviewLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString DetailLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestSelectorChoiceEntrySurface>
SelectorChoiceEntries;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString HypotheticalDeckId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString HypotheticalTrainingSessionId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString HypotheticalSurfaceId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString HypotheticalSurfaceLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString HypotheticalRuntimeModeId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString HypotheticalGeneratorType;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString HypotheticalPuzzleId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
EHyperTwistTrainingDeliveryMode HypotheticalDeliveryMode = EHyperTwistTrainingDeliveryMode::Timer;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 SelectorChoiceCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bMatchesCurrentSelection = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bStartsOwnedCaseImmediately = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bWouldReuseStoredRequest = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bWouldRequireLaunchRequestRefresh = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bWouldLeaveSelectorsUnresolved = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasStructurallyValidLaunchPayload = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bCanPreview = false;
bool IsStructurallyValid() const
{
return !OptionId.IsEmpty()
|| !Label.IsEmpty()
|| !PayloadIdentityLine.IsEmpty()
|| LaunchRequestPreviewKind
!= EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind::None
|| bCanPreview;
}
};
USTRUCT(BlueprintType)
struct FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface
{
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 PayloadSummaryLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString StoredRequestReuseSummaryLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString RefreshPreviewSummaryLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString CurrentSelectionPayloadLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString DetailLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestPreviewEntrySurface>
OptionEntries;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 StoredRequestReusableOptionCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 RefreshRequiredOptionCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 UnresolvedAfterChoiceOptionCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 PayloadUnavailableOptionCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 DirectOwnedCaseStartOptionCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 UnavailableOptionCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasSelector = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasLaunchRequestPreviewOptions = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasStoredRequestReusableOptions = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasRefreshRequiredOptions = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasUnresolvedAfterChoiceOptions = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasPayloadUnavailableOptions = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bCurrentSelectionWouldReuseStoredRequest = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bCurrentSelectionWouldRequireLaunchRequestRefresh = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bCurrentSelectionWouldLeaveSelectorsUnresolved = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bCurrentSelectionHasStructurallyValidLaunchPayload = false;
bool IsStructurallyValid() const
{
return !StatusLine.IsEmpty()
|| !SelectorId.IsEmpty()
|| OptionEntries.Num() > 0
|| bHasSelector
|| bHasLaunchRequestPreviewOptions;
}
};

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 stored-request delta / selector-diff surface over the already-cached selector/runtime state, so gameplay/Blueprint callers can render the exact current-vs-hypothetical-vs-stored selector change set for a chosen option without diffing cached selector choices or launch-request payloads directly
- the current next bounded packet is to expose one thin generated-mode selector option hypothetical launch-request payload / refresh-preview surface on those same two consumer surfaces so gameplay/Blueprint callers can render the concrete selector-choice payload, stored-request reuse posture, and request-refresh preview for a chosen option without rebuilding launch-config payloads directly
- the latest landed bounded `Phase 4` packet stays on that same gameplay-facing consumer lane and adds one thin generated-mode selector option hypothetical launch-request payload / refresh-preview surface over the already-cached selector/runtime state, so gameplay/Blueprint callers can render the concrete selector-choice payload, stored-request reuse posture, and request-refresh preview for a chosen option without rebuilding launch-config payloads directly
- the current next bounded packet is to expose one thin generated-mode selector option hypothetical launch-request metadata / origin-notes preview surface on those same two consumer surfaces so gameplay/Blueprint callers can render imported origin-path and notes provenance over that hypothetical payload without reading raw launch-config metadata 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,105 @@
# HyperTwist Phase 4 gameplay generated-mode selector option launch-request payload / refresh-preview 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 stored-request delta / selector-diff surface by exposing one parameterized option launch-request payload / refresh-preview surface for a chosen selector id.
The open tasks were:
- stop forcing gameplay / Blueprint callers to rebuild hypothetical generated-mode launch-config payloads just to explain what a chosen imported option would stage
- expose the concrete selector-choice payload, stored-request reuse posture, and refresh-on-execute preview for each imported option on the chosen selector
- keep the surface derived from already-cached gameplay consumer run, selector, and request state plus the already-landed option-menu, dispatch, and request-alignment surfaces instead of widening into backend logic
It is not:
- a selector-dispatch behavior change
- a stored launch-request persistence change
- a generated-mode execution-policy change
- a broader gameplay UI composition pass
## Scope
Bounded lane:
- add first-party generated-mode selector option launch-request payload / refresh-preview surfaces in shared training types
- add a coach-library helper that derives hypothetical launch payload, selector payload, stored-request reuse posture, and refresh preview from cached run state plus the already-landed option-menu, dispatch, and request-alignment surfaces
- expose that parameterized launch-request preview surface on `HyperTwistTrainingPanelWidget` and `HyperTwistTrainingSessionActor`
- keep the derivation presentation-only and rooted in the already-owned selector-choice / launch-request rules
Out of scope:
- changing selector-choice apply behavior
- changing stored launch-request creation, refresh, or execute behavior
- changing direct owned-case launch 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 option stored-request delta / selector-diff posture
- those same consumers already exposed truthful generated-mode option request-alignment / execute-preview posture
But one thin consumer seam was still open:
- callers could tell whether a chosen option stayed aligned with the stored request or required refresh
- but they still had to rebuild the hypothetical launch payload themselves to show the actual selector payload and request-refresh consequences behind that posture
That meant:
- alignment and delta posture were thin and reusable
- but concrete launch-request preview still leaked launch-config reconstruction work into callers
So the next bounded move was:
- keep the already-landed option-menu, dispatch, request-alignment, and stored-request-delta surfaces unchanged
- and expose one narrow launch-request payload / refresh-preview surface above those already-cached surfaces
## What landed
Primary code changes:
- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h`
- added `EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind`
- added `FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestSelectorChoiceEntrySurface`
- added `FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestPreviewEntrySurface`
- added `FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface`
- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h`
- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp`
- added `DeriveCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface(...)`
- launch-request preview is derived from cached run state plus the already-landed option-menu, dispatch, and request-alignment surfaces
- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h`
- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp`
- added `GetDisplayedCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface(const FString& SelectorId)`
- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h`
- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp`
- added `GetDisplayedCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface(const FString& SelectorId)`
Surface behavior:
- reports the concrete hypothetical payload identity for each imported option on the chosen selector, including deck, session, delivery, surface, runtime mode, generator, and puzzle context
- exposes the hypothetical selector payload row set for each option, including which selector choices match current cached selection and the stored request
- classifies each option as stored-request-reusable, refresh-required, unresolved-after-choice, payload-unavailable, direct-launch, or unavailable
- reports a compact stored-request reuse and refresh preview for the current cached selection on the chosen selector
- keeps the surface presentation-only and compatible with the already-landed generated-mode option-menu, dispatch, request-alignment, and stored-request-delta 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 hypothetical launch-request metadata / origin-notes preview surface so callers can render imported origin-path and notes provenance over the hypothetical payload without reading raw launch-config metadata directly.