diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp index 280d75a..23a2bd1 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp @@ -10794,3 +10794,419 @@ UHyperTwistTrainingCoachLibrary::DeriveCoachGeneratedModeSelectorOptionLaunchReq return Surface; } + +FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestEnvelopePreviewSurface +UHyperTwistTrainingCoachLibrary::DeriveCoachGeneratedModeSelectorOptionLaunchRequestEnvelopePreviewSurface( + const FHyperTwistTrainingRunState& RunState, + const FString& SelectorId, + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionMenuSurface& OptionMenuSurface, + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface& + OptionLaunchRequestPreviewSurface +) +{ + FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestEnvelopePreviewSurface Surface; + Surface.SelectorId = SelectorId; + Surface.SelectorLabel = OptionMenuSurface.SelectorLabel; + Surface.BindingLabel = OptionMenuSurface.BindingLabel; + Surface.Headline = TEXT("Generated Mode Selector Option Launch-Request Envelope Preview"); + Surface.bHasSelector = OptionMenuSurface.bHasSelector; + + if (!SelectorId.IsEmpty()) + { + Surface.Headline = OptionMenuSurface.SelectorLabel.IsEmpty() + ? FString::Printf( + TEXT("Generated Mode Selector Option Launch-Request Envelope Preview: %s"), + *SelectorId + ) + : FString::Printf( + TEXT("Generated Mode Selector Option Launch-Request Envelope Preview: %s"), + *OptionMenuSurface.SelectorLabel + ); + } + + if (SelectorId.IsEmpty()) + { + Surface.StatusLine = + TEXT("Generated-mode selector option launch-request envelope preview requires a selector id from the active selector roster."); + Surface.EnvelopeSummaryLine = TEXT("Stored request envelope: n/a"); + Surface.RequestSourceSummaryLine = TEXT("Request source preview: n/a"); + Surface.PersistenceSummaryLine = TEXT("Persistence preview: n/a"); + Surface.CurrentSelectionEnvelopeLine = 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 FHyperTwistTrainingImportedGeneratedModeLaunchRequest& LaunchRequest = + RunState.ImportedGeneratedModeLaunchRequest; + const bool bHasStoredRequestEnvelope = LaunchRequest.IsStructurallyValid(); + const bool bHasStoredRequestForActiveSurface = bHasStoredRequestEnvelope + && LaunchRequest.LaunchConfig.DeckId == RunState.ActiveDeck.DeckId + && LaunchRequest.LaunchConfig.SurfaceId == RuntimeSurface.SurfaceId + && LaunchRequest.LaunchConfig.RuntimeModeId.Equals( + RuntimeSurface.RuntimeModeId, + ESearchCase::IgnoreCase + ); + const FString ExecuteRequestSource = TEXT("runtime-imported-generated-mode-executor"); + + const auto BuildStoredEnvelopeLine = []( + const FString& RequestId, + const FString& RequestedAtUtc, + const FString& RequestSource + ) -> FString + { + if (RequestId.IsEmpty() && RequestedAtUtc.IsEmpty() && RequestSource.IsEmpty()) + { + return TEXT("Stored request envelope: none"); + } + + return FString::Printf( + TEXT("Stored request envelope: %s @ %s | source: %s"), + RequestId.IsEmpty() ? TEXT("n/a") : *RequestId, + RequestedAtUtc.IsEmpty() ? TEXT("n/a") : *RequestedAtUtc, + RequestSource.IsEmpty() ? TEXT("n/a") : *RequestSource + ); + }; + + if (!RunState.IsStructurallyValid() || !bHasGeneratedModeSurface) + { + Surface.StatusLine = OptionMenuSurface.StatusLine.IsEmpty() + ? TEXT("Generated-mode selector option launch-request envelope preview is not currently available.") + : OptionMenuSurface.StatusLine; + Surface.EnvelopeSummaryLine = TEXT("Stored request envelope: n/a"); + Surface.RequestSourceSummaryLine = TEXT("Request source preview: n/a"); + Surface.PersistenceSummaryLine = TEXT("Persistence preview: n/a"); + Surface.CurrentSelectionEnvelopeLine = OptionMenuSurface.bHasUnresolvedSelectionDetail + ? OptionMenuSurface.UnresolvedSelectionLine + : (OptionMenuSurface.bHasSelection + ? TEXT("Current selection remains cached, but no active launch-request envelope preview is available.") + : TEXT("Current selection: unresolved")); + Surface.DetailLine = OptionLaunchRequestPreviewSurface.StatusLine.IsEmpty() + ? TEXT("Launch-request preview: n/a") + : FString::Printf( + TEXT("Launch-request preview: %s"), + *OptionLaunchRequestPreviewSurface.StatusLine + ); + return Surface; + } + + for (const FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestPreviewEntrySurface& + PreviewEntry : OptionLaunchRequestPreviewSurface.OptionEntries) + { + FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestEnvelopePreviewEntrySurface + Entry; + Entry.OptionId = PreviewEntry.OptionId; + Entry.Label = PreviewEntry.Label; + Entry.OptionValue = PreviewEntry.OptionValue; + Entry.LaunchRequestPreviewKind = PreviewEntry.LaunchRequestPreviewKind; + Entry.CurrentStoredRequestId = LaunchRequest.RequestId; + Entry.CurrentStoredRequestedAtUtc = LaunchRequest.RequestedAtUtc; + Entry.CurrentStoredRequestSource = LaunchRequest.RequestSource; + Entry.ExecuteRequestSource = ExecuteRequestSource; + Entry.bMatchesCurrentSelection = PreviewEntry.bMatchesCurrentSelection; + Entry.bStartsOwnedCaseImmediately = PreviewEntry.bStartsOwnedCaseImmediately; + Entry.bHasStoredRequestEnvelope = bHasStoredRequestEnvelope; + Entry.StoredRequestEnvelopeLine = BuildStoredEnvelopeLine( + LaunchRequest.RequestId, + LaunchRequest.RequestedAtUtc, + LaunchRequest.RequestSource + ); + + switch (PreviewEntry.LaunchRequestPreviewKind) + { + case EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind:: + DirectOwnedCaseStart: + Entry.RequestIdLine = + TEXT("Request id: direct owned-case launch bypasses generated-mode launch-request staging."); + Entry.RequestedAtLine = + TEXT("Requested at: direct owned-case launch bypasses generated-mode launch-request staging."); + Entry.RequestSourceLine = + TEXT("Request source: direct owned-case launch bypasses generated-mode launch-request staging."); + Entry.PersistencePreviewLine = + TEXT("Persistence: execute would not create or reuse a generated-mode stored request envelope for this option."); + Entry.DetailLine = PreviewEntry.DetailLine.IsEmpty() + ? TEXT("Launch-request envelope preview is not applicable because this option starts a direct owned case instead of staging a generated-mode payload.") + : PreviewEntry.DetailLine; + Entry.bCanPreview = true; + ++Surface.DirectOwnedCaseStartOptionCount; + break; + + case EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind:: + StoredRequestReusable: + Entry.RequestIdLine = FString::Printf( + TEXT("Request id: reuse %s"), + LaunchRequest.RequestId.IsEmpty() ? TEXT("n/a") : *LaunchRequest.RequestId + ); + Entry.RequestedAtLine = FString::Printf( + TEXT("Requested at: reuse %s"), + LaunchRequest.RequestedAtUtc.IsEmpty() ? TEXT("n/a") : *LaunchRequest.RequestedAtUtc + ); + Entry.RequestSourceLine = FString::Printf( + TEXT("Request source: reuse %s"), + LaunchRequest.RequestSource.IsEmpty() ? TEXT("n/a") : *LaunchRequest.RequestSource + ); + Entry.PersistencePreviewLine = + TEXT("Persistence: execute reuses the current stored request envelope without generating a new request id or timestamp."); + Entry.bWouldReuseStoredRequestEnvelope = true; + Entry.bCanPreview = true; + ++Surface.StoredRequestReusableOptionCount; + break; + + case EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind:: + RefreshRequired: + Entry.RequestIdLine = + TEXT("Request id on execute: generated at execute time"); + Entry.RequestedAtLine = + TEXT("Requested at on execute: generated at execute time"); + Entry.RequestSourceLine = FString::Printf( + TEXT("Request source on execute: %s"), + *ExecuteRequestSource + ); + Entry.bCanPreview = true; + if (!bHasStoredRequestEnvelope) + { + Entry.PersistencePreviewLine = + TEXT("Persistence: execute would persist the first stored request envelope for this generated-mode payload."); + Entry.bWouldPersistFirstStoredRequestEnvelope = true; + ++Surface.FirstPersistOptionCount; + } + else if (bHasStoredRequestForActiveSurface) + { + Entry.PersistencePreviewLine = + TEXT("Persistence: execute would refresh the current stored request envelope for this active generated-mode surface."); + Entry.bWouldRefreshStoredRequestEnvelope = true; + ++Surface.RefreshExistingOptionCount; + } + else + { + Entry.PersistencePreviewLine = + TEXT("Persistence: execute would replace the currently persisted stored request envelope with one for this generated-mode payload."); + Entry.bWouldReplaceStoredRequestEnvelope = true; + ++Surface.ReplaceStoredRequestOptionCount; + } + break; + + case EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind:: + UnresolvedAfterChoice: + Entry.RequestIdLine = bHasStoredRequestEnvelope + ? FString::Printf( + TEXT("Current request id: %s"), + LaunchRequest.RequestId.IsEmpty() ? TEXT("n/a") : *LaunchRequest.RequestId + ) + : TEXT("Current request id: none"); + Entry.RequestedAtLine = bHasStoredRequestEnvelope + ? FString::Printf( + TEXT("Current requested-at: %s"), + LaunchRequest.RequestedAtUtc.IsEmpty() ? TEXT("n/a") : *LaunchRequest.RequestedAtUtc + ) + : TEXT("Current requested-at: none"); + Entry.RequestSourceLine = bHasStoredRequestEnvelope + ? FString::Printf( + TEXT("Current request source: %s"), + LaunchRequest.RequestSource.IsEmpty() ? TEXT("n/a") : *LaunchRequest.RequestSource + ) + : FString::Printf( + TEXT("Request source on execute: %s (after selectors are resolved)"), + *ExecuteRequestSource + ); + Entry.PersistencePreviewLine = + TEXT("Persistence: execute cannot create or refresh a stored request envelope until every required selector is resolved."); + Entry.bPersistencePreviewBlocked = true; + Entry.bCanPreview = true; + ++Surface.PersistenceBlockedOptionCount; + break; + + case EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind:: + PayloadUnavailable: + Entry.RequestIdLine = bHasStoredRequestEnvelope + ? FString::Printf( + TEXT("Current request id: %s"), + LaunchRequest.RequestId.IsEmpty() ? TEXT("n/a") : *LaunchRequest.RequestId + ) + : TEXT("Current request id: none"); + Entry.RequestedAtLine = bHasStoredRequestEnvelope + ? FString::Printf( + TEXT("Current requested-at: %s"), + LaunchRequest.RequestedAtUtc.IsEmpty() ? TEXT("n/a") : *LaunchRequest.RequestedAtUtc + ) + : TEXT("Current requested-at: none"); + Entry.RequestSourceLine = bHasStoredRequestEnvelope + ? FString::Printf( + TEXT("Current request source: %s"), + LaunchRequest.RequestSource.IsEmpty() ? TEXT("n/a") : *LaunchRequest.RequestSource + ) + : FString::Printf( + TEXT("Request source on execute: %s (once payload becomes valid)"), + *ExecuteRequestSource + ); + Entry.PersistencePreviewLine = + TEXT("Persistence: execute cannot create or refresh a stored request envelope until a structurally valid launch payload can be derived."); + Entry.bPersistencePreviewBlocked = true; + Entry.bCanPreview = true; + ++Surface.PersistenceBlockedOptionCount; + break; + + case EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind::Unavailable: + case EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind::None: + default: + Entry.StoredRequestEnvelopeLine = TEXT("Stored request envelope: unavailable"); + Entry.RequestIdLine = TEXT("Request id: unavailable"); + Entry.RequestedAtLine = TEXT("Requested at: unavailable"); + Entry.RequestSourceLine = TEXT("Request source: unavailable"); + Entry.PersistencePreviewLine = TEXT("Persistence: unavailable"); + Entry.DetailLine = + TEXT("Launch-request envelope preview is unavailable because this selector option has no generated-mode payload preview from the active surface."); + Entry.bCanPreview = false; + ++Surface.UnavailableOptionCount; + break; + } + + if (Entry.DetailLine.IsEmpty()) + { + Entry.DetailLine = PreviewEntry.DetailLine.IsEmpty() + ? Entry.PersistencePreviewLine + : FString::Printf( + TEXT("%s | %s"), + *Entry.PersistencePreviewLine, + *PreviewEntry.DetailLine + ); + } + + if (Entry.bMatchesCurrentSelection) + { + Surface.bCurrentSelectionHasStoredRequestEnvelope = Entry.bHasStoredRequestEnvelope; + Surface.bCurrentSelectionWouldReuseStoredRequestEnvelope = + Entry.bWouldReuseStoredRequestEnvelope; + Surface.bCurrentSelectionWouldPersistFirstStoredRequestEnvelope = + Entry.bWouldPersistFirstStoredRequestEnvelope; + Surface.bCurrentSelectionWouldRefreshStoredRequestEnvelope = + Entry.bWouldRefreshStoredRequestEnvelope; + Surface.bCurrentSelectionWouldReplaceStoredRequestEnvelope = + Entry.bWouldReplaceStoredRequestEnvelope; + Surface.CurrentSelectionEnvelopeLine = FString::Printf( + TEXT("%s | %s"), + *Entry.StoredRequestEnvelopeLine, + *Entry.PersistencePreviewLine + ); + } + + Surface.OptionEntries.Add(MoveTemp(Entry)); + } + + Surface.bHasEnvelopePreviewOptions = Surface.OptionEntries.Num() > 0; + Surface.bHasStoredRequestReusableOptions = Surface.StoredRequestReusableOptionCount > 0; + Surface.bHasFirstPersistOptions = Surface.FirstPersistOptionCount > 0; + Surface.bHasRefreshExistingOptions = Surface.RefreshExistingOptionCount > 0; + Surface.bHasReplaceStoredRequestOptions = Surface.ReplaceStoredRequestOptionCount > 0; + Surface.bHasPersistenceBlockedOptions = Surface.PersistenceBlockedOptionCount > 0; + + Surface.EnvelopeSummaryLine = Surface.OptionEntries.Num() > 0 + ? FString::Printf( + TEXT("Stored request envelope: %d reusable | %d first-persist | %d refresh | %d replace | %d blocked | %d direct-launch | %d unavailable"), + Surface.StoredRequestReusableOptionCount, + Surface.FirstPersistOptionCount, + Surface.RefreshExistingOptionCount, + Surface.ReplaceStoredRequestOptionCount, + Surface.PersistenceBlockedOptionCount, + Surface.DirectOwnedCaseStartOptionCount, + Surface.UnavailableOptionCount + ) + : TEXT("Stored request envelope: no imported options"); + Surface.RequestSourceSummaryLine = Surface.OptionEntries.Num() > 0 + ? FString::Printf( + TEXT("Request source preview: stored=%s | execute-refresh source=%s"), + bHasStoredRequestEnvelope + ? (LaunchRequest.RequestSource.IsEmpty() ? TEXT("n/a") : *LaunchRequest.RequestSource) + : TEXT("none"), + *ExecuteRequestSource + ) + : TEXT("Request source preview: n/a"); + Surface.PersistenceSummaryLine = Surface.OptionEntries.Num() > 0 + ? FString::Printf( + TEXT("Persistence preview: %d option%s reuse the current envelope; %d create first envelope; %d refresh existing; %d replace stored; %d remain blocked."), + Surface.StoredRequestReusableOptionCount, + Surface.StoredRequestReusableOptionCount == 1 ? TEXT("") : TEXT("s"), + Surface.FirstPersistOptionCount, + Surface.RefreshExistingOptionCount, + Surface.ReplaceStoredRequestOptionCount, + Surface.PersistenceBlockedOptionCount + ) + : TEXT("Persistence preview: no imported options"); + + if (Surface.CurrentSelectionEnvelopeLine.IsEmpty()) + { + if (OptionMenuSurface.bHasUnresolvedSelectionDetail) + { + Surface.CurrentSelectionEnvelopeLine = OptionMenuSurface.UnresolvedSelectionLine; + } + else if (OptionMenuSurface.bHasSelection) + { + Surface.CurrentSelectionEnvelopeLine = + TEXT("Current selection has no additional launch-request envelope preview."); + } + else if (OptionMenuSurface.bHasSelector) + { + Surface.CurrentSelectionEnvelopeLine = TEXT("Current selection: unresolved"); + } + else + { + Surface.CurrentSelectionEnvelopeLine = TEXT("Current selection: n/a"); + } + } + + if (OptionMenuSurface.bHasSelector && Surface.OptionEntries.Num() > 0) + { + Surface.StatusLine = FString::Printf( + TEXT("Generated-mode selector option launch-request envelope preview exposes %d reusable option%s, %d first-persist option%s, %d refresh option%s, %d replace option%s, %d blocked option%s, %d direct-launch option%s, and %d unavailable option%s."), + Surface.StoredRequestReusableOptionCount, + Surface.StoredRequestReusableOptionCount == 1 ? TEXT("") : TEXT("s"), + Surface.FirstPersistOptionCount, + Surface.FirstPersistOptionCount == 1 ? TEXT("") : TEXT("s"), + Surface.RefreshExistingOptionCount, + Surface.RefreshExistingOptionCount == 1 ? TEXT("") : TEXT("s"), + Surface.ReplaceStoredRequestOptionCount, + Surface.ReplaceStoredRequestOptionCount == 1 ? TEXT("") : TEXT("s"), + Surface.PersistenceBlockedOptionCount, + Surface.PersistenceBlockedOptionCount == 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 launch-request envelope preview is active, but this selector does not expose imported options."); + } + else + { + Surface.StatusLine = OptionMenuSurface.StatusLine.IsEmpty() + ? TEXT("Generated-mode selector option launch-request envelope 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 06d9d6b..181bbe5 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp @@ -853,6 +853,25 @@ UHyperTwistTrainingPanelWidget::GetDisplayedCoachGeneratedModeSelectorOptionLaun ); } +FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestEnvelopePreviewSurface +UHyperTwistTrainingPanelWidget::GetDisplayedCoachGeneratedModeSelectorOptionLaunchRequestEnvelopePreviewSurface( + const FString& SelectorId +) const +{ + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionMenuSurface OptionMenuSurface = + GetDisplayedCoachGeneratedModeSelectorOptionMenuSurface(SelectorId); + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface + OptionLaunchRequestPreviewSurface = + GetDisplayedCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface(SelectorId); + return UHyperTwistTrainingCoachLibrary:: + DeriveCoachGeneratedModeSelectorOptionLaunchRequestEnvelopePreviewSurface( + 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 5e62f6d..13e3149 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp @@ -834,6 +834,25 @@ AHyperTwistTrainingSessionActor::GetDisplayedCoachGeneratedModeSelectorOptionLau ); } +FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestEnvelopePreviewSurface +AHyperTwistTrainingSessionActor::GetDisplayedCoachGeneratedModeSelectorOptionLaunchRequestEnvelopePreviewSurface( + const FString& SelectorId +) const +{ + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionMenuSurface OptionMenuSurface = + GetDisplayedCoachGeneratedModeSelectorOptionMenuSurface(SelectorId); + const FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface + OptionLaunchRequestPreviewSurface = + GetDisplayedCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface(SelectorId); + return UHyperTwistTrainingCoachLibrary:: + DeriveCoachGeneratedModeSelectorOptionLaunchRequestEnvelopePreviewSurface( + 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 5fad70d..932281f 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h @@ -767,4 +767,14 @@ public: const FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestPreviewSurface& OptionLaunchRequestPreviewSurface ); + + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + static FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestEnvelopePreviewSurface + DeriveCoachGeneratedModeSelectorOptionLaunchRequestEnvelopePreviewSurface( + 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 0ccff7f..b27d1c0 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h @@ -290,6 +290,12 @@ public: const FString& SelectorId ) const; + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestEnvelopePreviewSurface + GetDisplayedCoachGeneratedModeSelectorOptionLaunchRequestEnvelopePreviewSurface( + 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 046fef5..0514864 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h @@ -271,6 +271,12 @@ public: const FString& SelectorId ) const; + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach") + FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestEnvelopePreviewSurface + GetDisplayedCoachGeneratedModeSelectorOptionLaunchRequestEnvelopePreviewSurface( + 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 746b88a..56a967f 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h @@ -9925,3 +9925,195 @@ struct FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestMetadataP || bHasMetadataPreviewOptions; } }; + +USTRUCT(BlueprintType) +struct FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestEnvelopePreviewEntrySurface +{ + 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 StoredRequestEnvelopeLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString RequestIdLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString RequestedAtLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString RequestSourceLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString PersistencePreviewLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString DetailLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString CurrentStoredRequestId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString CurrentStoredRequestedAtUtc; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString CurrentStoredRequestSource; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ExecuteRequestSource; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bMatchesCurrentSelection = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bStartsOwnedCaseImmediately = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasStoredRequestEnvelope = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bWouldReuseStoredRequestEnvelope = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bWouldPersistFirstStoredRequestEnvelope = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bWouldRefreshStoredRequestEnvelope = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bWouldReplaceStoredRequestEnvelope = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bPersistencePreviewBlocked = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCanPreview = false; + + bool IsStructurallyValid() const + { + return !OptionId.IsEmpty() + || !Label.IsEmpty() + || !StoredRequestEnvelopeLine.IsEmpty() + || LaunchRequestPreviewKind + != EHyperTwistTrainingCoachGeneratedModeOptionLaunchRequestPreviewKind::None + || bCanPreview; + } +}; + +USTRUCT(BlueprintType) +struct FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestEnvelopePreviewSurface +{ + 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 EnvelopeSummaryLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString RequestSourceSummaryLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString PersistenceSummaryLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString CurrentSelectionEnvelopeLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString DetailLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray + OptionEntries; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 StoredRequestReusableOptionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 FirstPersistOptionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 RefreshExistingOptionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 ReplaceStoredRequestOptionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 PersistenceBlockedOptionCount = 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 bHasEnvelopePreviewOptions = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasStoredRequestReusableOptions = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasFirstPersistOptions = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasRefreshExistingOptions = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasReplaceStoredRequestOptions = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasPersistenceBlockedOptions = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCurrentSelectionHasStoredRequestEnvelope = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCurrentSelectionWouldReuseStoredRequestEnvelope = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCurrentSelectionWouldPersistFirstStoredRequestEnvelope = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCurrentSelectionWouldRefreshStoredRequestEnvelope = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCurrentSelectionWouldReplaceStoredRequestEnvelope = false; + + bool IsStructurallyValid() const + { + return !StatusLine.IsEmpty() + || !SelectorId.IsEmpty() + || OptionEntries.Num() > 0 + || bHasSelector + || bHasEnvelopePreviewOptions; + } +}; diff --git a/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md b/docs/HYPERTWIST_ROADMAP_OVERHAUL_EXPANSION_GUIDE.md index f01c212..3455dfd 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 hypothetical launch-request metadata / origin-notes preview surface over the already-cached selector/runtime state, so gameplay/Blueprint callers can render imported origin-path and notes provenance over that hypothetical payload without reading raw launch-config metadata directly - - the current next bounded packet is to expose one thin generated-mode selector option stored launch-request envelope / request-source preview surface on those same two consumer surfaces so gameplay/Blueprint callers can render request id, request source, requested-at timestamp, and reuse-vs-refresh persistence context for a chosen option without reading raw launch-request envelope fields directly + - the latest landed bounded `Phase 4` packet stays on that same gameplay-facing consumer lane and adds one thin generated-mode selector option stored launch-request envelope / request-source preview surface over the already-cached selector/runtime state, so gameplay/Blueprint callers can render request id, request source, requested-at timestamp, and reuse-vs-refresh persistence context for a chosen option without reading raw launch-request envelope fields directly + - the current next bounded packet is to expose one thin generated-mode selector option owned execution case-identity / canonical-state preview surface on those same two consumer surfaces so gameplay/Blueprint callers can render the resulting generated case id and canonical-state ref for a chosen option without rebuilding executor naming rules directly - the 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_LAUNCH_REQUEST_ENVELOPE_REQUEST_SOURCE_PREVIEW_SURFACE_PACKET_2026-05-08.md b/docs/arch/HYPERTWIST_PHASE4_GAMEPLAY_GENERATED_MODE_SELECTOR_OPTION_LAUNCH_REQUEST_ENVELOPE_REQUEST_SOURCE_PREVIEW_SURFACE_PACKET_2026-05-08.md new file mode 100644 index 0000000..2ee5cdd --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE4_GAMEPLAY_GENERATED_MODE_SELECTOR_OPTION_LAUNCH_REQUEST_ENVELOPE_REQUEST_SOURCE_PREVIEW_SURFACE_PACKET_2026-05-08.md @@ -0,0 +1,103 @@ +# HyperTwist Phase 4 gameplay generated-mode selector option launch-request envelope / request-source preview surface packet + +Created on `2026-05-08` + +Status: + +- first-party HyperTwist packet +- bounded Phase `4` gameplay-facing consumer slice + +## Purpose + +This packet closes the next thin gameplay-facing consumer seam above the panel-widget and session-actor generated-mode selector option launch-request metadata / origin-notes preview surface by exposing one parameterized option launch-request envelope / request-source preview surface for a chosen selector id. + +The open tasks were: + +- stop forcing gameplay / Blueprint callers to read raw stored launch-request envelope fields just to explain request reuse, refresh, or first-persist posture for a chosen imported option +- expose request id, request source, requested-at timestamp, and reuse-vs-refresh persistence context for each imported option on the chosen selector +- keep the surface derived from already-cached gameplay consumer run, selector, stored request, and launch-request preview state instead of widening into backend logic + +It is not: + +- a selector-dispatch behavior change +- a stored launch-request persistence behavior change +- a generated-mode executor behavior change +- a broader gameplay UI composition pass + +## Scope + +Bounded lane: + +- add first-party generated-mode selector option launch-request envelope / request-source preview surfaces in shared training types +- add a coach-library helper that projects stored request id, requested-at timestamp, request source, and persistence posture over the already-landed launch-request preview surface +- expose that parameterized envelope preview surface on `HyperTwistTrainingPanelWidget` and `HyperTwistTrainingSessionActor` +- keep the derivation presentation-only and rooted in the already-owned selector/runtime/request surfaces + +Out of scope: + +- changing selector-choice apply behavior +- changing stored launch-request creation, refresh, or execute behavior +- changing generated-mode execution deck synthesis +- widening into dashboard-only or broader gameplay composition work + +## Why this was the right next packet + +Before this slice: + +- gameplay-facing panel/session consumers already exposed truthful hypothetical payload, metadata provenance, and refresh posture for imported selector options +- those same consumers already exposed truthful request-alignment and stored-request delta posture + +But one thin consumer seam was still open: + +- callers could tell whether a chosen option reused the stored request or required refresh +- but they still had to read raw stored launch-request envelope fields to explain which request id, timestamp, and source would stay in play or be replaced + +That meant: + +- launch payload and persistence posture were thin and reusable +- but stored envelope identity still leaked raw request-envelope access into callers + +So the next bounded move was: + +- keep the already-landed payload / refresh-preview and metadata / origin-notes preview surfaces unchanged +- and expose one narrow stored launch-request envelope / request-source preview surface above those cached launch-request semantics + +## What landed + +Primary code changes: + +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h` + - added `FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestEnvelopePreviewEntrySurface` + - added `FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestEnvelopePreviewSurface` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp` + - added `DeriveCoachGeneratedModeSelectorOptionLaunchRequestEnvelopePreviewSurface(...)` + - envelope preview is derived from cached run state plus the already-landed launch-request preview surface and stored request envelope fields +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp` + - added `GetDisplayedCoachGeneratedModeSelectorOptionLaunchRequestEnvelopePreviewSurface(const FString& SelectorId)` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp` + - added `GetDisplayedCoachGeneratedModeSelectorOptionLaunchRequestEnvelopePreviewSurface(const FString& SelectorId)` + +Surface behavior: + +- reports the current stored request envelope for each imported option on the chosen selector, including request id, requested-at timestamp, and request source when one already exists +- distinguishes reuse, first-persist, refresh-existing, replace-stored, blocked, direct-launch, and unavailable persistence posture without forcing callers to infer it from raw request-envelope state +- projects the execute-time request source used when a fresh stored request would be created or refreshed +- keeps blocked unresolved/payload-unavailable options inspectable instead of collapsing envelope state to an unhelpful 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, and launch-request metadata / origin-notes 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 case-identity / canonical-state preview surface so callers can render the resulting generated case id and canonical-state ref for a chosen option without rebuilding executor naming rules directly.