Expose gameplay generated-mode selector context surface

This commit is contained in:
axiomlogicnexus 2026-05-08 01:56:29 +02:00
parent 6b418f78b1
commit a8be3a2106
9 changed files with 433 additions and 2 deletions

View file

@ -6809,3 +6809,200 @@ UHyperTwistTrainingCoachLibrary::DeriveCoachGeneratedModeLaunchExecutionContextS
return Surface;
}
FHyperTwistTrainingCoachGeneratedModeSelectorApplicationContextSurface
UHyperTwistTrainingCoachLibrary::DeriveCoachGeneratedModeSelectorApplicationContextSurface(
const FHyperTwistTrainingDeck& ActiveDeck,
const FHyperTwistTrainingImportedRuntimeSelectionState& ImportedRuntimeSelectionState,
const FHyperTwistTrainingImportedGeneratedModeLaunchConfig& LaunchConfig,
const FHyperTwistTrainingCoachGeneratedModeLaunchExecutionContextSurface& LaunchExecutionContextSurface
)
{
FHyperTwistTrainingCoachGeneratedModeSelectorApplicationContextSurface Surface;
Surface.Headline = TEXT("Generated Mode Selectors");
const FHyperTwistTrainingImportedRuntimeSurface& RuntimeSurface = ActiveDeck.ImportedRuntimeSurface;
Surface.bHasGeneratedModeSurface = RuntimeSurface.IsStructurallyValid()
&& RuntimeSurface.SurfaceKind.Equals(TEXT("generated-mode"), ESearchCase::IgnoreCase);
Surface.bHasSelectionState = ImportedRuntimeSelectionState.IsStructurallyValid();
Surface.bHasLaunchConfig = LaunchConfig.IsStructurallyValid();
Surface.bWillRefreshLaunchRequestOnExecute =
LaunchExecutionContextSurface.bWillRefreshLaunchRequestOnExecute;
Surface.bSelectionStateMatchesSurface = !Surface.bHasGeneratedModeSurface
|| !Surface.bHasSelectionState
|| ImportedRuntimeSelectionState.SurfaceId == RuntimeSurface.SurfaceId;
Surface.SurfaceId = Surface.bHasLaunchConfig
? LaunchConfig.SurfaceId
: (Surface.bHasGeneratedModeSurface
? RuntimeSurface.SurfaceId
: ImportedRuntimeSelectionState.SurfaceId);
Surface.SurfaceLabel = Surface.bHasLaunchConfig
? LaunchConfig.SurfaceLabel
: (Surface.bHasGeneratedModeSurface ? RuntimeSurface.SurfaceLabel : FString());
Surface.SurfaceKind = Surface.bHasGeneratedModeSurface
? RuntimeSurface.SurfaceKind
: ImportedRuntimeSelectionState.SurfaceKind;
Surface.RuntimeModeId = Surface.bHasLaunchConfig
? LaunchConfig.RuntimeModeId
: (Surface.bHasGeneratedModeSurface ? RuntimeSurface.RuntimeModeId : FString());
Surface.GeneratorType = Surface.bHasLaunchConfig
? LaunchConfig.GeneratorType
: (Surface.bHasGeneratedModeSurface ? RuntimeSurface.GeneratorType : FString());
TSet<FString> RequiredSelectorIds;
if (Surface.bHasGeneratedModeSurface)
{
for (const FHyperTwistTrainingImportedRuntimeSelector& Selector : RuntimeSurface.Selectors)
{
if (Selector.IsStructurallyValid())
{
RequiredSelectorIds.Add(Selector.SelectorId);
}
}
}
Surface.RequiredSelectorCount = RequiredSelectorIds.Num();
TSet<FString> SelectedSelectorIds;
if (Surface.bHasSelectionState)
{
for (const FHyperTwistTrainingImportedRuntimeSelectorChoice& Choice :
ImportedRuntimeSelectionState.SelectorChoices)
{
if (!Choice.IsStructurallyValid())
{
continue;
}
if (RequiredSelectorIds.Num() > 0 && !RequiredSelectorIds.Contains(Choice.SelectorId))
{
continue;
}
SelectedSelectorIds.Add(Choice.SelectorId);
}
}
Surface.SelectedSelectorCount = SelectedSelectorIds.Num();
Surface.MissingSelectorCount = FMath::Max(
Surface.RequiredSelectorCount - Surface.SelectedSelectorCount,
0
);
Surface.bHasFullSelectorCoverage = Surface.bHasLaunchConfig
|| (Surface.bHasSelectionState
&& Surface.bSelectionStateMatchesSurface
&& Surface.RequiredSelectorCount > 0
&& Surface.MissingSelectorCount == 0);
const FString SurfaceLabelSummary = Surface.SurfaceLabel.IsEmpty()
? (Surface.SurfaceId.IsEmpty() ? TEXT("n/a") : Surface.SurfaceId)
: Surface.SurfaceLabel;
const FString SurfaceIdSummary = Surface.SurfaceId.IsEmpty() ? TEXT("n/a") : Surface.SurfaceId;
const FString SurfaceKindSummary = Surface.SurfaceKind.IsEmpty() ? TEXT("n/a") : Surface.SurfaceKind;
const FString RuntimeModeSummary = Surface.RuntimeModeId.IsEmpty() ? TEXT("n/a") : Surface.RuntimeModeId;
const FString GeneratorSummary = Surface.GeneratorType.IsEmpty() ? TEXT("n/a") : Surface.GeneratorType;
const FString CoverageSuffix = Surface.MissingSelectorCount > 0
? FString::Printf(TEXT(" | missing: %d"), Surface.MissingSelectorCount)
: FString();
if (Surface.RequiredSelectorCount > 0)
{
Surface.CoverageLine = FString::Printf(
TEXT("Coverage: %d/%d selectors selected%s"),
Surface.SelectedSelectorCount,
Surface.RequiredSelectorCount,
*CoverageSuffix
);
}
else if (Surface.bHasSelectionState)
{
Surface.CoverageLine = FString::Printf(
TEXT("Coverage: %d selector choice%s cached"),
Surface.SelectedSelectorCount,
Surface.SelectedSelectorCount == 1 ? TEXT("") : TEXT("s")
);
}
else
{
Surface.CoverageLine = TEXT("Coverage: no generated-mode selector choices are cached");
}
if (!Surface.bSelectionStateMatchesSurface && Surface.bHasGeneratedModeSurface && Surface.bHasSelectionState)
{
Surface.CoverageLine += TEXT(" | selection state is bound to a different surface");
}
Surface.IdentityLine = FString::Printf(
TEXT("Identity: %s | kind: %s | mode: %s | generator: %s"),
*SurfaceLabelSummary,
*SurfaceKindSummary,
*RuntimeModeSummary,
*GeneratorSummary
);
if (LaunchExecutionContextSurface.bHasExecutionTarget)
{
Surface.RefreshLine = Surface.bWillRefreshLaunchRequestOnExecute
? TEXT("Refresh posture: execute will rebuild the stored request from the current selector set.")
: TEXT("Refresh posture: execute can use the current stored request without rebuilding it.");
}
else if (Surface.bHasLaunchConfig || Surface.bHasGeneratedModeSurface)
{
Surface.RefreshLine =
TEXT("Refresh posture: no generated-mode execution target is currently staged.");
}
else
{
Surface.RefreshLine =
TEXT("Refresh posture: selector application must stage a generated-mode target first.");
}
if (Surface.bHasLaunchConfig)
{
Surface.StatusLine = Surface.bWillRefreshLaunchRequestOnExecute
? TEXT("Generated-mode selector application is complete; execute will refresh the stored request from the current selector set.")
: TEXT("Generated-mode selector application is complete for the active generated-mode surface.");
}
else if (Surface.bHasGeneratedModeSurface && Surface.bHasSelectionState && !Surface.bSelectionStateMatchesSurface)
{
Surface.StatusLine =
TEXT("Generated-mode selector application is cached against a different surface than the active generated-mode deck.");
}
else if (Surface.bHasGeneratedModeSurface && Surface.RequiredSelectorCount > 0 && Surface.MissingSelectorCount == 0)
{
Surface.StatusLine =
TEXT("Generated-mode selector application has full selector coverage, but launch execution is not yet staged.");
}
else if (Surface.bHasGeneratedModeSurface && Surface.RequiredSelectorCount > 0 && Surface.SelectedSelectorCount > 0)
{
Surface.StatusLine = FString::Printf(
TEXT("Generated-mode selector application is in progress; %d selector%s still need selection."),
Surface.MissingSelectorCount,
Surface.MissingSelectorCount == 1 ? TEXT("") : TEXT("s")
);
}
else if (Surface.bHasGeneratedModeSurface)
{
Surface.StatusLine =
TEXT("Generated-mode selector application has not started for the active generated-mode surface.");
}
else if (Surface.bHasSelectionState)
{
Surface.StatusLine =
TEXT("Generated-mode selector application is cached, but no active generated-mode surface is currently staged.");
}
else
{
Surface.StatusLine =
TEXT("Generated-mode selector application is not currently cached.");
}
Surface.DetailLine = FString::Printf(
TEXT("Surface: %s (%s) | Runtime: %s | Generator: %s | Launch posture: %s"),
*SurfaceLabelSummary,
*SurfaceIdSummary,
*RuntimeModeSummary,
*GeneratorSummary,
LaunchExecutionContextSurface.StatusLine.IsEmpty()
? TEXT("n/a")
: *LaunchExecutionContextSurface.StatusLine
);
return Surface;
}

