diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp index 49a508a..d2db6a3 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp @@ -10476,6 +10476,341 @@ UHyperTwistTrainingCoachLibrary:: return Surface; } +FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhaseRawRowNormalizedPhaseFusionPreviewSurface +UHyperTwistTrainingCoachLibrary:: + DeriveCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhaseRawRowNormalizedPhaseFusionPreviewSurface( + const FHyperTwistTrainingRunState& RunState, + const FString& SelectorId, + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionMenuSurface& OptionMenuSurface, + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface& + OptionLaunchRequestPreviewSurface + ) +{ + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhaseNormalizationSummaryPreviewSurface + NormalizationSummaryPreviewSurface = + DeriveCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhaseNormalizationSummaryPreviewSurface( + RunState, + SelectorId, + OptionMenuSurface, + OptionLaunchRequestPreviewSurface + ); + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhasePhaseIdLabelFallbackProvenancePreviewSurface + LabelProvenancePreviewSurface = + DeriveCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhasePhaseIdLabelFallbackProvenancePreviewSurface( + RunState, + SelectorId, + OptionMenuSurface, + OptionLaunchRequestPreviewSurface + ); + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhaseDuplicatePruningSurvivorProvenancePreviewSurface + DuplicatePruningPreviewSurface = + DeriveCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhaseDuplicatePruningSurvivorProvenancePreviewSurface( + RunState, + SelectorId, + OptionMenuSurface, + OptionLaunchRequestPreviewSurface + ); + + FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhaseRawRowNormalizedPhaseFusionPreviewSurface + Surface; + Surface.SelectorId = NormalizationSummaryPreviewSurface.SelectorId; + Surface.SelectorLabel = NormalizationSummaryPreviewSurface.SelectorLabel; + Surface.BindingLabel = NormalizationSummaryPreviewSurface.BindingLabel; + Surface.Headline = + TEXT("Generated Mode Selector Option Owned-Execution Split-Phase Raw-Row/Normalized-Phase Fusion Preview"); + Surface.StatusLine = NormalizationSummaryPreviewSurface.StatusLine; + Surface.NormalizationSummaryLine = + NormalizationSummaryPreviewSurface.NormalizationSummaryLine; + Surface.LabelFallbackSummaryLine = + NormalizationSummaryPreviewSurface.LabelFallbackSummaryLine; + Surface.PreviewPostureSummaryLine = + NormalizationSummaryPreviewSurface.PreviewPostureSummaryLine; + Surface.CurrentSelectionFusionLine = + NormalizationSummaryPreviewSurface.CurrentSelectionNormalizationSummaryLine; + Surface.DetailLine = NormalizationSummaryPreviewSurface.DetailLine; + Surface.DirectExactOptionCount = + NormalizationSummaryPreviewSurface.DirectExactOptionCount; + Surface.GeneratedExactOptionCount = + NormalizationSummaryPreviewSurface.GeneratedExactOptionCount; + Surface.BlockedOptionCount = + NormalizationSummaryPreviewSurface.BlockedOptionCount; + Surface.UnavailableOptionCount = + NormalizationSummaryPreviewSurface.UnavailableOptionCount; + Surface.RawSplitPhaseDefinitionCount = + NormalizationSummaryPreviewSurface.RawSplitPhaseDefinitionCount; + Surface.SurvivorPhaseCount = + NormalizationSummaryPreviewSurface.SurvivorPhaseCount; + Surface.DuplicatePrunedPhaseCount = + NormalizationSummaryPreviewSurface.DuplicatePrunedPhaseCount; + Surface.InvalidRawDefinitionCount = + NormalizationSummaryPreviewSurface.InvalidRawDefinitionCount; + Surface.ExplicitLabelPhaseCount = + NormalizationSummaryPreviewSurface.ExplicitLabelPhaseCount; + Surface.FallbackLabelPhaseCount = + NormalizationSummaryPreviewSurface.FallbackLabelPhaseCount; + Surface.OptionsWithFallbackLabelCount = + NormalizationSummaryPreviewSurface.OptionsWithFallbackLabelCount; + Surface.OptionsWithDuplicatePruningCount = + NormalizationSummaryPreviewSurface.OptionsWithDuplicatePruningCount; + Surface.OptionsWithInvalidRawDefinitionsCount = + NormalizationSummaryPreviewSurface.OptionsWithInvalidRawDefinitionsCount; + Surface.bHasSelector = NormalizationSummaryPreviewSurface.bHasSelector; + Surface.bHasOwnedExecutionSplitPhaseFusionPreviewOptions = + NormalizationSummaryPreviewSurface + .bHasOwnedExecutionSplitPhaseNormalizationSummaryPreviewOptions; + Surface.bHasDirectSplitPhaseFusionPreviewOptions = + NormalizationSummaryPreviewSurface + .bHasDirectSplitPhaseNormalizationSummaryPreviewOptions; + Surface.bHasGeneratedSplitPhaseFusionPreviewOptions = + NormalizationSummaryPreviewSurface + .bHasGeneratedSplitPhaseNormalizationSummaryPreviewOptions; + Surface.bHasBlockedSplitPhaseFusionPreviewOptions = + NormalizationSummaryPreviewSurface + .bHasBlockedSplitPhaseNormalizationSummaryPreviewOptions; + Surface.bCurrentSelectionHasExactSplitPhaseFusionPreview = + NormalizationSummaryPreviewSurface + .bCurrentSelectionHasExactSplitPhaseNormalizationSummaryPreview; + Surface.bCurrentSelectionRetainsActiveDeckSplitPhaseFusion = + NormalizationSummaryPreviewSurface + .bCurrentSelectionRetainsActiveDeckSplitPhaseNormalizationSummary; + Surface.bCurrentSelectionHasFallbackLabels = + NormalizationSummaryPreviewSurface.bCurrentSelectionHasFallbackLabels; + Surface.bCurrentSelectionHasDuplicatePrunedPhases = + NormalizationSummaryPreviewSurface + .bCurrentSelectionHasDuplicatePrunedPhases; + Surface.bCurrentSelectionHasInvalidRawDefinitions = + NormalizationSummaryPreviewSurface + .bCurrentSelectionHasInvalidRawDefinitions; + + if (!SelectorId.IsEmpty()) + { + Surface.Headline = OptionMenuSurface.SelectorLabel.IsEmpty() + ? FString::Printf( + TEXT("Generated Mode Selector Option Owned-Execution Split-Phase Raw-Row/Normalized-Phase Fusion Preview: %s"), + *SelectorId + ) + : FString::Printf( + TEXT("Generated Mode Selector Option Owned-Execution Split-Phase Raw-Row/Normalized-Phase Fusion Preview: %s"), + *OptionMenuSurface.SelectorLabel + ); + } + + TMap LabelEntryIndexByOptionId; + for (int32 EntryIndex = 0; + EntryIndex < LabelProvenancePreviewSurface.OptionEntries.Num(); + ++EntryIndex) + { + LabelEntryIndexByOptionId.FindOrAdd( + LabelProvenancePreviewSurface.OptionEntries[EntryIndex].OptionId + ) = EntryIndex; + } + + const auto BuildFusionUnavailableLabelLine = []( + const FString& Reason + ) -> FString + { + return Reason.IsEmpty() + ? TEXT("Normalized label provenance on execute: unavailable") + : Reason; + }; + + for (const FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhaseDuplicatePruningSurvivorProvenancePreviewEntrySurface& + DuplicateEntry : DuplicatePruningPreviewSurface.OptionEntries) + { + const int32* LabelEntryIndex = LabelEntryIndexByOptionId.Find( + DuplicateEntry.OptionId + ); + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhasePhaseIdLabelFallbackProvenancePreviewEntrySurface* + LabelEntry = LabelEntryIndex != nullptr + ? &LabelProvenancePreviewSurface.OptionEntries[*LabelEntryIndex] + : nullptr; + + TMap LabelPhaseIndexByPhaseId; + if (LabelEntry != nullptr) + { + for (int32 PhaseIndex = 0; + PhaseIndex < LabelEntry->PhaseEntries.Num(); + ++PhaseIndex) + { + LabelPhaseIndexByPhaseId.FindOrAdd( + LabelEntry->PhaseEntries[PhaseIndex].PhaseId + ) = PhaseIndex; + } + } + + FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhaseRawRowNormalizedPhaseFusionPreviewEntrySurface + Entry; + Entry.OptionId = DuplicateEntry.OptionId; + Entry.Label = DuplicateEntry.Label; + Entry.OptionValue = DuplicateEntry.OptionValue; + Entry.LaunchRequestPreviewKind = DuplicateEntry.LaunchRequestPreviewKind; + Entry.RawSplitPhaseCountLine = DuplicateEntry.RawSplitPhaseCountLine; + Entry.SurvivorCountLine = DuplicateEntry.SurvivorCountLine; + Entry.DuplicatePruningLine = DuplicateEntry.DuplicatePruningLine; + Entry.InvalidDefinitionLine = DuplicateEntry.InvalidDefinitionLine; + Entry.LabelProvenanceLine = LabelEntry != nullptr + ? LabelEntry->LabelProvenanceLine + : TEXT("Label provenance on execute: unavailable"); + Entry.FallbackPhaseIdLine = LabelEntry != nullptr + ? LabelEntry->FallbackPhaseIdLine + : TEXT("Phase-id fallback labels on execute: unavailable"); + Entry.RawSplitPhaseCount = DuplicateEntry.RawSplitPhaseCount; + Entry.SurvivorPhaseCount = DuplicateEntry.SurvivorPhaseCount; + Entry.DuplicatePrunedPhaseCount = DuplicateEntry.DuplicatePrunedPhaseCount; + Entry.InvalidRawDefinitionCount = DuplicateEntry.InvalidRawDefinitionCount; + Entry.ExplicitLabelPhaseCount = LabelEntry != nullptr + ? LabelEntry->ExplicitLabelPhaseCount + : 0; + Entry.FallbackLabelPhaseCount = LabelEntry != nullptr + ? LabelEntry->FallbackLabelPhaseCount + : 0; + Entry.bMatchesCurrentSelection = DuplicateEntry.bMatchesCurrentSelection; + Entry.bStartsOwnedCaseImmediately = + DuplicateEntry.bStartsOwnedCaseImmediately; + Entry.bHasExactFusionPreview = + DuplicateEntry.bHasExactSplitPhaseDuplicatePruningPreview + && LabelEntry != nullptr + && LabelEntry->bHasExactSplitPhaseLabelProvenancePreview; + Entry.bRetainsActiveDeckSplitPhaseFusion = + DuplicateEntry.bRetainsActiveDeckSplitPhaseDuplicatePruningProvenance + && LabelEntry != nullptr + && LabelEntry->bRetainsActiveDeckSplitPhaseLabelProvenance; + Entry.bHasFallbackLabels = LabelEntry != nullptr + && LabelEntry->bHasFallbackLabels; + Entry.bHasDuplicatePrunedPhases = + DuplicateEntry.bHasDuplicatePrunedPhases; + Entry.bHasInvalidRawDefinitions = + DuplicateEntry.bHasInvalidRawDefinitions; + Entry.bPreviewBlocked = DuplicateEntry.bPreviewBlocked; + Entry.bCanPreview = DuplicateEntry.bCanPreview; + Entry.PreviewPostureLine = DuplicateEntry.SplitPhaseSourceLine.IsEmpty() + ? DuplicateEntry.PreviewStabilityLine + : FString::Printf( + TEXT("Preview posture on execute: %s | %s"), + *DuplicateEntry.PreviewStabilityLine, + *DuplicateEntry.SplitPhaseSourceLine + ); + Entry.FusionSummaryLine = FString::Printf( + TEXT("Raw-row / normalized-phase fusion on execute: %d raw rows | %d survivors | %d explicit labels | %d phase-id fallbacks"), + Entry.RawSplitPhaseCount, + Entry.SurvivorPhaseCount, + Entry.ExplicitLabelPhaseCount, + Entry.FallbackLabelPhaseCount + ); + + for (const FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhaseDuplicatePruningSurvivorProvenancePreviewRawPhaseEntrySurface& + RawRow : DuplicateEntry.RawPhaseEntries) + { + FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhaseRawRowNormalizedPhaseFusionPreviewRowSurface + FusedRow; + FusedRow.PhaseId = RawRow.PhaseId; + FusedRow.SourceLabel = RawRow.SourceLabel; + FusedRow.RawOrder = RawRow.RawOrder; + FusedRow.EvaluationOrder = RawRow.EvaluationOrder; + FusedRow.NormalizedOrder = RawRow.NormalizedOrder; + FusedRow.RawDefinitionLine = RawRow.RawDefinitionLine; + FusedRow.NormalizationOutcomeLine = RawRow.NormalizationOutcomeLine; + FusedRow.DuplicatePruningProvenanceLine = + RawRow.DuplicatePruningProvenanceLine; + FusedRow.bSourceLabelWasExplicit = RawRow.bSourceLabelWasExplicit; + FusedRow.bSurvivesNormalization = RawRow.bSurvivesNormalization; + FusedRow.bDroppedBecauseDuplicate = RawRow.bDroppedBecauseDuplicate; + FusedRow.bDroppedBecauseInvalid = RawRow.bDroppedBecauseInvalid; + FusedRow.bCanPreview = RawRow.bCanPreview; + + if (RawRow.bSurvivesNormalization + && LabelEntry != nullptr + && LabelPhaseIndexByPhaseId.Contains(RawRow.PhaseId)) + { + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhasePhaseIdLabelFallbackProvenancePreviewPhaseEntrySurface& + LabelPhase = + LabelEntry->PhaseEntries[LabelPhaseIndexByPhaseId[RawRow.PhaseId]]; + FusedRow.NormalizedOrder = LabelPhase.Order; + FusedRow.NormalizedLabel = LabelPhase.NormalizedLabel; + FusedRow.LabelProvenanceLine = LabelPhase.LabelProvenanceLine; + FusedRow.bLabelFallsBackToPhaseId = + LabelPhase.bLabelFallsBackToPhaseId; + FusedRow.bHasNormalizedPhaseDetail = true; + FusedRow.FusedRowLine = FString::Printf( + TEXT("Raw row / normalized phase fusion on execute: evaluation #%d survives as normalized phase #%d with label %s (%s)."), + FusedRow.EvaluationOrder, + FusedRow.NormalizedOrder, + *FusedRow.NormalizedLabel, + *RawRow.PhaseId + ); + } + else if (RawRow.bSurvivesNormalization) + { + FusedRow.NormalizedLabel = RawRow.SurvivorNormalizedLabel; + FusedRow.LabelProvenanceLine = RawRow.SourceLabel.IsEmpty() + ? FString::Printf( + TEXT("Normalized label provenance on execute: phase-id fallback retained because the source label is empty (%s)"), + *RawRow.PhaseId + ) + : FString::Printf( + TEXT("Normalized label provenance on execute: explicit source label retained (%s)"), + *RawRow.SourceLabel + ); + FusedRow.bLabelFallsBackToPhaseId = RawRow.SourceLabel.IsEmpty(); + FusedRow.bHasNormalizedPhaseDetail = !FusedRow.NormalizedLabel.IsEmpty(); + FusedRow.FusedRowLine = FString::Printf( + TEXT("Raw row / normalized phase fusion on execute: evaluation #%d survives as normalized phase #%d with label %s (%s)."), + FusedRow.EvaluationOrder, + FusedRow.NormalizedOrder, + *FusedRow.NormalizedLabel, + *RawRow.PhaseId + ); + } + else if (RawRow.bDroppedBecauseDuplicate) + { + FusedRow.LabelProvenanceLine = FString::Printf( + TEXT("Normalized label provenance on execute: unavailable for this raw row because phase id %s was duplicate-pruned before normalized phase detail was retained."), + *RawRow.PhaseId + ); + FusedRow.FusedRowLine = FString::Printf( + TEXT("Raw row / normalized phase fusion on execute: evaluation #%d is duplicate-pruned, so fused normalized phase detail remains attached to the first valid surviving row for %s."), + FusedRow.EvaluationOrder, + *RawRow.PhaseId + ); + } + else if (RawRow.bDroppedBecauseInvalid) + { + FusedRow.LabelProvenanceLine = + TEXT("Normalized label provenance on execute: unavailable for this raw row because structurally invalid definitions are skipped before normalized phase detail is retained."); + FusedRow.FusedRowLine = FString::Printf( + TEXT("Raw row / normalized phase fusion on execute: evaluation #%d never reaches normalized phase detail because the raw row is structurally invalid."), + FusedRow.EvaluationOrder + ); + } + else + { + FusedRow.LabelProvenanceLine = + BuildFusionUnavailableLabelLine(TEXT("Normalized label provenance on execute: unavailable")); + FusedRow.FusedRowLine = + TEXT("Raw row / normalized phase fusion on execute: unavailable"); + } + + FusedRow.DetailLine = FString::Printf( + TEXT("%s | %s | %s"), + *FusedRow.NormalizationOutcomeLine, + *FusedRow.LabelProvenanceLine, + *FusedRow.FusedRowLine + ); + Entry.FusedRows.Add(MoveTemp(FusedRow)); + } + + Entry.DetailLine = FString::Printf( + TEXT("%s | %s | %s"), + *Entry.PreviewPostureLine, + *Entry.FusionSummaryLine, + *Entry.LabelProvenanceLine + ); + Surface.OptionEntries.Add(MoveTemp(Entry)); + } + + 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 d32995c..16cdcff 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp @@ -1138,6 +1138,25 @@ UHyperTwistTrainingPanelWidget::GetDisplayedCoachGeneratedModeSelectorOptionOwne ); } +FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhaseRawRowNormalizedPhaseFusionPreviewSurface +UHyperTwistTrainingPanelWidget::GetDisplayedCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhaseRawRowNormalizedPhaseFusionPreviewSurface( + const FString& SelectorId +) const +{ + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionMenuSurface OptionMenuSurface = + GetDisplayedCoachGeneratedModeSelectorOptionMenuSurface(SelectorId); + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface + OptionLaunchRequestPreviewSurface = + GetDisplayedCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface(SelectorId); + return UHyperTwistTrainingCoachLibrary:: + DeriveCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhaseRawRowNormalizedPhaseFusionPreviewSurface( + 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 fffbf33..878663b 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp @@ -1119,6 +1119,25 @@ AHyperTwistTrainingSessionActor::GetDisplayedCoachGeneratedModeSelectorOptionOwn ); } +FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhaseRawRowNormalizedPhaseFusionPreviewSurface +AHyperTwistTrainingSessionActor::GetDisplayedCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhaseRawRowNormalizedPhaseFusionPreviewSurface( + const FString& SelectorId +) const +{ + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionMenuSurface OptionMenuSurface = + GetDisplayedCoachGeneratedModeSelectorOptionMenuSurface(SelectorId); + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface + OptionLaunchRequestPreviewSurface = + GetDisplayedCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface(SelectorId); + return UHyperTwistTrainingCoachLibrary:: + DeriveCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhaseRawRowNormalizedPhaseFusionPreviewSurface( + 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 f3ee42c..3568f64 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h @@ -917,4 +917,14 @@ public: const FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface& OptionLaunchRequestPreviewSurface ); + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + static FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhaseRawRowNormalizedPhaseFusionPreviewSurface + DeriveCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhaseRawRowNormalizedPhaseFusionPreviewSurface( + 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 1d36773..d56ba98 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h @@ -380,6 +380,12 @@ public: const FString& SelectorId ) const; + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhaseRawRowNormalizedPhaseFusionPreviewSurface + GetDisplayedCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhaseRawRowNormalizedPhaseFusionPreviewSurface( + 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 f1b5df0..ecb23b4 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h @@ -361,6 +361,12 @@ public: const FString& SelectorId ) const; + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhaseRawRowNormalizedPhaseFusionPreviewSurface + GetDisplayedCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhaseRawRowNormalizedPhaseFusionPreviewSurface( + 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 3126445..d385807 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h @@ -13047,3 +13047,300 @@ struct FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPha || bHasOwnedExecutionSplitPhaseNormalizationSummaryPreviewOptions; } }; + +USTRUCT(BlueprintType) +struct FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhaseRawRowNormalizedPhaseFusionPreviewRowSurface +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString PhaseId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString SourceLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString NormalizedLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 RawOrder = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 EvaluationOrder = -1; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 NormalizedOrder = -1; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString RawDefinitionLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString NormalizationOutcomeLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString DuplicatePruningProvenanceLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString LabelProvenanceLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString FusedRowLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString DetailLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bSourceLabelWasExplicit = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bSurvivesNormalization = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bDroppedBecauseDuplicate = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bDroppedBecauseInvalid = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bLabelFallsBackToPhaseId = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasNormalizedPhaseDetail = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCanPreview = false; + + bool IsStructurallyValid() const + { + return !PhaseId.IsEmpty() + || !RawDefinitionLine.IsEmpty() + || !LabelProvenanceLine.IsEmpty() + || !FusedRowLine.IsEmpty() + || bCanPreview; + } +}; + +USTRUCT(BlueprintType) +struct FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhaseRawRowNormalizedPhaseFusionPreviewEntrySurface +{ + 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 RawSplitPhaseCountLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString SurvivorCountLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString DuplicatePruningLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString InvalidDefinitionLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString LabelProvenanceLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString FallbackPhaseIdLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString FusionSummaryLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString PreviewPostureLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString DetailLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray + FusedRows; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 RawSplitPhaseCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 SurvivorPhaseCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 DuplicatePrunedPhaseCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 InvalidRawDefinitionCount = 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 bHasExactFusionPreview = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bRetainsActiveDeckSplitPhaseFusion = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasFallbackLabels = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasDuplicatePrunedPhases = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasInvalidRawDefinitions = 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() + || !RawSplitPhaseCountLine.IsEmpty() + || !FusionSummaryLine.IsEmpty() + || !PreviewPostureLine.IsEmpty() + || LaunchRequestPreviewKind + != EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind::None + || bCanPreview; + } +}; + +USTRUCT(BlueprintType) +struct FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhaseRawRowNormalizedPhaseFusionPreviewSurface +{ + 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 NormalizationSummaryLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString LabelFallbackSummaryLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString PreviewPostureSummaryLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString CurrentSelectionFusionLine; + + 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 RawSplitPhaseDefinitionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 SurvivorPhaseCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 DuplicatePrunedPhaseCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 InvalidRawDefinitionCount = 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") + int32 OptionsWithDuplicatePruningCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 OptionsWithInvalidRawDefinitionsCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasSelector = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasOwnedExecutionSplitPhaseFusionPreviewOptions = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasDirectSplitPhaseFusionPreviewOptions = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasGeneratedSplitPhaseFusionPreviewOptions = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasBlockedSplitPhaseFusionPreviewOptions = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCurrentSelectionHasExactSplitPhaseFusionPreview = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCurrentSelectionRetainsActiveDeckSplitPhaseFusion = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCurrentSelectionHasFallbackLabels = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCurrentSelectionHasDuplicatePrunedPhases = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCurrentSelectionHasInvalidRawDefinitions = false; + + bool IsStructurallyValid() const + { + return !StatusLine.IsEmpty() + || !SelectorId.IsEmpty() + || OptionEntries.Num() > 0 + || bHasSelector + || bHasOwnedExecutionSplitPhaseFusionPreviewOptions; + } +}; diff --git a/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md b/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md index 54abd28..08520dc 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 normalization summary surface over the already-cached selector/runtime/request state, so gameplay/Blueprint callers can inspect raw definitions, survivors, label fallback, and blocked preview posture from one surface instead of merging the existing split-phase normalization previews themselves - - the current next bounded packet is to expose one thin generated-mode selector option owned execution split-phase raw-row / normalized-phase fusion preview surface on those same two consumer surfaces so gameplay/Blueprint callers can inspect duplicate-pruning survivors and normalized label-fallback phase detail from one fused row/phase surface instead of joining separate raw-row and normalized-phase arrays themselves + - 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 raw-row / normalized-phase fusion preview surface over the already-cached selector/runtime/request state, so gameplay/Blueprint callers can inspect duplicate-pruning survivors and normalized label-fallback phase detail from one fused row/phase surface instead of joining separate raw-row and normalized-phase arrays themselves + - the current next bounded packet is to expose one thin generated-mode selector option owned execution split-phase current-selection canonical inspect surface on those same two consumer surfaces so gameplay/Blueprint callers can render just the actively selected option’s fused split-phase normalization state without scanning the broader option array themselves - 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_RAW_ROW_NORMALIZED_PHASE_FUSION_PREVIEW_SURFACE_PACKET_2026-05-08.md b/docs/arch/HYPERTWIST_PHASE4_GAMEPLAY_GENERATED_MODE_SELECTOR_OPTION_OWNED_EXECUTION_SPLIT_PHASE_RAW_ROW_NORMALIZED_PHASE_FUSION_PREVIEW_SURFACE_PACKET_2026-05-08.md new file mode 100644 index 0000000..2b6e435 --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE4_GAMEPLAY_GENERATED_MODE_SELECTOR_OPTION_OWNED_EXECUTION_SPLIT_PHASE_RAW_ROW_NORMALIZED_PHASE_FUSION_PREVIEW_SURFACE_PACKET_2026-05-08.md @@ -0,0 +1,90 @@ +# HyperTwist Phase 4 gameplay generated-mode selector option owned execution split-phase raw-row / normalized-phase fusion 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 normalization summary preview surface by exposing one parameterized owned execution split-phase raw-row / normalized-phase fusion preview surface for a chosen selector id. + +The open tasks were: + +- stop forcing gameplay / Blueprint callers to join separate duplicate-pruning raw rows and normalized label-fallback phase arrays just to inspect one chosen option +- expose direct covered-case fused row/phase detail and generated owned-execution fused row/phase detail on the same thin consumer lane +- expose blocked preview posture from that same fused row/phase layer 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 raw-row / normalized-phase fusion preview surfaces in shared training types +- add a coach-library helper that composes the already-landed split-phase normalization summary, label-fallback provenance, and duplicate-pruning provenance surfaces into one fused row/phase layer +- expose that parameterized split-phase fusion 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 split-phase normalization summary plus the separate raw-definition duplicate-pruning and normalized label-fallback layers for imported selector options +- callers could already inspect those layers separately for a chosen option + +But one thin consumer seam was still open: + +- callers still had to join separate raw-row and normalized-phase arrays themselves just to render one fused split-phase inspection view for a chosen option + +That meant: + +- the underlying preview layers were already thin and reusable +- but a common gameplay-facing fused row/phase inspect surface still leaked row-to-phase join work into callers + +So the next bounded move was: + +- keep the already-landed normalization summary, label-fallback provenance, and duplicate-pruning provenance surfaces unchanged in behavior +- add one narrow fused row/phase surface above them that simply composes those existing layers into a caller-ready inspection view + +## What landed + +Primary code changes: + +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h` + - added `FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhaseRawRowNormalizedPhaseFusionPreviewRowSurface` + - added `FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhaseRawRowNormalizedPhaseFusionPreviewEntrySurface` + - added `FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhaseRawRowNormalizedPhaseFusionPreviewSurface` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp` + - added `DeriveCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhaseRawRowNormalizedPhaseFusionPreviewSurface(...)` + - the fused row/phase preview is composed from the already-landed split-phase normalization summary, label-fallback provenance, and duplicate-pruning provenance surfaces instead of rebuilding normalization behavior +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp` + - added `GetDisplayedCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhaseRawRowNormalizedPhaseFusionPreviewSurface(const FString& SelectorId)` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp` + - added `GetDisplayedCoachGeneratedModeSelectorOptionOwnedExecutionSplitPhaseRawRowNormalizedPhaseFusionPreviewSurface(const FString& SelectorId)` + +Consumer result: + +- gameplay / Blueprint callers can now inspect duplicate-pruning raw rows and the surviving normalized label-fallback phase detail from one fused row/phase surface for a chosen selector option instead of joining separate raw-row and normalized-phase arrays themselves + +## Validation + +- full `Development Editor|Win64` solution build for `C:\HyperTwist\UnrealHyperTwist\UnrealHyperTwist.sln` +- result: `0` warnings, `0` errors