diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp index bc2d064..a47a3b4 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp @@ -11703,3 +11703,463 @@ UHyperTwistTrainingCoachLibrary::DeriveCoachGeneratedModeSelectorOptionOwnedExec return Surface; } + +FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionPromptPreviewSurface +UHyperTwistTrainingCoachLibrary::DeriveCoachGeneratedModeSelectorOptionOwnedExecutionPromptPreviewSurface( + const FHyperTwistTrainingRunState& RunState, + const FString& SelectorId, + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionMenuSurface& OptionMenuSurface, + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface& + OptionLaunchRequestPreviewSurface +) +{ + FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionPromptPreviewSurface Surface; + Surface.SelectorId = SelectorId; + Surface.SelectorLabel = OptionMenuSurface.SelectorLabel; + Surface.BindingLabel = OptionMenuSurface.BindingLabel; + Surface.Headline = TEXT("Generated Mode Selector Option Owned-Execution Prompt Preview"); + Surface.bHasSelector = OptionMenuSurface.bHasSelector; + + if (!SelectorId.IsEmpty()) + { + Surface.Headline = OptionMenuSurface.SelectorLabel.IsEmpty() + ? FString::Printf( + TEXT("Generated Mode Selector Option Owned-Execution Prompt Preview: %s"), + *SelectorId + ) + : FString::Printf( + TEXT("Generated Mode Selector Option Owned-Execution Prompt Preview: %s"), + *OptionMenuSurface.SelectorLabel + ); + } + + if (SelectorId.IsEmpty()) + { + Surface.StatusLine = + TEXT("Generated-mode selector option owned-execution prompt preview requires a selector id from the active selector roster."); + Surface.PromptLabelSummaryLine = TEXT("Prompt label: n/a"); + Surface.PromptKindSummaryLine = TEXT("Prompt kind: n/a"); + Surface.CurrentSelectionPromptLine = TEXT("Current selection: n/a"); + Surface.DetailLine = OptionLaunchRequestPreviewSurface.StatusLine.IsEmpty() + ? TEXT("Launch-request preview: n/a") + : FString::Printf( + TEXT("Launch-request preview: %s"), + *OptionLaunchRequestPreviewSurface.StatusLine + ); + return Surface; + } + + const FHyperTwistTrainingImportedRuntimeSurface& RuntimeSurface = + RunState.ActiveDeck.ImportedRuntimeSurface; + const bool bHasGeneratedModeSurface = RuntimeSurface.IsStructurallyValid() + && RuntimeSurface.SurfaceKind.Equals(TEXT("generated-mode"), ESearchCase::IgnoreCase); + const FHyperTwistTrainingCase* SeedCase = RunState.ActiveDeck.Cases.FindByPredicate( + [](const FHyperTwistTrainingCase& Candidate) + { + return Candidate.IsStructurallyValid(); + } + ); + + const auto DescribePromptKind = [](const EHyperTwistTrainingPromptKind PromptKind) -> FString + { + switch (PromptKind) + { + case EHyperTwistTrainingPromptKind::Algorithm: + return TEXT("Algorithm"); + case EHyperTwistTrainingPromptKind::State: + return TEXT("State"); + case EHyperTwistTrainingPromptKind::Recognition: + return TEXT("Recognition"); + case EHyperTwistTrainingPromptKind::Memo: + return TEXT("Memo"); + case EHyperTwistTrainingPromptKind::Sequence: + return TEXT("Sequence"); + default: + return TEXT("Unknown"); + } + }; + + const auto ResolveGeneratedModePromptKind = []( + const FHyperTwistTrainingCase& InSeedCase, + const FString& RuntimeModeId, + const FString& GeneratorType + ) -> EHyperTwistTrainingPromptKind + { + const FString NormalizedRuntimeModeId = RuntimeModeId.ToLower(); + const FString NormalizedGeneratorType = GeneratorType.ToLower(); + if (NormalizedRuntimeModeId.Contains(TEXT("recognition")) + || NormalizedGeneratorType.Contains(TEXT("recognition"))) + { + return EHyperTwistTrainingPromptKind::Recognition; + } + if (NormalizedRuntimeModeId.Contains(TEXT("state")) + || NormalizedGeneratorType.Contains(TEXT("state")) + || NormalizedRuntimeModeId.Contains(TEXT("scramble")) + || NormalizedGeneratorType.Contains(TEXT("scramble"))) + { + return EHyperTwistTrainingPromptKind::State; + } + if ((InSeedCase.PromptKind == EHyperTwistTrainingPromptKind::Algorithm + || InSeedCase.PromptKind == EHyperTwistTrainingPromptKind::Sequence + || InSeedCase.PromptKind == EHyperTwistTrainingPromptKind::Memo) + && InSeedCase.CanonicalNotation.IsEmpty()) + { + return EHyperTwistTrainingPromptKind::State; + } + + return InSeedCase.PromptKind; + }; + + const auto BuildSelectorSummaryFromPreviewEntry = + [](const FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestPreviewEntrySurface& + PreviewEntry) -> FString + { + if (PreviewEntry.SelectorChoiceEntries.Num() <= 0) + { + return TEXT("no selector choices"); + } + + TArray SelectorParts; + for (const FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestSelectorChoiceEntrySurface& + ChoiceEntry : PreviewEntry.SelectorChoiceEntries) + { + const FString Label = ChoiceEntry.SelectorLabel.IsEmpty() + ? (ChoiceEntry.SelectorId.IsEmpty() ? FString(TEXT("n/a")) : ChoiceEntry.SelectorId) + : ChoiceEntry.SelectorLabel; + const FString Value = ChoiceEntry.DisplayValue.IsEmpty() + ? (ChoiceEntry.SelectedValue.IsEmpty() ? FString(TEXT("n/a")) : ChoiceEntry.SelectedValue) + : ChoiceEntry.DisplayValue; + SelectorParts.Add(FString::Printf(TEXT("%s=%s"), *Label, *Value)); + } + + return SelectorParts.Num() > 0 ? FString::Join(SelectorParts, TEXT(" | ")) + : TEXT("no selector choices"); + }; + + const auto BuildGeneratedPromptLabel = []( + const FString& PromptBaseLabel, + const FString& SelectorSummary + ) -> FString + { + return !PromptBaseLabel.IsEmpty() && !SelectorSummary.IsEmpty() + ? FString::Printf(TEXT("%s | %s"), *PromptBaseLabel, *SelectorSummary) + : PromptBaseLabel; + }; + + const auto BuildPromptLabelLine = [](const FString& PromptLabel) -> FString + { + return PromptLabel.IsEmpty() + ? TEXT("Prompt label: none") + : FString::Printf(TEXT("Prompt label: %s"), *PromptLabel); + }; + + const auto BuildPromptKindLine = [&DescribePromptKind]( + const EHyperTwistTrainingPromptKind PromptKind + ) -> FString + { + return FString::Printf( + TEXT("Prompt kind: %s"), + *DescribePromptKind(PromptKind) + ); + }; + + const auto IncrementPromptKindCount = [&Surface]( + const EHyperTwistTrainingPromptKind PromptKind + ) + { + switch (PromptKind) + { + case EHyperTwistTrainingPromptKind::Algorithm: + ++Surface.AlgorithmPromptKindOptionCount; + break; + case EHyperTwistTrainingPromptKind::State: + ++Surface.StatePromptKindOptionCount; + break; + case EHyperTwistTrainingPromptKind::Recognition: + ++Surface.RecognitionPromptKindOptionCount; + break; + case EHyperTwistTrainingPromptKind::Memo: + ++Surface.MemoPromptKindOptionCount; + break; + case EHyperTwistTrainingPromptKind::Sequence: + ++Surface.SequencePromptKindOptionCount; + break; + default: + break; + } + }; + + if (!RunState.IsStructurallyValid() || !bHasGeneratedModeSurface) + { + Surface.StatusLine = OptionMenuSurface.StatusLine.IsEmpty() + ? TEXT("Generated-mode selector option owned-execution prompt preview is not currently available.") + : OptionMenuSurface.StatusLine; + Surface.PromptLabelSummaryLine = TEXT("Prompt label: n/a"); + Surface.PromptKindSummaryLine = TEXT("Prompt kind: n/a"); + Surface.CurrentSelectionPromptLine = OptionMenuSurface.bHasUnresolvedSelectionDetail + ? OptionMenuSurface.UnresolvedSelectionLine + : (OptionMenuSurface.bHasSelection + ? TEXT("Current selection remains cached, but no active owned-execution prompt preview is available.") + : TEXT("Current selection: unresolved")); + Surface.DetailLine = FString::Printf( + TEXT("Launch-request preview: %s"), + OptionLaunchRequestPreviewSurface.StatusLine.IsEmpty() + ? TEXT("n/a") + : *OptionLaunchRequestPreviewSurface.StatusLine + ); + return Surface; + } + + for (const FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestPreviewEntrySurface& + PreviewEntry : OptionLaunchRequestPreviewSurface.OptionEntries) + { + FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionPromptPreviewEntrySurface + 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)) + { + Entry.PreviewPromptLabel = MatchingCase.PromptLabel.IsEmpty() + ? MatchingCase.CaseId + : MatchingCase.PromptLabel; + Entry.PreviewPromptKind = MatchingCase.PromptKind; + Entry.PreviewPromptKindLabel = DescribePromptKind(MatchingCase.PromptKind); + Entry.PromptLabelLine = BuildPromptLabelLine(Entry.PreviewPromptLabel); + Entry.PromptKindLine = BuildPromptKindLine(MatchingCase.PromptKind); + Entry.PromptSourceLine = + TEXT("Prompt source: exact direct covered-case prompt presentation."); + Entry.PreviewStabilityLine = + TEXT("Preview stability: exact direct owned-case prompt preview from imported option coverage."); + Entry.bHasExactPromptPreview = true; + Entry.bHasPromptLabelText = !Entry.PreviewPromptLabel.IsEmpty(); + Entry.bCanPreview = true; + ++Surface.DirectPromptPreviewOptionCount; + IncrementPromptKindCount(MatchingCase.PromptKind); + } + else + { + Entry.PromptLabelLine = TEXT("Prompt label: unavailable"); + Entry.PromptKindLine = TEXT("Prompt kind: unavailable"); + Entry.PromptSourceLine = + TEXT("Prompt source: unavailable because the covered case could not be re-resolved from the active deck."); + Entry.PreviewStabilityLine = TEXT("Preview stability: unavailable"); + Entry.bCanPreview = false; + ++Surface.UnavailableOptionCount; + } + break; + } + + case EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind:: + StoredRequestReusable: + case EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind:: + RefreshRequired: + if (SeedCase == nullptr) + { + Entry.PromptLabelLine = TEXT("Prompt label: unavailable"); + Entry.PromptKindLine = TEXT("Prompt kind: unavailable"); + Entry.PromptSourceLine = + TEXT("Prompt source: unavailable because the active generated-mode surface has no structurally valid seed case."); + Entry.PreviewStabilityLine = TEXT("Preview stability: unavailable"); + Entry.bCanPreview = false; + ++Surface.UnavailableOptionCount; + break; + } + + Entry.PreviewPromptKind = ResolveGeneratedModePromptKind( + *SeedCase, + PreviewEntry.HypotheticalRuntimeModeId, + PreviewEntry.HypotheticalGeneratorType + ); + Entry.PreviewPromptKindLabel = DescribePromptKind(Entry.PreviewPromptKind); + Entry.PreviewPromptLabel = BuildGeneratedPromptLabel( + !PreviewEntry.HypotheticalSurfaceLabel.IsEmpty() + ? PreviewEntry.HypotheticalSurfaceLabel + : RunState.ActiveDeck.Title, + BuildSelectorSummaryFromPreviewEntry(PreviewEntry) + ); + Entry.PromptLabelLine = BuildPromptLabelLine(Entry.PreviewPromptLabel); + Entry.PromptKindLine = BuildPromptKindLine(Entry.PreviewPromptKind); + Entry.PromptSourceLine = + TEXT("Prompt source: exact generated-mode owned execution prompt composition from the active surface and selector payload."); + Entry.PreviewStabilityLine = + PreviewEntry.LaunchRequestPreviewKind + == EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind:: + StoredRequestReusable + ? TEXT("Preview stability: exact while execute reuses the current stored request.") + : TEXT("Preview stability: exact even though execute still needs launch-request refresh."); + Entry.bHasExactPromptPreview = true; + Entry.bHasPromptLabelText = !Entry.PreviewPromptLabel.IsEmpty(); + Entry.bCanPreview = true; + ++Surface.GeneratedPromptPreviewOptionCount; + IncrementPromptKindCount(Entry.PreviewPromptKind); + break; + + case EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind:: + UnresolvedAfterChoice: + Entry.PromptLabelLine = + TEXT("Prompt label: blocked until every required selector is resolved."); + Entry.PromptKindLine = + TEXT("Prompt kind: blocked until every required selector is resolved."); + Entry.PromptSourceLine = + TEXT("Prompt source: blocked while selector coverage remains incomplete."); + Entry.PreviewStabilityLine = + TEXT("Preview stability: blocked while selector coverage remains incomplete."); + Entry.bPreviewBlocked = true; + Entry.bCanPreview = true; + ++Surface.BlockedOptionCount; + break; + + case EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind:: + PayloadUnavailable: + Entry.PromptLabelLine = + TEXT("Prompt label: blocked until a structurally valid launch payload can be derived."); + Entry.PromptKindLine = + TEXT("Prompt kind: blocked until a structurally valid launch payload can be derived."); + Entry.PromptSourceLine = + TEXT("Prompt source: blocked while the hypothetical generated-mode payload remains structurally invalid."); + 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.PromptLabelLine = TEXT("Prompt label: unavailable"); + Entry.PromptKindLine = TEXT("Prompt kind: unavailable"); + Entry.PromptSourceLine = TEXT("Prompt source: 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.bCurrentSelectionHasExactPromptPreview = Entry.bHasExactPromptPreview; + Surface.bCurrentSelectionHasPromptLabelText = Entry.bHasPromptLabelText; + Surface.CurrentSelectionPromptLine = FString::Printf( + TEXT("%s | %s"), + *Entry.PromptLabelLine, + *Entry.PromptKindLine + ); + } + + Surface.OptionEntries.Add(MoveTemp(Entry)); + } + + Surface.bHasOwnedExecutionPromptPreviewOptions = Surface.OptionEntries.Num() > 0; + Surface.bHasDirectPromptPreviewOptions = Surface.DirectPromptPreviewOptionCount > 0; + Surface.bHasGeneratedPromptPreviewOptions = + Surface.GeneratedPromptPreviewOptionCount > 0; + Surface.bHasBlockedPromptPreviewOptions = Surface.BlockedOptionCount > 0; + + Surface.PromptLabelSummaryLine = Surface.OptionEntries.Num() > 0 + ? FString::Printf( + TEXT("Prompt label: %d direct-case exact | %d generated exact | %d blocked | %d unavailable"), + Surface.DirectPromptPreviewOptionCount, + Surface.GeneratedPromptPreviewOptionCount, + Surface.BlockedOptionCount, + Surface.UnavailableOptionCount + ) + : TEXT("Prompt label: no imported options"); + Surface.PromptKindSummaryLine = Surface.OptionEntries.Num() > 0 + ? FString::Printf( + TEXT("Prompt kind: %d algorithm | %d state | %d recognition | %d memo | %d sequence"), + Surface.AlgorithmPromptKindOptionCount, + Surface.StatePromptKindOptionCount, + Surface.RecognitionPromptKindOptionCount, + Surface.MemoPromptKindOptionCount, + Surface.SequencePromptKindOptionCount + ) + : TEXT("Prompt kind: no imported options"); + + if (Surface.CurrentSelectionPromptLine.IsEmpty()) + { + if (OptionMenuSurface.bHasUnresolvedSelectionDetail) + { + Surface.CurrentSelectionPromptLine = OptionMenuSurface.UnresolvedSelectionLine; + } + else if (OptionMenuSurface.bHasSelection) + { + Surface.CurrentSelectionPromptLine = + TEXT("Current selection has no additional owned-execution prompt preview."); + } + else if (OptionMenuSurface.bHasSelector) + { + Surface.CurrentSelectionPromptLine = TEXT("Current selection: unresolved"); + } + else + { + Surface.CurrentSelectionPromptLine = TEXT("Current selection: n/a"); + } + } + + if (OptionMenuSurface.bHasSelector && Surface.OptionEntries.Num() > 0) + { + Surface.StatusLine = FString::Printf( + TEXT("Generated-mode selector option owned-execution prompt preview exposes %d direct-case exact option%s, %d generated exact option%s, %d blocked option%s, and %d unavailable option%s."), + Surface.DirectPromptPreviewOptionCount, + Surface.DirectPromptPreviewOptionCount == 1 ? TEXT("") : TEXT("s"), + Surface.GeneratedPromptPreviewOptionCount, + Surface.GeneratedPromptPreviewOptionCount == 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 prompt preview is active, but this selector does not expose imported options."); + } + else + { + Surface.StatusLine = OptionMenuSurface.StatusLine.IsEmpty() + ? TEXT("Generated-mode selector option owned-execution prompt 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 preview: %s | Selector: %s | Binding: %s"), + OptionLaunchRequestPreviewSurface.StatusLine.IsEmpty() + ? TEXT("n/a") + : *OptionLaunchRequestPreviewSurface.StatusLine, + *SelectorSummary, + *BindingSummary + ); + + return Surface; +} diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp index 6bc9a9e..1167d75 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp @@ -891,6 +891,25 @@ UHyperTwistTrainingPanelWidget::GetDisplayedCoachGeneratedModeSelectorOptionOwne ); } +FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionPromptPreviewSurface +UHyperTwistTrainingPanelWidget::GetDisplayedCoachGeneratedModeSelectorOptionOwnedExecutionPromptPreviewSurface( + const FString& SelectorId +) const +{ + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionMenuSurface OptionMenuSurface = + GetDisplayedCoachGeneratedModeSelectorOptionMenuSurface(SelectorId); + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface + OptionLaunchRequestPreviewSurface = + GetDisplayedCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface(SelectorId); + return UHyperTwistTrainingCoachLibrary:: + DeriveCoachGeneratedModeSelectorOptionOwnedExecutionPromptPreviewSurface( + 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 0bc287a..ffa20d0 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp @@ -872,6 +872,25 @@ AHyperTwistTrainingSessionActor::GetDisplayedCoachGeneratedModeSelectorOptionOwn ); } +FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionPromptPreviewSurface +AHyperTwistTrainingSessionActor::GetDisplayedCoachGeneratedModeSelectorOptionOwnedExecutionPromptPreviewSurface( + const FString& SelectorId +) const +{ + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionMenuSurface OptionMenuSurface = + GetDisplayedCoachGeneratedModeSelectorOptionMenuSurface(SelectorId); + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface + OptionLaunchRequestPreviewSurface = + GetDisplayedCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface(SelectorId); + return UHyperTwistTrainingCoachLibrary:: + DeriveCoachGeneratedModeSelectorOptionOwnedExecutionPromptPreviewSurface( + 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 d7e5d6f..184aea6 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h @@ -787,4 +787,14 @@ public: const FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestEnvelopePreviewSurface& OptionLaunchRequestEnvelopePreviewSurface ); + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + static FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionPromptPreviewSurface + DeriveCoachGeneratedModeSelectorOptionOwnedExecutionPromptPreviewSurface( + 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 cac0a0b..2b5bcb4 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h @@ -302,6 +302,12 @@ public: const FString& SelectorId ) const; + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionPromptPreviewSurface + GetDisplayedCoachGeneratedModeSelectorOptionOwnedExecutionPromptPreviewSurface( + 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 10987ff..442a1e2 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h @@ -283,6 +283,12 @@ public: const FString& SelectorId ) const; + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionPromptPreviewSurface + GetDisplayedCoachGeneratedModeSelectorOptionOwnedExecutionPromptPreviewSurface( + 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 b6e39c3..d9003a0 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h @@ -10310,3 +10310,169 @@ struct FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionCasePrev || bHasOwnedExecutionCasePreviewOptions; } }; + +USTRUCT(BlueprintType) +struct FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionPromptPreviewEntrySurface +{ + 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 PromptLabelLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString PromptKindLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString PromptSourceLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString PreviewStabilityLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString DetailLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString PreviewPromptLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + EHyperTwistTrainingPromptKind PreviewPromptKind = EHyperTwistTrainingPromptKind::Algorithm; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString PreviewPromptKindLabel; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bMatchesCurrentSelection = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bStartsOwnedCaseImmediately = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasExactPromptPreview = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasPromptLabelText = 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() + || !PromptLabelLine.IsEmpty() + || !PromptKindLine.IsEmpty() + || LaunchRequestPreviewKind + != EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind::None + || bCanPreview; + } +}; + +USTRUCT(BlueprintType) +struct FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionPromptPreviewSurface +{ + 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 PromptLabelSummaryLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString PromptKindSummaryLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString CurrentSelectionPromptLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString DetailLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray + OptionEntries; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 DirectPromptPreviewOptionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 GeneratedPromptPreviewOptionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 BlockedOptionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 UnavailableOptionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 AlgorithmPromptKindOptionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 StatePromptKindOptionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 RecognitionPromptKindOptionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 MemoPromptKindOptionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 SequencePromptKindOptionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasSelector = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasOwnedExecutionPromptPreviewOptions = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasDirectPromptPreviewOptions = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasGeneratedPromptPreviewOptions = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasBlockedPromptPreviewOptions = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCurrentSelectionHasExactPromptPreview = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCurrentSelectionHasPromptLabelText = false; + + bool IsStructurallyValid() const + { + return !StatusLine.IsEmpty() + || !SelectorId.IsEmpty() + || OptionEntries.Num() > 0 + || bHasSelector + || bHasOwnedExecutionPromptPreviewOptions; + } +}; diff --git a/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md b/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md index 19ef1d6..672cf5b 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 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 latest landed bounded `Phase 4` packet stays on that same gameplay-facing consumer lane and adds one thin generated-mode selector option owned execution prompt-label / prompt-kind preview surface over the already-cached selector/runtime/request state, so gameplay/Blueprint callers can render exact direct-case prompt presentation and exact generated owned prompt composition for a chosen option without rebuilding generated deck composition rules directly + - the current next bounded packet is to expose one thin generated-mode selector option owned execution delivery-mode / reveal-posture / time-target preview surface on those same two consumer surfaces so gameplay/Blueprint callers can render the resulting owned execution modality and reveal posture 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_PROMPT_LABEL_PROMPT_KIND_PREVIEW_SURFACE_PACKET_2026-05-08.md b/docs/arch/HYPERTWIST_PHASE4_GAMEPLAY_GENERATED_MODE_SELECTOR_OPTION_OWNED_EXECUTION_PROMPT_LABEL_PROMPT_KIND_PREVIEW_SURFACE_PACKET_2026-05-08.md new file mode 100644 index 0000000..1ce5f24 --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE4_GAMEPLAY_GENERATED_MODE_SELECTOR_OPTION_OWNED_EXECUTION_PROMPT_LABEL_PROMPT_KIND_PREVIEW_SURFACE_PACKET_2026-05-08.md @@ -0,0 +1,101 @@ +# HyperTwist Phase 4 gameplay generated-mode selector option owned execution prompt-label / prompt-kind 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 case-identity / canonical-state preview surface by exposing one parameterized owned execution prompt-label / prompt-kind preview surface for a chosen selector id. + +The open tasks were: + +- stop forcing gameplay / Blueprint callers to rebuild generated deck prompt composition rules just to explain the resulting owned prompt presentation for a chosen imported option +- expose exact direct covered-case prompt presentation plus exact generated owned prompt composition whenever the generated-mode payload is previewable +- keep the surface derived from already-cached gameplay consumer run, selector, and launch-request preview 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: + +- add first-party owned execution prompt preview surfaces in shared training types +- add a coach-library helper that projects prompt-label and prompt-kind preview over the already-landed launch-request preview surface +- expose that parameterized prompt 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 launch payload, request reuse, envelope identity, owned case identity, and canonical-state preview for imported selector options +- callers could tell which owned case target and canonical-state ref would result from a chosen option + +But one thin consumer seam was still open: + +- callers still had to rebuild generated prompt-label and prompt-kind composition from seed-case rules, runtime mode, generator type, surface label, and selector summary if they wanted to describe the resulting owned prompt presentation + +That meant: + +- target identity was already thin and reusable +- but visible prompt presentation still leaked generated deck composition rules into callers + +So the next bounded move was: + +- keep the already-landed owned case / canonical-state preview surface unchanged in behavior +- reuse the already-cached launch-request preview surface to expose one narrow owned execution prompt preview surface above it + +## What landed + +Primary code changes: + +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h` + - added `FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionPromptPreviewEntrySurface` + - added `FHyperTwistTrainingCoachGeneratedModeSelectorOptionOwnedExecutionPromptPreviewSurface` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp` + - added `DeriveCoachGeneratedModeSelectorOptionOwnedExecutionPromptPreviewSurface(...)` + - prompt preview is derived from cached run state plus the already-landed launch-request preview surface +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp` + - added `GetDisplayedCoachGeneratedModeSelectorOptionOwnedExecutionPromptPreviewSurface(const FString& SelectorId)` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp` + - added `GetDisplayedCoachGeneratedModeSelectorOptionOwnedExecutionPromptPreviewSurface(const FString& SelectorId)` + +Surface behavior: + +- reports exact direct covered-case prompt label and prompt kind for selector options that dispatch straight to owned cases +- reports exact generated owned prompt label and prompt kind whenever the chosen option yields a previewable generated-mode payload, including refresh-required options whose request id has not been persisted yet +- keeps unresolved or payload-invalid choices inspectable as blocked prompt-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, and owned execution case / canonical-state 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 delivery-mode / reveal-posture / time-target preview surface so callers can render the resulting owned execution modality and reveal posture for a chosen option without rebuilding generated deck composition rules directly.