View file

@ -677,6 +677,20 @@ UHyperTwistTrainingPanelWidget::GetDisplayedCoachGeneratedModeLaunchExecutionCon
);
}
FHyperTwistTrainingCoachGeneratedModeSelectorApplicationContextSurface
UHyperTwistTrainingPanelWidget::GetDisplayedCoachGeneratedModeSelectorApplicationContextSurface() const
{
const FHyperTwistTrainingCoachGeneratedModeLaunchExecutionContextSurface
LaunchExecutionContextSurface =
GetDisplayedCoachGeneratedModeLaunchExecutionContextSurface();
return UHyperTwistTrainingCoachLibrary::DeriveCoachGeneratedModeSelectorApplicationContextSurface(
CachedRunState.ActiveDeck,
CachedImportedRuntimeSelectionState,
CachedImportedGeneratedModeLaunchConfig,
LaunchExecutionContextSurface
);
}
FHyperTwistTrainingRunState UHyperTwistTrainingPanelWidget::StartCoachRecommendedRun(
const int32 MaxCases,
const FString& StartActionSourceLabel

View file

@ -658,6 +658,20 @@ AHyperTwistTrainingSessionActor::GetDisplayedCoachGeneratedModeLaunchExecutionCo
);
}
FHyperTwistTrainingCoachGeneratedModeSelectorApplicationContextSurface
AHyperTwistTrainingSessionActor::GetDisplayedCoachGeneratedModeSelectorApplicationContextSurface() const
{
const FHyperTwistTrainingCoachGeneratedModeLaunchExecutionContextSurface
LaunchExecutionContextSurface =
GetDisplayedCoachGeneratedModeLaunchExecutionContextSurface();
return UHyperTwistTrainingCoachLibrary::DeriveCoachGeneratedModeSelectorApplicationContextSurface(
CachedRunState.ActiveDeck,
CachedImportedRuntimeSelectionState,
CachedImportedGeneratedModeLaunchConfig,
LaunchExecutionContextSurface
);
}
FHyperTwistTrainingRunState AHyperTwistTrainingSessionActor::StartCoachRecommendedTrainingRun(
const int32 MaxCases
)

