Skip to main content

fortress_rollback/input_queue/
prediction.rs

1//! Input prediction strategies for rollback networking.
2//!
3//! This module contains the [`PredictionStrategy`] trait and built-in implementations
4//! for predicting player inputs when actual inputs haven't arrived yet.
5//!
6//! # Determinism Requirement
7//!
8//! **CRITICAL**: All prediction strategies MUST be deterministic across all peers.
9//! Both peers must produce the exact same predicted input given the same arguments.
10//! If predictions differ between peers, they will desync during rollback.
11//!
12//! # Built-in Strategies
13//!
14//! - [`RepeatLastConfirmed`]: Repeats the last confirmed input (default)
15//! - [`BlankPrediction`]: Always returns the default (blank) input
16//!
17//! # Custom Strategies
18//!
19//! You can implement custom prediction strategies for game-specific behavior:
20//!
21//! ```ignore
22//! use fortress_rollback::{Frame, PredictionStrategy};
23//!
24//! struct MyPrediction;
25//!
26//! impl<I: Copy + Default> PredictionStrategy<I> for MyPrediction {
27//!     fn predict(&self, frame: Frame, last_confirmed_input: Option<I>, _player_index: usize) -> I {
28//!         // For a fighting game, you might predict "hold block" as a safe default
29//!         // This MUST be deterministic - don't use random values or timing-dependent data!
30//!         last_confirmed_input.unwrap_or_default()
31//!     }
32//! }
33//! ```
34
35use crate::Frame;
36
37/// Defines the strategy used to predict inputs when we haven't received the actual input yet.
38///
39/// Input prediction is crucial for rollback networking - when we need to advance the game
40/// but haven't received a remote player's input, we must predict what they will do.
41///
42/// # Determinism Requirement
43///
44/// **CRITICAL**: The prediction strategy MUST be deterministic across all peers.
45/// Both peers must produce the exact same predicted input given the same arguments.
46/// If predictions differ between peers, they will desync during rollback.
47///
48/// The default implementation (`RepeatLastConfirmed`) is deterministic because:
49/// - `last_confirmed_input` is synchronized across all peers via the network protocol
50/// - Both peers will have received and confirmed the same input before using it for prediction
51///
52/// # Custom Strategies
53///
54/// You can implement custom prediction strategies for game-specific behavior:
55///
56/// ```ignore
57/// struct MyPrediction;
58///
59/// impl<I: Copy + Default> PredictionStrategy<I> for MyPrediction {
60///     fn predict(&self, frame: Frame, last_confirmed_input: Option<I>, _player_index: usize) -> I {
61///         // For a fighting game, you might predict "hold block" as a safe default
62///         // This MUST be deterministic - don't use random values or timing-dependent data!
63///         last_confirmed_input.unwrap_or_default()
64///     }
65/// }
66/// ```
67pub trait PredictionStrategy<I: Copy + Default>: Send + Sync {
68    /// Predicts the input for a player when their actual input hasn't arrived yet.
69    ///
70    /// # Arguments
71    ///
72    /// * `frame` - The frame number we're predicting for
73    /// * `last_confirmed_input` - The most recent confirmed input from this player, if any.
74    ///   This is deterministic across all peers since confirmed inputs are synchronized.
75    /// * `player_index` - The index of the player we're predicting for
76    ///
77    /// # Returns
78    ///
79    /// The predicted input to use. Must be deterministic across all peers.
80    fn predict(&self, frame: Frame, last_confirmed_input: Option<I>, player_index: usize) -> I;
81}
82
83/// The default prediction strategy: repeat the last confirmed input.
84///
85/// This strategy is deterministic because `last_confirmed_input` is guaranteed
86/// to be the same across all peers after synchronization.
87///
88/// If there is no confirmed input yet (e.g., at the start of the game),
89/// this returns the default input value.
90#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
91pub struct RepeatLastConfirmed;
92
93impl std::fmt::Display for RepeatLastConfirmed {
94    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95        write!(f, "RepeatLastConfirmed")
96    }
97}
98
99impl<I: Copy + Default> PredictionStrategy<I> for RepeatLastConfirmed {
100    fn predict(&self, _frame: Frame, last_confirmed_input: Option<I>, _player_index: usize) -> I {
101        last_confirmed_input.unwrap_or_default()
102    }
103}
104
105/// A prediction strategy that always returns the default (blank) input.
106///
107/// This is useful when you want a "do nothing" prediction, which can be
108/// safer for some game types where repeating the last input could be dangerous.
109#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
110pub struct BlankPrediction;
111
112impl std::fmt::Display for BlankPrediction {
113    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
114        write!(f, "BlankPrediction")
115    }
116}
117
118impl<I: Copy + Default> PredictionStrategy<I> for BlankPrediction {
119    fn predict(&self, _frame: Frame, _last_confirmed: Option<I>, _player_index: usize) -> I {
120        I::default()
121    }
122}
123
124#[cfg(test)]
125#[allow(
126    clippy::panic,
127    clippy::unwrap_used,
128    clippy::expect_used,
129    clippy::indexing_slicing
130)]
131mod tests {
132    use super::*;
133    use serde::{Deserialize, Serialize};
134
135    #[repr(C)]
136    #[derive(Copy, Clone, PartialEq, Default, Serialize, Deserialize, Debug)]
137    struct TestInput {
138        inp: u8,
139    }
140
141    #[test]
142    fn test_blank_prediction_strategy() {
143        let strategy = BlankPrediction;
144
145        // Should always return default regardless of last confirmed
146        let result: TestInput = strategy.predict(Frame::new(0), Some(TestInput { inp: 42 }), 0);
147        assert_eq!(result, TestInput::default());
148
149        let result: TestInput = strategy.predict(Frame::new(10), None, 1);
150        assert_eq!(result, TestInput::default());
151    }
152
153    #[test]
154    fn test_repeat_last_confirmed_strategy() {
155        let strategy = RepeatLastConfirmed;
156
157        // Should return last confirmed input when available
158        let result: TestInput = strategy.predict(Frame::new(5), Some(TestInput { inp: 99 }), 0);
159        assert_eq!(result.inp, 99);
160
161        // Should return default when no last confirmed
162        let result: TestInput = strategy.predict(Frame::new(0), None, 0);
163        assert_eq!(result, TestInput::default());
164    }
165
166    #[test]
167    fn test_prediction_strategy_player_index_ignored_by_default() {
168        // Both default strategies ignore player_index
169        let repeat = RepeatLastConfirmed;
170        let blank = BlankPrediction;
171
172        let last_input = Some(TestInput { inp: 42 });
173
174        // Same result regardless of player_index
175        for player_idx in 0..10 {
176            let repeat_result: TestInput = repeat.predict(Frame::new(5), last_input, player_idx);
177            assert_eq!(repeat_result.inp, 42);
178
179            let blank_result: TestInput = blank.predict(Frame::new(5), last_input, player_idx);
180            assert_eq!(blank_result, TestInput::default());
181        }
182    }
183
184    #[test]
185    fn test_repeat_last_confirmed_debug() {
186        let strategy = RepeatLastConfirmed;
187        let debug_str = format!("{:?}", strategy);
188        assert!(debug_str.contains("RepeatLastConfirmed"));
189    }
190
191    #[test]
192    fn test_blank_prediction_debug() {
193        let strategy = BlankPrediction;
194        let debug_str = format!("{:?}", strategy);
195        assert!(debug_str.contains("BlankPrediction"));
196    }
197
198    #[test]
199    fn test_repeat_last_confirmed_display() {
200        assert_eq!(RepeatLastConfirmed.to_string(), "RepeatLastConfirmed");
201    }
202
203    #[test]
204    fn test_blank_prediction_display() {
205        assert_eq!(BlankPrediction.to_string(), "BlankPrediction");
206    }
207
208    #[test]
209    fn test_repeat_last_confirmed_copy() {
210        let a = RepeatLastConfirmed;
211        let b = a; // Copy
212        let debug_a = format!("{:?}", a);
213        let debug_b = format!("{:?}", b);
214        assert_eq!(debug_a, debug_b);
215    }
216
217    #[test]
218    fn test_blank_prediction_copy() {
219        let a = BlankPrediction;
220        let b = a; // Copy
221        let debug_a = format!("{:?}", a);
222        let debug_b = format!("{:?}", b);
223        assert_eq!(debug_a, debug_b);
224    }
225}