From d1f0708b9cb8e20cd11160a4e967fa688354ab8e Mon Sep 17 00:00:00 2001 From: axiomlogicnexus Date: Fri, 8 May 2026 14:58:11 +0200 Subject: [PATCH] Expose gameplay selector owned-execution case preview surface --- .../HyperTwistTrainingCoachLibrary.cpp | 493 ++++++++++++++++++ .../HyperTwistTrainingPanelWidget.cpp | 19 + .../HyperTwistTrainingSessionActor.cpp | 19 + .../HyperTwistTrainingCoachLibrary.h | 10 + .../HyperTwistTrainingPanelWidget.h | 6 + .../HyperTwistTrainingSessionActor.h | 6 + .../HyperTwistTrainingTypes.h | 193 +++++++ ...RTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md | 4 +- ...STATE_PREVIEW_SURFACE_PACKET_2026-05-08.md | 104 ++++ 9 files changed, 852 insertions(+), 2 deletions(-) create mode 100644 docs/arch/HYPERTWIST_PHASE4_GAMEPLAY_GENERATED_MODE_SELECTOR_OPTION_OWNED_EXECUTION_CASE_IDENTITY_CANONICAL_STATE_PREVIEW_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 23a2bd1..bc2d064 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp @@ -10906,6 +10906,15 @@ UHyperTwistTrainingCoachLibrary::DeriveCoachGeneratedModeSelectorOptionLaunchReq Entry.Label = PreviewEntry.Label; Entry.OptionValue = PreviewEntry.OptionValue; Entry.LaunchRequestPreviewKind = PreviewEntry.LaunchRequestPreviewKind; + Entry.HypotheticalDeckId = PreviewEntry.HypotheticalDeckId.IsEmpty() + ? RunState.ActiveDeck.DeckId + : PreviewEntry.HypotheticalDeckId; + Entry.HypotheticalSurfaceId = PreviewEntry.HypotheticalSurfaceId.IsEmpty() + ? RuntimeSurface.SurfaceId + : PreviewEntry.HypotheticalSurfaceId; + Entry.HypotheticalRuntimeModeId = PreviewEntry.HypotheticalRuntimeModeId.IsEmpty() + ? RuntimeSurface.RuntimeModeId + : PreviewEntry.HypotheticalRuntimeModeId; Entry.CurrentStoredRequestId = LaunchRequest.RequestId; Entry.CurrentStoredRequestedAtUtc = LaunchRequest.RequestedAtUtc; Entry.CurrentStoredRequestSource = LaunchRequest.RequestSource; @@ -11210,3 +11219,487 @@ UHyperTwistTrainingCoachLibrary::DeriveCoachGeneratedModeSelectorOptionLaunchReq return Surface; } + +FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionCasePreviewSurface +UHyperTwistTrainingCoachLibrary::DeriveCoachGeneratedModeSelectorOptionOwnedExecutionCasePreviewSurface( + const FHyperTwistTrainingRunState& RunState, + const FString& SelectorId, + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionMenuSurface& OptionMenuSurface, + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestEnvelopePreviewSurface& + OptionLaunchRequestEnvelopePreviewSurface +) +{ + FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionCasePreviewSurface Surface; + Surface.SelectorId = SelectorId; + Surface.SelectorLabel = OptionMenuSurface.SelectorLabel; + Surface.BindingLabel = OptionMenuSurface.BindingLabel; + Surface.Headline = TEXT("Generated Mode Selector Option Owned-Execution Case Preview"); + Surface.bHasSelector = OptionMenuSurface.bHasSelector; + + if (!SelectorId.IsEmpty()) + { + Surface.Headline = OptionMenuSurface.SelectorLabel.IsEmpty() + ? FString::Printf( + TEXT("Generated Mode Selector Option Owned-Execution Case Preview: %s"), + *SelectorId + ) + : FString::Printf( + TEXT("Generated Mode Selector Option Owned-Execution Case Preview: %s"), + *OptionMenuSurface.SelectorLabel + ); + } + + if (SelectorId.IsEmpty()) + { + Surface.StatusLine = + TEXT("Generated-mode selector option owned-execution case preview requires a selector id from the active selector roster."); + Surface.OwnedCaseSummaryLine = TEXT("Owned case target: n/a"); + Surface.CanonicalStateSummaryLine = TEXT("Canonical state ref: n/a"); + Surface.CurrentSelectionPreviewLine = TEXT("Current selection: n/a"); + Surface.DetailLine = OptionLaunchRequestEnvelopePreviewSurface.StatusLine.IsEmpty() + ? TEXT("Launch-request envelope preview: n/a") + : FString::Printf( + TEXT("Launch-request envelope preview: %s"), + *OptionLaunchRequestEnvelopePreviewSurface.StatusLine + ); + return Surface; + } + + const FHyperTwistTrainingImportedRuntimeSurface& RuntimeSurface = + RunState.ActiveDeck.ImportedRuntimeSurface; + const bool bHasGeneratedModeSurface = RuntimeSurface.IsStructurallyValid() + && RuntimeSurface.SurfaceKind.Equals(TEXT("generated-mode"), ESearchCase::IgnoreCase); + + const auto SanitizeGeneratedModeToken = [](const FString& Value) -> FString + { + FString Sanitized; + Sanitized.Reserve(Value.Len()); + + for (const TCHAR Character : Value) + { + if (FChar::IsAlnum(Character)) + { + Sanitized.AppendChar(FChar::ToLower(Character)); + } + else if (Sanitized.IsEmpty() || Sanitized[Sanitized.Len() - 1] != TEXT('-')) + { + Sanitized.AppendChar(TEXT('-')); + } + } + + while (Sanitized.StartsWith(TEXT("-"))) + { + Sanitized.RightChopInline(1, EAllowShrinking::No); + } + while (Sanitized.EndsWith(TEXT("-"))) + { + Sanitized.LeftChopInline(1, EAllowShrinking::No); + } + + return Sanitized.IsEmpty() ? TEXT("value") : Sanitized; + }; + + const auto BuildGeneratedModeCaseId = [&SanitizeGeneratedModeToken]( + const FString& DeckId, + const FString& RequestId + ) -> FString + { + return DeckId.IsEmpty() || RequestId.IsEmpty() + ? FString() + : FString::Printf( + TEXT("%s/generated/%s"), + *DeckId, + *SanitizeGeneratedModeToken(RequestId) + ); + }; + + const auto BuildGeneratedModeCanonicalStateRef = [&SanitizeGeneratedModeToken]( + const FString& SurfaceId, + const FString& RuntimeModeId, + const FString& RequestId + ) -> FString + { + return SurfaceId.IsEmpty() || RuntimeModeId.IsEmpty() || RequestId.IsEmpty() + ? FString() + : FString::Printf( + TEXT("generated-mode/%s/%s/%s"), + *SanitizeGeneratedModeToken(SurfaceId), + *SanitizeGeneratedModeToken(RuntimeModeId), + *SanitizeGeneratedModeToken(RequestId) + ); + }; + + const auto BuildGeneratedModeCaseIdTemplate = [](const FString& DeckId) -> FString + { + const FString DeckSummary = DeckId.IsEmpty() ? TEXT("") : DeckId; + return FString::Printf(TEXT("%s/generated/"), *DeckSummary); + }; + + const auto BuildGeneratedModeCanonicalStateRefTemplate = [&SanitizeGeneratedModeToken]( + const FString& SurfaceId, + const FString& RuntimeModeId + ) -> FString + { + const FString SurfaceToken = SurfaceId.IsEmpty() + ? TEXT("") + : SanitizeGeneratedModeToken(SurfaceId); + const FString RuntimeToken = RuntimeModeId.IsEmpty() + ? TEXT("") + : SanitizeGeneratedModeToken(RuntimeModeId); + return FString::Printf( + TEXT("generated-mode/%s/%s/"), + *SurfaceToken, + *RuntimeToken + ); + }; + + const auto BuildCoveredCaseTargetLine = []( + const FString& CaseId, + const FString& CaseLabel + ) -> FString + { + if (CaseId.IsEmpty() && CaseLabel.IsEmpty()) + { + return TEXT("Owned case target: covered owned case"); + } + if (CaseLabel.IsEmpty() || CaseLabel == CaseId) + { + return FString::Printf( + TEXT("Owned case target: %s"), + CaseId.IsEmpty() ? TEXT("n/a") : *CaseId + ); + } + return FString::Printf( + TEXT("Owned case target: %s [%s]"), + *CaseLabel, + CaseId.IsEmpty() ? TEXT("n/a") : *CaseId + ); + }; + + if (!RunState.IsStructurallyValid() || !bHasGeneratedModeSurface) + { + Surface.StatusLine = OptionMenuSurface.StatusLine.IsEmpty() + ? TEXT("Generated-mode selector option owned-execution case preview is not currently available.") + : OptionMenuSurface.StatusLine; + Surface.OwnedCaseSummaryLine = TEXT("Owned case target: n/a"); + Surface.CanonicalStateSummaryLine = TEXT("Canonical state ref: n/a"); + Surface.CurrentSelectionPreviewLine = OptionMenuSurface.bHasUnresolvedSelectionDetail + ? OptionMenuSurface.UnresolvedSelectionLine + : (OptionMenuSurface.bHasSelection + ? TEXT("Current selection remains cached, but no active owned-execution case preview is available.") + : TEXT("Current selection: unresolved")); + Surface.DetailLine = FString::Printf( + TEXT("Launch-request envelope preview: %s"), + OptionLaunchRequestEnvelopePreviewSurface.StatusLine.IsEmpty() + ? TEXT("n/a") + : *OptionLaunchRequestEnvelopePreviewSurface.StatusLine + ); + return Surface; + } + + for (const FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestEnvelopePreviewEntrySurface& + PreviewEntry : OptionLaunchRequestEnvelopePreviewSurface.OptionEntries) + { + FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionCasePreviewEntrySurface + Entry; + Entry.OptionId = PreviewEntry.OptionId; + Entry.Label = PreviewEntry.Label; + Entry.OptionValue = PreviewEntry.OptionValue; + Entry.LaunchRequestPreviewKind = PreviewEntry.LaunchRequestPreviewKind; + Entry.bMatchesCurrentSelection = PreviewEntry.bMatchesCurrentSelection; + Entry.bStartsOwnedCaseImmediately = PreviewEntry.bStartsOwnedCaseImmediately; + + switch (PreviewEntry.LaunchRequestPreviewKind) + { + case EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind:: + DirectOwnedCaseStart: + { + FHyperTwistTrainingCase MatchingCase; + if (UHyperTwistTrainingCatalogLibrary::TryGetImportedRuntimeSelectorOptionCaseFromDeck( + RunState.ActiveDeck, + SelectorId, + PreviewEntry.OptionId, + MatchingCase)) + { + const FString CaseLabel = MatchingCase.PromptLabel.IsEmpty() + ? MatchingCase.CaseId + : MatchingCase.PromptLabel; + Entry.PreviewCaseId = MatchingCase.CaseId; + Entry.PreviewCanonicalStateRef = MatchingCase.CanonicalStateRef; + Entry.OwnedCaseTargetLine = + BuildCoveredCaseTargetLine(MatchingCase.CaseId, CaseLabel); + Entry.CanonicalStateRefLine = MatchingCase.CanonicalStateRef.IsEmpty() + ? TEXT("Canonical state ref: none on the covered owned case.") + : FString::Printf( + TEXT("Canonical state ref: %s"), + *MatchingCase.CanonicalStateRef + ); + Entry.PreviewStabilityLine = + TEXT("Preview stability: exact direct owned-case target from imported option coverage."); + Entry.bHasExactOwnedCaseTarget = !Entry.PreviewCaseId.IsEmpty(); + Entry.bHasExactCanonicalStateRef = !Entry.PreviewCanonicalStateRef.IsEmpty(); + Entry.bMissingCanonicalStateRef = + Entry.bHasExactOwnedCaseTarget && !Entry.bHasExactCanonicalStateRef; + Entry.bCanPreview = true; + ++Surface.DirectOwnedCaseTargetOptionCount; + if (Entry.bHasExactCanonicalStateRef) + { + ++Surface.ExactCanonicalStateRefOptionCount; + } + else if (Entry.bHasExactOwnedCaseTarget) + { + ++Surface.MissingCanonicalStateRefOptionCount; + } + } + else + { + Entry.OwnedCaseTargetLine = + TEXT("Owned case target: covered owned case could not be resolved from the active deck."); + Entry.CanonicalStateRefLine = TEXT("Canonical state ref: unavailable"); + Entry.PreviewStabilityLine = + TEXT("Preview stability: unavailable because the covered case could not be re-resolved from the active deck."); + Entry.bCanPreview = false; + ++Surface.UnavailableOptionCount; + } + break; + } + + case EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind:: + StoredRequestReusable: + Entry.PreviewCaseId = BuildGeneratedModeCaseId( + PreviewEntry.HypotheticalDeckId, + PreviewEntry.CurrentStoredRequestId + ); + Entry.PreviewCanonicalStateRef = BuildGeneratedModeCanonicalStateRef( + PreviewEntry.HypotheticalSurfaceId, + PreviewEntry.HypotheticalRuntimeModeId, + PreviewEntry.CurrentStoredRequestId + ); + Entry.OwnedCaseTargetLine = Entry.PreviewCaseId.IsEmpty() + ? TEXT("Owned case target on execute: unavailable") + : FString::Printf( + TEXT("Owned case target on execute: %s"), + *Entry.PreviewCaseId + ); + Entry.CanonicalStateRefLine = Entry.PreviewCanonicalStateRef.IsEmpty() + ? TEXT("Canonical state ref on execute: unavailable") + : FString::Printf( + TEXT("Canonical state ref on execute: %s"), + *Entry.PreviewCanonicalStateRef + ); + Entry.PreviewStabilityLine = + TEXT("Preview stability: exact while execute reuses the current stored request envelope."); + Entry.bHasExactOwnedCaseTarget = !Entry.PreviewCaseId.IsEmpty(); + Entry.bHasExactCanonicalStateRef = !Entry.PreviewCanonicalStateRef.IsEmpty(); + Entry.bMissingCanonicalStateRef = + Entry.bHasExactOwnedCaseTarget && !Entry.bHasExactCanonicalStateRef; + Entry.bCanPreview = true; + ++Surface.StoredRequestExactTargetOptionCount; + if (Entry.bHasExactCanonicalStateRef) + { + ++Surface.ExactCanonicalStateRefOptionCount; + } + else if (Entry.bHasExactOwnedCaseTarget) + { + ++Surface.MissingCanonicalStateRefOptionCount; + } + break; + + case EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind:: + RefreshRequired: + Entry.PreviewCaseIdTemplate = + BuildGeneratedModeCaseIdTemplate(PreviewEntry.HypotheticalDeckId); + Entry.PreviewCanonicalStateRefTemplate = + BuildGeneratedModeCanonicalStateRefTemplate( + PreviewEntry.HypotheticalSurfaceId, + PreviewEntry.HypotheticalRuntimeModeId + ); + Entry.OwnedCaseTargetLine = FString::Printf( + TEXT("Owned case target after request refresh: %s"), + Entry.PreviewCaseIdTemplate.IsEmpty() + ? TEXT("unavailable") + : *Entry.PreviewCaseIdTemplate + ); + Entry.CanonicalStateRefLine = FString::Printf( + TEXT("Canonical state ref after request refresh: %s"), + Entry.PreviewCanonicalStateRefTemplate.IsEmpty() + ? TEXT("unavailable") + : *Entry.PreviewCanonicalStateRefTemplate + ); + Entry.PreviewStabilityLine = PreviewEntry.bWouldPersistFirstStoredRequestEnvelope + ? TEXT("Preview stability: template only until execute persists the first stored request envelope.") + : (PreviewEntry.bWouldRefreshStoredRequestEnvelope + ? TEXT("Preview stability: template only until execute refreshes the stored request envelope.") + : TEXT("Preview stability: template only until execute replaces the stored request envelope.")); + Entry.bHasOwnedCaseTargetTemplate = !Entry.PreviewCaseIdTemplate.IsEmpty(); + Entry.bHasCanonicalStateRefTemplate = + !Entry.PreviewCanonicalStateRefTemplate.IsEmpty(); + Entry.bCanPreview = true; + ++Surface.RefreshTemplateTargetOptionCount; + if (Entry.bHasCanonicalStateRefTemplate) + { + ++Surface.TemplateCanonicalStateRefOptionCount; + } + break; + + case EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind:: + UnresolvedAfterChoice: + Entry.OwnedCaseTargetLine = + TEXT("Owned case target: blocked until every required selector is resolved."); + Entry.CanonicalStateRefLine = + TEXT("Canonical state ref: blocked until every required selector is resolved."); + Entry.PreviewStabilityLine = + TEXT("Preview stability: blocked while selector coverage remains incomplete."); + Entry.bPreviewBlocked = true; + Entry.bCanPreview = true; + ++Surface.BlockedOptionCount; + break; + + case EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind:: + PayloadUnavailable: + Entry.OwnedCaseTargetLine = + TEXT("Owned case target: blocked until a structurally valid launch payload can be derived."); + Entry.CanonicalStateRefLine = + TEXT("Canonical state ref: blocked until a structurally valid launch payload can be derived."); + Entry.PreviewStabilityLine = + TEXT("Preview stability: blocked while the hypothetical generated-mode payload remains structurally invalid."); + Entry.bPreviewBlocked = true; + Entry.bCanPreview = true; + ++Surface.BlockedOptionCount; + break; + + case EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind::Unavailable: + case EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind::None: + default: + Entry.OwnedCaseTargetLine = TEXT("Owned case target: unavailable"); + Entry.CanonicalStateRefLine = TEXT("Canonical state ref: unavailable"); + Entry.PreviewStabilityLine = TEXT("Preview stability: unavailable"); + Entry.bCanPreview = false; + ++Surface.UnavailableOptionCount; + break; + } + + Entry.DetailLine = PreviewEntry.DetailLine.IsEmpty() + ? Entry.PreviewStabilityLine + : FString::Printf( + TEXT("%s | %s"), + *Entry.PreviewStabilityLine, + *PreviewEntry.DetailLine + ); + + if (Entry.bMatchesCurrentSelection) + { + Surface.bCurrentSelectionHasExactOwnedCaseTarget = + Entry.bHasExactOwnedCaseTarget; + Surface.bCurrentSelectionHasOwnedCaseTargetTemplate = + Entry.bHasOwnedCaseTargetTemplate; + Surface.bCurrentSelectionHasExactCanonicalStateRef = + Entry.bHasExactCanonicalStateRef; + Surface.bCurrentSelectionHasCanonicalStateRefTemplate = + Entry.bHasCanonicalStateRefTemplate; + Surface.bCurrentSelectionMissingCanonicalStateRef = + Entry.bMissingCanonicalStateRef; + Surface.CurrentSelectionPreviewLine = FString::Printf( + TEXT("%s | %s"), + *Entry.OwnedCaseTargetLine, + *Entry.CanonicalStateRefLine + ); + } + + Surface.OptionEntries.Add(MoveTemp(Entry)); + } + + Surface.bHasOwnedExecutionCasePreviewOptions = Surface.OptionEntries.Num() > 0; + Surface.bHasDirectOwnedCaseTargets = Surface.DirectOwnedCaseTargetOptionCount > 0; + Surface.bHasStoredRequestExactTargets = + Surface.StoredRequestExactTargetOptionCount > 0; + Surface.bHasRefreshTemplateTargets = Surface.RefreshTemplateTargetOptionCount > 0; + Surface.bHasBlockedTargets = Surface.BlockedOptionCount > 0; + + Surface.OwnedCaseSummaryLine = Surface.OptionEntries.Num() > 0 + ? FString::Printf( + TEXT("Owned case target: %d direct-case | %d stored-request exact | %d refresh-template | %d blocked | %d unavailable"), + Surface.DirectOwnedCaseTargetOptionCount, + Surface.StoredRequestExactTargetOptionCount, + Surface.RefreshTemplateTargetOptionCount, + Surface.BlockedOptionCount, + Surface.UnavailableOptionCount + ) + : TEXT("Owned case target: no imported options"); + Surface.CanonicalStateSummaryLine = Surface.OptionEntries.Num() > 0 + ? FString::Printf( + TEXT("Canonical state ref: %d exact | %d template | %d missing | %d blocked | %d unavailable"), + Surface.ExactCanonicalStateRefOptionCount, + Surface.TemplateCanonicalStateRefOptionCount, + Surface.MissingCanonicalStateRefOptionCount, + Surface.BlockedOptionCount, + Surface.UnavailableOptionCount + ) + : TEXT("Canonical state ref: no imported options"); + + if (Surface.CurrentSelectionPreviewLine.IsEmpty()) + { + if (OptionMenuSurface.bHasUnresolvedSelectionDetail) + { + Surface.CurrentSelectionPreviewLine = OptionMenuSurface.UnresolvedSelectionLine; + } + else if (OptionMenuSurface.bHasSelection) + { + Surface.CurrentSelectionPreviewLine = + TEXT("Current selection has no additional owned-execution case preview."); + } + else if (OptionMenuSurface.bHasSelector) + { + Surface.CurrentSelectionPreviewLine = TEXT("Current selection: unresolved"); + } + else + { + Surface.CurrentSelectionPreviewLine = TEXT("Current selection: n/a"); + } + } + + if (OptionMenuSurface.bHasSelector && Surface.OptionEntries.Num() > 0) + { + Surface.StatusLine = FString::Printf( + TEXT("Generated-mode selector option owned-execution case preview exposes %d direct-case option%s, %d stored-request exact option%s, %d refresh-template option%s, %d blocked option%s, and %d unavailable option%s."), + Surface.DirectOwnedCaseTargetOptionCount, + Surface.DirectOwnedCaseTargetOptionCount == 1 ? TEXT("") : TEXT("s"), + Surface.StoredRequestExactTargetOptionCount, + Surface.StoredRequestExactTargetOptionCount == 1 ? TEXT("") : TEXT("s"), + Surface.RefreshTemplateTargetOptionCount, + Surface.RefreshTemplateTargetOptionCount == 1 ? TEXT("") : TEXT("s"), + Surface.BlockedOptionCount, + Surface.BlockedOptionCount == 1 ? TEXT("") : TEXT("s"), + Surface.UnavailableOptionCount, + Surface.UnavailableOptionCount == 1 ? TEXT("") : TEXT("s") + ); + } + else if (OptionMenuSurface.bHasSelector) + { + Surface.StatusLine = + TEXT("Generated-mode selector option owned-execution case preview is active, but this selector does not expose imported options."); + } + else + { + Surface.StatusLine = OptionMenuSurface.StatusLine.IsEmpty() + ? TEXT("Generated-mode selector option owned-execution case 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("Launch-request envelope preview: %s | Selector: %s | Binding: %s"), + OptionLaunchRequestEnvelopePreviewSurface.StatusLine.IsEmpty() + ? TEXT("n/a") + : *OptionLaunchRequestEnvelopePreviewSurface.StatusLine, + *SelectorSummary, + *BindingSummary + ); + + return Surface; +} diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp index 181bbe5..6bc9a9e 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp @@ -872,6 +872,25 @@ UHyperTwistTrainingPanelWidget::GetDisplayedCoachGeneratedModeSelectorOptionLaun ); } +FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionCasePreviewSurface +UHyperTwistTrainingPanelWidget::GetDisplayedCoachGeneratedModeSelectorOptionOwnedExecutionCasePreviewSurface( + const FString& SelectorId +) const +{ + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionMenuSurface OptionMenuSurface = + GetDisplayedCoachGeneratedModeSelectorOptionMenuSurface(SelectorId); + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestEnvelopePreviewSurface + OptionLaunchRequestEnvelopePreviewSurface = + GetDisplayedCoachGeneratedModeSelectorOptionLaunchRequestEnvelopePreviewSurface(SelectorId); + return UHyperTwistTrainingCoachLibrary:: + DeriveCoachGeneratedModeSelectorOptionOwnedExecutionCasePreviewSurface( + CachedRunState, + SelectorId, + OptionMenuSurface, + OptionLaunchRequestEnvelopePreviewSurface + ); +} + 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 13e3149..0bc287a 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp @@ -853,6 +853,25 @@ AHyperTwistTrainingSessionActor::GetDisplayedCoachGeneratedModeSelectorOptionLau ); } +FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionCasePreviewSurface +AHyperTwistTrainingSessionActor::GetDisplayedCoachGeneratedModeSelectorOptionOwnedExecutionCasePreviewSurface( + const FString& SelectorId +) const +{ + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionMenuSurface OptionMenuSurface = + GetDisplayedCoachGeneratedModeSelectorOptionMenuSurface(SelectorId); + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestEnvelopePreviewSurface + OptionLaunchRequestEnvelopePreviewSurface = + GetDisplayedCoachGeneratedModeSelectorOptionLaunchRequestEnvelopePreviewSurface(SelectorId); + return UHyperTwistTrainingCoachLibrary:: + DeriveCoachGeneratedModeSelectorOptionOwnedExecutionCasePreviewSurface( + CachedRunState, + SelectorId, + OptionMenuSurface, + OptionLaunchRequestEnvelopePreviewSurface + ); +} + 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 932281f..d7e5d6f 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h @@ -777,4 +777,14 @@ public: const FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface& OptionLaunchRequestPreviewSurface ); + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + static FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionCasePreviewSurface + DeriveCoachGeneratedModeSelectorOptionOwnedExecutionCasePreviewSurface( + const FHyperTwistTrainingRunState& RunState, + const FString& SelectorId, + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionMenuSurface& OptionMenuSurface, + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestEnvelopePreviewSurface& + OptionLaunchRequestEnvelopePreviewSurface + ); }; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h index b27d1c0..cac0a0b 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h @@ -296,6 +296,12 @@ public: const FString& SelectorId ) const; + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionCasePreviewSurface + GetDisplayedCoachGeneratedModeSelectorOptionOwnedExecutionCasePreviewSurface( + 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 0514864..10987ff 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h @@ -277,6 +277,12 @@ public: const FString& SelectorId ) const; + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionCasePreviewSurface + GetDisplayedCoachGeneratedModeSelectorOptionOwnedExecutionCasePreviewSurface( + 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 56a967f..b6e39c3 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h @@ -9962,6 +9962,15 @@ struct FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestEnvelopeP UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FString DetailLine; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString HypotheticalDeckId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString HypotheticalSurfaceId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString HypotheticalRuntimeModeId; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FString CurrentStoredRequestId; @@ -10117,3 +10126,187 @@ struct FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestEnvelopeP || bHasEnvelopePreviewOptions; } }; + +USTRUCT(BlueprintType) +struct FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionCasePreviewEntrySurface +{ + 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 OwnedCaseTargetLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString CanonicalStateRefLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString PreviewStabilityLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString DetailLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString PreviewCaseId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString PreviewCanonicalStateRef; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString PreviewCaseIdTemplate; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString PreviewCanonicalStateRefTemplate; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bMatchesCurrentSelection = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bStartsOwnedCaseImmediately = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasExactOwnedCaseTarget = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasOwnedCaseTargetTemplate = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasExactCanonicalStateRef = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasCanonicalStateRefTemplate = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bMissingCanonicalStateRef = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bPreviewBlocked = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCanPreview = false; + + bool IsStructurallyValid() const + { + return !OptionId.IsEmpty() + || !Label.IsEmpty() + || !OwnedCaseTargetLine.IsEmpty() + || !CanonicalStateRefLine.IsEmpty() + || LaunchRequestPreviewKind + != EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind::None + || bCanPreview; + } +}; + +USTRUCT(BlueprintType) +struct FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionCasePreviewSurface +{ + 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 OwnedCaseSummaryLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString CanonicalStateSummaryLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString CurrentSelectionPreviewLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString DetailLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray + OptionEntries; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 DirectOwnedCaseTargetOptionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 StoredRequestExactTargetOptionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 RefreshTemplateTargetOptionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 BlockedOptionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 UnavailableOptionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 ExactCanonicalStateRefOptionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 TemplateCanonicalStateRefOptionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 MissingCanonicalStateRefOptionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasSelector = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasOwnedExecutionCasePreviewOptions = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasDirectOwnedCaseTargets = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasStoredRequestExactTargets = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasRefreshTemplateTargets = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasBlockedTargets = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCurrentSelectionHasExactOwnedCaseTarget = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCurrentSelectionHasOwnedCaseTargetTemplate = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCurrentSelectionHasExactCanonicalStateRef = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCurrentSelectionHasCanonicalStateRefTemplate = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCurrentSelectionMissingCanonicalStateRef = false; + + bool IsStructurallyValid() const + { + return !StatusLine.IsEmpty() + || !SelectorId.IsEmpty() + || OptionEntries.Num() > 0 + || bHasSelector + || bHasOwnedExecutionCasePreviewOptions; + } +}; diff --git a/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md b/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md index 3455dfd..19ef1d6 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 stored launch-request envelope / request-source preview surface over the already-cached selector/runtime state, so gameplay/Blueprint callers can render request id, request source, requested-at timestamp, and reuse-vs-refresh persistence context for a chosen option without reading raw launch-request envelope fields directly - - the current next bounded packet is to expose one thin generated-mode selector option owned execution case-identity / canonical-state preview surface on those same two consumer surfaces so gameplay/Blueprint callers can render the resulting generated case id and canonical-state ref for a chosen option without rebuilding executor naming 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 owned execution case-identity / canonical-state preview surface over the already-cached selector/runtime/request state, so gameplay/Blueprint callers can render exact direct-case targets, exact stored-request-derived generated case ids, and refresh-template canonical-state refs for a chosen option without rebuilding executor naming rules directly + - the current next bounded packet is to expose one thin generated-mode selector option owned execution prompt-label / prompt-kind preview surface on those same two consumer surfaces so gameplay/Blueprint callers can render the resulting owned prompt presentation for a chosen option without rebuilding generated deck composition 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_OWNED_EXECUTION_CASE_IDENTITY_CANONICAL_STATE_PREVIEW_SURFACE_PACKET_2026-05-08.md b/docs/arch/HYPERTWIST_PHASE4_GAMEPLAY_GENERATED_MODE_SELECTOR_OPTION_OWNED_EXECUTION_CASE_IDENTITY_CANONICAL_STATE_PREVIEW_SURFACE_PACKET_2026-05-08.md new file mode 100644 index 0000000..7f007b0 --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE4_GAMEPLAY_GENERATED_MODE_SELECTOR_OPTION_OWNED_EXECUTION_CASE_IDENTITY_CANONICAL_STATE_PREVIEW_SURFACE_PACKET_2026-05-08.md @@ -0,0 +1,104 @@ +# HyperTwist Phase 4 gameplay generated-mode selector option owned execution case-identity / canonical-state 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 launch-request envelope / request-source preview surface by exposing one parameterized owned execution case-identity / canonical-state preview surface for a chosen selector id. + +The open tasks were: + +- stop forcing gameplay / Blueprint callers to rebuild generated-mode executor naming rules just to explain the resulting owned case target for a chosen imported option +- expose exact direct covered-case targets, exact stored-request-derived generated case ids, and refresh-template case / canonical-state previews on the chosen selector +- keep the surface derived from already-cached gameplay consumer run, selector, stored request, and launch-request envelope state instead of widening into backend logic + +It is not: + +- a selector-dispatch behavior change +- a generated-mode launch-request persistence behavior change +- a generated-mode execution deck synthesis change +- a broader gameplay UI composition pass + +## Scope + +Bounded lane: + +- extend the launch-request envelope preview entry surface with the hypothetical deck / surface / runtime identity already derived by the underlying launch-request preview layer +- add first-party owned execution case preview surfaces in shared training types +- add a coach-library helper that projects owned case identity and canonical-state preview over the already-landed envelope preview surface +- expose that parameterized owned execution case preview surface on `HyperTwistTrainingPanelWidget` and `HyperTwistTrainingSessionActor` +- keep the derivation presentation-only and rooted in the already-owned selector/runtime/request surfaces + +Out of scope: + +- changing selector-choice apply behavior +- changing stored launch-request creation, refresh, or execute behavior +- changing generated-mode execution deck synthesis +- widening into dashboard-only or broader gameplay composition work + +## Why this was the right next packet + +Before this slice: + +- gameplay-facing panel/session consumers already exposed truthful hypothetical payload, request reuse, refresh posture, metadata provenance, and stored envelope identity for imported selector options +- callers could tell whether a chosen option launched a covered case directly, reused a stored generated-mode request, or required request refresh + +But one thin consumer seam was still open: + +- callers still had to reconstruct executor case-id and canonical-state naming from request id, deck id, surface id, and runtime mode if they wanted to describe the resulting owned execution target + +That meant: + +- request and persistence posture were already thin and reusable +- but resulting owned execution target identity still leaked executor naming rules into callers + +So the next bounded move was: + +- keep the already-landed launch-request envelope preview surface unchanged in behavior +- reuse that cached identity to expose one narrow owned execution case / canonical-state preview surface above it + +## What landed + +Primary code changes: + +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h` + - extended `FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestEnvelopePreviewEntrySurface` with hypothetical deck / surface / runtime identity + - added `FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionCasePreviewEntrySurface` + - added `FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionCasePreviewSurface` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp` + - added `DeriveCoachGeneratedModeSelectorOptionOwnedExecutionCasePreviewSurface(...)` + - owned case preview is derived from cached run state plus the already-landed envelope preview surface +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp` + - added `GetDisplayedCoachGeneratedModeSelectorOptionOwnedExecutionCasePreviewSurface(const FString& SelectorId)` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp` + - added `GetDisplayedCoachGeneratedModeSelectorOptionOwnedExecutionCasePreviewSurface(const FString& SelectorId)` + +Surface behavior: + +- reports exact direct covered-case targets for selector options that dispatch straight to owned cases +- reports exact generated owned case ids and canonical-state refs when the chosen option reuses the current stored generated-mode request envelope +- reports deterministic refresh templates when the chosen option would require first-persist, refresh, or replace of the stored generated-mode request envelope +- keeps unresolved or payload-invalid choices inspectable as blocked preview states instead of collapsing them into a generic unavailable branch +- keeps the surface presentation-only and compatible with the already-landed launch / execution, request-alignment, stored-request delta, launch-request payload / refresh-preview, launch-request metadata / origin-notes preview, and launch-request envelope / request-source preview 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 owned execution prompt-label / prompt-kind preview surface so callers can render the resulting owned prompt presentation for a chosen option without rebuilding generated deck composition rules directly.