View file

@ -668,4 +668,14 @@ public:
const FHyperTwistTrainingCoachVerificationRecognitionReviewContextSurface&
VerificationRecognitionReviewContextSurface
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
static FHyperTwistTrainingCoachGeneratedModeSelectorApplicationContextSurface
DeriveCoachGeneratedModeSelectorApplicationContextSurface(
const FHyperTwistTrainingDeck& ActiveDeck,
const FHyperTwistTrainingImportedRuntimeSelectionState& ImportedRuntimeSelectionState,
const FHyperTwistTrainingImportedGeneratedModeLaunchConfig& LaunchConfig,
const FHyperTwistTrainingCoachGeneratedModeLaunchExecutionContextSurface&
LaunchExecutionContextSurface
);
};

View file

@ -248,6 +248,10 @@ public:
FHyperTwistTrainingCoachGeneratedModeLaunchExecutionContextSurface
GetDisplayedCoachGeneratedModeLaunchExecutionContextSurface() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
FHyperTwistTrainingCoachGeneratedModeSelectorApplicationContextSurface
GetDisplayedCoachGeneratedModeSelectorApplicationContextSurface() const;
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach")
FHyperTwistTrainingRunState StartCoachRecommendedRun(
int32 MaxCases,

View file

@ -229,6 +229,10 @@ public:
FHyperTwistTrainingCoachGeneratedModeLaunchExecutionContextSurface
GetDisplayedCoachGeneratedModeLaunchExecutionContextSurface() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
FHyperTwistTrainingCoachGeneratedModeSelectorApplicationContextSurface
GetDisplayedCoachGeneratedModeSelectorApplicationContextSurface() const;
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach")
FHyperTwistTrainingRunState StartCoachRecommendedTrainingRun(int32 MaxCases);

View file

@ -8387,3 +8387,79 @@ struct FHyperTwistTrainingCoachGeneratedModeLaunchExecutionContextSurface
|| bHasBlockedExecution;
}
};
USTRUCT(BlueprintType)
struct FHyperTwistTrainingCoachGeneratedModeSelectorApplicationContextSurface
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString Headline;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString StatusLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString CoverageLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString IdentityLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString RefreshLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString DetailLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString SurfaceId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString SurfaceLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString SurfaceKind;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString RuntimeModeId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString GeneratorType;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 SelectedSelectorCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 RequiredSelectorCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 MissingSelectorCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasGeneratedModeSurface = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasSelectionState = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasLaunchConfig = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bSelectionStateMatchesSurface = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHasFullSelectorCoverage = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bWillRefreshLaunchRequestOnExecute = false;
bool IsStructurallyValid() const
{
return !StatusLine.IsEmpty()
|| !CoverageLine.IsEmpty()
|| !IdentityLine.IsEmpty()
|| bHasGeneratedModeSurface
|| bHasSelectionState
|| bHasLaunchConfig;
}
};

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 launch-request / execution context surface over the already-cached generated-mode request / execution panel state, so gameplay/Blueprint callers can render latest generated-mode request, runtime mode, selector summary, execution readiness, and blocked-reason context without reading raw request/execution fields directly
- the current next bounded packet is to expose one thin generated-mode selector/application context surface on those same two consumer surfaces so gameplay/Blueprint callers can render active selector-state coverage, generator/runtime identity, and request-refresh implications without reading raw imported selection/config payloads directly
- the latest landed bounded `Phase 4` packet stays on that same gameplay-facing consumer lane and adds one thin generated-mode selector/application context surface over the already-cached imported selection and launch-config state, so gameplay/Blueprint callers can render active selector-state coverage, generator/runtime identity, and request-refresh implications without reading raw imported selection/config payloads directly
- the current next bounded packet is to expose one thin generated-mode selector roster / option-availability surface on those same two consumer surfaces so gameplay/Blueprint callers can render active selector labels, bindings, and unresolved option availability without reading raw imported runtime selector arrays or option 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

