diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp index da6f49b..3681164 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp @@ -9041,6 +9041,498 @@ UHyperTwistTrainingCoachLibrary:: return Surface; } +FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhasePhaseIdLabelFallbackProvenancePreviewSurface +UHyperTwistTrainingCoachLibrary:: + DeriveCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhasePhaseIdLabelFallbackProvenancePreviewSurface( + const FHyperTwistTrainingRunState& RunState, + const FString& SelectorId, + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionMenuSurface& OptionMenuSurface, + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface& + OptionLaunchRequestPreviewSurface + ) +{ + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhaseDetailRetentionPreviewSurface + SplitPhaseDetailPreviewSurface = + DeriveCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhaseDetailRetentionPreviewSurface( + RunState, + SelectorId, + OptionMenuSurface, + OptionLaunchRequestPreviewSurface + ); + + FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhasePhaseIdLabelFallbackProvenancePreviewSurface + Surface; + Surface.SelectorId = SelectorId; + Surface.SelectorLabel = OptionMenuSurface.SelectorLabel; + Surface.BindingLabel = OptionMenuSurface.BindingLabel; + Surface.Headline = + TEXT("Generated Mode Selector Option Owned-Execution Split-Phase Phase-Id/Label-Fallback Provenance Preview"); + Surface.bHasSelector = OptionMenuSurface.bHasSelector; + + if (!SelectorId.IsEmpty()) + { + Surface.Headline = OptionMenuSurface.SelectorLabel.IsEmpty() + ? FString::Printf( + TEXT("Generated Mode Selector Option Owned-Execution Split-Phase Phase-Id/Label-Fallback Provenance Preview: %s"), + *SelectorId + ) + : FString::Printf( + TEXT("Generated Mode Selector Option Owned-Execution Split-Phase Phase-Id/Label-Fallback Provenance Preview: %s"), + *OptionMenuSurface.SelectorLabel + ); + } + + if (SelectorId.IsEmpty()) + { + Surface.StatusLine = + TEXT("Generated-mode selector option owned-execution split-phase phase-id/label-fallback provenance preview requires a selector id from the active selector roster."); + Surface.SplitPhaseCountSummaryLine = TEXT("Split-phase label provenance: n/a"); + Surface.LabelProvenanceSummaryLine = TEXT("Label-fallback detail: n/a"); + Surface.CurrentSelectionSplitPhaseProvenanceLine = TEXT("Current selection: n/a"); + Surface.DetailLine = SplitPhaseDetailPreviewSurface.DetailLine.IsEmpty() + ? TEXT("Launch-request preview: n/a") + : SplitPhaseDetailPreviewSurface.DetailLine; + return Surface; + } + + const FHyperTwistTrainingImportedRuntimeSurface& RuntimeSurface = + RunState.ActiveDeck.ImportedRuntimeSurface; + const bool bHasGeneratedModeSurface = RuntimeSurface.IsStructurallyValid() + && RuntimeSurface.SurfaceKind.Equals(TEXT("generated-mode"), ESearchCase::IgnoreCase); + + if (!RunState.IsStructurallyValid() || !bHasGeneratedModeSurface) + { + Surface.StatusLine = SplitPhaseDetailPreviewSurface.StatusLine; + Surface.SplitPhaseCountSummaryLine = TEXT("Split-phase label provenance: n/a"); + Surface.LabelProvenanceSummaryLine = TEXT("Label-fallback detail: n/a"); + Surface.CurrentSelectionSplitPhaseProvenanceLine = + OptionMenuSurface.bHasUnresolvedSelectionDetail + ? OptionMenuSurface.UnresolvedSelectionLine + : (OptionMenuSurface.bHasSelection + ? TEXT("Current selection remains cached, but no active owned-execution split-phase label provenance preview is available.") + : TEXT("Current selection: unresolved")); + Surface.DetailLine = SplitPhaseDetailPreviewSurface.DetailLine; + return Surface; + } + + const FHyperTwistTrainingTimingPolicy RawTimingPolicy = RunState.ActiveDeck.TimingPolicy; + const FHyperTwistTrainingTimingPolicy PreviewTimingPolicy = + UHyperTwistTrainingLibrary::NormalizeTimingPolicy(RawTimingPolicy); + + const auto BuildPhaseProvenanceEntries = []( + const FHyperTwistTrainingTimingPolicy& SourceTimingPolicy, + const FHyperTwistTrainingTimingPolicy& NormalizedTimingPolicy, + TArray& + OutEntries, + int32& OutExplicitLabelPhaseCount, + int32& OutFallbackLabelPhaseCount + ) + { + OutEntries.Reset(); + OutExplicitLabelPhaseCount = 0; + OutFallbackLabelPhaseCount = 0; + + TArray SortedPhases = + SourceTimingPolicy.SplitPhases; + SortedPhases.Sort([]( + const FHyperTwistTrainingSplitPhaseDefinition& Left, + const FHyperTwistTrainingSplitPhaseDefinition& Right + ) + { + if (Left.Order != Right.Order) + { + return Left.Order < Right.Order; + } + + return Left.PhaseId < Right.PhaseId; + }); + + TSet SeenPhaseIds; + int32 NextOrder = 0; + for (const FHyperTwistTrainingSplitPhaseDefinition& PhaseDefinition : SortedPhases) + { + if (!PhaseDefinition.IsStructurallyValid() + || SeenPhaseIds.Contains(PhaseDefinition.PhaseId)) + { + continue; + } + + SeenPhaseIds.Add(PhaseDefinition.PhaseId); + + FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhasePhaseIdLabelFallbackProvenancePreviewPhaseEntrySurface + PhaseEntry; + PhaseEntry.PhaseId = PhaseDefinition.PhaseId; + PhaseEntry.Order = NextOrder++; + PhaseEntry.SourceLabel = PhaseDefinition.Label; + PhaseEntry.bSourceLabelWasExplicit = !PhaseDefinition.Label.IsEmpty(); + PhaseEntry.bLabelFallsBackToPhaseId = !PhaseEntry.bSourceLabelWasExplicit; + + const int32 EntryIndex = OutEntries.Num(); + const FString NormalizedLabel = + NormalizedTimingPolicy.SplitPhases.IsValidIndex(EntryIndex) + && NormalizedTimingPolicy.SplitPhases[EntryIndex].PhaseId + == PhaseDefinition.PhaseId + ? NormalizedTimingPolicy.SplitPhases[EntryIndex].Label + : (PhaseDefinition.Label.IsEmpty() + ? PhaseDefinition.PhaseId + : PhaseDefinition.Label); + PhaseEntry.NormalizedLabel = NormalizedLabel; + PhaseEntry.OrderLine = FString::Printf( + TEXT("Normalized phase order on execute: #%d"), + PhaseEntry.Order + ); + PhaseEntry.NormalizedLabelLine = FString::Printf( + TEXT("Normalized phase label on execute: %s (%s)"), + *PhaseEntry.NormalizedLabel, + *PhaseEntry.PhaseId + ); + PhaseEntry.LabelProvenanceLine = PhaseEntry.bSourceLabelWasExplicit + ? FString::Printf( + TEXT("Label provenance on execute: explicit source label retained (%s)"), + *PhaseDefinition.Label + ) + : FString::Printf( + TEXT("Label provenance on execute: phase-id fallback retained because the source label is empty (%s)"), + *PhaseEntry.PhaseId + ); + PhaseEntry.DetailLine = FString::Printf( + TEXT("%s | %s | %s"), + *PhaseEntry.OrderLine, + *PhaseEntry.NormalizedLabelLine, + *PhaseEntry.LabelProvenanceLine + ); + PhaseEntry.bCanPreview = true; + + if (PhaseEntry.bSourceLabelWasExplicit) + { + ++OutExplicitLabelPhaseCount; + } + else + { + ++OutFallbackLabelPhaseCount; + } + + OutEntries.Add(MoveTemp(PhaseEntry)); + } + }; + + const auto BuildSplitPhaseCountLine = []( + const int32 SplitPhaseCount + ) -> FString + { + return SplitPhaseCount > 0 + ? FString::Printf( + TEXT("Split-phase label provenance on execute: %d retained phase%s"), + SplitPhaseCount, + SplitPhaseCount == 1 ? TEXT("") : TEXT("s") + ) + : FString(TEXT("Split-phase label provenance on execute: none")); + }; + + const auto BuildLabelProvenanceLine = []( + const int32 ExplicitLabelPhaseCount, + const int32 FallbackLabelPhaseCount + ) -> FString + { + return FString::Printf( + TEXT("Label provenance on execute: %d explicit | %d phase-id fallback"), + ExplicitLabelPhaseCount, + FallbackLabelPhaseCount + ); + }; + + const auto BuildFallbackPhaseIdLine = []( + const TArray& + PhaseEntries + ) -> FString + { + TArray FallbackPhaseIds; + for (const FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhasePhaseIdLabelFallbackProvenancePreviewPhaseEntrySurface& + PhaseEntry : PhaseEntries) + { + if (PhaseEntry.bLabelFallsBackToPhaseId) + { + FallbackPhaseIds.Add(PhaseEntry.PhaseId); + } + } + + return FallbackPhaseIds.Num() > 0 + ? FString::Printf( + TEXT("Phase-id fallback labels on execute: %s"), + *FString::Join(FallbackPhaseIds, TEXT(", ")) + ) + : FString(TEXT("Phase-id fallback labels on execute: none")); + }; + + TArray + TemplatePhaseEntries; + int32 TemplateExplicitLabelPhaseCount = 0; + int32 TemplateFallbackLabelPhaseCount = 0; + BuildPhaseProvenanceEntries( + RawTimingPolicy, + PreviewTimingPolicy, + TemplatePhaseEntries, + TemplateExplicitLabelPhaseCount, + TemplateFallbackLabelPhaseCount + ); + const int32 TemplateSplitPhaseCount = TemplatePhaseEntries.Num(); + const bool bTemplateHasSplitPhases = TemplateSplitPhaseCount > 0; + const bool bTemplateHasFallbackLabels = TemplateFallbackLabelPhaseCount > 0; + const FString TemplateSplitPhaseCountLine = BuildSplitPhaseCountLine( + TemplateSplitPhaseCount + ); + const FString TemplateLabelProvenanceLine = BuildLabelProvenanceLine( + TemplateExplicitLabelPhaseCount, + TemplateFallbackLabelPhaseCount + ); + const FString TemplateFallbackPhaseIdLine = BuildFallbackPhaseIdLine( + TemplatePhaseEntries + ); + + for (const FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhaseDetailRetentionPreviewEntrySurface& + SplitPhaseEntry : SplitPhaseDetailPreviewSurface.OptionEntries) + { + FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhasePhaseIdLabelFallbackProvenancePreviewEntrySurface + Entry; + Entry.OptionId = SplitPhaseEntry.OptionId; + Entry.Label = SplitPhaseEntry.Label; + Entry.OptionValue = SplitPhaseEntry.OptionValue; + Entry.LaunchRequestPreviewKind = SplitPhaseEntry.LaunchRequestPreviewKind; + Entry.bMatchesCurrentSelection = SplitPhaseEntry.bMatchesCurrentSelection; + Entry.bStartsOwnedCaseImmediately = SplitPhaseEntry.bStartsOwnedCaseImmediately; + + switch (SplitPhaseEntry.LaunchRequestPreviewKind) + { + case EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind:: + DirectOwnedCaseStart: + Entry.PhaseEntries = TemplatePhaseEntries; + Entry.SplitPhaseCount = TemplateSplitPhaseCount; + Entry.ExplicitLabelPhaseCount = TemplateExplicitLabelPhaseCount; + Entry.FallbackLabelPhaseCount = TemplateFallbackLabelPhaseCount; + Entry.bHasSplitPhases = bTemplateHasSplitPhases; + Entry.bHasFallbackLabels = bTemplateHasFallbackLabels; + Entry.SplitPhaseCountLine = TemplateSplitPhaseCountLine; + Entry.LabelProvenanceLine = TemplateLabelProvenanceLine; + Entry.FallbackPhaseIdLine = TemplateFallbackPhaseIdLine; + Entry.SplitPhaseSourceLine = + TEXT("Split-phase provenance source: exact normalized active-deck split-phase labels retained on direct covered-case start."); + Entry.PreviewStabilityLine = SplitPhaseEntry.PreviewStabilityLine; + Entry.bHasExactSplitPhaseLabelProvenancePreview = true; + Entry.bRetainsActiveDeckSplitPhaseLabelProvenance = true; + Entry.bCanPreview = true; + ++Surface.DirectExactOptionCount; + if (Entry.bHasSplitPhases) + { + ++Surface.NonEmptySplitPhaseOptionCount; + } + else + { + ++Surface.EmptySplitPhaseOptionCount; + } + if (Entry.bHasFallbackLabels) + { + ++Surface.OptionsWithFallbackLabelCount; + } + Surface.TotalPreviewedSplitPhaseCount += Entry.SplitPhaseCount; + Surface.ExplicitLabelPhaseCount += Entry.ExplicitLabelPhaseCount; + Surface.FallbackLabelPhaseCount += Entry.FallbackLabelPhaseCount; + break; + + case EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind:: + StoredRequestReusable: + case EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind:: + RefreshRequired: + Entry.PhaseEntries = TemplatePhaseEntries; + Entry.SplitPhaseCount = TemplateSplitPhaseCount; + Entry.ExplicitLabelPhaseCount = TemplateExplicitLabelPhaseCount; + Entry.FallbackLabelPhaseCount = TemplateFallbackLabelPhaseCount; + Entry.bHasSplitPhases = bTemplateHasSplitPhases; + Entry.bHasFallbackLabels = bTemplateHasFallbackLabels; + Entry.SplitPhaseCountLine = TemplateSplitPhaseCountLine; + Entry.LabelProvenanceLine = TemplateLabelProvenanceLine; + Entry.FallbackPhaseIdLine = TemplateFallbackPhaseIdLine; + Entry.SplitPhaseSourceLine = + TEXT("Split-phase provenance source: exact normalized source-deck split-phase labels retained on generated owned execution."); + Entry.PreviewStabilityLine = SplitPhaseEntry.PreviewStabilityLine; + Entry.bHasExactSplitPhaseLabelProvenancePreview = true; + Entry.bRetainsActiveDeckSplitPhaseLabelProvenance = true; + Entry.bCanPreview = true; + ++Surface.GeneratedExactOptionCount; + if (Entry.bHasSplitPhases) + { + ++Surface.NonEmptySplitPhaseOptionCount; + } + else + { + ++Surface.EmptySplitPhaseOptionCount; + } + if (Entry.bHasFallbackLabels) + { + ++Surface.OptionsWithFallbackLabelCount; + } + Surface.TotalPreviewedSplitPhaseCount += Entry.SplitPhaseCount; + Surface.ExplicitLabelPhaseCount += Entry.ExplicitLabelPhaseCount; + Surface.FallbackLabelPhaseCount += Entry.FallbackLabelPhaseCount; + break; + + case EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind:: + UnresolvedAfterChoice: + Entry.SplitPhaseCountLine = + TEXT("Split-phase label provenance on execute: blocked until every required selector is resolved."); + Entry.LabelProvenanceLine = + TEXT("Label provenance on execute: blocked until every required selector is resolved."); + Entry.FallbackPhaseIdLine = + TEXT("Phase-id fallback labels on execute: blocked until every required selector is resolved."); + Entry.SplitPhaseSourceLine = + TEXT("Split-phase provenance source: blocked while selector coverage remains incomplete."); + Entry.PreviewStabilityLine = SplitPhaseEntry.PreviewStabilityLine; + Entry.bPreviewBlocked = true; + Entry.bCanPreview = true; + ++Surface.BlockedOptionCount; + break; + + case EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind:: + PayloadUnavailable: + Entry.SplitPhaseCountLine = + TEXT("Split-phase label provenance on execute: blocked until a structurally valid launch payload can be derived."); + Entry.LabelProvenanceLine = + TEXT("Label provenance on execute: blocked until a structurally valid launch payload can be derived."); + Entry.FallbackPhaseIdLine = + TEXT("Phase-id fallback labels on execute: blocked until a structurally valid launch payload can be derived."); + Entry.SplitPhaseSourceLine = + TEXT("Split-phase provenance source: blocked while the hypothetical generated-mode payload remains structurally invalid."); + Entry.PreviewStabilityLine = SplitPhaseEntry.PreviewStabilityLine; + Entry.bPreviewBlocked = true; + Entry.bCanPreview = true; + ++Surface.BlockedOptionCount; + break; + + case EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind::Unavailable: + case EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind::None: + default: + Entry.SplitPhaseCountLine = + TEXT("Split-phase label provenance on execute: unavailable"); + Entry.LabelProvenanceLine = + TEXT("Label provenance on execute: unavailable"); + Entry.FallbackPhaseIdLine = + TEXT("Phase-id fallback labels on execute: unavailable"); + Entry.SplitPhaseSourceLine = + TEXT("Split-phase provenance source: unavailable"); + Entry.PreviewStabilityLine = SplitPhaseEntry.PreviewStabilityLine; + Entry.bCanPreview = false; + ++Surface.UnavailableOptionCount; + break; + } + + const FString EntryDetail = Entry.SplitPhaseSourceLine.IsEmpty() + ? Entry.PreviewStabilityLine + : FString::Printf( + TEXT("%s | %s"), + *Entry.PreviewStabilityLine, + *Entry.SplitPhaseSourceLine + ); + Entry.DetailLine = EntryDetail; + + if (Entry.bMatchesCurrentSelection) + { + Surface.bCurrentSelectionHasExactSplitPhaseLabelProvenancePreview = + Entry.bHasExactSplitPhaseLabelProvenancePreview; + Surface.bCurrentSelectionRetainsActiveDeckSplitPhaseLabelProvenance = + Entry.bRetainsActiveDeckSplitPhaseLabelProvenance; + Surface.bCurrentSelectionHasSplitPhases = Entry.bHasSplitPhases; + Surface.bCurrentSelectionHasFallbackLabels = Entry.bHasFallbackLabels; + Surface.CurrentSelectionSplitPhaseProvenanceLine = FString::Printf( + TEXT("%s | %s | %s"), + *Entry.SplitPhaseCountLine, + *Entry.LabelProvenanceLine, + *Entry.FallbackPhaseIdLine + ); + } + + Surface.OptionEntries.Add(MoveTemp(Entry)); + } + + Surface.bHasOwnedExecutionSplitPhaseLabelProvenancePreviewOptions = + Surface.OptionEntries.Num() > 0; + Surface.bHasDirectSplitPhaseLabelProvenancePreviewOptions = + Surface.DirectExactOptionCount > 0; + Surface.bHasGeneratedSplitPhaseLabelProvenancePreviewOptions = + Surface.GeneratedExactOptionCount > 0; + Surface.bHasBlockedSplitPhaseLabelProvenancePreviewOptions = + Surface.BlockedOptionCount > 0; + + Surface.SplitPhaseCountSummaryLine = Surface.OptionEntries.Num() > 0 + ? FString::Printf( + TEXT("Split-phase label provenance: %d options with retained phases | %d options with none | %d total phases | %d blocked | %d unavailable"), + Surface.NonEmptySplitPhaseOptionCount, + Surface.EmptySplitPhaseOptionCount, + Surface.TotalPreviewedSplitPhaseCount, + Surface.BlockedOptionCount, + Surface.UnavailableOptionCount + ) + : TEXT("Split-phase label provenance: no imported options"); + Surface.LabelProvenanceSummaryLine = Surface.OptionEntries.Num() > 0 + ? FString::Printf( + TEXT("Label-fallback detail: %d explicit labels | %d phase-id fallbacks | %d options with at least one fallback"), + Surface.ExplicitLabelPhaseCount, + Surface.FallbackLabelPhaseCount, + Surface.OptionsWithFallbackLabelCount + ) + : TEXT("Label-fallback detail: no imported options"); + + if (Surface.CurrentSelectionSplitPhaseProvenanceLine.IsEmpty()) + { + if (OptionMenuSurface.bHasUnresolvedSelectionDetail) + { + Surface.CurrentSelectionSplitPhaseProvenanceLine = + OptionMenuSurface.UnresolvedSelectionLine; + } + else if (OptionMenuSurface.bHasSelection) + { + Surface.CurrentSelectionSplitPhaseProvenanceLine = + TEXT("Current selection has no additional owned-execution split-phase label provenance preview."); + } + else if (OptionMenuSurface.bHasSelector) + { + Surface.CurrentSelectionSplitPhaseProvenanceLine = + TEXT("Current selection: unresolved"); + } + else + { + Surface.CurrentSelectionSplitPhaseProvenanceLine = + TEXT("Current selection: n/a"); + } + } + + if (OptionMenuSurface.bHasSelector && Surface.OptionEntries.Num() > 0) + { + Surface.StatusLine = FString::Printf( + TEXT("Generated-mode selector option owned-execution split-phase phase-id/label-fallback provenance preview exposes %d direct exact option%s, %d generated exact option%s, %d blocked option%s, and %d unavailable option%s."), + Surface.DirectExactOptionCount, + Surface.DirectExactOptionCount == 1 ? TEXT("") : TEXT("s"), + Surface.GeneratedExactOptionCount, + Surface.GeneratedExactOptionCount == 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 split-phase phase-id/label-fallback provenance preview is active, but this selector does not expose imported options."); + } + else + { + Surface.StatusLine = SplitPhaseDetailPreviewSurface.StatusLine; + } + + Surface.DetailLine = SplitPhaseDetailPreviewSurface.DetailLine; + + return Surface; +} + FHyperTwistTrainingCoachQueueControlSurface UHyperTwistTrainingCoachLibrary::DeriveCoachQueueControlSurface( const FHyperTwistTrainingCoachSessionQueueState& QueueState, diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp index ab73ab1..d062f29 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp @@ -1081,6 +1081,25 @@ UHyperTwistTrainingPanelWidget::GetDisplayedCoachGeneratedModeSelectorOptionOwne ); } +FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhasePhaseIdLabelFallbackProvenancePreviewSurface +UHyperTwistTrainingPanelWidget::GetDisplayedCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhasePhaseIdLabelFallbackProvenancePreviewSurface( + const FString& SelectorId +) const +{ + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionMenuSurface OptionMenuSurface = + GetDisplayedCoachGeneratedModeSelectorOptionMenuSurface(SelectorId); + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface + OptionLaunchRequestPreviewSurface = + GetDisplayedCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface(SelectorId); + return UHyperTwistTrainingCoachLibrary:: + DeriveCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhasePhaseIdLabelFallbackProvenancePreviewSurface( + CachedRunState, + SelectorId, + OptionMenuSurface, + OptionLaunchRequestPreviewSurface + ); +} + 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 a37bb71..e91c9fb 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp @@ -1062,6 +1062,25 @@ AHyperTwistTrainingSessionActor::GetDisplayedCoachGeneratedModeSelectorOptionOwn ); } +FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhasePhaseIdLabelFallbackProvenancePreviewSurface +AHyperTwistTrainingSessionActor::GetDisplayedCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhasePhaseIdLabelFallbackProvenancePreviewSurface( + const FString& SelectorId +) const +{ + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionMenuSurface OptionMenuSurface = + GetDisplayedCoachGeneratedModeSelectorOptionMenuSurface(SelectorId); + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface + OptionLaunchRequestPreviewSurface = + GetDisplayedCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface(SelectorId); + return UHyperTwistTrainingCoachLibrary:: + DeriveCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhasePhaseIdLabelFallbackProvenancePreviewSurface( + CachedRunState, + SelectorId, + OptionMenuSurface, + OptionLaunchRequestPreviewSurface + ); +} + 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 a3d0bb0..e6178c2 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h @@ -887,4 +887,14 @@ public: const FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface& OptionLaunchRequestPreviewSurface ); + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + static FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhasePhaseIdLabelFallbackProvenancePreviewSurface + DeriveCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhasePhaseIdLabelFallbackProvenancePreviewSurface( + const FHyperTwistTrainingRunState& RunState, + const FString& SelectorId, + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionMenuSurface& OptionMenuSurface, + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface& + OptionLaunchRequestPreviewSurface + ); }; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h index 54c1cd0..d8f72d3 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h @@ -362,6 +362,12 @@ public: const FString& SelectorId ) const; + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhasePhaseIdLabelFallbackProvenancePreviewSurface + GetDisplayedCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhasePhaseIdLabelFallbackProvenancePreviewSurface( + 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 7a882ae..e0ea2c8 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h @@ -343,6 +343,12 @@ public: const FString& SelectorId ) const; + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhasePhaseIdLabelFallbackProvenancePreviewSurface + GetDisplayedCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhasePhaseIdLabelFallbackProvenancePreviewSurface( + 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 50b77fc..69589c6 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h @@ -12328,3 +12328,240 @@ struct FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPha || bHasOwnedExecutionSplitPhaseDetailRetentionPreviewOptions; } }; + +USTRUCT(BlueprintType) +struct FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhasePhaseIdLabelFallbackProvenancePreviewPhaseEntrySurface +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString PhaseId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString NormalizedLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString SourceLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 Order = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString OrderLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString NormalizedLabelLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString LabelProvenanceLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString DetailLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bSourceLabelWasExplicit = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bLabelFallsBackToPhaseId = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCanPreview = false; + + bool IsStructurallyValid() const + { + return !PhaseId.IsEmpty() + || !NormalizedLabel.IsEmpty() + || !OrderLine.IsEmpty() + || !LabelProvenanceLine.IsEmpty() + || bCanPreview; + } +}; + +USTRUCT(BlueprintType) +struct FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhasePhaseIdLabelFallbackProvenancePreviewEntrySurface +{ + 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 SplitPhaseCountLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString LabelProvenanceLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString FallbackPhaseIdLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString SplitPhaseSourceLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString PreviewStabilityLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString DetailLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray + PhaseEntries; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 SplitPhaseCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 ExplicitLabelPhaseCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 FallbackLabelPhaseCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bMatchesCurrentSelection = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bStartsOwnedCaseImmediately = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasExactSplitPhaseLabelProvenancePreview = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bRetainsActiveDeckSplitPhaseLabelProvenance = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasSplitPhases = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasFallbackLabels = 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() + || !SplitPhaseCountLine.IsEmpty() + || !LabelProvenanceLine.IsEmpty() + || !FallbackPhaseIdLine.IsEmpty() + || LaunchRequestPreviewKind + != EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind::None + || bCanPreview; + } +}; + +USTRUCT(BlueprintType) +struct FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhasePhaseIdLabelFallbackProvenancePreviewSurface +{ + 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 SplitPhaseCountSummaryLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString LabelProvenanceSummaryLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString CurrentSelectionSplitPhaseProvenanceLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString DetailLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray + OptionEntries; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 DirectExactOptionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 GeneratedExactOptionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 BlockedOptionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 UnavailableOptionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 NonEmptySplitPhaseOptionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 EmptySplitPhaseOptionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 TotalPreviewedSplitPhaseCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 ExplicitLabelPhaseCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 FallbackLabelPhaseCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 OptionsWithFallbackLabelCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasSelector = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasOwnedExecutionSplitPhaseLabelProvenancePreviewOptions = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasDirectSplitPhaseLabelProvenancePreviewOptions = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasGeneratedSplitPhaseLabelProvenancePreviewOptions = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasBlockedSplitPhaseLabelProvenancePreviewOptions = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCurrentSelectionHasExactSplitPhaseLabelProvenancePreview = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCurrentSelectionRetainsActiveDeckSplitPhaseLabelProvenance = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCurrentSelectionHasSplitPhases = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCurrentSelectionHasFallbackLabels = false; + + bool IsStructurallyValid() const + { + return !StatusLine.IsEmpty() + || !SelectorId.IsEmpty() + || OptionEntries.Num() > 0 + || bHasSelector + || bHasOwnedExecutionSplitPhaseLabelProvenancePreviewOptions; + } +}; diff --git a/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md b/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md index 80ae2a5..140b52a 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 owned execution split-phase detail retention preview surface over the already-cached selector/runtime/request state, so gameplay/Blueprint callers can inspect normalized phase order and labels for a chosen option without rebuilding timing-policy normalization rules directly - - the current next bounded packet is to expose one thin generated-mode selector option owned execution split-phase phase-id / label-fallback provenance preview surface on those same two consumer surfaces so gameplay/Blueprint callers can distinguish explicit labels from phase-id fallback without rebuilding timing-policy normalization 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 split-phase phase-id / label-fallback provenance preview surface over the already-cached selector/runtime/request state, so gameplay/Blueprint callers can distinguish explicit labels from phase-id fallback without rebuilding timing-policy normalization rules directly + - the current next bounded packet is to expose one thin generated-mode selector option owned execution split-phase duplicate-pruning / survivor provenance preview surface on those same two consumer surfaces so gameplay/Blueprint callers can inspect which raw split-phase definitions actually survive normalization and duplicate rejection without rebuilding timing-policy normalization 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_SPLIT_PHASE_PHASE_ID_LABEL_FALLBACK_PROVENANCE_PREVIEW_SURFACE_PACKET_2026-05-08.md b/docs/arch/HYPERTWIST_PHASE4_GAMEPLAY_GENERATED_MODE_SELECTOR_OPTION_OWNED_EXECUTION_SPLIT_PHASE_PHASE_ID_LABEL_FALLBACK_PROVENANCE_PREVIEW_SURFACE_PACKET_2026-05-08.md new file mode 100644 index 0000000..4867891 --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE4_GAMEPLAY_GENERATED_MODE_SELECTOR_OPTION_OWNED_EXECUTION_SPLIT_PHASE_PHASE_ID_LABEL_FALLBACK_PROVENANCE_PREVIEW_SURFACE_PACKET_2026-05-08.md @@ -0,0 +1,105 @@ +# HyperTwist Phase 4 gameplay generated-mode selector option owned execution split-phase phase-id / label-fallback provenance 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 owned execution split-phase detail retention preview surface by exposing one parameterized owned execution split-phase phase-id / label-fallback provenance preview surface for a chosen selector id. + +The open tasks were: + +- stop forcing gameplay / Blueprint callers to rebuild timing-policy normalization rules just to distinguish explicit split-phase labels from normalization fallback to `PhaseId` +- expose direct covered-case split-phase label provenance and generated owned-execution split-phase label provenance on the same thin consumer lane +- expose per-phase label provenance without widening into backend execution behavior + +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: + +- add first-party owned execution split-phase phase-id / label-fallback provenance preview surfaces in shared training types +- add a coach-library helper that reconstructs the retained normalized split-phase set from the raw timing policy using the same sort / dedupe / fallback rule as timing normalization +- expose that parameterized split-phase provenance 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 owned case identity, prompt presentation, execution modality, execution scope, provenance posture, deck container posture, retained runtime-surface identity, retained delivery-mode posture, retained deck capability posture, retained timing-policy posture, and retained normalized split-phase detail for imported selector options +- callers could already inspect retained normalized phase order and labels for a chosen option + +But one thin consumer seam was still open: + +- callers still had to reconstruct whether each retained normalized phase label came from an explicit source label or from the normalization fallback that substitutes `PhaseId` when the source label is empty + +That meant: + +- split-phase detail preview was already thin and reusable +- but phase-id / label-fallback provenance still leaked timing-policy normalization rules into callers + +So the next bounded move was: + +- keep the already-landed owned execution split-phase detail retention preview surface unchanged in behavior +- reuse that split-phase detail preview as the exact/blocked/unavailable option-state baseline and expose one narrow label-provenance surface above it + +## What landed + +Primary code changes: + +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h` + - added `FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhasePhaseIdLabelFallbackProvenancePreviewPhaseEntrySurface` + - added `FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhasePhaseIdLabelFallbackProvenancePreviewEntrySurface` + - added `FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhasePhaseIdLabelFallbackProvenancePreviewSurface` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp` + - added `DeriveCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhasePhaseIdLabelFallbackProvenancePreviewSurface(...)` + - split-phase label provenance preview is derived from cached run state plus the already-landed split-phase detail retention preview surface and reconstructs the retained normalized split-phase set from the raw timing policy +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp` + - added `GetDisplayedCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhasePhaseIdLabelFallbackProvenancePreviewSurface(const FString& SelectorId)` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp` + - added `GetDisplayedCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhasePhaseIdLabelFallbackProvenancePreviewSurface(const FString& SelectorId)` + +Surface behavior: + +- reports exact normalized retained split-phase label provenance for direct covered-case starts +- reports exact normalized retained split-phase label provenance for generated owned-execution paths +- distinguishes explicit source labels from phase-id fallback per retained phase entry +- exposes option-level counts for explicit labels, phase-id fallbacks, and options that contain at least one fallback label +- keeps reusable stored-request cases and refresh-required generated cases exact because execute still retains the same normalized split-phase label provenance even when request refresh is required +- keeps unresolved or payload-invalid choices inspectable as blocked split-phase label-provenance 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, launch-request envelope / request-source preview, owned execution case / canonical-state preview, owned execution prompt preview, owned execution delivery preview, owned execution scope preview, owned execution provenance preview, owned execution deck-title / selection-policy preview, owned execution deck-id / runtime-surface retention preview, owned execution delivery-mode roster / allowed-mode retention preview, owned execution deck capability retention preview, owned execution timing-policy retention preview, and owned execution split-phase detail retention 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 split-phase duplicate-pruning / survivor provenance preview surface so callers can inspect which raw split-phase definitions actually survive normalization and duplicate rejection without rebuilding timing-policy normalization rules directly.