From 23ff059aca4222b555c6a4144d6d7c7dc86cf6a7 Mon Sep 17 00:00:00 2001 From: axiomlogicnexus Date: Thu, 7 May 2026 04:44:57 +0200 Subject: [PATCH] Persist template-signal decision-bias provenance --- .../HyperTwistCoachDashboardWidget.cpp | 88 +++++++++++++++++-- .../HyperTwistTrainingTypes.h | 13 +++ ...ISION_BIAS_PROVENANCE_PACKET_2026-05-07.md | 47 ++++++++++ 3 files changed, 142 insertions(+), 6 deletions(-) create mode 100644 docs/arch/HYPERTWIST_PHASE4_TEMPLATE_SIGNAL_DECISION_BIAS_PROVENANCE_PACKET_2026-05-07.md diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp index 1fe5488..a9db9fc 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp @@ -355,6 +355,22 @@ namespace HyperTwistCoachDashboardWidgetInternal } } + FString ResolveTemplateLaunchSelectionTemplateSignalSupportDecisionBiasKindLabel( + const EHyperTwistTrainingTemplateLaunchSelectionTemplateSignalSupportDecisionBiasKind + TemplateSignalSupportDecisionBiasKind + ) + { + switch (TemplateSignalSupportDecisionBiasKind) + { + case EHyperTwistTrainingTemplateLaunchSelectionTemplateSignalSupportDecisionBiasKind:: + DecisionSplitGuard: + return TEXT("decision-split-guard"); + case EHyperTwistTrainingTemplateLaunchSelectionTemplateSignalSupportDecisionBiasKind::None: + default: + return TEXT("none"); + } + } + FString ResolveTemplateLaunchSelectionPeerSupportKindDispositionLabel( const EHyperTwistTrainingTemplateLaunchSelectionPeerSupportKindDisposition PeerSupportKindDisposition @@ -418,7 +434,8 @@ namespace HyperTwistCoachDashboardWidgetInternal const FHyperTwistTrainingSessionTemplate& PeerTemplate, const FHyperTwistTrainingTemplateLaunchScopedStats* PeerStats, const EHyperTwistTrainingTemplateLaunchSelectionAnalyticsSupportKind AnalyticsSupportKind, - const bool bUsedTemplateSignalSupportBiasSnapshotGuard = false + const bool bUsedTemplateSignalSupportBiasSnapshotGuard = false, + const bool bUsedTemplateSignalSupportDecisionBiasGuard = false ) { FHyperTwistTrainingTemplateLaunchSelectionProvenance Provenance; @@ -477,6 +494,12 @@ namespace HyperTwistCoachDashboardWidgetInternal SupportBiasSnapshotGuard : EHyperTwistTrainingTemplateLaunchSelectionTemplateSignalSupportDecisionKind:: None; + Provenance.TemplateSignalSupportDecisionBiasKind = + bUsedTemplateSignalSupportDecisionBiasGuard + ? EHyperTwistTrainingTemplateLaunchSelectionTemplateSignalSupportDecisionBiasKind:: + DecisionSplitGuard + : EHyperTwistTrainingTemplateLaunchSelectionTemplateSignalSupportDecisionBiasKind:: + None; Provenance.TemplateSignalSupportBiasSnapshot = BuildTemplateLaunchTemplateSignalSupportBiasSnapshot(SelectedStats); Provenance.ComparablePeerConfidenceSnapshot = @@ -485,7 +508,18 @@ namespace HyperTwistCoachDashboardWidgetInternal BuildTemplateLaunchComparablePeerConfidenceAuditClause( SelectedStats, TEXT("chosen template")); - if (bUsedTemplateSignalSupportBiasSnapshotGuard) + 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."), + *ResolveDashboardTemplateLaunchDisplayTitle(PeerTemplate), + *ResolveDashboardTemplateLaunchDisplayTitle(SelectedTemplate), + *SelectedComparablePeerConfidenceAuditClause, + *BuildTemplateLaunchAnalyticsTieBreakSignalFragment(SelectedStats), + *BuildTemplateLaunchAnalyticsTieBreakSignalFragment(PeerStats) + ); + } + else if (bUsedTemplateSignalSupportBiasSnapshotGuard) { 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 deciding the winner: %s | chosen %s | peer %s."), @@ -1297,6 +1331,30 @@ namespace HyperTwistCoachDashboardWidgetInternal ); } + FString BuildTemplateLaunchSelectionTemplateSignalSupportDecisionBiasDetailFragment( + const FHyperTwistTrainingTemplateLaunchSelectionProvenance* SelectionProvenance + ) + { + if (SelectionProvenance == nullptr + || SelectionProvenance->ReasonClass + != EHyperTwistTrainingTemplateLaunchSelectionReasonClass::AnalyticsTieBreak + || SelectionProvenance->AnalyticsSupportKind + != EHyperTwistTrainingTemplateLaunchSelectionAnalyticsSupportKind:: + TemplateScopedOutcomeSignal + || SelectionProvenance->TemplateSignalSupportDecisionBiasKind + == EHyperTwistTrainingTemplateLaunchSelectionTemplateSignalSupportDecisionBiasKind:: + None) + { + return FString(); + } + + return FString::Printf( + TEXT(" | template-signal decision-bias %s"), + *ResolveTemplateLaunchSelectionTemplateSignalSupportDecisionBiasKindLabel( + SelectionProvenance->TemplateSignalSupportDecisionBiasKind) + ); + } + FString BuildTemplateLaunchSelectionTemplateSignalSupportBiasDetailFragment( const FHyperTwistTrainingTemplateLaunchSelectionProvenance* SelectionProvenance ) @@ -1406,6 +1464,9 @@ namespace HyperTwistCoachDashboardWidgetInternal const FString TemplateSignalSupportDecisionDetailFragment = BuildTemplateLaunchSelectionTemplateSignalSupportDecisionDetailFragment( SelectionProvenance); + const FString TemplateSignalSupportDecisionBiasDetailFragment = + BuildTemplateLaunchSelectionTemplateSignalSupportDecisionBiasDetailFragment( + SelectionProvenance); const FString TemplateSignalSupportBiasDetailFragment = BuildTemplateLaunchSelectionTemplateSignalSupportBiasDetailFragment( SelectionProvenance); @@ -1417,6 +1478,7 @@ namespace HyperTwistCoachDashboardWidgetInternal const FString SelectionReasonDetailFragment = AnalyticsSupportDetailFragment + TemplateSignalSupportDetailFragment + TemplateSignalSupportDecisionDetailFragment + + TemplateSignalSupportDecisionBiasDetailFragment + TemplateSignalSupportBiasDetailFragment + PeerSupportDispositionDetailFragment + ComparablePeerConfidenceDetailFragment; @@ -2482,6 +2544,7 @@ namespace HyperTwistCoachDashboardWidgetInternal EHyperTwistTrainingTemplateLaunchSelectionAnalyticsSupportKind CandidateAnalyticsSupportKind = EHyperTwistTrainingTemplateLaunchSelectionAnalyticsSupportKind::None; bool bCandidateUsedTemplateSignalSupportBiasSnapshotGuard = false; + bool bCandidateUsedTemplateSignalSupportDecisionBiasGuard = false; }; FTemplateLaunchAnalyticsPreferenceDecision ResolveTemplateAnalyticsPreferenceDecision( @@ -2519,15 +2582,23 @@ namespace HyperTwistCoachDashboardWidgetInternal } return Decision; } - const int32 CandidateTemplateSignalSupportBiasBonus = + const int32 CandidateTemplateSignalSupportBiasSnapshotGuardBonus = HasTemplateLaunchTemplateSignalSupportBias(CandidateStats) && HasTemplateLaunchTemplateSignalSupportBiasSnapshotBias(CandidateStats) + ? 1 + : 0; + const int32 IncumbentTemplateSignalSupportBiasSnapshotGuardBonus = + HasTemplateLaunchTemplateSignalSupportBias(IncumbentStats) + && HasTemplateLaunchTemplateSignalSupportBiasSnapshotBias(IncumbentStats) + ? 1 + : 0; + const int32 CandidateTemplateSignalSupportBiasBonus = + CandidateTemplateSignalSupportBiasSnapshotGuardBonus > 0 && HasTemplateLaunchTemplateSignalSupportDecisionBias(CandidateStats) ? 1 : 0; const int32 IncumbentTemplateSignalSupportBiasBonus = - HasTemplateLaunchTemplateSignalSupportBias(IncumbentStats) - && HasTemplateLaunchTemplateSignalSupportBiasSnapshotBias(IncumbentStats) + IncumbentTemplateSignalSupportBiasSnapshotGuardBonus > 0 && HasTemplateLaunchTemplateSignalSupportDecisionBias(IncumbentStats) ? 1 : 0; @@ -2543,6 +2614,9 @@ namespace HyperTwistCoachDashboardWidgetInternal EHyperTwistTrainingTemplateLaunchSelectionAnalyticsSupportKind:: TemplateScopedOutcomeSignal; Decision.bCandidateUsedTemplateSignalSupportBiasSnapshotGuard = true; + Decision.bCandidateUsedTemplateSignalSupportDecisionBiasGuard = + CandidateTemplateSignalSupportBiasSnapshotGuardBonus + == IncumbentTemplateSignalSupportBiasSnapshotGuardBonus; } return Decision; } @@ -9296,7 +9370,9 @@ const FHyperTwistTrainingSessionTemplate* UHyperTwistCoachDashboardWidget::FindP PreferredStats, AnalyticsDecision.CandidateAnalyticsSupportKind, AnalyticsDecision - .bCandidateUsedTemplateSignalSupportBiasSnapshotGuard + .bCandidateUsedTemplateSignalSupportBiasSnapshotGuard, + AnalyticsDecision + .bCandidateUsedTemplateSignalSupportDecisionBiasGuard ); } } diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h index d55e854..7b8d43f 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h @@ -1097,6 +1097,14 @@ enum class EHyperTwistTrainingTemplateLaunchSelectionTemplateSignalSupportDecisi SupportBiasSnapshotGuard UMETA(DisplayName = "Support Bias Snapshot Guard") }; +UENUM(BlueprintType) +enum class EHyperTwistTrainingTemplateLaunchSelectionTemplateSignalSupportDecisionBiasKind : + uint8 +{ + None UMETA(DisplayName = "None"), + DecisionSplitGuard UMETA(DisplayName = "Decision Split Guard") +}; + UENUM(BlueprintType) enum class EHyperTwistTrainingTemplateLaunchComparablePeerConfidenceSnapshotState : uint8 { @@ -1186,6 +1194,11 @@ struct FHyperTwistTrainingTemplateLaunchSelectionProvenance TemplateSignalSupportDecisionKind = EHyperTwistTrainingTemplateLaunchSelectionTemplateSignalSupportDecisionKind::None; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + EHyperTwistTrainingTemplateLaunchSelectionTemplateSignalSupportDecisionBiasKind + TemplateSignalSupportDecisionBiasKind = + EHyperTwistTrainingTemplateLaunchSelectionTemplateSignalSupportDecisionBiasKind::None; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FHyperTwistTrainingTemplateLaunchTemplateSignalSupportBiasSnapshot TemplateSignalSupportBiasSnapshot; diff --git a/docs/arch/HYPERTWIST_PHASE4_TEMPLATE_SIGNAL_DECISION_BIAS_PROVENANCE_PACKET_2026-05-07.md b/docs/arch/HYPERTWIST_PHASE4_TEMPLATE_SIGNAL_DECISION_BIAS_PROVENANCE_PACKET_2026-05-07.md new file mode 100644 index 0000000..fc47e1a --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE4_TEMPLATE_SIGNAL_DECISION_BIAS_PROVENANCE_PACKET_2026-05-07.md @@ -0,0 +1,47 @@ +# HyperTwist Phase 4 template-signal decision-bias provenance packet + +## Purpose + +This packet persists when the new decision-split guard is what actually changes a tied broader template-signal winner. That keeps selector audits precise: dashboard detail, template history, and recap can now distinguish plain `SupportBiasSnapshotGuard` wins from wins that were specifically reinforced by the stricter decision-split guard. + +## Scope + +- add a compact template-signal decision-bias enum under template launch selection provenance +- stamp that field only when the stricter decision-split guard is what separates two otherwise equal snapshot-guard candidates +- update broader template-signal decision summary wording so it explicitly names the decision-split guard when it specifically breaks the remaining tie +- append the stored decision-bias marker through the shared selector-detail formatter used by live detail, template history, and recap + +## What landed + +- `HyperTwistTrainingTypes.h` + - added `EHyperTwistTrainingTemplateLaunchSelectionTemplateSignalSupportDecisionBiasKind` + - nested `TemplateSignalSupportDecisionBiasKind` under `FHyperTwistTrainingTemplateLaunchSelectionProvenance` +- `HyperTwistCoachDashboardWidget.cpp` + - added `ResolveTemplateLaunchSelectionTemplateSignalSupportDecisionBiasKindLabel(...)` + - broader analytics provenance now stamps: + - `DecisionSplitGuard` when the stricter decision-split guard specifically breaks the remaining tie between snapshot-guard candidates + - broader analytics decision summaries now explicitly call out that decision-split guard when it changes the winner + - shared selector detail now appends `template-signal decision-bias decision-split-guard` for runs that were specifically reinforced by the new guard + - widened the analytics tie-break decision struct so the winner path can distinguish: + - plain `SupportBiasSnapshotGuard` wins + - `SupportBiasSnapshotGuard` wins that required the stricter decision-split guard + +## Product effect + +- live `[Template Launch]` detail now explicitly shows when a broader template-signal winner was not just a plain snapshot-guard win, but one specifically reinforced by the decision-split guard +- template history and recap preserve that same distinction from stored provenance +- plain snapshot-guard wins remain distinguishable because they do not show the new decision-bias detail + +## Acceptance criteria + +- broader template-signal selection provenance persists a distinct decision-bias kind when the decision-split guard changes the winner +- shared selector detail shows that stored decision-bias kind in live detail, template history, and recap +- broader analytics decision summaries explicitly mention when the decision-split guard specifically breaks the remaining tie +- plain `SupportBiasSnapshotGuard` wins continue to render without fabricated decision-split reinforcement claims + +## Validation checklist + +1. full `Development Editor|Win64` build succeeds +2. confirm broader analytics provenance stamps `TemplateSignalSupportDecisionBiasKind = DecisionSplitGuard` only when the stricter decision-split guard changes the winner +3. confirm shared selector detail shows `template-signal decision-bias decision-split-guard` +4. confirm plain snapshot-guard wins continue to render without the new decision-bias detail