diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp index f96f00f..950bbef 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp @@ -959,6 +959,37 @@ namespace HyperTwistCoachDashboardWidgetInternal ); } } + const FHyperTwistTrainingTemplateLaunchSelectionScopedStats& PlainDecisionSplitGuardStats = + TemplateStats + ->DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotGuardDecisionSplitGuardPlainStats; + const FHyperTwistTrainingTemplateLaunchSelectionScopedStats& + DecisionBiasSnapshotGuardStats = + TemplateStats + ->DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotGuardDecisionSplitGuardDecisionBiasSnapshotGuardStats; + if (PlainDecisionSplitGuardStats.IsStructurallyValid() + || DecisionBiasSnapshotGuardStats.IsStructurallyValid()) + { + Fragment += FString::Printf( + TEXT(" | %s | %s"), + *BuildTemplateLaunchSelectionLaneAnalyticsSummary( + PlainDecisionSplitGuardStats, + TEXT("plain-decision-split-guard")), + *BuildTemplateLaunchSelectionLaneAnalyticsSummary( + DecisionBiasSnapshotGuardStats, + TEXT("decision-bias-snapshot-guard")) + ); + if (PlainDecisionSplitGuardStats.GetOutcomeRunCount() > 0 + && DecisionBiasSnapshotGuardStats.GetOutcomeRunCount() > 0) + { + Fragment += FString::Printf( + TEXT(" | snapshot-guarded-minus-plain-decision-split delta success %+.2f | delta mistakes %+.2f"), + DecisionBiasSnapshotGuardStats.OverallSuccessRate + - PlainDecisionSplitGuardStats.OverallSuccessRate, + DecisionBiasSnapshotGuardStats.AverageMistakesPerOutcomeRun + - PlainDecisionSplitGuardStats.AverageMistakesPerOutcomeRun + ); + } + } return Fragment; } diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp index eeec8ee..482c8ca 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp @@ -9174,6 +9174,10 @@ UHyperTwistTrainingRepositoryLibrary::ListTemplateLaunchScopedStatsForUser( DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotGuardNoDecisionBiasAccumulator; FSelectionScopedAccumulator DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotGuardDecisionSplitGuardAccumulator; + FSelectionScopedAccumulator + DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotGuardDecisionSplitGuardPlainAccumulator; + FSelectionScopedAccumulator + DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotGuardDecisionSplitGuardDecisionBiasSnapshotGuardAccumulator; FSelectionScopedAccumulator DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotEarnedAccumulator; FSelectionScopedAccumulator @@ -9391,6 +9395,26 @@ UHyperTwistTrainingRepositoryLibrary::ListTemplateLaunchScopedStatsForUser( .DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotGuardDecisionSplitGuardAccumulator, Provenance.SelectionProvenance ); + if (Provenance.SelectionProvenance + .TemplateSignalSupportDecisionBiasSnapshotDecisionKind + == + EHyperTwistTrainingTemplateLaunchSelectionTemplateSignalSupportDecisionBiasSnapshotDecisionKind:: + DecisionBiasSnapshotGuard) + { + AccumulateSelectionScopedStats( + Accumulator + .DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotGuardDecisionSplitGuardDecisionBiasSnapshotGuardAccumulator, + Provenance.SelectionProvenance + ); + } + else + { + AccumulateSelectionScopedStats( + Accumulator + .DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotGuardDecisionSplitGuardPlainAccumulator, + Provenance.SelectionProvenance + ); + } } else { @@ -9794,6 +9818,16 @@ UHyperTwistTrainingRepositoryLibrary::ListTemplateLaunchScopedStatsForUser( Accumulator .DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotGuardDecisionSplitGuardAccumulator .Stats; + Accumulator.Stats + .DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotGuardDecisionSplitGuardPlainStats = + Accumulator + .DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotGuardDecisionSplitGuardPlainAccumulator + .Stats; + Accumulator.Stats + .DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotGuardDecisionSplitGuardDecisionBiasSnapshotGuardStats = + Accumulator + .DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotGuardDecisionSplitGuardDecisionBiasSnapshotGuardAccumulator + .Stats; Accumulator.Stats .DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotEarnedStats = Accumulator diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h index 03fdef3..1edb8c1 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h @@ -2391,6 +2391,14 @@ struct FHyperTwistTrainingTemplateLaunchScopedStats FHyperTwistTrainingTemplateLaunchSelectionScopedStats DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotGuardDecisionSplitGuardStats; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FHyperTwistTrainingTemplateLaunchSelectionScopedStats + DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotGuardDecisionSplitGuardPlainStats; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FHyperTwistTrainingTemplateLaunchSelectionScopedStats + DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotGuardDecisionSplitGuardDecisionBiasSnapshotGuardStats; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FHyperTwistTrainingTemplateLaunchSelectionScopedStats DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotEarnedStats; diff --git a/docs/arch/HYPERTWIST_PHASE4_TEMPLATE_SIGNAL_DECISION_BIAS_SNAPSHOT_GUARD_ANALYTICS_PACKET_2026-05-07.md b/docs/arch/HYPERTWIST_PHASE4_TEMPLATE_SIGNAL_DECISION_BIAS_SNAPSHOT_GUARD_ANALYTICS_PACKET_2026-05-07.md new file mode 100644 index 0000000..f094fad --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE4_TEMPLATE_SIGNAL_DECISION_BIAS_SNAPSHOT_GUARD_ANALYTICS_PACKET_2026-05-07.md @@ -0,0 +1,46 @@ +# HyperTwist Phase 4 template-signal decision-bias snapshot guard analytics packet + +## Purpose + +This packet aggregates the newly persisted historical decision-bias snapshot guard provenance back into repo-backed template analytics. The dashboard can now compare plain `DecisionSplitGuard` launches against the subset where earned-vs-non-earned decision-bias snapshot history became the final winner instead of treating every decision-split win as the same lane. + +## Scope + +- add repo-backed template-scoped stats buckets for `DecisionSplitGuard` launches with and without the persisted `DecisionBiasSnapshotGuard` marker +- route stored `TemplateSignalSupportDecisionBiasSnapshotDecisionKind` through template launch scoped analytics derivation +- extend the shared `template-signal decision` analytics fragment so live dashboard detail, template history, and recap expose the new nested decision-split split +- report outcome deltas when both plain decision-split and history-reinforced decision-bias-snapshot-guard lanes have outcome history + +## What landed + +- `HyperTwistTrainingTypes.h` + - added `DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotGuardDecisionSplitGuardPlainStats` + - added `DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotGuardDecisionSplitGuardDecisionBiasSnapshotGuardStats` +- `HyperTwistTrainingRepositoryLibrary.cpp` + - added matching scoped accumulators + - `DecisionSplitGuard` runs with stored `TemplateSignalSupportDecisionBiasSnapshotDecisionKind = DecisionBiasSnapshotGuard` now flow into the history-reinforced bucket + - `DecisionSplitGuard` runs without that retained marker, including older records, flow into the plain decision-split bucket +- `HyperTwistCoachDashboardWidget.cpp` + - extended `BuildTemplateLaunchAnalyticsTieBreakTemplateSignalDecisionSplitFragment(...)` + - shared analytics detail now appends plain decision-split vs history-reinforced decision-bias-snapshot-guard lane summaries under the existing decision split + - nested split shows `snapshot-guarded-minus-plain-decision-split` success and mistake deltas when both lanes have outcome history + +## Product effect + +- operators can now see whether the historical decision-bias snapshot guard subset of `DecisionSplitGuard` launches is actually outperforming plain decision-split launches +- template launch history and recap inherit the same nested split automatically because they already reuse the shared analytics detail formatter +- older decision-split runs remain analyzable without migration because missing historical guard provenance is treated as plain decision-split history + +## Acceptance criteria + +- repo-backed template launch scoped stats retain separate `DecisionSplitGuard` buckets for plain wins and `DecisionBiasSnapshotGuard`-reinforced wins +- shared dashboard analytics detail renders the nested decision-split split anywhere template launch analytics are shown +- nested historical-guard deltas appear only when both decision-split lanes have outcome history +- older `DecisionSplitGuard` runs without retained historical-guard provenance continue to participate in analytics through the plain decision-split bucket + +## Validation checklist + +1. full `Development Editor|Win64` build succeeds +2. confirm scoped analytics route stored `TemplateSignalSupportDecisionBiasSnapshotDecisionKind = DecisionBiasSnapshotGuard` into the reinforced bucket +3. confirm shared analytics detail shows `plain-decision-split-guard ... | decision-bias-snapshot-guard ...` +4. confirm older `DecisionSplitGuard` runs without retained historical-guard provenance still surface under `plain-decision-split-guard`