From 3521d4ca4d83aa710c661804e91c97e34b0b2557 Mon Sep 17 00:00:00 2001 From: axiomlogicnexus Date: Fri, 8 May 2026 02:48:42 +0200 Subject: [PATCH] Expose gameplay generated-mode selector option dispatch surface --- .../HyperTwistTrainingCoachLibrary.cpp | 232 ++++++++++++++++++ .../HyperTwistTrainingPanelWidget.cpp | 17 ++ .../HyperTwistTrainingSessionActor.cpp | 17 ++ .../HyperTwistTrainingCoachLibrary.h | 9 + .../HyperTwistTrainingPanelWidget.h | 4 + .../HyperTwistTrainingSessionActor.h | 4 + .../HyperTwistTrainingTypes.h | 127 ++++++++++ ...RTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md | 4 +- ...TION_DISPATCH_SURFACE_PACKET_2026-05-08.md | 106 ++++++++ 9 files changed, 518 insertions(+), 2 deletions(-) create mode 100644 docs/arch/HYPERTWIST_PHASE4_GAMEPLAY_GENERATED_MODE_SELECTOR_OPTION_DISPATCH_SURFACE_PACKET_2026-05-08.md diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp index afbf384..f9b0ab2 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp @@ -7583,3 +7583,235 @@ UHyperTwistTrainingCoachLibrary::DeriveCoachGeneratedModeSelectorOptionActionabi return Surface; } + +FHyperTwistTrainingCoachGeneratedModeSelectorOptionDispatchSurface +UHyperTwistTrainingCoachLibrary::DeriveCoachGeneratedModeSelectorOptionDispatchSurface( + const FString& SelectorId, + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionMenuSurface& OptionMenuSurface, + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionActionabilitySurface& + OptionActionabilitySurface +) +{ + FHyperTwistTrainingCoachGeneratedModeSelectorOptionDispatchSurface Surface; + Surface.SelectorId = SelectorId; + Surface.SelectorLabel = OptionMenuSurface.SelectorLabel; + Surface.BindingLabel = OptionMenuSurface.BindingLabel; + Surface.Headline = TEXT("Generated Mode Selector Option Dispatch"); + Surface.bHasSelector = OptionMenuSurface.bHasSelector; + + if (!SelectorId.IsEmpty()) + { + Surface.Headline = OptionMenuSurface.SelectorLabel.IsEmpty() + ? FString::Printf(TEXT("Generated Mode Selector Option Dispatch: %s"), *SelectorId) + : FString::Printf( + TEXT("Generated Mode Selector Option Dispatch: %s"), + *OptionMenuSurface.SelectorLabel + ); + } + + if (SelectorId.IsEmpty()) + { + Surface.StatusLine = + TEXT("Generated-mode selector option dispatch requires a selector id from the active selector roster."); + Surface.DispatchSummaryLine = TEXT("Dispatch: n/a"); + Surface.CurrentSelectionDispatchLine = TEXT("Current selection: n/a"); + Surface.DetailLine = OptionActionabilitySurface.StatusLine.IsEmpty() + ? TEXT("Coverage: n/a") + : FString::Printf(TEXT("Coverage: %s"), *OptionActionabilitySurface.StatusLine); + return Surface; + } + + for (const FHyperTwistTrainingCoachGeneratedModeSelectorOptionEntrySurface& OptionEntry : + OptionMenuSurface.OptionEntries) + { + FHyperTwistTrainingCoachGeneratedModeSelectorOptionDispatchEntrySurface Entry; + Entry.OptionId = OptionEntry.OptionId; + Entry.Label = OptionEntry.Label; + Entry.OptionValue = OptionEntry.OptionValue; + Entry.bMatchesCurrentSelection = OptionEntry.bMatchesCurrentSelection; + Entry.GeneratedChoiceSelectedValue = OptionEntry.OptionValue.IsEmpty() + ? OptionEntry.OptionId + : OptionEntry.OptionValue; + Entry.GeneratedChoiceDisplayValue = OptionEntry.Label.IsEmpty() + ? Entry.GeneratedChoiceSelectedValue + : OptionEntry.Label; + + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionActionabilityEntrySurface* + MatchingActionabilityEntry = + OptionActionabilitySurface.OptionEntries.FindByPredicate( + [&OptionEntry]( + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionActionabilityEntrySurface& + Candidate + ) + { + return Candidate.OptionId == OptionEntry.OptionId; + } + ); + const bool bHasCoveredCase = + MatchingActionabilityEntry != nullptr && MatchingActionabilityEntry->bHasCoveredCase; + + if (bHasCoveredCase) + { + Entry.DispatchKind = + EHyperTwistTrainingCoachGeneratedModeOptionDispatchKind::DirectCaseLaunch; + Entry.DirectLaunchOptionId = OptionEntry.OptionId; + Entry.DispatchLine = TEXT("Dispatch: launch direct owned case"); + Entry.DetailLine = MatchingActionabilityEntry->MatchedCaseLabel.IsEmpty() + ? TEXT("Launch path: ApplyActiveImportedRuntimeSelectorOption") + : FString::Printf( + TEXT("Launch path: ApplyActiveImportedRuntimeSelectorOption | Case: %s"), + *MatchingActionabilityEntry->MatchedCaseLabel + ); + Entry.bCanDispatch = true; + ++Surface.DirectLaunchOptionCount; + } + else if (OptionMenuSurface.bHasSelector) + { + Entry.DispatchKind = + EHyperTwistTrainingCoachGeneratedModeOptionDispatchKind::ApplyGeneratedChoice; + Entry.DispatchLine = TEXT("Dispatch: apply generated-mode selector choice"); + Entry.DetailLine = FString::Printf( + TEXT("Choice path: ApplyActiveImportedGeneratedModeSelectorChoice | Value: %s"), + Entry.GeneratedChoiceSelectedValue.IsEmpty() + ? TEXT("n/a") + : *Entry.GeneratedChoiceSelectedValue + ); + Entry.bCanDispatch = !Entry.GeneratedChoiceSelectedValue.IsEmpty(); + ++Surface.GeneratedChoiceOptionCount; + } + else + { + Entry.DispatchKind = EHyperTwistTrainingCoachGeneratedModeOptionDispatchKind::Unavailable; + Entry.DispatchLine = TEXT("Dispatch: unavailable"); + Entry.DetailLine = TEXT("Option dispatch is unavailable because the selector is not active."); + Entry.bCanDispatch = false; + ++Surface.UnavailableOptionCount; + } + + if (Entry.bMatchesCurrentSelection) + { + Surface.bCurrentSelectionDispatchesDirectLaunch = + Entry.DispatchKind + == EHyperTwistTrainingCoachGeneratedModeOptionDispatchKind::DirectCaseLaunch; + Surface.bCurrentSelectionDispatchesGeneratedChoice = + Entry.DispatchKind + == EHyperTwistTrainingCoachGeneratedModeOptionDispatchKind::ApplyGeneratedChoice; + } + + if (!Entry.bCanDispatch + && Entry.DispatchKind + != EHyperTwistTrainingCoachGeneratedModeOptionDispatchKind::Unavailable) + { + ++Surface.UnavailableOptionCount; + } + + Surface.OptionEntries.Add(MoveTemp(Entry)); + } + + Surface.bHasDispatchOptions = Surface.OptionEntries.Num() > 0; + Surface.bHasDirectLaunchOptions = Surface.DirectLaunchOptionCount > 0; + Surface.bHasGeneratedChoiceOptions = Surface.GeneratedChoiceOptionCount > 0; + + if (Surface.bHasDispatchOptions) + { + Surface.DispatchSummaryLine = FString::Printf( + TEXT("Dispatch: %d direct-launch | %d generated-choice | %d unavailable"), + Surface.DirectLaunchOptionCount, + Surface.GeneratedChoiceOptionCount, + Surface.UnavailableOptionCount + ); + } + else + { + Surface.DispatchSummaryLine = TEXT("Dispatch: no imported options"); + } + + if (Surface.bCurrentSelectionDispatchesDirectLaunch) + { + Surface.CurrentSelectionDispatchLine = + TEXT("Current selection dispatches as direct owned-case launch."); + } + else if (Surface.bCurrentSelectionDispatchesGeneratedChoice) + { + Surface.CurrentSelectionDispatchLine = + TEXT("Current selection dispatches as generated-mode selector choice apply."); + } + else if (OptionMenuSurface.bHasUnresolvedSelectionDetail) + { + Surface.CurrentSelectionDispatchLine = OptionMenuSurface.UnresolvedSelectionLine.IsEmpty() + ? TEXT("Current selection does not currently map to an imported option.") + : OptionMenuSurface.UnresolvedSelectionLine; + } + else if (OptionMenuSurface.bHasSelection) + { + Surface.CurrentSelectionDispatchLine = + TEXT("Current selection has no additional dispatch detail."); + } + else if (OptionMenuSurface.bHasSelector) + { + Surface.CurrentSelectionDispatchLine = TEXT("Current selection: unresolved"); + } + else + { + Surface.CurrentSelectionDispatchLine = TEXT("Current selection: n/a"); + } + + if (OptionMenuSurface.bHasSelector && Surface.DirectLaunchOptionCount > 0 + && Surface.GeneratedChoiceOptionCount == 0 && Surface.UnavailableOptionCount == 0) + { + Surface.StatusLine = FString::Printf( + TEXT("Generated-mode selector option dispatch exposes %d direct-launch option%s and no fallback choice applies."), + Surface.DirectLaunchOptionCount, + Surface.DirectLaunchOptionCount == 1 ? TEXT("") : TEXT("s") + ); + } + else if (OptionMenuSurface.bHasSelector && Surface.GeneratedChoiceOptionCount > 0 + && Surface.DirectLaunchOptionCount == 0 && Surface.UnavailableOptionCount == 0) + { + Surface.StatusLine = FString::Printf( + TEXT("Generated-mode selector option dispatch exposes %d generated-choice option%s and no direct-launch cases."), + Surface.GeneratedChoiceOptionCount, + Surface.GeneratedChoiceOptionCount == 1 ? TEXT("") : TEXT("s") + ); + } + else if (OptionMenuSurface.bHasSelector && Surface.bHasDispatchOptions) + { + Surface.StatusLine = FString::Printf( + TEXT("Generated-mode selector option dispatch exposes %d direct-launch option%s, %d generated-choice option%s, and %d unavailable option%s."), + Surface.DirectLaunchOptionCount, + Surface.DirectLaunchOptionCount == 1 ? TEXT("") : TEXT("s"), + Surface.GeneratedChoiceOptionCount, + Surface.GeneratedChoiceOptionCount == 1 ? TEXT("") : TEXT("s"), + Surface.UnavailableOptionCount, + Surface.UnavailableOptionCount == 1 ? TEXT("") : TEXT("s") + ); + } + else if (OptionMenuSurface.bHasSelector) + { + Surface.StatusLine = + TEXT("Generated-mode selector option dispatch is active, but this selector does not expose imported options."); + } + else + { + Surface.StatusLine = OptionMenuSurface.StatusLine.IsEmpty() + ? TEXT("Generated-mode selector option dispatch 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("Coverage: %s | Selector: %s | Binding: %s"), + OptionActionabilitySurface.StatusLine.IsEmpty() + ? TEXT("n/a") + : *OptionActionabilitySurface.StatusLine, + *SelectorSummary, + *BindingSummary + ); + + return Surface; +} diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp index e69710b..6f7f0f8 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp @@ -733,6 +733,23 @@ UHyperTwistTrainingPanelWidget::GetDisplayedCoachGeneratedModeSelectorOptionActi ); } +FHyperTwistTrainingCoachGeneratedModeSelectorOptionDispatchSurface +UHyperTwistTrainingPanelWidget::GetDisplayedCoachGeneratedModeSelectorOptionDispatchSurface( + const FString& SelectorId +) const +{ + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionMenuSurface OptionMenuSurface = + GetDisplayedCoachGeneratedModeSelectorOptionMenuSurface(SelectorId); + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionActionabilitySurface + OptionActionabilitySurface = + GetDisplayedCoachGeneratedModeSelectorOptionActionabilitySurface(SelectorId); + return UHyperTwistTrainingCoachLibrary::DeriveCoachGeneratedModeSelectorOptionDispatchSurface( + SelectorId, + OptionMenuSurface, + OptionActionabilitySurface + ); +} + FHyperTwistTrainingRunState UHyperTwistTrainingPanelWidget::StartCoachRecommendedRun( const int32 MaxCases, const FString& StartActionSourceLabel diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp index 966c6f4..2c4286c 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp @@ -714,6 +714,23 @@ AHyperTwistTrainingSessionActor::GetDisplayedCoachGeneratedModeSelectorOptionAct ); } +FHyperTwistTrainingCoachGeneratedModeSelectorOptionDispatchSurface +AHyperTwistTrainingSessionActor::GetDisplayedCoachGeneratedModeSelectorOptionDispatchSurface( + const FString& SelectorId +) const +{ + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionMenuSurface OptionMenuSurface = + GetDisplayedCoachGeneratedModeSelectorOptionMenuSurface(SelectorId); + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionActionabilitySurface + OptionActionabilitySurface = + GetDisplayedCoachGeneratedModeSelectorOptionActionabilitySurface(SelectorId); + return UHyperTwistTrainingCoachLibrary::DeriveCoachGeneratedModeSelectorOptionDispatchSurface( + SelectorId, + OptionMenuSurface, + OptionActionabilitySurface + ); +} + FHyperTwistTrainingRunState AHyperTwistTrainingSessionActor::StartCoachRecommendedTrainingRun( const int32 MaxCases ) diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h index 32d8d10..83207b3 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h @@ -704,4 +704,13 @@ public: const FString& SelectorId, const FHyperTwistTrainingCoachGeneratedModeSelectorOptionMenuSurface& OptionMenuSurface ); + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + static FHyperTwistTrainingCoachGeneratedModeSelectorOptionDispatchSurface + DeriveCoachGeneratedModeSelectorOptionDispatchSurface( + const FString& SelectorId, + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionMenuSurface& OptionMenuSurface, + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionActionabilitySurface& + OptionActionabilitySurface + ); }; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h index 562a869..1f687dd 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h @@ -264,6 +264,10 @@ public: FHyperTwistTrainingCoachGeneratedModeSelectorOptionActionabilitySurface GetDisplayedCoachGeneratedModeSelectorOptionActionabilitySurface(const FString& SelectorId) const; + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingCoachGeneratedModeSelectorOptionDispatchSurface + GetDisplayedCoachGeneratedModeSelectorOptionDispatchSurface(const FString& SelectorId) const; + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach") FHyperTwistTrainingRunState StartCoachRecommendedRun( int32 MaxCases, diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h index 5ea0b33..3d1ac24 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h @@ -245,6 +245,10 @@ public: FHyperTwistTrainingCoachGeneratedModeSelectorOptionActionabilitySurface GetDisplayedCoachGeneratedModeSelectorOptionActionabilitySurface(const FString& SelectorId) const; + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingCoachGeneratedModeSelectorOptionDispatchSurface + GetDisplayedCoachGeneratedModeSelectorOptionDispatchSurface(const FString& SelectorId) const; + UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach") FHyperTwistTrainingRunState StartCoachRecommendedTrainingRun(int32 MaxCases); diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h index 5be1ddf..b92e54d 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h @@ -71,6 +71,15 @@ enum class EHyperTwistTrainingCoachEligibilityKind : uint8 NoEligibleAction UMETA(DisplayName = "No Eligible Action") }; +UENUM(BlueprintType) +enum class EHyperTwistTrainingCoachGeneratedModeOptionDispatchKind : uint8 +{ + None UMETA(DisplayName = "None"), + DirectCaseLaunch UMETA(DisplayName = "Direct Case Launch"), + ApplyGeneratedChoice UMETA(DisplayName = "Apply Generated Choice"), + Unavailable UMETA(DisplayName = "Unavailable") +}; + UENUM(BlueprintType) enum class EHyperTwistTrainingPromptKind : uint8 { @@ -8776,3 +8785,121 @@ struct FHyperTwistTrainingCoachGeneratedModeSelectorOptionActionabilitySurface || bHasCaseCoverage; } }; + +USTRUCT(BlueprintType) +struct FHyperTwistTrainingCoachGeneratedModeSelectorOptionDispatchEntrySurface +{ + 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") + EHyperTwistTrainingCoachGeneratedModeOptionDispatchKind DispatchKind = + EHyperTwistTrainingCoachGeneratedModeOptionDispatchKind::None; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString DispatchLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString DetailLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString DirectLaunchOptionId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString GeneratedChoiceSelectedValue; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString GeneratedChoiceDisplayValue; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bMatchesCurrentSelection = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCanDispatch = false; + + bool IsStructurallyValid() const + { + return !OptionId.IsEmpty() + || !Label.IsEmpty() + || !DispatchLine.IsEmpty() + || DispatchKind != EHyperTwistTrainingCoachGeneratedModeOptionDispatchKind::None + || bCanDispatch; + } +}; + +USTRUCT(BlueprintType) +struct FHyperTwistTrainingCoachGeneratedModeSelectorOptionDispatchSurface +{ + 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 DispatchSummaryLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString CurrentSelectionDispatchLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString DetailLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray OptionEntries; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 DirectLaunchOptionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 GeneratedChoiceOptionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 UnavailableOptionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasSelector = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasDispatchOptions = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasDirectLaunchOptions = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasGeneratedChoiceOptions = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCurrentSelectionDispatchesDirectLaunch = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCurrentSelectionDispatchesGeneratedChoice = false; + + bool IsStructurallyValid() const + { + return !StatusLine.IsEmpty() + || !SelectorId.IsEmpty() + || OptionEntries.Num() > 0 + || bHasSelector + || bHasDispatchOptions; + } +}; diff --git a/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md b/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md index 60ffaf6..9acbd56 100644 --- a/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md +++ b/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md @@ -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 actionability / case-coverage surface over the already-cached selector/runtime state, so gameplay/Blueprint callers can render which imported options still map cleanly to owned cases and which remain selection-only choices without reading raw option-case payloads or runtime probes directly - - the current next bounded packet is to expose one thin generated-mode selector option interaction / dispatch surface on those same two consumer surfaces 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 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 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 diff --git a/docs/arch/HYPERTWIST_PHASE4_GAMEPLAY_GENERATED_MODE_SELECTOR_OPTION_DISPATCH_SURFACE_PACKET_2026-05-08.md b/docs/arch/HYPERTWIST_PHASE4_GAMEPLAY_GENERATED_MODE_SELECTOR_OPTION_DISPATCH_SURFACE_PACKET_2026-05-08.md new file mode 100644 index 0000000..41d00b1 --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE4_GAMEPLAY_GENERATED_MODE_SELECTOR_OPTION_DISPATCH_SURFACE_PACKET_2026-05-08.md @@ -0,0 +1,106 @@ +# HyperTwist Phase 4 gameplay generated-mode selector option interaction / dispatch 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 case-coverage surface by exposing one parameterized option interaction / dispatch surface for a chosen selector id. + +The open tasks were: + +- stop forcing gameplay / Blueprint callers to reconstruct runtime action-path rules just to decide whether choosing an imported option should launch a direct owned case or apply a generated-mode selector choice +- expose exact dispatch posture and dispatch arguments for each imported option +- keep the surface derived from already-cached gameplay consumer selector/runtime state plus the already-landed option-detail and case-coverage surfaces instead of widening into new backend behavior + +It is not: + +- a new selector-application behavior packet +- a generated-mode execution packet +- a launch-request refresh / execute-impact packet +- a broader gameplay UI composition pass + +## Scope + +Bounded lane: + +- add first-party generated-mode selector option dispatch surfaces in shared training types +- add a coach-library helper that derives dispatch posture from the requested selector id plus the already-landed option-detail and case-coverage surfaces +- expose that parameterized dispatch surface on `HyperTwistTrainingPanelWidget` and `HyperTwistTrainingSessionActor` +- keep the derivation presentation-only and rooted in the already-owned runtime action paths + +Out of scope: + +- changing direct option-launch behavior +- changing generated-mode selector-choice apply behavior +- changing launch-request freshness rules +- widening into execute-impact posture yet + +## 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 selector / application context +- those same consumers already exposed truthful selector roster / option-availability summary +- those same consumers already exposed truthful chosen-selector option detail +- those same consumers already exposed truthful chosen-selector option case coverage + +But one thin consumer seam was still open: + +- callers could tell whether an option still mapped to an owned case +- but they still had to know raw runtime branching rules to decide whether interacting with that option should launch a direct case, apply a generated-mode selector choice, or stay unavailable + +That meant: + +- case coverage was thin and reusable +- but actual option dispatch posture still leaked runtime action-path rules into callers + +So the next bounded move was: + +- keep the already-landed generated-mode context, roster, option-detail, and option-coverage surfaces unchanged +- and expose one narrow option interaction / dispatch surface above those already-cached surfaces + +## What landed + +Primary code changes: + +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h` + - added `EHyperTwistTrainingCoachGeneratedModeOptionDispatchKind` + - added `FHyperTwistTrainingCoachGeneratedModeSelectorOptionDispatchEntrySurface` + - added `FHyperTwistTrainingCoachGeneratedModeSelectorOptionDispatchSurface` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp` + - added `DeriveCoachGeneratedModeSelectorOptionDispatchSurface(...)` + - dispatch posture is derived from the requested selector id plus the already-landed option-detail and option-coverage surfaces +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp` + - added `GetDisplayedCoachGeneratedModeSelectorOptionDispatchSurface(const FString& SelectorId)` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp` + - added `GetDisplayedCoachGeneratedModeSelectorOptionDispatchSurface(const FString& SelectorId)` + +Surface behavior: + +- reports whether each imported option dispatches as direct owned-case launch, generated-mode selector-choice apply, or unavailable +- exposes exact dispatch arguments for direct launch and generated-choice apply flows +- reports how the current cached selection would dispatch right now +- keeps the surface presentation-only and compatible with the already-landed generated-mode option-detail and case-coverage 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 execution-impact / launch-request-refresh surface so callers can render whether choosing an option 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.