diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp index ff97ca6..d13f0d3 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp @@ -117,6 +117,10 @@ namespace HyperTwistCoachDashboardWidgetInternal const FHyperTwistTrainingTemplateLaunchScopedStats* TemplateStats ); + FString BuildTemplateLaunchAnalyticsTieBreakTemplateSignalSupportSplitFragment( + const FHyperTwistTrainingTemplateLaunchScopedStats* TemplateStats + ); + FString BuildTemplateLaunchAnalyticsTieBreakTemplateSignalConfidenceSnapshotFragment( const FHyperTwistTrainingTemplateLaunchScopedStats* TemplateStats ); @@ -192,7 +196,7 @@ namespace HyperTwistCoachDashboardWidgetInternal } return FString::Printf( - TEXT("avg recommended %d | avg focus %d | avg completed attempts %d | total success/failure/timeout/dnf %d/%d/%d/%d | avg mistakes %.2f | review-backed %d | coach-review %d | short set %d | selector split %s | tie-break support %s | template-signal confidence snapshot %s | peer support split %s | comparable-peer confidence %s | last launch %s | last completed %s"), + TEXT("avg recommended %d | avg focus %d | avg completed attempts %d | total success/failure/timeout/dnf %d/%d/%d/%d | avg mistakes %.2f | review-backed %d | coach-review %d | short set %d | selector split %s | tie-break support %s | template-signal support %s | template-signal confidence snapshot %s | peer support split %s | comparable-peer confidence %s | last launch %s | last completed %s"), TemplateStats->AverageRecommendedCaseCount, TemplateStats->AverageFocusCaseCount, TemplateStats->AverageCompletedAttemptCount, @@ -206,6 +210,7 @@ namespace HyperTwistCoachDashboardWidgetInternal TemplateStats->ShortReviewSetLaunchCount, *BuildTemplateLaunchSelectionSplitAnalyticsFragment(TemplateStats), *BuildTemplateLaunchAnalyticsTieBreakSupportSplitFragment(TemplateStats), + *BuildTemplateLaunchAnalyticsTieBreakTemplateSignalSupportSplitFragment(TemplateStats), *BuildTemplateLaunchAnalyticsTieBreakTemplateSignalConfidenceSnapshotFragment( TemplateStats), *BuildTemplateLaunchAnalyticsTieBreakPeerDispositionSplitFragment(TemplateStats), @@ -652,6 +657,51 @@ namespace HyperTwistCoachDashboardWidgetInternal return Fragment; } + FString BuildTemplateLaunchAnalyticsTieBreakTemplateSignalSupportSplitFragment( + const FHyperTwistTrainingTemplateLaunchScopedStats* TemplateStats + ) + { + if (TemplateStats == nullptr || !TemplateStats->IsStructurallyValid()) + { + return TEXT("no template-signal support split is available for this template id yet"); + } + + const FHyperTwistTrainingTemplateLaunchSelectionScopedStats& BroaderOutcomeOnlyStats = + TemplateStats->DashboardAnalyticsTieBreakTemplateScopedSignalBroaderOutcomeOnlyStats; + const FHyperTwistTrainingTemplateLaunchSelectionScopedStats& ConfidenceLaneBiasStats = + TemplateStats->DashboardAnalyticsTieBreakTemplateScopedSignalConfidenceLaneBiasStats; + if (!BroaderOutcomeOnlyStats.IsStructurallyValid() + && !ConfidenceLaneBiasStats.IsStructurallyValid()) + { + return TEXT("no template-signal support split is available for this template id yet"); + } + + FString Fragment = FString::Printf( + TEXT("%s | %s"), + *BuildTemplateLaunchSelectionLaneAnalyticsSummary( + BroaderOutcomeOnlyStats, + TEXT("broader-outcome-only")), + *BuildTemplateLaunchSelectionLaneAnalyticsSummary( + ConfidenceLaneBiasStats, + TEXT("confidence-lane-bias")) + ); + if (BroaderOutcomeOnlyStats.IsStructurallyValid() + && ConfidenceLaneBiasStats.IsStructurallyValid() + && BroaderOutcomeOnlyStats.GetOutcomeRunCount() > 0 + && ConfidenceLaneBiasStats.GetOutcomeRunCount() > 0) + { + Fragment += FString::Printf( + TEXT(" | bias-minus-broader delta success %+.2f | delta mistakes %+.2f"), + ConfidenceLaneBiasStats.OverallSuccessRate + - BroaderOutcomeOnlyStats.OverallSuccessRate, + ConfidenceLaneBiasStats.AverageMistakesPerOutcomeRun + - BroaderOutcomeOnlyStats.AverageMistakesPerOutcomeRun + ); + } + + return Fragment; + } + FString BuildTemplateLaunchAnalyticsTieBreakPeerDispositionSplitFragment( const FHyperTwistTrainingTemplateLaunchScopedStats* TemplateStats ) diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp index c83442c..e746574 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp @@ -9162,6 +9162,10 @@ UHyperTwistTrainingRepositoryLibrary::ListTemplateLaunchScopedStatsForUser( FSelectionScopedAccumulator DashboardPrimarySelectionAccumulator; FSelectionScopedAccumulator DashboardAnalyticsTieBreakSelectionAccumulator; FSelectionScopedAccumulator DashboardAnalyticsTieBreakTemplateScopedSignalAccumulator; + FSelectionScopedAccumulator + DashboardAnalyticsTieBreakTemplateScopedSignalBroaderOutcomeOnlyAccumulator; + FSelectionScopedAccumulator + DashboardAnalyticsTieBreakTemplateScopedSignalConfidenceLaneBiasAccumulator; FSelectionScopedAccumulator DashboardAnalyticsTieBreakTemplateScopedSignalNoRetainedComparablePeerConfidenceLaneAccumulator; FSelectionScopedAccumulator @@ -9331,6 +9335,24 @@ UHyperTwistTrainingRepositoryLibrary::ListTemplateLaunchScopedStatsForUser( Accumulator.DashboardAnalyticsTieBreakTemplateScopedSignalAccumulator, Provenance.SelectionProvenance ); + if (Provenance.SelectionProvenance.TemplateSignalSupportKind == + EHyperTwistTrainingTemplateLaunchSelectionTemplateSignalSupportKind:: + ConfidenceLaneBias) + { + AccumulateSelectionScopedStats( + Accumulator + .DashboardAnalyticsTieBreakTemplateScopedSignalConfidenceLaneBiasAccumulator, + Provenance.SelectionProvenance + ); + } + else + { + AccumulateSelectionScopedStats( + Accumulator + .DashboardAnalyticsTieBreakTemplateScopedSignalBroaderOutcomeOnlyAccumulator, + Provenance.SelectionProvenance + ); + } const EHyperTwistTrainingTemplateLaunchComparablePeerConfidenceSnapshotState SnapshotState = Provenance.SelectionProvenance.ComparablePeerConfidenceSnapshot.State; @@ -9498,6 +9520,14 @@ UHyperTwistTrainingRepositoryLibrary::ListTemplateLaunchScopedStatsForUser( FinalizeSelectionScopedAccumulator( Accumulator.DashboardAnalyticsTieBreakTemplateScopedSignalAccumulator ); + FinalizeSelectionScopedAccumulator( + Accumulator + .DashboardAnalyticsTieBreakTemplateScopedSignalBroaderOutcomeOnlyAccumulator + ); + FinalizeSelectionScopedAccumulator( + Accumulator + .DashboardAnalyticsTieBreakTemplateScopedSignalConfidenceLaneBiasAccumulator + ); FinalizeSelectionScopedAccumulator( Accumulator .DashboardAnalyticsTieBreakTemplateScopedSignalNoRetainedComparablePeerConfidenceLaneAccumulator @@ -9536,6 +9566,12 @@ UHyperTwistTrainingRepositoryLibrary::ListTemplateLaunchScopedStatsForUser( Accumulator.DashboardAnalyticsTieBreakTemplateScopedSignalAccumulator.Stats .PeerSupportKindDisposition = EHyperTwistTrainingTemplateLaunchSelectionPeerSupportKindDisposition::None; + Accumulator.DashboardAnalyticsTieBreakTemplateScopedSignalBroaderOutcomeOnlyAccumulator + .Stats.PeerSupportKindDisposition = + EHyperTwistTrainingTemplateLaunchSelectionPeerSupportKindDisposition::None; + Accumulator.DashboardAnalyticsTieBreakTemplateScopedSignalConfidenceLaneBiasAccumulator + .Stats.PeerSupportKindDisposition = + EHyperTwistTrainingTemplateLaunchSelectionPeerSupportKindDisposition::None; Accumulator .DashboardAnalyticsTieBreakTemplateScopedSignalNoRetainedComparablePeerConfidenceLaneAccumulator .Stats.PeerSupportKindDisposition = @@ -9556,6 +9592,14 @@ UHyperTwistTrainingRepositoryLibrary::ListTemplateLaunchScopedStatsForUser( Accumulator.DashboardAnalyticsTieBreakSelectionAccumulator.Stats; Accumulator.Stats.DashboardAnalyticsTieBreakTemplateScopedSignalStats = Accumulator.DashboardAnalyticsTieBreakTemplateScopedSignalAccumulator.Stats; + Accumulator.Stats.DashboardAnalyticsTieBreakTemplateScopedSignalBroaderOutcomeOnlyStats = + Accumulator + .DashboardAnalyticsTieBreakTemplateScopedSignalBroaderOutcomeOnlyAccumulator + .Stats; + Accumulator.Stats.DashboardAnalyticsTieBreakTemplateScopedSignalConfidenceLaneBiasStats = + Accumulator + .DashboardAnalyticsTieBreakTemplateScopedSignalConfidenceLaneBiasAccumulator + .Stats; Accumulator.Stats .DashboardAnalyticsTieBreakTemplateScopedSignalNoRetainedComparablePeerConfidenceLaneStats = Accumulator diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h index 7d3f0b1..1378c9e 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h @@ -2258,6 +2258,14 @@ struct FHyperTwistTrainingTemplateLaunchScopedStats FHyperTwistTrainingTemplateLaunchSelectionScopedStats DashboardAnalyticsTieBreakTemplateScopedSignalStats; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FHyperTwistTrainingTemplateLaunchSelectionScopedStats + DashboardAnalyticsTieBreakTemplateScopedSignalBroaderOutcomeOnlyStats; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FHyperTwistTrainingTemplateLaunchSelectionScopedStats + DashboardAnalyticsTieBreakTemplateScopedSignalConfidenceLaneBiasStats; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FHyperTwistTrainingTemplateLaunchSelectionScopedStats DashboardAnalyticsTieBreakTemplateScopedSignalNoRetainedComparablePeerConfidenceLaneStats; diff --git a/docs/arch/HYPERTWIST_PHASE4_TEMPLATE_SIGNAL_SUPPORT_SPLIT_ANALYTICS_PACKET_2026-05-07.md b/docs/arch/HYPERTWIST_PHASE4_TEMPLATE_SIGNAL_SUPPORT_SPLIT_ANALYTICS_PACKET_2026-05-07.md new file mode 100644 index 0000000..d02469e --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE4_TEMPLATE_SIGNAL_SUPPORT_SPLIT_ANALYTICS_PACKET_2026-05-07.md @@ -0,0 +1,49 @@ +# HyperTwist Phase 4 template-signal support split analytics packet + +## Purpose + +This packet pushes the persisted broader `TemplateScopedOutcomeSignal` support kind back into repo-backed template analytics. The dashboard can now separate broader analytics wins that were plain `broader-outcome-only` launches from wins where the `confidence-lane-bias` actually broke the tie, which makes it measurable whether that bias is improving outcomes or just resolving equality. + +## Scope + +- add repo-backed template analytics buckets for broader template-signal launches split by: + - `broader-outcome-only` + - `confidence-lane-bias` +- treat older broader template-signal launches with no retained support kind as `broader-outcome-only` +- surface the new split through the shared template analytics detail used by live detail, template history, and recap +- show success and mistake deltas when both support buckets have outcome history + +## What landed + +- `HyperTwistTrainingTypes.h` + - added `DashboardAnalyticsTieBreakTemplateScopedSignalBroaderOutcomeOnlyStats` + - added `DashboardAnalyticsTieBreakTemplateScopedSignalConfidenceLaneBiasStats` +- `HyperTwistTrainingRepositoryLibrary.cpp` + - widened the template launch scoped stats accumulator with matching broader template-signal support buckets + - routes `TemplateScopedOutcomeSignal` launches with: + - `confidence-lane-bias` into the confidence-lane-bias bucket + - `broader-outcome-only` or missing legacy support kind into the broader-outcome-only bucket + - finalizes and persists both new buckets into repo-backed template analytics +- `HyperTwistCoachDashboardWidget.cpp` + - added `BuildTemplateLaunchAnalyticsTieBreakTemplateSignalSupportSplitFragment(...)` + - shared analytics detail now appends a `template-signal support` section showing both support buckets plus `bias-minus-broader` deltas when both lanes have outcome runs + +## Product effect + +- live `[Template Launch]` detail now shows whether stored broader analytics outcomes are concentrated in plain broader-outcome wins or confidence-lane-bias wins +- template launch history and recap inherit that same repo-backed split automatically through the shared analytics formatter +- operators can now inspect whether the confidence-lane bias is actually outperforming plain broader template-signal tie-breaks + +## Acceptance criteria + +- repo-backed template analytics persist separate broader template-signal buckets for `broader-outcome-only` and `confidence-lane-bias` +- legacy broader template-signal launches without retained support kind fold into `broader-outcome-only` +- shared analytics detail shows both support buckets +- when both support buckets have outcome history, shared analytics detail reports `bias-minus-broader` success and mistake deltas + +## Validation checklist + +1. full `Development Editor|Win64` build succeeds +2. confirm repository analytics route `confidence-lane-bias` and `broader-outcome-only` launches into separate buckets +3. confirm legacy runs with no support kind still appear under `broader-outcome-only` +4. confirm live detail, template history, and recap all show the new `template-signal support` split through shared analytics detail