Persist template-signal decision-bias provenance

This commit is contained in:
axiomlogicnexus 2026-05-07 04:44:57 +02:00
parent 493da3b30a
commit 23ff059aca
3 changed files with 142 additions and 6 deletions

View file

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

View file

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

View file

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