1use super::reducers::BoostStats;
2use crate::*;
3
4pub const BOOST_INVARIANT_BASE_TOLERANCE_RAW: f32 = 2.0;
11pub const BOOST_INVARIANT_PER_PICKUP_TOLERANCE_RAW: f32 = 0.3;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14pub enum BoostInvariantKind {
15 BucketAmounts,
16 NominalPickupAmount,
17 NominalStolenPickupAmount,
18 CurrentAmount,
19}
20
21impl BoostInvariantKind {
22 pub const ALL: [Self; 4] = [
23 Self::BucketAmounts,
24 Self::NominalPickupAmount,
25 Self::NominalStolenPickupAmount,
26 Self::CurrentAmount,
27 ];
28
29 pub fn label(self) -> &'static str {
30 match self {
31 Self::BucketAmounts => "bucket_amounts",
32 Self::NominalPickupAmount => "nominal_pickup_amount",
33 Self::NominalStolenPickupAmount => "nominal_stolen_pickup_amount",
34 Self::CurrentAmount => "current_amount",
35 }
36 }
37}
38
39#[derive(Debug, Clone, PartialEq)]
40pub struct BoostInvariantViolation {
41 pub kind: BoostInvariantKind,
42 pub expected: f32,
43 pub actual: f32,
44 pub diff: f32,
45 pub tolerance: f32,
46}
47
48impl BoostInvariantViolation {
49 pub fn message(&self) -> String {
50 match self.kind {
51 BoostInvariantKind::BucketAmounts => format!(
52 "amount_collected_big + amount_collected_small should match amount_collected \
53 (actual={:.1}, expected={:.1}, diff={:.1}, tolerance={:.1})",
54 self.actual, self.expected, self.diff, self.tolerance
55 ),
56 BoostInvariantKind::NominalPickupAmount => format!(
57 "amount_collected + overfill_total should match nominal pad value from pickup counts \
58 (actual={:.1}, expected={:.1}, diff={:.1}, tolerance={:.1})",
59 self.actual, self.expected, self.diff, self.tolerance
60 ),
61 BoostInvariantKind::NominalStolenPickupAmount => format!(
62 "amount_stolen + overfill_from_stolen should match nominal stolen pad value from pickup counts \
63 (actual={:.1}, expected={:.1}, diff={:.1}, tolerance={:.1})",
64 self.actual, self.expected, self.diff, self.tolerance
65 ),
66 BoostInvariantKind::CurrentAmount => format!(
67 "amount_obtained - amount_used should match observed current boost \
68 (actual={:.1}, expected={:.1}, diff={:.1}, tolerance={:.1})",
69 self.actual, self.expected, self.diff, self.tolerance
70 ),
71 }
72 }
73}
74
75pub fn nominal_pickup_amount_from_counts(stats: &BoostStats) -> f32 {
76 stats.big_pads_collected as f32 * BOOST_MAX_AMOUNT
77 + stats.small_pads_collected as f32 * boost_percent_to_amount(12.0)
78}
79
80pub fn nominal_stolen_pickup_amount_from_counts(stats: &BoostStats) -> f32 {
81 stats.big_pads_stolen as f32 * BOOST_MAX_AMOUNT
82 + stats.small_pads_stolen as f32 * boost_percent_to_amount(12.0)
83}
84
85fn nominal_pickup_tolerance(pickup_count: u32) -> f32 {
86 BOOST_INVARIANT_BASE_TOLERANCE_RAW
87 + BOOST_INVARIANT_PER_PICKUP_TOLERANCE_RAW * pickup_count as f32
88}
89
90fn push_violation(
91 violations: &mut Vec<BoostInvariantViolation>,
92 kind: BoostInvariantKind,
93 expected: f32,
94 actual: f32,
95 tolerance: f32,
96) {
97 let diff = (actual - expected).abs();
98 if diff > tolerance {
99 violations.push(BoostInvariantViolation {
100 kind,
101 expected,
102 actual,
103 diff,
104 tolerance,
105 });
106 }
107}
108
109pub fn boost_invariant_violations(
112 stats: &BoostStats,
113 observed_boost_amount: Option<f32>,
114) -> Vec<BoostInvariantViolation> {
115 let mut violations = Vec::new();
116
117 push_violation(
118 &mut violations,
119 BoostInvariantKind::BucketAmounts,
120 stats.amount_collected,
121 stats.amount_collected_big + stats.amount_collected_small,
122 1.0,
123 );
124 push_violation(
125 &mut violations,
126 BoostInvariantKind::NominalPickupAmount,
127 nominal_pickup_amount_from_counts(stats),
128 stats.amount_collected + stats.overfill_total,
129 nominal_pickup_tolerance(stats.big_pads_collected + stats.small_pads_collected),
130 );
131 push_violation(
132 &mut violations,
133 BoostInvariantKind::NominalStolenPickupAmount,
134 nominal_stolen_pickup_amount_from_counts(stats),
135 stats.amount_stolen + stats.overfill_from_stolen,
136 nominal_pickup_tolerance(stats.big_pads_stolen + stats.small_pads_stolen),
137 );
138 if let Some(current_boost_amount) = observed_boost_amount {
139 push_violation(
140 &mut violations,
141 BoostInvariantKind::CurrentAmount,
142 current_boost_amount,
143 stats.amount_obtained() - stats.amount_used,
144 1.0,
145 );
146 }
147
148 violations
149}