diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp index 7ffb4e7..f96f00f 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp @@ -182,6 +182,10 @@ namespace HyperTwistCoachDashboardWidgetInternal const FHyperTwistTrainingTemplateLaunchSelectionProvenance* SelectionProvenance ); + FString BuildTemplateLaunchSelectionTemplateSignalSupportDecisionBiasSnapshotDecisionDetailFragment( + const FHyperTwistTrainingTemplateLaunchSelectionProvenance* SelectionProvenance + ); + FString BuildTemplateLaunchSelectionTemplateSignalSupportDecisionBiasSnapshotDetailFragment( const FHyperTwistTrainingTemplateLaunchSelectionProvenance* SelectionProvenance ); @@ -392,6 +396,23 @@ namespace HyperTwistCoachDashboardWidgetInternal } } + FString ResolveTemplateLaunchSelectionTemplateSignalSupportDecisionBiasSnapshotDecisionKindLabel( + const EHyperTwistTrainingTemplateLaunchSelectionTemplateSignalSupportDecisionBiasSnapshotDecisionKind + TemplateSignalSupportDecisionBiasSnapshotDecisionKind + ) + { + switch (TemplateSignalSupportDecisionBiasSnapshotDecisionKind) + { + case EHyperTwistTrainingTemplateLaunchSelectionTemplateSignalSupportDecisionBiasSnapshotDecisionKind:: + DecisionBiasSnapshotGuard: + return TEXT("decision-bias-snapshot-guard"); + case EHyperTwistTrainingTemplateLaunchSelectionTemplateSignalSupportDecisionBiasSnapshotDecisionKind:: + None: + default: + return TEXT("none"); + } + } + FString ResolveTemplateLaunchSelectionPeerSupportKindDispositionLabel( const EHyperTwistTrainingTemplateLaunchSelectionPeerSupportKindDisposition PeerSupportKindDisposition @@ -456,7 +477,8 @@ namespace HyperTwistCoachDashboardWidgetInternal const FHyperTwistTrainingTemplateLaunchScopedStats* PeerStats, const EHyperTwistTrainingTemplateLaunchSelectionAnalyticsSupportKind AnalyticsSupportKind, const bool bUsedTemplateSignalSupportBiasSnapshotGuard = false, - const bool bUsedTemplateSignalSupportDecisionBiasGuard = false + const bool bUsedTemplateSignalSupportDecisionBiasGuard = false, + const bool bUsedTemplateSignalSupportDecisionBiasSnapshotGuard = false ) { FHyperTwistTrainingTemplateLaunchSelectionProvenance Provenance; @@ -521,6 +543,12 @@ namespace HyperTwistCoachDashboardWidgetInternal DecisionSplitGuard : EHyperTwistTrainingTemplateLaunchSelectionTemplateSignalSupportDecisionBiasKind:: None; + Provenance.TemplateSignalSupportDecisionBiasSnapshotDecisionKind = + bUsedTemplateSignalSupportDecisionBiasSnapshotGuard + ? EHyperTwistTrainingTemplateLaunchSelectionTemplateSignalSupportDecisionBiasSnapshotDecisionKind:: + DecisionBiasSnapshotGuard + : EHyperTwistTrainingTemplateLaunchSelectionTemplateSignalSupportDecisionBiasSnapshotDecisionKind:: + None; Provenance.TemplateSignalSupportBiasSnapshot = BuildTemplateLaunchTemplateSignalSupportBiasSnapshot(SelectedStats); Provenance.TemplateSignalSupportDecisionBiasSnapshot = @@ -531,7 +559,18 @@ namespace HyperTwistCoachDashboardWidgetInternal BuildTemplateLaunchComparablePeerConfidenceAuditClause( SelectedStats, TEXT("chosen template")); - if (bUsedTemplateSignalSupportDecisionBiasGuard) + if (bUsedTemplateSignalSupportDecisionBiasSnapshotGuard) + { + Provenance.DecisionSummaryLine = FString::Printf( + TEXT("Selector: tied with %s on primary dashboard priority; analytics preferred %s on broader template-scoped outcome signal with earned-vs-non-earned support-bias snapshot guard reinforcement, then the decision-split guard tied again, and earned-vs-non-earned decision-bias snapshot history specifically broke the remaining tie: %s | chosen %s | peer %s."), + *ResolveDashboardTemplateLaunchDisplayTitle(PeerTemplate), + *ResolveDashboardTemplateLaunchDisplayTitle(SelectedTemplate), + *SelectedComparablePeerConfidenceAuditClause, + *BuildTemplateLaunchAnalyticsTieBreakSignalFragment(SelectedStats), + *BuildTemplateLaunchAnalyticsTieBreakSignalFragment(PeerStats) + ); + } + else if (bUsedTemplateSignalSupportDecisionBiasGuard) { Provenance.DecisionSummaryLine = FString::Printf( TEXT("Selector: tied with %s on primary dashboard priority; analytics preferred %s on broader template-scoped outcome signal with earned-vs-non-earned support-bias snapshot guard reinforcement, with the decision-split guard specifically breaking the remaining tie: %s | chosen %s | peer %s."), @@ -1492,6 +1531,30 @@ namespace HyperTwistCoachDashboardWidgetInternal ); } + FString BuildTemplateLaunchSelectionTemplateSignalSupportDecisionBiasSnapshotDecisionDetailFragment( + const FHyperTwistTrainingTemplateLaunchSelectionProvenance* SelectionProvenance + ) + { + if (SelectionProvenance == nullptr + || SelectionProvenance->ReasonClass + != EHyperTwistTrainingTemplateLaunchSelectionReasonClass::AnalyticsTieBreak + || SelectionProvenance->AnalyticsSupportKind + != EHyperTwistTrainingTemplateLaunchSelectionAnalyticsSupportKind:: + TemplateScopedOutcomeSignal + || SelectionProvenance->TemplateSignalSupportDecisionBiasSnapshotDecisionKind + == EHyperTwistTrainingTemplateLaunchSelectionTemplateSignalSupportDecisionBiasSnapshotDecisionKind:: + None) + { + return FString(); + } + + return FString::Printf( + TEXT(" | template-signal decision-bias-snapshot %s"), + *ResolveTemplateLaunchSelectionTemplateSignalSupportDecisionBiasSnapshotDecisionKindLabel( + SelectionProvenance->TemplateSignalSupportDecisionBiasSnapshotDecisionKind) + ); + } + FString BuildTemplateLaunchSelectionTemplateSignalSupportBiasDetailFragment( const FHyperTwistTrainingTemplateLaunchSelectionProvenance* SelectionProvenance ) @@ -1647,6 +1710,9 @@ namespace HyperTwistCoachDashboardWidgetInternal const FString TemplateSignalSupportDecisionBiasDetailFragment = BuildTemplateLaunchSelectionTemplateSignalSupportDecisionBiasDetailFragment( SelectionProvenance); + const FString TemplateSignalSupportDecisionBiasSnapshotDecisionDetailFragment = + BuildTemplateLaunchSelectionTemplateSignalSupportDecisionBiasSnapshotDecisionDetailFragment( + SelectionProvenance); const FString TemplateSignalSupportDecisionBiasSnapshotDetailFragment = BuildTemplateLaunchSelectionTemplateSignalSupportDecisionBiasSnapshotDetailFragment( SelectionProvenance); @@ -1662,6 +1728,7 @@ namespace HyperTwistCoachDashboardWidgetInternal AnalyticsSupportDetailFragment + TemplateSignalSupportDetailFragment + TemplateSignalSupportDecisionDetailFragment + TemplateSignalSupportDecisionBiasDetailFragment + + TemplateSignalSupportDecisionBiasSnapshotDecisionDetailFragment + TemplateSignalSupportDecisionBiasSnapshotDetailFragment + TemplateSignalSupportBiasDetailFragment + PeerSupportDispositionDetailFragment @@ -2850,6 +2917,7 @@ namespace HyperTwistCoachDashboardWidgetInternal EHyperTwistTrainingTemplateLaunchSelectionAnalyticsSupportKind::None; bool bCandidateUsedTemplateSignalSupportBiasSnapshotGuard = false; bool bCandidateUsedTemplateSignalSupportDecisionBiasGuard = false; + bool bCandidateUsedTemplateSignalSupportDecisionBiasSnapshotGuard = false; }; FTemplateLaunchAnalyticsPreferenceDecision ResolveTemplateAnalyticsPreferenceDecision( @@ -2963,6 +3031,7 @@ namespace HyperTwistCoachDashboardWidgetInternal TemplateScopedOutcomeSignal; Decision.bCandidateUsedTemplateSignalSupportBiasSnapshotGuard = true; Decision.bCandidateUsedTemplateSignalSupportDecisionBiasGuard = true; + Decision.bCandidateUsedTemplateSignalSupportDecisionBiasSnapshotGuard = true; } return Decision; } @@ -9718,7 +9787,9 @@ const FHyperTwistTrainingSessionTemplate* UHyperTwistCoachDashboardWidget::FindP AnalyticsDecision .bCandidateUsedTemplateSignalSupportBiasSnapshotGuard, AnalyticsDecision - .bCandidateUsedTemplateSignalSupportDecisionBiasGuard + .bCandidateUsedTemplateSignalSupportDecisionBiasGuard, + AnalyticsDecision + .bCandidateUsedTemplateSignalSupportDecisionBiasSnapshotGuard ); } } diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h index c26aecb..03fdef3 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h @@ -1105,6 +1105,15 @@ enum class EHyperTwistTrainingTemplateLaunchSelectionTemplateSignalSupportDecisi DecisionSplitGuard UMETA(DisplayName = "Decision Split Guard") }; +UENUM(BlueprintType) +enum class + EHyperTwistTrainingTemplateLaunchSelectionTemplateSignalSupportDecisionBiasSnapshotDecisionKind : + uint8 +{ + None UMETA(DisplayName = "None"), + DecisionBiasSnapshotGuard UMETA(DisplayName = "Decision Bias Snapshot Guard") +}; + UENUM(BlueprintType) enum class EHyperTwistTrainingTemplateLaunchTemplateSignalSupportDecisionBiasSnapshotState : uint8 @@ -1231,6 +1240,12 @@ struct FHyperTwistTrainingTemplateLaunchSelectionProvenance TemplateSignalSupportDecisionBiasKind = EHyperTwistTrainingTemplateLaunchSelectionTemplateSignalSupportDecisionBiasKind::None; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + EHyperTwistTrainingTemplateLaunchSelectionTemplateSignalSupportDecisionBiasSnapshotDecisionKind + TemplateSignalSupportDecisionBiasSnapshotDecisionKind = + EHyperTwistTrainingTemplateLaunchSelectionTemplateSignalSupportDecisionBiasSnapshotDecisionKind:: + None; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FHyperTwistTrainingTemplateLaunchTemplateSignalSupportBiasSnapshot TemplateSignalSupportBiasSnapshot; diff --git a/docs/arch/HYPERTWIST_PHASE4_TEMPLATE_SIGNAL_DECISION_BIAS_SNAPSHOT_GUARD_PROVENANCE_PACKET_2026-05-07.md b/docs/arch/HYPERTWIST_PHASE4_TEMPLATE_SIGNAL_DECISION_BIAS_SNAPSHOT_GUARD_PROVENANCE_PACKET_2026-05-07.md new file mode 100644 index 0000000..5a9bf1b --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE4_TEMPLATE_SIGNAL_DECISION_BIAS_SNAPSHOT_GUARD_PROVENANCE_PACKET_2026-05-07.md @@ -0,0 +1,45 @@ +# HyperTwist Phase 4 template-signal decision-bias snapshot guard provenance packet + +## Purpose + +This packet persists when the new historical decision-bias snapshot guard actually changes a tied broader template-signal winner. That keeps selector audits precise: dashboard detail, template history, and recap can now distinguish plain `DecisionSplitGuard` wins from wins that were specifically reinforced by earned-vs-non-earned decision-bias snapshot history. + +## Scope + +- add a distinct structured provenance marker for the historical decision-bias snapshot guard +- stamp that marker only when the earned-vs-non-earned decision-bias snapshot guard decides a broader template-signal tie +- surface that marker through the shared selector-detail path already used by live template launch detail, template history, and template-backed recap + +## What landed + +- `HyperTwistTrainingTypes.h` + - added `EHyperTwistTrainingTemplateLaunchSelectionTemplateSignalSupportDecisionBiasSnapshotDecisionKind` + - added `TemplateSignalSupportDecisionBiasSnapshotDecisionKind` to `FHyperTwistTrainingTemplateLaunchSelectionProvenance` +- `HyperTwistCoachDashboardWidget.cpp` + - broader analytics provenance now stamps: + - `TemplateSignalSupportDecisionBiasKind = DecisionSplitGuard` when the current reinforced-vs-plain snapshot-guard lane breaks the tie + - `TemplateSignalSupportDecisionBiasSnapshotDecisionKind = DecisionBiasSnapshotGuard` only when the new historical earned-vs-non-earned snapshot history then breaks the remaining tie + - shared selector detail now appends: + - `template-signal decision-bias decision-split-guard` + - and, when applicable, `template-signal decision-bias-snapshot decision-bias-snapshot-guard` + - decision summary wording now distinguishes: + - plain decision-split wins + - from wins where the decision-split lane tied again and historical decision-bias snapshot history became the final decider + +## Product effect + +- live `[Template Launch]` detail now explicitly shows when a broader template-signal winner came from the historical decision-bias snapshot guard +- template launch history and recap preserve that distinction instead of collapsing all reinforced broader wins into the same `DecisionSplitGuard` reason + +## Acceptance criteria + +- broader template-signal selection provenance persists a distinct decision kind when the decision-bias snapshot guard changes the winner +- live detail, template history, and recap all show that retained decision kind through the shared selector-detail path +- plain `DecisionSplitGuard` wins remain distinguishable from history-reinforced wins + +## Validation checklist + +1. full `Development Editor|Win64` build succeeds +2. confirm `FHyperTwistTrainingTemplateLaunchSelectionProvenance` persists `TemplateSignalSupportDecisionBiasSnapshotDecisionKind` +3. confirm broader analytics provenance stamps that field only when the historical decision-bias snapshot guard decides the tie +4. confirm shared selector detail shows `template-signal decision-bias-snapshot decision-bias-snapshot-guard` only for those history-reinforced wins