diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp index 6dd68cf..eda0f98 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp @@ -8868,3 +8868,726 @@ UHyperTwistTrainingCoachLibrary::DeriveCoachGeneratedModeSelectorOptionRequestAl return Surface; } + +FHyperTwistTrainingCoachGeneratedModeSelectorOptionStoredRequestDeltaSurface +UHyperTwistTrainingCoachLibrary::DeriveCoachGeneratedModeSelectorOptionStoredRequestDeltaSurface( + const FHyperTwistTrainingRunState& RunState, + const FString& SelectorId, + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionMenuSurface& OptionMenuSurface, + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionDispatchSurface& OptionDispatchSurface, + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionRequestAlignmentSurface& + OptionRequestAlignmentSurface +) +{ + FHyperTwistTrainingCoachGeneratedModeSelectorOptionStoredRequestDeltaSurface Surface; + Surface.SelectorId = SelectorId; + Surface.SelectorLabel = OptionMenuSurface.SelectorLabel; + Surface.BindingLabel = OptionMenuSurface.BindingLabel; + Surface.Headline = TEXT("Generated Mode Selector Option Stored-Request Delta"); + Surface.bHasSelector = OptionMenuSurface.bHasSelector; + + if (!SelectorId.IsEmpty()) + { + Surface.Headline = OptionMenuSurface.SelectorLabel.IsEmpty() + ? FString::Printf( + TEXT("Generated Mode Selector Option Stored-Request Delta: %s"), + *SelectorId + ) + : FString::Printf( + TEXT("Generated Mode Selector Option Stored-Request Delta: %s"), + *OptionMenuSurface.SelectorLabel + ); + } + + if (SelectorId.IsEmpty()) + { + Surface.StatusLine = + TEXT("Generated-mode selector option stored-request delta requires a selector id from the active selector roster."); + Surface.SelectorDiffSummaryLine = TEXT("Selector diff: n/a"); + Surface.CurrentToHypotheticalSummaryLine = TEXT("Current -> hypothetical: n/a"); + Surface.StoredRequestToHypotheticalSummaryLine = TEXT("Stored request -> hypothetical: n/a"); + Surface.CurrentToStoredRequestSummaryLine = TEXT("Current -> stored request: n/a"); + Surface.CurrentSelectionDeltaLine = TEXT("Current selection: n/a"); + Surface.DetailLine = OptionRequestAlignmentSurface.StatusLine.IsEmpty() + ? TEXT("Request alignment: n/a") + : FString::Printf( + TEXT("Request alignment: %s"), + *OptionRequestAlignmentSurface.StatusLine + ); + return Surface; + } + + const FHyperTwistTrainingImportedRuntimeSurface& RuntimeSurface = RunState.ActiveDeck.ImportedRuntimeSurface; + const bool bHasGeneratedModeSurface = RuntimeSurface.IsStructurallyValid() + && RuntimeSurface.SurfaceKind.Equals(TEXT("generated-mode"), ESearchCase::IgnoreCase); + const FHyperTwistTrainingImportedRuntimeSelector* MatchingSelector = + RuntimeSurface.Selectors.FindByPredicate( + [&SelectorId](const FHyperTwistTrainingImportedRuntimeSelector& Candidate) + { + return Candidate.SelectorId == SelectorId && Candidate.IsStructurallyValid(); + } + ); + const FString SelectorBinding = MatchingSelector != nullptr + ? MatchingSelector->SelectorBinding + : OptionMenuSurface.BindingLabel; + const FHyperTwistTrainingImportedGeneratedModeLaunchRequest& LaunchRequest = + RunState.ImportedGeneratedModeLaunchRequest; + const bool bHasComparableStoredRequest = LaunchRequest.IsStructurallyValid() + && LaunchRequest.LaunchConfig.DeckId == RunState.ActiveDeck.DeckId + && LaunchRequest.LaunchConfig.SurfaceId == RuntimeSurface.SurfaceId + && LaunchRequest.LaunchConfig.RuntimeModeId.Equals( + RuntimeSurface.RuntimeModeId, + ESearchCase::IgnoreCase + ); + + const auto DescribeSelector = []( + const FHyperTwistTrainingImportedRuntimeSelector& Selector + ) -> FString + { + if (!Selector.Label.IsEmpty()) + { + return Selector.Label; + } + if (!Selector.SelectorBinding.IsEmpty()) + { + return Selector.SelectorBinding; + } + return Selector.SelectorId; + }; + + const auto FindSelectorChoice = []( + const TArray& Choices, + const FString& InSelectorId + ) -> const FHyperTwistTrainingImportedRuntimeSelectorChoice* + { + return Choices.FindByPredicate( + [&InSelectorId](const FHyperTwistTrainingImportedRuntimeSelectorChoice& Candidate) + { + return Candidate.SelectorId == InSelectorId && Candidate.IsStructurallyValid(); + } + ); + }; + + const auto DoChoicesMatch = []( + const FHyperTwistTrainingImportedRuntimeSelectorChoice* LeftChoice, + const FHyperTwistTrainingImportedRuntimeSelectorChoice* RightChoice + ) -> bool + { + if (LeftChoice == nullptr || RightChoice == nullptr) + { + return LeftChoice == nullptr && RightChoice == nullptr; + } + + return LeftChoice->SelectedValue == RightChoice->SelectedValue; + }; + + const auto DescribeChoiceValue = []( + const FHyperTwistTrainingImportedRuntimeSelectorChoice* Choice, + const TCHAR* MissingText + ) -> FString + { + if (Choice == nullptr) + { + return MissingText; + } + + if (Choice->DisplayValue.IsEmpty() || Choice->DisplayValue == Choice->SelectedValue) + { + return Choice->SelectedValue.IsEmpty() ? FString(MissingText) : Choice->SelectedValue; + } + + return FString::Printf( + TEXT("%s [%s]"), + *Choice->DisplayValue, + Choice->SelectedValue.IsEmpty() ? TEXT("n/a") : *Choice->SelectedValue + ); + }; + + const auto BuildPairDeltaLine = []( + const TCHAR* Label, + const FString& LeftValue, + const FString& RightValue, + const bool bMatches + ) -> FString + { + return bMatches + ? FString::Printf(TEXT("%s: no change (%s)"), Label, *RightValue) + : FString::Printf(TEXT("%s: %s -> %s"), Label, *LeftValue, *RightValue); + }; + + const auto BuildAggregateDeltaLine = []( + const TCHAR* Label, + const TArray& ChangedSelectorLabels + ) -> FString + { + if (ChangedSelectorLabels.Num() == 0) + { + return FString::Printf(TEXT("%s: no selector change"), Label); + } + + const int32 PreviewCount = FMath::Min(3, ChangedSelectorLabels.Num()); + TArray PreviewLabels; + for (int32 Index = 0; Index < PreviewCount; ++Index) + { + PreviewLabels.Add(ChangedSelectorLabels[Index]); + } + + return FString::Printf( + TEXT("%s: %d selector change%s (%s%s)"), + Label, + ChangedSelectorLabels.Num(), + ChangedSelectorLabels.Num() == 1 ? TEXT("") : TEXT("s"), + *FString::Join(PreviewLabels, TEXT(", ")), + ChangedSelectorLabels.Num() > PreviewCount + ? *FString::Printf(TEXT(" (+%d more)"), ChangedSelectorLabels.Num() - PreviewCount) + : TEXT("") + ); + }; + + const auto BuildFallbackUnresolvedSelectorLine = [&DescribeSelector, &FindSelectorChoice, &RuntimeSurface]( + const FHyperTwistTrainingImportedRuntimeSelectionState& SelectionState, + int32& OutUnresolvedSelectorCount + ) -> FString + { + OutUnresolvedSelectorCount = 0; + TArray UnresolvedSelectorLabels; + + for (const FHyperTwistTrainingImportedRuntimeSelector& Selector : RuntimeSurface.Selectors) + { + if (!Selector.IsStructurallyValid()) + { + continue; + } + + if (FindSelectorChoice(SelectionState.SelectorChoices, Selector.SelectorId) != nullptr) + { + continue; + } + + ++OutUnresolvedSelectorCount; + UnresolvedSelectorLabels.Add(DescribeSelector(Selector)); + } + + if (OutUnresolvedSelectorCount == 0) + { + return TEXT("Unresolved after choice: none"); + } + + const int32 PreviewCount = FMath::Min(3, UnresolvedSelectorLabels.Num()); + TArray PreviewLabels; + for (int32 Index = 0; Index < PreviewCount; ++Index) + { + PreviewLabels.Add(UnresolvedSelectorLabels[Index]); + } + + return FString::Printf( + TEXT("Unresolved after choice: %s%s"), + *FString::Join(PreviewLabels, TEXT(", ")), + UnresolvedSelectorLabels.Num() > PreviewCount + ? *FString::Printf(TEXT(" (+%d more)"), UnresolvedSelectorLabels.Num() - PreviewCount) + : TEXT("") + ); + }; + + if (!RunState.IsStructurallyValid() || !bHasGeneratedModeSurface) + { + Surface.StatusLine = OptionMenuSurface.StatusLine.IsEmpty() + ? TEXT("Generated-mode selector option stored-request delta is not currently available.") + : OptionMenuSurface.StatusLine; + Surface.SelectorDiffSummaryLine = TEXT("Selector diff: n/a"); + Surface.CurrentToHypotheticalSummaryLine = TEXT("Current -> hypothetical: n/a"); + Surface.StoredRequestToHypotheticalSummaryLine = TEXT("Stored request -> hypothetical: n/a"); + Surface.CurrentToStoredRequestSummaryLine = TEXT("Current -> stored request: n/a"); + Surface.CurrentSelectionDeltaLine = OptionMenuSurface.bHasUnresolvedSelectionDetail + ? OptionMenuSurface.UnresolvedSelectionLine + : (OptionMenuSurface.bHasSelection + ? TEXT("Current selection remains cached, but no active stored-request delta preview is available.") + : TEXT("Current selection: unresolved")); + Surface.DetailLine = FString::Printf( + TEXT("Dispatch: %s | Request alignment: %s"), + OptionDispatchSurface.StatusLine.IsEmpty() + ? TEXT("n/a") + : *OptionDispatchSurface.StatusLine, + OptionRequestAlignmentSurface.StatusLine.IsEmpty() + ? TEXT("n/a") + : *OptionRequestAlignmentSurface.StatusLine + ); + return Surface; + } + + int32 PreviewableGeneratedOptionCount = 0; + int32 CurrentToHypotheticalDeltaOptionCount = 0; + int32 StoredRequestToHypotheticalDeltaOptionCount = 0; + int32 CurrentToStoredRequestDeltaOptionCount = 0; + + for (const FHyperTwistTrainingCoachGeneratedModeSelectorOptionDispatchEntrySurface& DispatchEntry : + OptionDispatchSurface.OptionEntries) + { + FHyperTwistTrainingCoachGeneratedModeSelectorOptionStoredRequestDeltaEntrySurface Entry; + Entry.OptionId = DispatchEntry.OptionId; + Entry.Label = DispatchEntry.Label; + Entry.OptionValue = DispatchEntry.OptionValue; + Entry.bMatchesCurrentSelection = DispatchEntry.bMatchesCurrentSelection; + + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionRequestAlignmentEntrySurface* + MatchingRequestAlignmentEntry = + OptionRequestAlignmentSurface.OptionEntries.FindByPredicate( + [&DispatchEntry]( + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionRequestAlignmentEntrySurface& + Candidate + ) + { + return Candidate.OptionId == DispatchEntry.OptionId; + } + ); + + if (DispatchEntry.DispatchKind + == EHyperTwistTrainingCoachGeneratedModeOptionDispatchKind::DirectCaseLaunch + && DispatchEntry.bCanDispatch) + { + Entry.StoredRequestDeltaKind = + EHyperTwistTrainingCoachGeneratedModeOptionStoredRequestDeltaKind::DirectOwnedCaseStart; + Entry.HypotheticalSelectorSummaryLine = + MatchingRequestAlignmentEntry == nullptr + || MatchingRequestAlignmentEntry->HypotheticalSelectorSummaryLine.IsEmpty() + ? TEXT("Hypothetical selectors: direct owned-case launch bypasses generated-mode selector-choice apply.") + : MatchingRequestAlignmentEntry->HypotheticalSelectorSummaryLine; + Entry.CurrentToHypotheticalDeltaLine = + TEXT("Current -> hypothetical: direct owned-case launch bypasses selector diff preview."); + Entry.StoredRequestToHypotheticalDeltaLine = + TEXT("Stored request -> hypothetical: direct owned-case launch bypasses selector diff preview."); + Entry.CurrentToStoredRequestDeltaLine = + TEXT("Current -> stored request: direct owned-case launch bypasses selector diff preview."); + Entry.UnresolvedSelectorsLine = + MatchingRequestAlignmentEntry == nullptr + || MatchingRequestAlignmentEntry->UnresolvedSelectorsLine.IsEmpty() + ? TEXT("Unresolved after choice: n/a") + : MatchingRequestAlignmentEntry->UnresolvedSelectorsLine; + Entry.ExecutePreviewLine = + MatchingRequestAlignmentEntry == nullptr + || MatchingRequestAlignmentEntry->ExecutePreviewLine.IsEmpty() + ? TEXT("Execute preview: direct owned-case launch starts immediately.") + : MatchingRequestAlignmentEntry->ExecutePreviewLine; + Entry.DetailLine = + MatchingRequestAlignmentEntry == nullptr + || MatchingRequestAlignmentEntry->DetailLine.IsEmpty() + ? TEXT("Direct option launch bypasses generated-mode selector diff preview for this interaction.") + : MatchingRequestAlignmentEntry->DetailLine; + Entry.bStartsOwnedCaseImmediately = true; + Entry.bCanPreview = true; + ++Surface.DirectOwnedCaseStartOptionCount; + } + else if (DispatchEntry.DispatchKind + == EHyperTwistTrainingCoachGeneratedModeOptionDispatchKind::ApplyGeneratedChoice + && DispatchEntry.bCanDispatch && MatchingSelector != nullptr) + { + ++PreviewableGeneratedOptionCount; + + FHyperTwistTrainingImportedRuntimeSelectionState HypotheticalSelectionState = + RunState.ImportedRuntimeSelectionState; + if (!HypotheticalSelectionState.IsStructurallyValid()) + { + HypotheticalSelectionState.SurfaceId = RuntimeSurface.SurfaceId; + HypotheticalSelectionState.SurfaceKind = RuntimeSurface.SurfaceKind; + } + + const FString SelectedValue = DispatchEntry.GeneratedChoiceSelectedValue.IsEmpty() + ? (DispatchEntry.OptionValue.IsEmpty() + ? DispatchEntry.OptionId + : DispatchEntry.OptionValue) + : DispatchEntry.GeneratedChoiceSelectedValue; + const FString DisplayValue = DispatchEntry.GeneratedChoiceDisplayValue.IsEmpty() + ? (DispatchEntry.Label.IsEmpty() ? SelectedValue : DispatchEntry.Label) + : DispatchEntry.GeneratedChoiceDisplayValue; + + FHyperTwistTrainingImportedRuntimeSelectorChoice* ExistingChoice = + HypotheticalSelectionState.SelectorChoices.FindByPredicate( + [&SelectorId](const FHyperTwistTrainingImportedRuntimeSelectorChoice& Candidate) + { + return Candidate.SelectorId == SelectorId; + } + ); + if (ExistingChoice != nullptr) + { + ExistingChoice->SelectorBinding = SelectorBinding; + ExistingChoice->SelectedValue = SelectedValue; + ExistingChoice->DisplayValue = DisplayValue.IsEmpty() ? SelectedValue : DisplayValue; + } + else + { + FHyperTwistTrainingImportedRuntimeSelectorChoice Choice; + Choice.SelectorId = SelectorId; + Choice.SelectorBinding = SelectorBinding; + Choice.SelectedValue = SelectedValue; + Choice.DisplayValue = DisplayValue.IsEmpty() ? SelectedValue : DisplayValue; + HypotheticalSelectionState.SelectorChoices.Add(MoveTemp(Choice)); + } + + Entry.HypotheticalSelectorSummaryLine = + MatchingRequestAlignmentEntry == nullptr + || MatchingRequestAlignmentEntry->HypotheticalSelectorSummaryLine.IsEmpty() + ? OptionRequestAlignmentSurface.CurrentSelectionPreviewLine + : MatchingRequestAlignmentEntry->HypotheticalSelectorSummaryLine; + Entry.ExecutePreviewLine = + MatchingRequestAlignmentEntry == nullptr + || MatchingRequestAlignmentEntry->ExecutePreviewLine.IsEmpty() + ? TEXT("Execute preview: unavailable.") + : MatchingRequestAlignmentEntry->ExecutePreviewLine; + + if (MatchingRequestAlignmentEntry != nullptr) + { + Entry.UnresolvedSelectorsLine = MatchingRequestAlignmentEntry->UnresolvedSelectorsLine; + Entry.UnresolvedSelectorCount = MatchingRequestAlignmentEntry->UnresolvedSelectorCount; + Entry.bWouldLeaveSelectorsUnresolved = + MatchingRequestAlignmentEntry->bWouldLeaveSelectorsUnresolved; + } + else + { + Entry.UnresolvedSelectorsLine = BuildFallbackUnresolvedSelectorLine( + HypotheticalSelectionState, + Entry.UnresolvedSelectorCount + ); + Entry.bWouldLeaveSelectorsUnresolved = Entry.UnresolvedSelectorCount > 0; + } + + TArray CurrentToHypotheticalChangedSelectorLabels; + TArray StoredRequestToHypotheticalChangedSelectorLabels; + TArray CurrentToStoredRequestChangedSelectorLabels; + + for (const FHyperTwistTrainingImportedRuntimeSelector& Selector : RuntimeSurface.Selectors) + { + if (!Selector.IsStructurallyValid()) + { + continue; + } + + const FString SelectorLabel = DescribeSelector(Selector); + const FHyperTwistTrainingImportedRuntimeSelectorChoice* CurrentChoice = + FindSelectorChoice( + RunState.ImportedRuntimeSelectionState.SelectorChoices, + Selector.SelectorId + ); + const FHyperTwistTrainingImportedRuntimeSelectorChoice* HypotheticalChoice = + FindSelectorChoice( + HypotheticalSelectionState.SelectorChoices, + Selector.SelectorId + ); + const FHyperTwistTrainingImportedRuntimeSelectorChoice* StoredRequestChoice = + bHasComparableStoredRequest + ? FindSelectorChoice( + LaunchRequest.LaunchConfig.SelectorChoices, + Selector.SelectorId + ) + : nullptr; + + FHyperTwistTrainingCoachGeneratedModeSelectorOptionSelectorDiffEntrySurface + SelectorDiffEntry; + SelectorDiffEntry.SelectorId = Selector.SelectorId; + SelectorDiffEntry.SelectorLabel = SelectorLabel; + SelectorDiffEntry.BindingLabel = Selector.SelectorBinding; + SelectorDiffEntry.CurrentSelectionValue = + DescribeChoiceValue(CurrentChoice, TEXT("unresolved")); + SelectorDiffEntry.HypotheticalSelectionValue = + DescribeChoiceValue(HypotheticalChoice, TEXT("unresolved")); + SelectorDiffEntry.StoredRequestSelectionValue = + DescribeChoiceValue(StoredRequestChoice, TEXT("not stored")); + SelectorDiffEntry.bIsTargetSelector = Selector.SelectorId == SelectorId; + SelectorDiffEntry.bHasCurrentSelection = CurrentChoice != nullptr; + SelectorDiffEntry.bHasHypotheticalSelection = HypotheticalChoice != nullptr; + SelectorDiffEntry.bHasStoredRequestSelection = StoredRequestChoice != nullptr; + SelectorDiffEntry.bCurrentMatchesHypothetical = + DoChoicesMatch(CurrentChoice, HypotheticalChoice); + SelectorDiffEntry.bStoredRequestMatchesHypothetical = + DoChoicesMatch(StoredRequestChoice, HypotheticalChoice); + SelectorDiffEntry.bCurrentMatchesStoredRequest = + DoChoicesMatch(CurrentChoice, StoredRequestChoice); + SelectorDiffEntry.CurrentToHypotheticalDeltaLine = BuildPairDeltaLine( + TEXT("Current -> hypothetical"), + SelectorDiffEntry.CurrentSelectionValue, + SelectorDiffEntry.HypotheticalSelectionValue, + SelectorDiffEntry.bCurrentMatchesHypothetical + ); + SelectorDiffEntry.StoredRequestToHypotheticalDeltaLine = BuildPairDeltaLine( + TEXT("Stored request -> hypothetical"), + SelectorDiffEntry.StoredRequestSelectionValue, + SelectorDiffEntry.HypotheticalSelectionValue, + SelectorDiffEntry.bStoredRequestMatchesHypothetical + ); + SelectorDiffEntry.CurrentToStoredRequestDeltaLine = BuildPairDeltaLine( + TEXT("Current -> stored request"), + SelectorDiffEntry.CurrentSelectionValue, + SelectorDiffEntry.StoredRequestSelectionValue, + SelectorDiffEntry.bCurrentMatchesStoredRequest + ); + + if (!SelectorDiffEntry.bCurrentMatchesHypothetical) + { + ++Entry.CurrentToHypotheticalChangedSelectorCount; + CurrentToHypotheticalChangedSelectorLabels.Add(SelectorLabel); + } + if (!SelectorDiffEntry.bStoredRequestMatchesHypothetical) + { + ++Entry.StoredRequestToHypotheticalChangedSelectorCount; + StoredRequestToHypotheticalChangedSelectorLabels.Add(SelectorLabel); + } + if (!SelectorDiffEntry.bCurrentMatchesStoredRequest) + { + ++Entry.CurrentToStoredRequestChangedSelectorCount; + CurrentToStoredRequestChangedSelectorLabels.Add(SelectorLabel); + } + + Entry.SelectorDiffEntries.Add(MoveTemp(SelectorDiffEntry)); + } + + Entry.bHasCurrentToHypotheticalDelta = + Entry.CurrentToHypotheticalChangedSelectorCount > 0; + Entry.bHasStoredRequestToHypotheticalDelta = + Entry.StoredRequestToHypotheticalChangedSelectorCount > 0; + Entry.bHasCurrentToStoredRequestDelta = + Entry.CurrentToStoredRequestChangedSelectorCount > 0; + Entry.CurrentToHypotheticalDeltaLine = BuildAggregateDeltaLine( + TEXT("Current -> hypothetical"), + CurrentToHypotheticalChangedSelectorLabels + ); + Entry.StoredRequestToHypotheticalDeltaLine = BuildAggregateDeltaLine( + TEXT("Stored request -> hypothetical"), + StoredRequestToHypotheticalChangedSelectorLabels + ); + Entry.CurrentToStoredRequestDeltaLine = BuildAggregateDeltaLine( + TEXT("Current -> stored request"), + CurrentToStoredRequestChangedSelectorLabels + ); + + if (Entry.bWouldLeaveSelectorsUnresolved) + { + Entry.StoredRequestDeltaKind = + EHyperTwistTrainingCoachGeneratedModeOptionStoredRequestDeltaKind:: + UnresolvedAfterChoice; + ++Surface.UnresolvedAfterChoiceOptionCount; + } + else if (!Entry.bHasCurrentToHypotheticalDelta + && !Entry.bHasStoredRequestToHypotheticalDelta) + { + Entry.StoredRequestDeltaKind = + EHyperTwistTrainingCoachGeneratedModeOptionStoredRequestDeltaKind::NoSelectorDelta; + ++Surface.NoSelectorDeltaOptionCount; + } + else if (Entry.bHasCurrentToHypotheticalDelta + && !Entry.bHasStoredRequestToHypotheticalDelta) + { + Entry.StoredRequestDeltaKind = + EHyperTwistTrainingCoachGeneratedModeOptionStoredRequestDeltaKind:: + CurrentDeltaOnly; + ++Surface.CurrentDeltaOnlyOptionCount; + } + else if (!Entry.bHasCurrentToHypotheticalDelta + && Entry.bHasStoredRequestToHypotheticalDelta) + { + Entry.StoredRequestDeltaKind = + EHyperTwistTrainingCoachGeneratedModeOptionStoredRequestDeltaKind:: + StoredRequestDeltaOnly; + ++Surface.StoredRequestDeltaOnlyOptionCount; + } + else + { + Entry.StoredRequestDeltaKind = + EHyperTwistTrainingCoachGeneratedModeOptionStoredRequestDeltaKind:: + CurrentAndStoredDelta; + ++Surface.CurrentAndStoredDeltaOptionCount; + } + + const FString RequestAlignmentDetail = + MatchingRequestAlignmentEntry == nullptr + || MatchingRequestAlignmentEntry->DetailLine.IsEmpty() + ? TEXT("Request alignment detail: n/a") + : MatchingRequestAlignmentEntry->DetailLine; + const FString StoredRequestAlignmentLine = + MatchingRequestAlignmentEntry == nullptr + || MatchingRequestAlignmentEntry->StoredRequestAlignmentLine.IsEmpty() + ? TEXT("Stored request alignment: n/a") + : MatchingRequestAlignmentEntry->StoredRequestAlignmentLine; + Entry.DetailLine = FString::Printf( + TEXT("%s | %s | %s"), + *Entry.CurrentToStoredRequestDeltaLine, + *StoredRequestAlignmentLine, + *RequestAlignmentDetail + ); + Entry.bCanPreview = true; + + if (Entry.bHasCurrentToHypotheticalDelta) + { + ++CurrentToHypotheticalDeltaOptionCount; + } + if (Entry.bHasStoredRequestToHypotheticalDelta) + { + ++StoredRequestToHypotheticalDeltaOptionCount; + } + if (Entry.bHasCurrentToStoredRequestDelta) + { + ++CurrentToStoredRequestDeltaOptionCount; + } + } + else + { + Entry.StoredRequestDeltaKind = + EHyperTwistTrainingCoachGeneratedModeOptionStoredRequestDeltaKind::Unavailable; + Entry.HypotheticalSelectorSummaryLine = TEXT("Hypothetical selectors: unavailable"); + Entry.CurrentToHypotheticalDeltaLine = TEXT("Current -> hypothetical: unavailable"); + Entry.StoredRequestToHypotheticalDeltaLine = + TEXT("Stored request -> hypothetical: unavailable"); + Entry.CurrentToStoredRequestDeltaLine = TEXT("Current -> stored request: unavailable"); + Entry.UnresolvedSelectorsLine = TEXT("Unresolved after choice: unavailable"); + Entry.ExecutePreviewLine = TEXT("Execute preview: unavailable."); + Entry.DetailLine = + TEXT("Stored-request delta preview is unavailable because this selector option cannot dispatch from the active generated-mode surface."); + Entry.bCanPreview = false; + ++Surface.UnavailableOptionCount; + } + + if (Entry.bMatchesCurrentSelection) + { + Surface.bCurrentSelectionHasCurrentToHypotheticalDelta = + Entry.bHasCurrentToHypotheticalDelta; + Surface.bCurrentSelectionHasStoredRequestToHypotheticalDelta = + Entry.bHasStoredRequestToHypotheticalDelta; + Surface.bCurrentSelectionHasCurrentToStoredRequestDelta = + Entry.bHasCurrentToStoredRequestDelta; + Surface.bCurrentSelectionWouldLeaveSelectorsUnresolved = + Entry.bWouldLeaveSelectorsUnresolved; + Surface.CurrentSelectionDeltaLine = Entry.bStartsOwnedCaseImmediately + ? FString::Printf( + TEXT("%s | %s"), + *Entry.CurrentToHypotheticalDeltaLine, + *Entry.ExecutePreviewLine + ) + : FString::Printf( + TEXT("%s | %s | %s | %s"), + *Entry.CurrentToHypotheticalDeltaLine, + *Entry.StoredRequestToHypotheticalDeltaLine, + *Entry.CurrentToStoredRequestDeltaLine, + Entry.bWouldLeaveSelectorsUnresolved + ? *Entry.UnresolvedSelectorsLine + : *Entry.ExecutePreviewLine + ); + } + + Surface.OptionEntries.Add(MoveTemp(Entry)); + } + + Surface.bHasStoredRequestDeltaOptions = PreviewableGeneratedOptionCount > 0; + Surface.bHasCurrentToHypotheticalDeltaOptions = CurrentToHypotheticalDeltaOptionCount > 0; + Surface.bHasStoredRequestToHypotheticalDeltaOptions = + StoredRequestToHypotheticalDeltaOptionCount > 0; + Surface.bHasCurrentToStoredRequestDeltaOptions = + CurrentToStoredRequestDeltaOptionCount > 0; + Surface.bHasUnresolvedAfterChoiceOptions = Surface.UnresolvedAfterChoiceOptionCount > 0; + + Surface.SelectorDiffSummaryLine = Surface.OptionEntries.Num() > 0 + ? FString::Printf( + TEXT("Selector diff: %d no-delta | %d current-only | %d stored-only | %d current+stored | %d unresolved | %d direct-launch | %d unavailable"), + Surface.NoSelectorDeltaOptionCount, + Surface.CurrentDeltaOnlyOptionCount, + Surface.StoredRequestDeltaOnlyOptionCount, + Surface.CurrentAndStoredDeltaOptionCount, + Surface.UnresolvedAfterChoiceOptionCount, + Surface.DirectOwnedCaseStartOptionCount, + Surface.UnavailableOptionCount + ) + : TEXT("Selector diff: no imported options"); + Surface.CurrentToHypotheticalSummaryLine = PreviewableGeneratedOptionCount > 0 + ? FString::Printf( + TEXT("Current -> hypothetical: %d option%s change at least one selector; %d keep selector state unchanged."), + CurrentToHypotheticalDeltaOptionCount, + CurrentToHypotheticalDeltaOptionCount == 1 ? TEXT("") : TEXT("s"), + PreviewableGeneratedOptionCount - CurrentToHypotheticalDeltaOptionCount + ) + : TEXT("Current -> hypothetical: no previewable generated-choice options"); + Surface.StoredRequestToHypotheticalSummaryLine = PreviewableGeneratedOptionCount > 0 + ? FString::Printf( + TEXT("Stored request -> hypothetical: %d option%s diverge from the stored request baseline; %d stay aligned."), + StoredRequestToHypotheticalDeltaOptionCount, + StoredRequestToHypotheticalDeltaOptionCount == 1 ? TEXT("") : TEXT("s"), + PreviewableGeneratedOptionCount - StoredRequestToHypotheticalDeltaOptionCount + ) + : TEXT("Stored request -> hypothetical: no previewable generated-choice options"); + Surface.CurrentToStoredRequestSummaryLine = PreviewableGeneratedOptionCount > 0 + ? FString::Printf( + TEXT("Current -> stored request: %d option%s retain at least one selector delta from the stored request baseline."), + CurrentToStoredRequestDeltaOptionCount, + CurrentToStoredRequestDeltaOptionCount == 1 ? TEXT("") : TEXT("s") + ) + : TEXT("Current -> stored request: no previewable generated-choice options"); + + if (Surface.CurrentSelectionDeltaLine.IsEmpty()) + { + if (OptionMenuSurface.bHasUnresolvedSelectionDetail) + { + Surface.CurrentSelectionDeltaLine = OptionMenuSurface.UnresolvedSelectionLine; + } + else if (OptionMenuSurface.bHasSelection) + { + Surface.CurrentSelectionDeltaLine = + TEXT("Current selection has no additional stored-request delta preview."); + } + else if (OptionMenuSurface.bHasSelector) + { + Surface.CurrentSelectionDeltaLine = TEXT("Current selection: unresolved"); + } + else + { + Surface.CurrentSelectionDeltaLine = TEXT("Current selection: n/a"); + } + } + + if (OptionMenuSurface.bHasSelector && Surface.OptionEntries.Num() > 0) + { + Surface.StatusLine = FString::Printf( + TEXT("Generated-mode selector option stored-request delta exposes %d no-delta option%s, %d current-delta-only option%s, %d stored-request-delta-only option%s, %d current-and-stored-delta option%s, %d unresolved option%s, %d direct-launch option%s, and %d unavailable option%s."), + Surface.NoSelectorDeltaOptionCount, + Surface.NoSelectorDeltaOptionCount == 1 ? TEXT("") : TEXT("s"), + Surface.CurrentDeltaOnlyOptionCount, + Surface.CurrentDeltaOnlyOptionCount == 1 ? TEXT("") : TEXT("s"), + Surface.StoredRequestDeltaOnlyOptionCount, + Surface.StoredRequestDeltaOnlyOptionCount == 1 ? TEXT("") : TEXT("s"), + Surface.CurrentAndStoredDeltaOptionCount, + Surface.CurrentAndStoredDeltaOptionCount == 1 ? TEXT("") : TEXT("s"), + Surface.UnresolvedAfterChoiceOptionCount, + Surface.UnresolvedAfterChoiceOptionCount == 1 ? TEXT("") : TEXT("s"), + Surface.DirectOwnedCaseStartOptionCount, + Surface.DirectOwnedCaseStartOptionCount == 1 ? TEXT("") : TEXT("s"), + Surface.UnavailableOptionCount, + Surface.UnavailableOptionCount == 1 ? TEXT("") : TEXT("s") + ); + } + else if (OptionMenuSurface.bHasSelector) + { + Surface.StatusLine = + TEXT("Generated-mode selector option stored-request delta is active, but this selector does not expose imported options."); + } + else + { + Surface.StatusLine = OptionMenuSurface.StatusLine.IsEmpty() + ? TEXT("Generated-mode selector option stored-request delta 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("Dispatch: %s | Request alignment: %s | Selector: %s | Binding: %s"), + OptionDispatchSurface.StatusLine.IsEmpty() + ? TEXT("n/a") + : *OptionDispatchSurface.StatusLine, + OptionRequestAlignmentSurface.StatusLine.IsEmpty() + ? TEXT("n/a") + : *OptionRequestAlignmentSurface.StatusLine, + *SelectorSummary, + *BindingSummary + ); + + return Surface; +} diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp index 2a585f3..3793eaa 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp @@ -792,6 +792,27 @@ UHyperTwistTrainingPanelWidget::GetDisplayedCoachGeneratedModeSelectorOptionRequ ); } +FHyperTwistTrainingCoachGeneratedModeSelectorOptionStoredRequestDeltaSurface +UHyperTwistTrainingPanelWidget::GetDisplayedCoachGeneratedModeSelectorOptionStoredRequestDeltaSurface( + const FString& SelectorId +) const +{ + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionMenuSurface OptionMenuSurface = + GetDisplayedCoachGeneratedModeSelectorOptionMenuSurface(SelectorId); + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionDispatchSurface OptionDispatchSurface = + GetDisplayedCoachGeneratedModeSelectorOptionDispatchSurface(SelectorId); + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionRequestAlignmentSurface + OptionRequestAlignmentSurface = + GetDisplayedCoachGeneratedModeSelectorOptionRequestAlignmentSurface(SelectorId); + return UHyperTwistTrainingCoachLibrary::DeriveCoachGeneratedModeSelectorOptionStoredRequestDeltaSurface( + CachedRunState, + SelectorId, + OptionMenuSurface, + OptionDispatchSurface, + OptionRequestAlignmentSurface + ); +} + 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 b582180..5c9cf9e 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp @@ -773,6 +773,27 @@ AHyperTwistTrainingSessionActor::GetDisplayedCoachGeneratedModeSelectorOptionReq ); } +FHyperTwistTrainingCoachGeneratedModeSelectorOptionStoredRequestDeltaSurface +AHyperTwistTrainingSessionActor::GetDisplayedCoachGeneratedModeSelectorOptionStoredRequestDeltaSurface( + const FString& SelectorId +) const +{ + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionMenuSurface OptionMenuSurface = + GetDisplayedCoachGeneratedModeSelectorOptionMenuSurface(SelectorId); + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionDispatchSurface OptionDispatchSurface = + GetDisplayedCoachGeneratedModeSelectorOptionDispatchSurface(SelectorId); + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionRequestAlignmentSurface + OptionRequestAlignmentSurface = + GetDisplayedCoachGeneratedModeSelectorOptionRequestAlignmentSurface(SelectorId); + return UHyperTwistTrainingCoachLibrary::DeriveCoachGeneratedModeSelectorOptionStoredRequestDeltaSurface( + CachedRunState, + SelectorId, + OptionMenuSurface, + OptionDispatchSurface, + OptionRequestAlignmentSurface + ); +} + 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 8e8db77..52b76c8 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h @@ -735,4 +735,15 @@ public: const FHyperTwistTrainingCoachGeneratedModeSelectorOptionExecutionImpactSurface& OptionExecutionImpactSurface ); + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + static FHyperTwistTrainingCoachGeneratedModeSelectorOptionStoredRequestDeltaSurface + DeriveCoachGeneratedModeSelectorOptionStoredRequestDeltaSurface( + const FHyperTwistTrainingRunState& RunState, + const FString& SelectorId, + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionMenuSurface& OptionMenuSurface, + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionDispatchSurface& OptionDispatchSurface, + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionRequestAlignmentSurface& + OptionRequestAlignmentSurface + ); }; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h index 0cf753a..6c03b1f 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h @@ -276,6 +276,10 @@ public: FHyperTwistTrainingCoachGeneratedModeSelectorOptionRequestAlignmentSurface GetDisplayedCoachGeneratedModeSelectorOptionRequestAlignmentSurface(const FString& SelectorId) const; + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingCoachGeneratedModeSelectorOptionStoredRequestDeltaSurface + GetDisplayedCoachGeneratedModeSelectorOptionStoredRequestDeltaSurface(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 9c3109c..f507ba5 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h @@ -257,6 +257,10 @@ public: FHyperTwistTrainingCoachGeneratedModeSelectorOptionRequestAlignmentSurface GetDisplayedCoachGeneratedModeSelectorOptionRequestAlignmentSurface(const FString& SelectorId) const; + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingCoachGeneratedModeSelectorOptionStoredRequestDeltaSurface + GetDisplayedCoachGeneratedModeSelectorOptionStoredRequestDeltaSurface(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 8827699..42e4963 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h @@ -103,6 +103,19 @@ enum class EHyperTwistTrainingCoachGeneratedModeOptionRequestAlignmentKind : uin Unavailable UMETA(DisplayName = "Unavailable") }; +UENUM(BlueprintType) +enum class EHyperTwistTrainingCoachGeneratedModeOptionStoredRequestDeltaKind : uint8 +{ + None UMETA(DisplayName = "None"), + DirectOwnedCaseStart UMETA(DisplayName = "Direct Owned Case Start"), + NoSelectorDelta UMETA(DisplayName = "No Selector Delta"), + CurrentDeltaOnly UMETA(DisplayName = "Current Delta Only"), + StoredRequestDeltaOnly UMETA(DisplayName = "Stored-Request Delta Only"), + CurrentAndStoredDelta UMETA(DisplayName = "Current And Stored Delta"), + UnresolvedAfterChoice UMETA(DisplayName = "Unresolved After Choice"), + Unavailable UMETA(DisplayName = "Unavailable") +}; + UENUM(BlueprintType) enum class EHyperTwistTrainingPromptKind : uint8 { @@ -9229,3 +9242,257 @@ struct FHyperTwistTrainingCoachGeneratedModeSelectorOptionRequestAlignmentSurfac || bHasRequestAlignmentOptions; } }; + +USTRUCT(BlueprintType) +struct FHyperTwistTrainingCoachGeneratedModeSelectorOptionSelectorDiffEntrySurface +{ + 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 CurrentSelectionValue; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString HypotheticalSelectionValue; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString StoredRequestSelectionValue; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString CurrentToHypotheticalDeltaLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString StoredRequestToHypotheticalDeltaLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString CurrentToStoredRequestDeltaLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bIsTargetSelector = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasCurrentSelection = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasHypotheticalSelection = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasStoredRequestSelection = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCurrentMatchesHypothetical = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bStoredRequestMatchesHypothetical = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCurrentMatchesStoredRequest = false; + + bool IsStructurallyValid() const + { + return !SelectorId.IsEmpty() + || !SelectorLabel.IsEmpty() + || !CurrentToHypotheticalDeltaLine.IsEmpty() + || bHasCurrentSelection + || bHasHypotheticalSelection + || bHasStoredRequestSelection; + } +}; + +USTRUCT(BlueprintType) +struct FHyperTwistTrainingCoachGeneratedModeSelectorOptionStoredRequestDeltaEntrySurface +{ + 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") + EHyperTwistTrainingCoachGeneratedModeOptionStoredRequestDeltaKind StoredRequestDeltaKind = + EHyperTwistTrainingCoachGeneratedModeOptionStoredRequestDeltaKind::None; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString HypotheticalSelectorSummaryLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString CurrentToHypotheticalDeltaLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString StoredRequestToHypotheticalDeltaLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString CurrentToStoredRequestDeltaLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString UnresolvedSelectorsLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ExecutePreviewLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString DetailLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray + SelectorDiffEntries; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 CurrentToHypotheticalChangedSelectorCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 StoredRequestToHypotheticalChangedSelectorCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 CurrentToStoredRequestChangedSelectorCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 UnresolvedSelectorCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bMatchesCurrentSelection = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bStartsOwnedCaseImmediately = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasCurrentToHypotheticalDelta = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasStoredRequestToHypotheticalDelta = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasCurrentToStoredRequestDelta = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bWouldLeaveSelectorsUnresolved = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCanPreview = false; + + bool IsStructurallyValid() const + { + return !OptionId.IsEmpty() + || !Label.IsEmpty() + || !HypotheticalSelectorSummaryLine.IsEmpty() + || StoredRequestDeltaKind + != EHyperTwistTrainingCoachGeneratedModeOptionStoredRequestDeltaKind::None + || bCanPreview; + } +}; + +USTRUCT(BlueprintType) +struct FHyperTwistTrainingCoachGeneratedModeSelectorOptionStoredRequestDeltaSurface +{ + 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 SelectorDiffSummaryLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString CurrentToHypotheticalSummaryLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString StoredRequestToHypotheticalSummaryLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString CurrentToStoredRequestSummaryLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString CurrentSelectionDeltaLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString DetailLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray + OptionEntries; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 NoSelectorDeltaOptionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 CurrentDeltaOnlyOptionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 StoredRequestDeltaOnlyOptionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 CurrentAndStoredDeltaOptionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 UnresolvedAfterChoiceOptionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 DirectOwnedCaseStartOptionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 UnavailableOptionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasSelector = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasStoredRequestDeltaOptions = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasCurrentToHypotheticalDeltaOptions = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasStoredRequestToHypotheticalDeltaOptions = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasCurrentToStoredRequestDeltaOptions = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasUnresolvedAfterChoiceOptions = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCurrentSelectionHasCurrentToHypotheticalDelta = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCurrentSelectionHasStoredRequestToHypotheticalDelta = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCurrentSelectionHasCurrentToStoredRequestDelta = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCurrentSelectionWouldLeaveSelectorsUnresolved = false; + + bool IsStructurallyValid() const + { + return !StatusLine.IsEmpty() + || !SelectorId.IsEmpty() + || OptionEntries.Num() > 0 + || bHasSelector + || bHasStoredRequestDeltaOptions; + } +}; diff --git a/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md b/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md index b30ab4b..159d3c1 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 request-alignment / execute-preview surface over the already-cached selector/runtime state, so gameplay/Blueprint callers can render hypothetical selector summary, stored-request alignment, unresolved-selector preview, and execute preview for a chosen option without diffing cached selector choices or launch-request payloads directly - - the current next bounded packet is to expose one thin generated-mode selector option stored-request delta / selector-diff surface on those same two consumer surfaces so gameplay/Blueprint callers can render the exact current-vs-hypothetical-vs-stored selector change set for a chosen option without diffing cached selector choices or launch-request payloads directly + - the latest landed bounded `Phase 4` packet stays on that same gameplay-facing consumer lane and adds one thin generated-mode selector option stored-request delta / selector-diff surface over the already-cached selector/runtime state, so gameplay/Blueprint callers can render the exact current-vs-hypothetical-vs-stored selector change set for a chosen option without diffing cached selector choices or launch-request payloads directly + - the current next bounded packet is to expose one thin generated-mode selector option hypothetical launch-request payload / refresh-preview surface on those same two consumer surfaces so gameplay/Blueprint callers can render the concrete selector-choice payload, stored-request reuse posture, and request-refresh preview for a chosen option without rebuilding launch-config payloads directly - the current execution-discipline rule that broad refactor / monolith-splitting work should not interrupt the active bounded roadmap packet unless structure is actually blocking it ## Current execution-reality references diff --git a/docs/arch/HYPERTWIST_PHASE4_GAMEPLAY_GENERATED_MODE_SELECTOR_OPTION_STORED_REQUEST_DELTA_SELECTOR_DIFF_SURFACE_PACKET_2026-05-08.md b/docs/arch/HYPERTWIST_PHASE4_GAMEPLAY_GENERATED_MODE_SELECTOR_OPTION_STORED_REQUEST_DELTA_SELECTOR_DIFF_SURFACE_PACKET_2026-05-08.md new file mode 100644 index 0000000..cb6a3fb --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE4_GAMEPLAY_GENERATED_MODE_SELECTOR_OPTION_STORED_REQUEST_DELTA_SELECTOR_DIFF_SURFACE_PACKET_2026-05-08.md @@ -0,0 +1,105 @@ +# HyperTwist Phase 4 gameplay generated-mode selector option stored-request delta / selector-diff 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 request-alignment surface by exposing one parameterized option stored-request delta / selector-diff surface for a chosen selector id. + +The open tasks were: + +- stop forcing gameplay / Blueprint callers to diff cached selector choices and stored launch-request payloads just to render the exact selector change set behind a chosen imported option +- expose the exact current-vs-hypothetical-vs-stored selector delta posture for each imported option, plus the current selection’s own delta posture +- keep the surface derived from already-cached gameplay consumer run, selector, and request state plus the already-landed option-detail, dispatch, and request-alignment surfaces instead of widening into backend logic + +It is not: + +- a selector-dispatch behavior change +- a launch-request persistence change +- a generated-mode backend execution change +- a broader gameplay UI composition pass + +## Scope + +Bounded lane: + +- add first-party generated-mode selector option stored-request delta / selector-diff surfaces in shared training types +- add a coach-library helper that derives current-vs-hypothetical-vs-stored selector diffs from cached run state plus the already-landed option-detail, dispatch, and request-alignment surfaces +- expose that parameterized stored-request delta surface on `HyperTwistTrainingPanelWidget` and `HyperTwistTrainingSessionActor` +- keep the derivation presentation-only and rooted in the already-owned selector-choice / launch-request rules + +Out of scope: + +- changing selector-choice apply behavior +- changing direct owned-case launch behavior +- changing stored launch-request creation, refresh, or execute behavior +- widening into higher-level UI composition or dashboard-only work + +## Why this was the right next packet + +Before this slice: + +- gameplay-facing panel/session consumers already exposed truthful generated-mode option request-alignment / execute-preview posture +- those same consumers already exposed truthful generated-mode option dispatch posture + +But one thin consumer seam was still open: + +- callers could tell whether a chosen option stayed aligned with the stored request, required refresh, or left selectors unresolved +- but they still had to diff the concrete selector choices themselves to explain exactly what changed between current, hypothetical, and stored baselines + +That meant: + +- request-alignment posture was thin and reusable +- but exact selector-diff reasoning still leaked cached launch-request and selector-choice comparison work into callers + +So the next bounded move was: + +- keep the already-landed option-detail, dispatch, and request-alignment surfaces unchanged +- and expose one narrow stored-request delta / selector-diff surface above those already-cached surfaces + +## What landed + +Primary code changes: + +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h` + - added `EHyperTwistTrainingCoachGeneratedModeOptionStoredRequestDeltaKind` + - added `FHyperTwistTrainingCoachGeneratedModeSelectorOptionSelectorDiffEntrySurface` + - added `FHyperTwistTrainingCoachGeneratedModeSelectorOptionStoredRequestDeltaEntrySurface` + - added `FHyperTwistTrainingCoachGeneratedModeSelectorOptionStoredRequestDeltaSurface` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp` + - added `DeriveCoachGeneratedModeSelectorOptionStoredRequestDeltaSurface(...)` + - stored-request delta preview is derived from cached run state plus the already-landed option-detail, dispatch, and request-alignment surfaces +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp` + - added `GetDisplayedCoachGeneratedModeSelectorOptionStoredRequestDeltaSurface(const FString& SelectorId)` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp` + - added `GetDisplayedCoachGeneratedModeSelectorOptionStoredRequestDeltaSurface(const FString& SelectorId)` + +Surface behavior: + +- reports the exact current-vs-hypothetical-vs-stored selector change set for each imported option on the chosen selector +- exposes per-selector diff rows with current, hypothetical, and stored request values plus pairwise delta lines +- classifies each option as no-delta, current-delta-only, stored-request-delta-only, current-and-stored delta, unresolved-after-choice, direct-launch, or unavailable +- reports a compact stored-request delta preview for the current cached selection on the chosen selector +- keeps the surface presentation-only and compatible with the already-landed generated-mode option-detail, dispatch, and request-alignment 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 hypothetical launch-request payload / refresh-preview surface so callers can render the concrete selector-choice payload, stored-request reuse posture, and request-refresh preview for a chosen option without rebuilding launch-config payloads directly.