View file

@ -0,0 +1,112 @@
# HyperTwist Phase 4 gameplay generated-mode selector / application context 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 helpers by exposing one pure selector / application context surface over the already-cached imported selection state and generated-mode launch-config state.
The open tasks were:
- stop forcing gameplay / Blueprint callers to read raw imported selection-state payloads and launch-config payloads just to render selector coverage, generator/runtime identity, and request-refresh implications
- expose one thin first-party context surface that summarizes active selector coverage, generated-mode surface identity, runtime mode, generator type, and refresh-on-execute posture
- keep that surface derived from already-cached gameplay consumer state instead of widening into backend selector application or execution logic
It is not:
- a new selector-application behavior packet
- a generated-mode execution packet
- a selector-catalog UI composition pass
- a broader gameplay HUD packet
## Scope
Bounded lane:
- add a first-party generated-mode selector / application context surface in shared training types
- add a coach-library helper that derives the surface from the active deck imported runtime surface, cached imported selection state, cached generated-mode launch config, and the already-landed launch / execution context surface
- expose that derived context surface on `HyperTwistTrainingPanelWidget` and `HyperTwistTrainingSessionActor`
- keep the derivation presentation-only and rooted in already-cached gameplay consumer state
Out of scope:
- changing selector-application persistence behavior
- changing generated-mode launch-config derivation rules
- changing generated-mode execution behavior
- importing dashboard-only generated-mode inspection into gameplay consumers
## Why this was the right next packet
Before this slice:
- gameplay-facing panel/session consumers already exposed a truthful generated-mode launch / execution context surface
But one thin consumer seam was still open:
- callers could explain current generated-mode request and execution posture
- but they still had to read raw imported selection/config payloads to explain whether selector application was complete, what runtime/generator identity those selections targeted, and whether execute would rebuild the stored request
That meant:
- execution posture was thin and reusable
- but selector-application context still leaked raw imported payloads into callers
So the next bounded move was:
- keep the already-landed launch / execution context surface unchanged
- and expose one narrow selector / application context surface above the cached imported selection/config state
## What landed
Primary code changes:
- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h`
- added `FHyperTwistTrainingCoachGeneratedModeSelectorApplicationContextSurface`
- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCoachLibrary.h`
- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCoachLibrary.cpp`
- added `DeriveCoachGeneratedModeSelectorApplicationContextSurface(...)`
- context is derived from the active imported runtime surface, cached imported selection state, cached generated-mode launch config, and the already-landed launch / execution context surface
- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingPanelWidget.h`
- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp`
- added `GetDisplayedCoachGeneratedModeSelectorApplicationContextSurface()`
- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSessionActor.h`
- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSessionActor.cpp`
- added `GetDisplayedCoachGeneratedModeSelectorApplicationContextSurface()`
## Product effect
The gameplay-facing generated-mode consumer layer is now more complete:
- gameplay / Blueprint callers can read one pure surface to render selector coverage, missing-selector count, generated-mode surface identity, runtime mode, generator type, and request-refresh implications
- callers no longer need to inspect raw imported selection-state or generated-mode launch-config payloads directly
- the surface stays aligned with the already-landed generated-mode launch / execution context surface without widening backend behavior
## Acceptance criteria
- shared training types expose a narrow generated-mode selector / application context surface
- coach-library derivation consumes already-cached gameplay consumer state rather than adding new runtime fetches
- panel-widget callers can fetch the displayed selector / application context surface directly
- session-actor callers can fetch the displayed selector / application context surface directly
- full product build succeeds
## Validation checklist
1. build `UnrealHyperTwist.sln` / `UnrealHyperTwistEditor`
2. confirm the new selector / application context surface compiles in shared types and coach library
3. confirm panel-widget getters compile and return the derived context surface
4. confirm session-actor getters compile and return the derived context surface
5. confirm no backend selector-application or generated-mode execution behavior was pulled into the packet
## Validation result
- full `Development Editor|Win64` build succeeded on `2026-05-08`
- build completed with `0` warnings and `0` errors
## Next bounded follow-on slice
- stay on the same gameplay-facing consumer lane and expose one thin generated-mode selector roster / option-availability surface on `HyperTwistTrainingPanelWidget` and `HyperTwistTrainingSessionActor`, so gameplay callers can render active selector labels, bindings, and unresolved option availability without reading raw imported runtime selector arrays or option payloads directly