Expose gameplay selector launch-request envelope preview surface

This commit is contained in:
axiomlogicnexus 2026-05-08 14:39:24 +02:00
parent 4ce2d0eaa9
commit 46f649ca50
9 changed files with 773 additions and 2 deletions

View file

@ -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;
}

View file

@ -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

View file

@ -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
)

View file

@ -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
);
};

View file

@ -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,

View file

@ -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);

View file

@ -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<FHyperTwistTrainingCoachGeneratedModeSelectorOptionLaunchRequestEnvelopePreviewEntrySurface>
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;
}
};

View file

@ -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

View file

@ -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.