diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp index 8a3af9b..d609b4c 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp @@ -121,6 +121,10 @@ namespace HyperTwistCoachDashboardWidgetInternal const FHyperTwistTrainingTemplateLaunchScopedStats* TemplateStats ); + FString BuildTemplateLaunchAnalyticsTieBreakTemplateSignalDecisionSplitFragment( + const FHyperTwistTrainingTemplateLaunchScopedStats* TemplateStats + ); + FString BuildTemplateLaunchAnalyticsTieBreakTemplateSignalSupportBiasSnapshotFragment( const FHyperTwistTrainingTemplateLaunchScopedStats* TemplateStats ); @@ -221,7 +225,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 support %s | template-signal support-bias snapshot %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 decision %s | template-signal support-bias snapshot %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, @@ -236,6 +240,8 @@ namespace HyperTwistCoachDashboardWidgetInternal *BuildTemplateLaunchSelectionSplitAnalyticsFragment(TemplateStats), *BuildTemplateLaunchAnalyticsTieBreakSupportSplitFragment(TemplateStats), *BuildTemplateLaunchAnalyticsTieBreakTemplateSignalSupportSplitFragment(TemplateStats), + *BuildTemplateLaunchAnalyticsTieBreakTemplateSignalDecisionSplitFragment( + TemplateStats), *BuildTemplateLaunchAnalyticsTieBreakTemplateSignalSupportBiasSnapshotFragment( TemplateStats), *BuildTemplateLaunchAnalyticsTieBreakTemplateSignalConfidenceSnapshotFragment( @@ -766,6 +772,52 @@ namespace HyperTwistCoachDashboardWidgetInternal return Fragment; } + FString BuildTemplateLaunchAnalyticsTieBreakTemplateSignalDecisionSplitFragment( + const FHyperTwistTrainingTemplateLaunchScopedStats* TemplateStats + ) + { + if (TemplateStats == nullptr || !TemplateStats->IsStructurallyValid()) + { + return TEXT("no template-signal decision split is available for this template id yet"); + } + + const FHyperTwistTrainingTemplateLaunchSelectionScopedStats& NoSnapshotGuardStats = + TemplateStats + ->DashboardAnalyticsTieBreakTemplateScopedSignalNoSupportBiasSnapshotGuardStats; + const FHyperTwistTrainingTemplateLaunchSelectionScopedStats& SnapshotGuardStats = + TemplateStats + ->DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotGuardStats; + if (!NoSnapshotGuardStats.IsStructurallyValid() + && !SnapshotGuardStats.IsStructurallyValid()) + { + return TEXT("no template-signal decision split is available for this template id yet"); + } + + FString Fragment = FString::Printf( + TEXT("%s | %s"), + *BuildTemplateLaunchSelectionLaneAnalyticsSummary( + NoSnapshotGuardStats, + TEXT("no-snapshot-guard")), + *BuildTemplateLaunchSelectionLaneAnalyticsSummary( + SnapshotGuardStats, + TEXT("support-bias-snapshot-guard")) + ); + if (NoSnapshotGuardStats.IsStructurallyValid() + && SnapshotGuardStats.IsStructurallyValid() + && NoSnapshotGuardStats.GetOutcomeRunCount() > 0 + && SnapshotGuardStats.GetOutcomeRunCount() > 0) + { + Fragment += FString::Printf( + TEXT(" | snapshot-guard-minus-no-guard delta success %+.2f | delta mistakes %+.2f"), + SnapshotGuardStats.OverallSuccessRate - NoSnapshotGuardStats.OverallSuccessRate, + SnapshotGuardStats.AverageMistakesPerOutcomeRun + - NoSnapshotGuardStats.AverageMistakesPerOutcomeRun + ); + } + + return Fragment; + } + FString BuildTemplateLaunchAnalyticsTieBreakTemplateSignalSupportBiasSnapshotFragment( const FHyperTwistTrainingTemplateLaunchScopedStats* TemplateStats ) diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp index 904985e..d89dfbe 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp @@ -9166,6 +9166,10 @@ UHyperTwistTrainingRepositoryLibrary::ListTemplateLaunchScopedStatsForUser( DashboardAnalyticsTieBreakTemplateScopedSignalBroaderOutcomeOnlyAccumulator; FSelectionScopedAccumulator DashboardAnalyticsTieBreakTemplateScopedSignalConfidenceLaneBiasAccumulator; + FSelectionScopedAccumulator + DashboardAnalyticsTieBreakTemplateScopedSignalNoSupportBiasSnapshotGuardAccumulator; + FSelectionScopedAccumulator + DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotGuardAccumulator; FSelectionScopedAccumulator DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotEarnedAccumulator; FSelectionScopedAccumulator @@ -9359,6 +9363,24 @@ UHyperTwistTrainingRepositoryLibrary::ListTemplateLaunchScopedStatsForUser( Provenance.SelectionProvenance ); } + if (Provenance.SelectionProvenance.TemplateSignalSupportDecisionKind == + EHyperTwistTrainingTemplateLaunchSelectionTemplateSignalSupportDecisionKind:: + SupportBiasSnapshotGuard) + { + AccumulateSelectionScopedStats( + Accumulator + .DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotGuardAccumulator, + Provenance.SelectionProvenance + ); + } + else + { + AccumulateSelectionScopedStats( + Accumulator + .DashboardAnalyticsTieBreakTemplateScopedSignalNoSupportBiasSnapshotGuardAccumulator, + Provenance.SelectionProvenance + ); + } const EHyperTwistTrainingTemplateLaunchTemplateSignalSupportBiasSnapshotState SupportBiasSnapshotState = Provenance.SelectionProvenance.TemplateSignalSupportBiasSnapshot.State; @@ -9564,6 +9586,14 @@ UHyperTwistTrainingRepositoryLibrary::ListTemplateLaunchScopedStatsForUser( Accumulator .DashboardAnalyticsTieBreakTemplateScopedSignalConfidenceLaneBiasAccumulator ); + FinalizeSelectionScopedAccumulator( + Accumulator + .DashboardAnalyticsTieBreakTemplateScopedSignalNoSupportBiasSnapshotGuardAccumulator + ); + FinalizeSelectionScopedAccumulator( + Accumulator + .DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotGuardAccumulator + ); FinalizeSelectionScopedAccumulator( Accumulator .DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotEarnedAccumulator @@ -9620,6 +9650,14 @@ UHyperTwistTrainingRepositoryLibrary::ListTemplateLaunchScopedStatsForUser( Accumulator.DashboardAnalyticsTieBreakTemplateScopedSignalConfidenceLaneBiasAccumulator .Stats.PeerSupportKindDisposition = EHyperTwistTrainingTemplateLaunchSelectionPeerSupportKindDisposition::None; + Accumulator + .DashboardAnalyticsTieBreakTemplateScopedSignalNoSupportBiasSnapshotGuardAccumulator + .Stats.PeerSupportKindDisposition = + EHyperTwistTrainingTemplateLaunchSelectionPeerSupportKindDisposition::None; + Accumulator + .DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotGuardAccumulator + .Stats.PeerSupportKindDisposition = + EHyperTwistTrainingTemplateLaunchSelectionPeerSupportKindDisposition::None; Accumulator .DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotEarnedAccumulator .Stats.PeerSupportKindDisposition = @@ -9660,6 +9698,16 @@ UHyperTwistTrainingRepositoryLibrary::ListTemplateLaunchScopedStatsForUser( Accumulator .DashboardAnalyticsTieBreakTemplateScopedSignalConfidenceLaneBiasAccumulator .Stats; + Accumulator.Stats + .DashboardAnalyticsTieBreakTemplateScopedSignalNoSupportBiasSnapshotGuardStats = + Accumulator + .DashboardAnalyticsTieBreakTemplateScopedSignalNoSupportBiasSnapshotGuardAccumulator + .Stats; + Accumulator.Stats + .DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotGuardStats = + Accumulator + .DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotGuardAccumulator + .Stats; Accumulator.Stats .DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotEarnedStats = Accumulator diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h index 3016fe7..d55e854 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h @@ -2311,6 +2311,14 @@ struct FHyperTwistTrainingTemplateLaunchScopedStats FHyperTwistTrainingTemplateLaunchSelectionScopedStats DashboardAnalyticsTieBreakTemplateScopedSignalConfidenceLaneBiasStats; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FHyperTwistTrainingTemplateLaunchSelectionScopedStats + DashboardAnalyticsTieBreakTemplateScopedSignalNoSupportBiasSnapshotGuardStats; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FHyperTwistTrainingTemplateLaunchSelectionScopedStats + DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotGuardStats; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FHyperTwistTrainingTemplateLaunchSelectionScopedStats DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotEarnedStats; diff --git a/docs/arch/HYPERTWIST_PHASE4_TEMPLATE_SIGNAL_DECISION_SPLIT_ANALYTICS_PACKET_2026-05-07.md b/docs/arch/HYPERTWIST_PHASE4_TEMPLATE_SIGNAL_DECISION_SPLIT_ANALYTICS_PACKET_2026-05-07.md new file mode 100644 index 0000000..f742023 --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE4_TEMPLATE_SIGNAL_DECISION_SPLIT_ANALYTICS_PACKET_2026-05-07.md @@ -0,0 +1,46 @@ +# HyperTwist Phase 4 template-signal decision split analytics packet + +## Purpose + +This packet aggregates the newly persisted template-signal support decision kind back into repo-backed template analytics. The dashboard can now compare broader template-signal launches that did not need the historical support-bias snapshot guard against launches where that guard explicitly decided the winner. + +## Scope + +- add repo-backed template-scoped stats buckets for broader template-signal launches with and without the `SupportBiasSnapshotGuard` decision +- route stored `TemplateSignalSupportDecisionKind` through template launch scoped analytics derivation +- append a shared `template-signal decision` analytics fragment so the live dashboard launch row, template history, and recap all expose the new split +- report outcome deltas when both decision lanes have meaningful outcome history + +## What landed + +- `HyperTwistTrainingTypes.h` + - added `DashboardAnalyticsTieBreakTemplateScopedSignalNoSupportBiasSnapshotGuardStats` + - added `DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotGuardStats` +- `HyperTwistTrainingRepositoryLibrary.cpp` + - added matching scoped accumulators + - broader `TemplateScopedOutcomeSignal` runs with `TemplateSignalSupportDecisionKind = SupportBiasSnapshotGuard` now flow into the guard bucket + - all other broader template-signal runs, including older records with no retained decision kind, flow into the `no-support-bias-snapshot-guard` bucket +- `HyperTwistCoachDashboardWidget.cpp` + - added `BuildTemplateLaunchAnalyticsTieBreakTemplateSignalDecisionSplitFragment(...)` + - shared analytics detail now appends `template-signal decision ...` + - decision split shows both lanes plus `snapshot-guard-minus-no-guard` success and mistake deltas when both lanes have outcome history + +## Product effect + +- operators can now see whether broader template-signal launches that were explicitly decided by the historical support-bias snapshot guard are outperforming the broader template-signal launches that were selected without that guard +- template launch history and recap inherit the same decision split automatically because they already reuse the shared analytics detail formatter +- older runs remain analyzable without migration because missing decision-kind provenance is treated as `no-snapshot-guard` + +## Acceptance criteria + +- repo-backed template launch scoped stats retain separate broader template-signal buckets for `SupportBiasSnapshotGuard` and `no-snapshot-guard` +- shared dashboard analytics detail renders the new `template-signal decision` fragment anywhere template launch analytics are shown +- decision split deltas appear only when both decision lanes have outcome history +- older runs without retained `TemplateSignalSupportDecisionKind` continue to participate in analytics through the `no-snapshot-guard` bucket + +## Validation checklist + +1. full `Development Editor|Win64` build succeeds +2. confirm template launch scoped analytics route stored `TemplateSignalSupportDecisionKind = SupportBiasSnapshotGuard` into the new guard bucket +3. confirm shared analytics detail shows `template-signal decision no-snapshot-guard ... | support-bias-snapshot-guard ...` +4. confirm older broader template-signal runs without retained decision kind still surface under `no-snapshot-guard`