Explain template support-kind selector rationale

This commit is contained in:
axiomlogicnexus 2026-05-07 01:54:14 +02:00
parent c41603393b
commit 7ffe2cd443
2 changed files with 145 additions and 7 deletions

View file

@ -207,6 +207,38 @@ namespace HyperTwistCoachDashboardWidgetInternal
}
}
FString BuildTemplateLaunchAnalyticsSupportKindGuardSignalFragment(
const FHyperTwistTrainingTemplateLaunchScopedStats* TemplateStats
)
{
if (TemplateStats == nullptr || !TemplateStats->IsStructurallyValid())
{
return FString();
}
const FHyperTwistTrainingTemplateLaunchSelectionScopedStats& TemplateSignalStats =
TemplateStats->DashboardAnalyticsTieBreakTemplateScopedSignalStats;
const FHyperTwistTrainingTemplateLaunchSelectionScopedStats& SplitConfidenceStats =
TemplateStats->DashboardAnalyticsTieBreakSelectorSplitConfidenceStats;
if (!TemplateSignalStats.IsStructurallyValid()
|| !SplitConfidenceStats.IsStructurallyValid()
|| TemplateSignalStats.GetOutcomeRunCount() <= 0
|| SplitConfidenceStats.GetOutcomeRunCount() <= 0)
{
return FString();
}
return FString::Printf(
TEXT("split-confidence outcomes %d | success %.2f | avg mistakes %.2f vs template-signal outcomes %d | success %.2f | avg mistakes %.2f"),
SplitConfidenceStats.GetOutcomeRunCount(),
SplitConfidenceStats.OverallSuccessRate,
SplitConfidenceStats.AverageMistakesPerOutcomeRun,
TemplateSignalStats.GetOutcomeRunCount(),
TemplateSignalStats.OverallSuccessRate,
TemplateSignalStats.AverageMistakesPerOutcomeRun
);
}
FHyperTwistTrainingTemplateLaunchSelectionProvenance BuildTemplateLaunchAnalyticsTieBreakSelectionProvenance(
const FHyperTwistTrainingSessionTemplate& SelectedTemplate,
const FHyperTwistTrainingTemplateLaunchScopedStats* SelectedStats,
@ -222,16 +254,32 @@ namespace HyperTwistCoachDashboardWidgetInternal
Provenance.AnalyticsSupportKind = AnalyticsSupportKind;
Provenance.PeerTemplateId = PeerTemplate.TemplateId;
Provenance.PeerTemplateTitle = ResolveDashboardTemplateLaunchDisplayTitle(PeerTemplate);
const FString SupportKindGuardSignalFragment =
BuildTemplateLaunchAnalyticsSupportKindGuardSignalFragment(SelectedStats);
if (AnalyticsSupportKind
== EHyperTwistTrainingTemplateLaunchSelectionAnalyticsSupportKind::SelectorSplitConfidence)
{
Provenance.DecisionSummaryLine = FString::Printf(
TEXT("Selector: tied with %s on primary dashboard priority; analytics preferred %s with selector-split confidence support: chosen %s | peer %s."),
*ResolveDashboardTemplateLaunchDisplayTitle(PeerTemplate),
*ResolveDashboardTemplateLaunchDisplayTitle(SelectedTemplate),
*BuildTemplateLaunchAnalyticsTieBreakSignalFragment(SelectedStats),
*BuildTemplateLaunchAnalyticsTieBreakSignalFragment(PeerStats)
);
if (!SupportKindGuardSignalFragment.IsEmpty())
{
Provenance.DecisionSummaryLine = FString::Printf(
TEXT("Selector: tied with %s on primary dashboard priority; analytics preferred %s with selector-split confidence support after split-confidence beat template-signal on stored support-kind outcomes: %s | chosen %s | peer %s."),
*ResolveDashboardTemplateLaunchDisplayTitle(PeerTemplate),
*ResolveDashboardTemplateLaunchDisplayTitle(SelectedTemplate),
*SupportKindGuardSignalFragment,
*BuildTemplateLaunchAnalyticsTieBreakSignalFragment(SelectedStats),
*BuildTemplateLaunchAnalyticsTieBreakSignalFragment(PeerStats)
);
}
else
{
Provenance.DecisionSummaryLine = FString::Printf(
TEXT("Selector: tied with %s on primary dashboard priority; analytics preferred %s with selector-split confidence support: chosen %s | peer %s."),
*ResolveDashboardTemplateLaunchDisplayTitle(PeerTemplate),
*ResolveDashboardTemplateLaunchDisplayTitle(SelectedTemplate),
*BuildTemplateLaunchAnalyticsTieBreakSignalFragment(SelectedStats),
*BuildTemplateLaunchAnalyticsTieBreakSignalFragment(PeerStats)
);
}
}
else
{

View file

@ -0,0 +1,90 @@
# HyperTwist Phase 4 template launch support-kind rationale packet
Created on `2026-05-07`
Status:
- first-party HyperTwist packet
- bounded Phase `4` dashboard selector explanation slice
## Purpose
This packet surfaces the support-kind confidence guard result directly in the analytics tie-break rationale, so the dashboard can say when a winner was reinforced because its `SelectorSplitConfidence` bucket beat its `TemplateScopedOutcomeSignal` bucket on stored support-kind outcomes.
The open tasks were:
- stop leaving the new support-kind confidence guard implicit in selector internals
- explain selector-split-confidence-backed tie-break wins using the same stored support-kind evidence that justified the extra confidence point
- make the live dashboard detail, template launch history, and recap rationale more specific without widening the persisted selection schema again
It is not:
- another selector score change packet
- a new repository aggregation packet
- a new provenance schema packet
- a new dashboard analytics screen
## Scope
Bounded lane:
- add a concise formatter for the winning template's support-kind guard signal
- use that formatter only when `SelectorSplitConfidence` actually backed an analytics tie-break winner
- fold that guard result into the persisted analytics tie-break decision summary line
- let the existing selector detail fragment continue surfacing that richer rationale across live launch, history, and recap
Out of scope:
- changing support-kind bucket derivation
- changing the actual selector confidence thresholds
- adding another selection provenance field
- changing non-dashboard selection rationale
## Why this was the right next packet
The previous packet made the selector confidence bonus answer to the right repo-backed support-kind evidence.
That still left one operator-facing gap:
- the dashboard could now use the right support-kind split
- but the visible selector rationale still mostly described only the broader analytics tie-break decision
- and it did not explicitly say when the support-kind confidence guard itself had reinforced the winner
So the next honest move was:
- keep the evidence source exactly aligned with the selector logic
- format that support-kind guard result concisely
- and inject it into the same persisted rationale already shown by live launch detail, template history, and recap
## What landed
Primary code changes:
- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp`
- added a concise support-kind guard signal formatter for the winning template
- when `SelectorSplitConfidence` backs the analytics tie-break winner, the persisted decision summary now says that split-confidence beat template-signal on stored support-kind outcomes and includes the support-kind outcome comparison
- falls back to the prior concise wording if that support-kind fragment is unexpectedly unavailable
## Product effect
Analytics tie-break rationale is now closer to the real selector logic:
- broader template-scoped analytics tie-break wins still read the same way
- selector-split-confidence-backed wins now say that the winner beat the template-signal bucket on stored support-kind outcomes
- that richer rationale is persisted and automatically shows up on the live `[Template Launch]` detail, template launch history, and template-backed recap
## Acceptance criteria
- `SelectorSplitConfidence`-backed analytics tie-break rationale includes the stored support-kind guard result
- rationale continues to fall back safely if the support-kind fragment is unavailable
- live launch detail, template history, and recap all inherit the richer rationale through existing selection detail plumbing
- full product build succeeds
## Validation checklist
1. build `UnrealHyperTwist.sln` / `UnrealHyperTwistEditor`
2. confirm selector-split-confidence-backed analytics tie-break rationale mentions support-kind guard outperformance
3. confirm broader template-signal-only analytics tie-break rationale remains unchanged
4. confirm the richer rationale appears on live launch detail, template history, and recap through persisted selection provenance
That is the packet.