subtr_actor/stats/accumulators/
flick.rs1use super::*;
2
3const FLICK_HIGH_CONFIDENCE: f32 = 0.80;
4
5#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, ts_rs::TS)]
7#[ts(export)]
8pub struct FlickStats {
9 pub count: u32,
10 pub high_confidence_count: u32,
11 pub is_last_flick: bool,
12 pub last_flick_time: Option<f32>,
13 pub last_flick_frame: Option<usize>,
14 pub time_since_last_flick: Option<f32>,
15 pub frames_since_last_flick: Option<usize>,
16 pub last_confidence: Option<f32>,
17 pub best_confidence: f32,
18 pub cumulative_confidence: f32,
19 pub cumulative_setup_duration: f32,
20 pub cumulative_ball_speed_change: f32,
21 #[serde(default, skip_serializing_if = "LabeledCounts::is_empty")]
22 pub labeled_event_counts: LabeledCounts,
23}
24
25impl FlickStats {
26 pub fn average_confidence(&self) -> f32 {
27 if self.count == 0 {
28 0.0
29 } else {
30 self.cumulative_confidence / self.count as f32
31 }
32 }
33
34 pub fn average_setup_duration(&self) -> f32 {
35 if self.count == 0 {
36 0.0
37 } else {
38 self.cumulative_setup_duration / self.count as f32
39 }
40 }
41
42 pub fn average_ball_speed_change(&self) -> f32 {
43 if self.count == 0 {
44 0.0
45 } else {
46 self.cumulative_ball_speed_change / self.count as f32
47 }
48 }
49
50 fn record_event(&mut self, event: &FlickEvent) {
51 self.labeled_event_counts.increment([
52 confidence_band_label(event.confidence >= FLICK_HIGH_CONFIDENCE),
53 flick_kind_label(&event.kind),
54 flick_setup_rotation_direction_label(&event.setup_rotation_direction),
55 ]);
56 self.sync_legacy_counts();
57 self.last_flick_time = Some(event.time);
58 self.last_flick_frame = Some(event.frame);
59 self.last_confidence = Some(event.confidence);
60 self.best_confidence = self.best_confidence.max(event.confidence);
61 self.cumulative_confidence += event.confidence;
62 self.cumulative_setup_duration += event.setup_duration;
63 self.cumulative_ball_speed_change += event.ball_speed_change;
64 }
65
66 pub fn event_count_with_labels(&self, labels: &[StatLabel]) -> u32 {
67 self.labeled_event_counts.count_matching(labels)
68 }
69
70 pub fn complete_labeled_event_counts(&self) -> LabeledCounts {
71 LabeledCounts::complete_from_label_sets(
72 &[
73 &CONFIDENCE_BAND_LABELS,
74 &FLICK_KIND_LABELS,
75 &FLICK_SETUP_ROTATION_DIRECTION_LABELS,
76 ],
77 &self.labeled_event_counts,
78 )
79 }
80
81 fn sync_legacy_counts(&mut self) {
82 self.count = self.labeled_event_counts.total();
83 self.high_confidence_count = self.event_count_with_labels(&[confidence_band_label(true)]);
84 }
85}
86
87#[derive(Debug, Clone, Default, PartialEq)]
89pub struct FlickStatsAccumulator {
90 player_stats: HashMap<PlayerId, FlickStats>,
91 current_last_flick_player: Option<PlayerId>,
92}
93
94impl FlickStatsAccumulator {
95 pub fn new() -> Self {
96 Self::default()
97 }
98
99 pub fn player_stats(&self) -> &HashMap<PlayerId, FlickStats> {
100 &self.player_stats
101 }
102
103 pub fn begin_sample(&mut self, frame: &FrameInfo) {
104 for stats in self.player_stats.values_mut() {
105 stats.is_last_flick = false;
106 stats.time_since_last_flick = stats
107 .last_flick_time
108 .map(|time| (frame.time - time).max(0.0));
109 stats.frames_since_last_flick = stats
110 .last_flick_frame
111 .map(|last_frame| frame.frame_number.saturating_sub(last_frame));
112 }
113
114 if let Some(player_id) = self.current_last_flick_player.as_ref() {
115 if let Some(stats) = self.player_stats.get_mut(player_id) {
116 stats.is_last_flick = true;
117 }
118 }
119 }
120
121 pub fn apply_event(&mut self, event: &FlickEvent, frame: &FrameInfo) {
122 let stats = self.player_stats.entry(event.player.clone()).or_default();
123 stats.record_event(event);
124 stats.is_last_flick = true;
125 stats.time_since_last_flick = Some((frame.time - event.time).max(0.0));
126 stats.frames_since_last_flick = Some(frame.frame_number.saturating_sub(event.frame));
127
128 self.current_last_flick_player = Some(event.player.clone());
129 }
130
131 pub fn reset_current_last_event_marker(&mut self) {
132 self.current_last_flick_player = None;
133 }
134}