diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp index 2996f3c..04dabac 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp @@ -7006,3 +7006,166 @@ UHyperTwistTrainingCoachLibrary::DeriveCoachGeneratedModeSelectorApplicationCont return Surface; } + +FHyperTwistTrainingCoachGeneratedModeSelectorRosterSurface +UHyperTwistTrainingCoachLibrary::DeriveCoachGeneratedModeSelectorRosterSurface( + const FHyperTwistTrainingDeck& ActiveDeck, + const FHyperTwistTrainingImportedRuntimeSelectionState& ImportedRuntimeSelectionState, + const FHyperTwistTrainingCoachGeneratedModeSelectorApplicationContextSurface& + SelectorApplicationContextSurface +) +{ + FHyperTwistTrainingCoachGeneratedModeSelectorRosterSurface Surface; + Surface.Headline = TEXT("Generated Mode Selector Roster"); + Surface.CoverageLine = SelectorApplicationContextSurface.CoverageLine; + + const FHyperTwistTrainingImportedRuntimeSurface& RuntimeSurface = ActiveDeck.ImportedRuntimeSurface; + const bool bHasGeneratedModeSurface = RuntimeSurface.IsStructurallyValid() + && RuntimeSurface.SurfaceKind.Equals(TEXT("generated-mode"), ESearchCase::IgnoreCase); + + for (const FHyperTwistTrainingImportedRuntimeSelector& Selector : RuntimeSurface.Selectors) + { + if (!Selector.IsStructurallyValid()) + { + continue; + } + + FHyperTwistTrainingCoachGeneratedModeSelectorRosterEntrySurface Entry; + Entry.SelectorId = Selector.SelectorId; + Entry.Label = Selector.Label; + Entry.BindingLabel = Selector.SelectorBinding; + + const FHyperTwistTrainingImportedRuntimeSelectorChoice* MatchingChoice = + ImportedRuntimeSelectionState.SelectorChoices.FindByPredicate( + [&Selector](const FHyperTwistTrainingImportedRuntimeSelectorChoice& Candidate) + { + return Candidate.SelectorId == Selector.SelectorId && Candidate.IsStructurallyValid(); + } + ); + Entry.bHasSelection = MatchingChoice != nullptr; + Entry.bIsResolved = MatchingChoice != nullptr; + if (MatchingChoice != nullptr) + { + Entry.SelectedValue = MatchingChoice->SelectedValue; + Entry.SelectedDisplayValue = MatchingChoice->DisplayValue.IsEmpty() + ? MatchingChoice->SelectedValue + : MatchingChoice->DisplayValue; + } + + for (const FHyperTwistTrainingImportedRuntimeSelectorOption& Option : Selector.Options) + { + if (Option.IsStructurallyValid()) + { + ++Entry.AvailableOptionCount; + } + } + Entry.bHasAvailableOptions = Entry.AvailableOptionCount > 0; + Entry.SelectionLine = Entry.bHasSelection + ? FString::Printf( + TEXT("Selection: %s"), + Entry.SelectedDisplayValue.IsEmpty() ? TEXT("n/a") : *Entry.SelectedDisplayValue + ) + : TEXT("Selection: unresolved"); + Entry.OptionAvailabilityLine = Entry.bHasAvailableOptions + ? FString::Printf( + TEXT("Options: %d available"), + Entry.AvailableOptionCount + ) + : TEXT("Options: no imported options"); + + Surface.SelectorEntries.Add(MoveTemp(Entry)); + } + + Surface.TotalSelectorCount = Surface.SelectorEntries.Num(); + for (const FHyperTwistTrainingCoachGeneratedModeSelectorRosterEntrySurface& Entry : + Surface.SelectorEntries) + { + if (Entry.bIsResolved) + { + ++Surface.ResolvedSelectorCount; + } + if (!Entry.bIsResolved && Entry.bHasAvailableOptions) + { + ++Surface.UnresolvedWithAvailableOptionsCount; + } + } + Surface.UnresolvedSelectorCount = Surface.TotalSelectorCount - Surface.ResolvedSelectorCount; + Surface.bHasSelectorRoster = Surface.TotalSelectorCount > 0; + Surface.bHasUnresolvedSelectors = Surface.UnresolvedSelectorCount > 0; + Surface.bHasResolvableUnresolvedSelectors = Surface.UnresolvedWithAvailableOptionsCount > 0; + Surface.bAllUnresolvedSelectorsHaveOptions = Surface.UnresolvedSelectorCount > 0 + && Surface.UnresolvedSelectorCount == Surface.UnresolvedWithAvailableOptionsCount; + + TArray UnresolvedLabels; + for (const FHyperTwistTrainingCoachGeneratedModeSelectorRosterEntrySurface& Entry : + Surface.SelectorEntries) + { + if (!Entry.bIsResolved) + { + UnresolvedLabels.Add(Entry.Label.IsEmpty() ? Entry.SelectorId : Entry.Label); + } + } + + if (Surface.bHasSelectorRoster && Surface.UnresolvedSelectorCount == 0) + { + Surface.StatusLine = + TEXT("Generated-mode selector roster is fully resolved for the active generated-mode surface."); + } + else if (Surface.bHasSelectorRoster) + { + Surface.StatusLine = FString::Printf( + TEXT("Generated-mode selector roster has %d unresolved selector%s; %d still expose imported options."), + Surface.UnresolvedSelectorCount, + Surface.UnresolvedSelectorCount == 1 ? TEXT("") : TEXT("s"), + Surface.UnresolvedWithAvailableOptionsCount + ); + } + else if (bHasGeneratedModeSurface) + { + Surface.StatusLine = + TEXT("Generated-mode selector roster is empty for the active generated-mode surface."); + } + else if (SelectorApplicationContextSurface.bHasSelectionState) + { + Surface.StatusLine = + TEXT("Generated-mode selector roster is not active, but cached selector choices remain."); + } + else + { + Surface.StatusLine = + TEXT("Generated-mode selector roster is not currently available."); + } + + if (UnresolvedLabels.Num() == 0) + { + Surface.UnresolvedSelectorsLine = TEXT("Unresolved: none"); + } + else + { + const int32 PreviewCount = FMath::Min(UnresolvedLabels.Num(), 3); + TArray PreviewLabels; + PreviewLabels.Append(UnresolvedLabels.GetData(), PreviewCount); + Surface.UnresolvedSelectorsLine = FString::Printf( + TEXT("Unresolved: %s%s"), + *FString::Join(PreviewLabels, TEXT(", ")), + UnresolvedLabels.Num() > PreviewCount + ? *FString::Printf(TEXT(" (+%d more)"), UnresolvedLabels.Num() - PreviewCount) + : TEXT("") + ); + } + + Surface.DetailLine = FString::Printf( + TEXT("Selector application: %s | Identity: %s | Refresh: %s"), + SelectorApplicationContextSurface.StatusLine.IsEmpty() + ? TEXT("n/a") + : *SelectorApplicationContextSurface.StatusLine, + SelectorApplicationContextSurface.IdentityLine.IsEmpty() + ? TEXT("n/a") + : *SelectorApplicationContextSurface.IdentityLine, + SelectorApplicationContextSurface.RefreshLine.IsEmpty() + ? TEXT("n/a") + : *SelectorApplicationContextSurface.RefreshLine + ); + + return Surface; +} diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp index 9c04a6b..8ea7ac8 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp @@ -691,6 +691,19 @@ UHyperTwistTrainingPanelWidget::GetDisplayedCoachGeneratedModeSelectorApplicatio ); } +FHyperTwistTrainingCoachGeneratedModeSelectorRosterSurface +UHyperTwistTrainingPanelWidget::GetDisplayedCoachGeneratedModeSelectorRosterSurface() const +{ + const FHyperTwistTrainingCoachGeneratedModeSelectorApplicationContextSurface + SelectorApplicationContextSurface = + GetDisplayedCoachGeneratedModeSelectorApplicationContextSurface(); + return UHyperTwistTrainingCoachLibrary::DeriveCoachGeneratedModeSelectorRosterSurface( + CachedRunState.ActiveDeck, + CachedImportedRuntimeSelectionState, + SelectorApplicationContextSurface + ); +} + 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 99f04cd..e04c6b4 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp @@ -672,6 +672,19 @@ AHyperTwistTrainingSessionActor::GetDisplayedCoachGeneratedModeSelectorApplicati ); } +FHyperTwistTrainingCoachGeneratedModeSelectorRosterSurface +AHyperTwistTrainingSessionActor::GetDisplayedCoachGeneratedModeSelectorRosterSurface() const +{ + const FHyperTwistTrainingCoachGeneratedModeSelectorApplicationContextSurface + SelectorApplicationContextSurface = + GetDisplayedCoachGeneratedModeSelectorApplicationContextSurface(); + return UHyperTwistTrainingCoachLibrary::DeriveCoachGeneratedModeSelectorRosterSurface( + CachedRunState.ActiveDeck, + CachedImportedRuntimeSelectionState, + SelectorApplicationContextSurface + ); +} + 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 32d8bf8..c73d26b 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h @@ -678,4 +678,13 @@ public: const FHyperTwistTrainingCoachGeneratedModeLaunchExecutionContextSurface& LaunchExecutionContextSurface ); + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + static FHyperTwistTrainingCoachGeneratedModeSelectorRosterSurface + DeriveCoachGeneratedModeSelectorRosterSurface( + const FHyperTwistTrainingDeck& ActiveDeck, + const FHyperTwistTrainingImportedRuntimeSelectionState& ImportedRuntimeSelectionState, + const FHyperTwistTrainingCoachGeneratedModeSelectorApplicationContextSurface& + SelectorApplicationContextSurface + ); }; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h index 060bf65..1dbfd87 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h @@ -252,6 +252,10 @@ public: FHyperTwistTrainingCoachGeneratedModeSelectorApplicationContextSurface GetDisplayedCoachGeneratedModeSelectorApplicationContextSurface() const; + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingCoachGeneratedModeSelectorRosterSurface + GetDisplayedCoachGeneratedModeSelectorRosterSurface() 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 3aad2bc..90a2217 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h @@ -233,6 +233,10 @@ public: FHyperTwistTrainingCoachGeneratedModeSelectorApplicationContextSurface GetDisplayedCoachGeneratedModeSelectorApplicationContextSurface() const; + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingCoachGeneratedModeSelectorRosterSurface + GetDisplayedCoachGeneratedModeSelectorRosterSurface() 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 9c1ab90..4964b5c 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h @@ -8463,3 +8463,109 @@ struct FHyperTwistTrainingCoachGeneratedModeSelectorApplicationContextSurface || bHasLaunchConfig; } }; + +USTRUCT(BlueprintType) +struct FHyperTwistTrainingCoachGeneratedModeSelectorRosterEntrySurface +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString SelectorId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString Label; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString BindingLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString SelectedValue; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString SelectedDisplayValue; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString SelectionLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString OptionAvailabilityLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 AvailableOptionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasSelection = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasAvailableOptions = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bIsResolved = false; + + bool IsStructurallyValid() const + { + return !SelectorId.IsEmpty() + || !Label.IsEmpty() + || !SelectionLine.IsEmpty() + || AvailableOptionCount > 0 + || bHasSelection + || bHasAvailableOptions; + } +}; + +USTRUCT(BlueprintType) +struct FHyperTwistTrainingCoachGeneratedModeSelectorRosterSurface +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString Headline; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString StatusLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString CoverageLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString UnresolvedSelectorsLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString DetailLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray SelectorEntries; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 TotalSelectorCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 ResolvedSelectorCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 UnresolvedSelectorCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 UnresolvedWithAvailableOptionsCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasSelectorRoster = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasUnresolvedSelectors = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasResolvableUnresolvedSelectors = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bAllUnresolvedSelectorsHaveOptions = false; + + bool IsStructurallyValid() const + { + return !StatusLine.IsEmpty() + || !CoverageLine.IsEmpty() + || SelectorEntries.Num() > 0 + || bHasSelectorRoster + || bHasUnresolvedSelectors; + } +}; diff --git a/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md b/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md index 88cd55d..9a7d49f 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/application context surface over the already-cached imported selection and launch-config state, so gameplay/Blueprint callers can render active selector-state coverage, generator/runtime identity, and request-refresh implications without reading raw imported selection/config payloads directly - - the current next bounded packet is to expose one thin generated-mode selector roster / option-availability surface on those same two consumer surfaces so gameplay/Blueprint callers can render active selector labels, bindings, and unresolved option availability without reading raw imported runtime selector arrays or option payloads directly + - the latest landed bounded `Phase 4` packet stays on that same gameplay-facing consumer lane and adds one thin generated-mode selector roster / option-availability surface over the active imported runtime surface and cached selection state, so gameplay/Blueprint callers can render active selector labels, bindings, selected values, and unresolved option availability without reading raw imported selector arrays or option payloads directly + - the current next bounded packet is to expose one thin generated-mode selector option-menu / detail surface on those same two consumer surfaces so gameplay/Blueprint callers can render normalized selector-option ids, labels, and unresolved-selection detail for a chosen selector without reading raw imported option 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 diff --git a/docs/arch/HYPERTWIST_PHASE4_GAMEPLAY_GENERATED_MODE_SELECTOR_ROSTER_AVAILABILITY_SURFACE_PACKET_2026-05-08.md b/docs/arch/HYPERTWIST_PHASE4_GAMEPLAY_GENERATED_MODE_SELECTOR_ROSTER_AVAILABILITY_SURFACE_PACKET_2026-05-08.md new file mode 100644 index 0000000..e4dce41 --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE4_GAMEPLAY_GENERATED_MODE_SELECTOR_ROSTER_AVAILABILITY_SURFACE_PACKET_2026-05-08.md @@ -0,0 +1,102 @@ +# HyperTwist Phase 4 gameplay generated-mode selector roster / option-availability 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 helpers by exposing one pure selector-roster / option-availability surface over the active imported runtime surface and cached selector-choice state. + +The open tasks were: + +- stop forcing gameplay / Blueprint callers to read raw imported runtime selector arrays just to list active selectors, bindings, selected values, and unresolved selector coverage +- stop forcing those same callers to inspect raw option payloads just to tell whether unresolved selectors still expose imported options +- keep the surface derived from already-cached gameplay consumer state instead of widening into selector-application behavior or new runtime fetches + +It is not: + +- a selector option-menu UI packet +- a new selector-application behavior packet +- a generated-mode execution packet +- a broader gameplay HUD composition pass + +## Scope + +Bounded lane: + +- add first-party generated-mode selector-roster surfaces in shared training types +- add a coach-library helper that derives the roster from the active deck imported runtime surface, cached imported selection state, and the already-landed selector / application context surface +- expose that derived roster surface on `HyperTwistTrainingPanelWidget` and `HyperTwistTrainingSessionActor` +- keep the derivation presentation-only and rooted in already-cached gameplay consumer state + +Out of scope: + +- changing selector-choice persistence +- changing generated-mode launch-config derivation rules +- changing selector-option application behavior +- widening into parameterized selector option-menu/detail surfaces 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 + +But one thin consumer seam was still open: + +- callers could tell whether selector application was complete +- but they still had to read raw runtime selectors and option payloads to explain which selectors were active, which bindings they mapped to, what was already selected, and whether unresolved selectors still exposed imported options + +That meant: + +- selector-application posture was thin and reusable +- but selector roster and option-availability detail still leaked raw imported payloads into callers + +So the next bounded move was: + +- keep the already-landed launch / execution and selector / application context surfaces unchanged +- and expose one narrow selector-roster / option-availability surface above the cached selector state + +## What landed + +Primary code changes: + +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h` + - added `FHyperTwistTrainingCoachGeneratedModeSelectorRosterEntrySurface` + - added `FHyperTwistTrainingCoachGeneratedModeSelectorRosterSurface` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp` + - added `DeriveCoachGeneratedModeSelectorRosterSurface(...)` + - roster is derived from the active imported runtime surface, cached imported selection state, and the already-landed selector / application context surface +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp` + - added `GetDisplayedCoachGeneratedModeSelectorRosterSurface()` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp` + - added `GetDisplayedCoachGeneratedModeSelectorRosterSurface()` + +Surface behavior: + +- reports whether a generated-mode selector roster is currently active +- exposes one entry per structurally valid imported selector with selector id, label, binding label, selected value/display value, and imported-option availability +- reports aggregate selector coverage, unresolved selector count, unresolved selectors with imported options, and a short unresolved-selector preview line +- keeps the surface presentation-only and compatible with the already-landed generated-mode context 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 parameterized generated-mode selector option-menu / detail surface so callers can render normalized option ids, labels, and unresolved-selection detail for a chosen selector without reading raw imported option payloads directly.