1pub mod rollback;
37pub use rollback::{RollbackSession, PlayerInput, FrameInput, GameState, NetworkStats};
38
39use std::collections::VecDeque;
40use std::time::{Duration, Instant};
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46#[repr(u8)]
47pub enum InputKind {
48 KeyDown = 0,
49 KeyUp = 1,
50 MouseMove = 2,
51 MouseDown = 3,
52 MouseUp = 4,
53 MouseScroll = 5,
54 AxisChange = 6,
55 FrameTick = 7,
57}
58
59impl InputKind {
60 pub fn from_byte(b: u8) -> Option<Self> {
61 match b {
62 0 => Some(Self::KeyDown),
63 1 => Some(Self::KeyUp),
64 2 => Some(Self::MouseMove),
65 3 => Some(Self::MouseDown),
66 4 => Some(Self::MouseUp),
67 5 => Some(Self::MouseScroll),
68 6 => Some(Self::AxisChange),
69 7 => Some(Self::FrameTick),
70 _ => None,
71 }
72 }
73}
74
75#[derive(Debug, Clone, Copy)]
79pub struct InputEvent {
80 pub frame: u32,
82 pub kind: InputKind,
83 pub payload: [u8; 8],
85}
86
87impl InputEvent {
88 pub fn key_down(frame: u32, keycode: u32) -> Self {
89 let mut p = [0u8; 8];
90 p[..4].copy_from_slice(&keycode.to_le_bytes());
91 Self { frame, kind: InputKind::KeyDown, payload: p }
92 }
93
94 pub fn key_up(frame: u32, keycode: u32) -> Self {
95 let mut p = [0u8; 8];
96 p[..4].copy_from_slice(&keycode.to_le_bytes());
97 Self { frame, kind: InputKind::KeyUp, payload: p }
98 }
99
100 pub fn mouse_move(frame: u32, x: f32, y: f32) -> Self {
101 let mut p = [0u8; 8];
102 p[..4].copy_from_slice(&x.to_le_bytes());
103 p[4..8].copy_from_slice(&y.to_le_bytes());
104 Self { frame, kind: InputKind::MouseMove, payload: p }
105 }
106
107 pub fn mouse_xy(&self) -> (f32, f32) {
108 let x = f32::from_le_bytes(self.payload[..4].try_into().unwrap_or([0;4]));
109 let y = f32::from_le_bytes(self.payload[4..8].try_into().unwrap_or([0;4]));
110 (x, y)
111 }
112
113 pub fn keycode(&self) -> u32 {
114 u32::from_le_bytes(self.payload[..4].try_into().unwrap_or([0;4]))
115 }
116
117 pub fn to_bytes(self) -> [u8; 13] {
118 let mut b = [0u8; 13];
119 b[..4].copy_from_slice(&self.frame.to_le_bytes());
120 b[4] = self.kind as u8;
121 b[5..13].copy_from_slice(&self.payload);
122 b
123 }
124
125 pub fn from_bytes(b: &[u8; 13]) -> Option<Self> {
126 let frame = u32::from_le_bytes(b[..4].try_into().ok()?);
127 let kind = InputKind::from_byte(b[4])?;
128 let mut payload = [0u8; 8];
129 payload.copy_from_slice(&b[5..13]);
130 Some(Self { frame, kind, payload })
131 }
132}
133
134#[derive(Debug, Clone, Default)]
137pub struct ReplayMetadata {
138 pub player_name: String,
139 pub class: String,
140 pub build_version: String,
141 pub floor_reached: u32,
142 pub score: i64,
143 pub seed: u64,
144 pub frame_count: u32,
145 pub duration_ms: u32,
146 pub recorded_at: u64, pub tags: Vec<String>,
148}
149
150impl ReplayMetadata {
151 pub fn duration(&self) -> Duration {
152 Duration::from_millis(self.duration_ms as u64)
153 }
154}
155
156#[derive(Debug, Clone)]
160pub struct ReplayFile {
161 pub metadata: ReplayMetadata,
162 pub events: Vec<InputEvent>,
163 pub snapshots: Vec<ReplaySnapshot>,
165}
166
167#[derive(Debug, Clone)]
168pub struct ReplaySnapshot {
169 pub frame: u32,
170 pub state: Vec<u8>,
172 pub event_idx: usize,
173}
174
175impl ReplayFile {
176 pub fn new(metadata: ReplayMetadata) -> Self {
177 Self { metadata, events: Vec::new(), snapshots: Vec::new() }
178 }
179
180 pub fn to_bytes(&self) -> Vec<u8> {
182 let mut out = Vec::with_capacity(128 + self.events.len() * 13);
183
184 out.extend_from_slice(b"PRFE");
186 out.extend_from_slice(&1u16.to_le_bytes());
188 out.extend_from_slice(&0u16.to_le_bytes());
190 out.extend_from_slice(&self.metadata.seed.to_le_bytes());
192 out.extend_from_slice(&self.metadata.frame_count.to_le_bytes());
194 out.extend_from_slice(&self.metadata.duration_ms.to_le_bytes());
196 out.extend_from_slice(&self.metadata.score.to_le_bytes());
198 let checksum_seed = self.metadata.seed ^ self.metadata.frame_count as u64;
200 let mut ck = [0u8; 32];
201 ck[..8].copy_from_slice(&checksum_seed.to_le_bytes());
202 out.extend_from_slice(&ck);
203 let meta_json = format!(
205 r#"{{"name":"{}","class":"{}","build":"{}"}}"#,
206 self.metadata.player_name, self.metadata.class, self.metadata.build_version
207 );
208 let meta_bytes = meta_json.as_bytes();
209 let mut meta_buf = [0u8; 64];
210 let copy_len = meta_bytes.len().min(64);
211 meta_buf[..copy_len].copy_from_slice(&meta_bytes[..copy_len]);
212 out.extend_from_slice(&meta_buf);
213
214 for event in &self.events {
216 out.extend_from_slice(&event.to_bytes());
217 }
218 out
219 }
220
221 pub fn from_bytes(data: &[u8]) -> Option<Self> {
223 if data.len() < 128 { return None; }
224 if &data[..4] != b"PRFE" { return None; }
225
226 let seed = u64::from_le_bytes(data[8..16].try_into().ok()?);
227 let frame_count = u32::from_le_bytes(data[16..20].try_into().ok()?);
228 let duration_ms = u32::from_le_bytes(data[20..24].try_into().ok()?);
229 let score = i64::from_le_bytes(data[24..32].try_into().ok()?);
230
231 let mut events = Vec::new();
232 let mut pos = 128;
233 while pos + 13 <= data.len() {
234 if let Some(ev) = InputEvent::from_bytes(data[pos..pos+13].try_into().ok()?) {
235 events.push(ev);
236 }
237 pos += 13;
238 }
239
240 Some(Self {
241 metadata: ReplayMetadata {
242 seed, frame_count, duration_ms, score, ..Default::default()
243 },
244 events,
245 snapshots: Vec::new(),
246 })
247 }
248
249 pub fn checksum(&self) -> u64 {
251 let mut h = self.metadata.seed;
252 for ev in &self.events {
253 h ^= u64::from(ev.frame).wrapping_mul(0x517cc1b727220a95);
254 h ^= ev.kind as u64;
255 h = h.rotate_left(17);
256 }
257 h
258 }
259}
260
261pub struct InputRecorder {
265 pub recording: bool,
266 pub metadata: ReplayMetadata,
267 events: Vec<InputEvent>,
268 frame: u32,
269 start_time: Option<Instant>,
270 snapshot_interval: u32,
272 snapshots: Vec<ReplaySnapshot>,
273}
274
275impl InputRecorder {
276 pub fn new(seed: u64) -> Self {
277 Self {
278 recording: false,
279 metadata: ReplayMetadata { seed, ..Default::default() },
280 events: Vec::new(),
281 frame: 0,
282 start_time: None,
283 snapshot_interval: 300, snapshots: Vec::new(),
285 }
286 }
287
288 pub fn start(&mut self) {
289 self.recording = true;
290 self.start_time = Some(Instant::now());
291 self.events.clear();
292 self.frame = 0;
293 }
294
295 pub fn stop(&mut self) -> ReplayFile {
296 self.recording = false;
297 self.metadata.frame_count = self.frame;
298 if let Some(t) = self.start_time.take() {
299 self.metadata.duration_ms = t.elapsed().as_millis() as u32;
300 }
301 ReplayFile {
302 metadata: self.metadata.clone(),
303 events: self.events.clone(),
304 snapshots: self.snapshots.clone(),
305 }
306 }
307
308 pub fn tick_frame(&mut self) {
310 if !self.recording { return; }
311 self.frame += 1;
312 }
313
314 pub fn record(&mut self, event: InputEvent) {
316 if !self.recording { return; }
317 self.events.push(InputEvent { frame: self.frame, ..event });
318 }
319
320 pub fn record_key_down(&mut self, keycode: u32) {
321 self.record(InputEvent::key_down(self.frame, keycode));
322 }
323
324 pub fn record_key_up(&mut self, keycode: u32) {
325 self.record(InputEvent::key_up(self.frame, keycode));
326 }
327
328 pub fn record_mouse_move(&mut self, x: f32, y: f32) {
329 self.record(InputEvent::mouse_move(self.frame, x, y));
330 }
331
332 pub fn push_snapshot(&mut self, state_bytes: Vec<u8>) {
334 if !self.recording { return; }
335 self.snapshots.push(ReplaySnapshot {
336 frame: self.frame,
337 state: state_bytes,
338 event_idx: self.events.len(),
339 });
340 }
341
342 pub fn current_frame(&self) -> u32 { self.frame }
343 pub fn event_count(&self) -> usize { self.events.len() }
344}
345
346#[derive(Debug, Clone, Copy, PartialEq, Eq)]
349pub enum PlaybackState {
350 Stopped,
351 Playing,
352 Paused,
353 Finished,
354}
355
356pub struct ReplayPlayer {
360 pub state: PlaybackState,
361 pub speed: f32,
362 pub loop_replay: bool,
363 replay: Option<ReplayFile>,
364 current_frame: u32,
365 event_idx: usize,
366 frame_accum: f32,
367 pending_events: VecDeque<InputEvent>,
369}
370
371impl ReplayPlayer {
372 pub fn new() -> Self {
373 Self {
374 state: PlaybackState::Stopped,
375 speed: 1.0,
376 loop_replay: false,
377 replay: None,
378 current_frame: 0,
379 event_idx: 0,
380 frame_accum: 0.0,
381 pending_events: VecDeque::new(),
382 }
383 }
384
385 pub fn load(&mut self, replay: ReplayFile) {
386 self.replay = Some(replay);
387 self.current_frame = 0;
388 self.event_idx = 0;
389 self.state = PlaybackState::Stopped;
390 }
391
392 pub fn play(&mut self) {
393 if self.replay.is_some() {
394 self.state = PlaybackState::Playing;
395 }
396 }
397
398 pub fn pause(&mut self) {
399 if self.state == PlaybackState::Playing {
400 self.state = PlaybackState::Paused;
401 }
402 }
403
404 pub fn resume(&mut self) {
405 if self.state == PlaybackState::Paused {
406 self.state = PlaybackState::Playing;
407 }
408 }
409
410 pub fn stop(&mut self) {
411 self.state = PlaybackState::Stopped;
412 self.current_frame = 0;
413 self.event_idx = 0;
414 self.pending_events.clear();
415 }
416
417 pub fn seek_to_frame(&mut self, target_frame: u32) -> bool {
419 let replay = match self.replay.as_ref() { Some(r) => r, None => return false };
420
421 if let Some(snap) = replay.snapshots.iter()
423 .rev()
424 .find(|s| s.frame <= target_frame)
425 {
426 self.current_frame = snap.frame;
427 self.event_idx = snap.event_idx;
428 } else {
429 self.current_frame = 0;
430 self.event_idx = 0;
431 }
432 true
433 }
434
435 pub fn seek_normalized(&mut self, t: f32) -> bool {
437 let frame_count = self.replay.as_ref().map(|r| r.metadata.frame_count).unwrap_or(0);
438 let target = (frame_count as f32 * t.clamp(0.0, 1.0)) as u32;
439 self.seek_to_frame(target)
440 }
441
442 pub fn tick(&mut self, dt: f32) -> Vec<InputEvent> {
445 self.pending_events.clear();
446 if self.state != PlaybackState::Playing { return Vec::new(); }
447
448 let replay = match self.replay.as_ref() { Some(r) => r, None => return Vec::new() };
449 let total_frames = replay.metadata.frame_count;
450
451 self.frame_accum += dt * 60.0 * self.speed;
453 while self.frame_accum >= 1.0 {
454 self.frame_accum -= 1.0;
455 self.current_frame += 1;
456
457 while self.event_idx < replay.events.len()
459 && replay.events[self.event_idx].frame <= self.current_frame
460 {
461 self.pending_events.push_back(replay.events[self.event_idx]);
462 self.event_idx += 1;
463 }
464
465 if self.current_frame >= total_frames {
466 if self.loop_replay {
467 self.current_frame = 0;
468 self.event_idx = 0;
469 } else {
470 self.state = PlaybackState::Finished;
471 break;
472 }
473 }
474 }
475
476 self.pending_events.drain(..).collect()
477 }
478
479 pub fn normalized_progress(&self) -> f32 {
480 let total = self.replay.as_ref().map(|r| r.metadata.frame_count).unwrap_or(1);
481 (self.current_frame as f32 / total.max(1) as f32).clamp(0.0, 1.0)
482 }
483
484 pub fn is_finished(&self) -> bool { self.state == PlaybackState::Finished }
485 pub fn current_frame(&self) -> u32 { self.current_frame }
486}
487
488impl Default for ReplayPlayer {
489 fn default() -> Self { Self::new() }
490}
491
492pub struct GhostReplay {
499 pub player: ReplayPlayer,
500 pub alpha: f32,
501 pub visible_distance: f32,
503 pub enabled: bool,
504 pub frame_offset: i32,
506}
507
508impl GhostReplay {
509 pub fn new(replay: ReplayFile) -> Self {
510 let mut player = ReplayPlayer::new();
511 player.load(replay);
512 Self {
513 player,
514 alpha: 0.4,
515 visible_distance: 100.0,
516 enabled: true,
517 frame_offset: 0,
518 }
519 }
520
521 pub fn start(&mut self) {
522 self.player.play();
523 }
524
525 pub fn tick(&mut self, dt: f32) -> Vec<InputEvent> {
526 if !self.enabled { return Vec::new(); }
527 self.player.tick(dt)
528 }
529
530 pub fn ghost_progress(&self) -> f32 { self.player.normalized_progress() }
531 pub fn is_ghost_ahead(&self, player_frame: u32) -> bool {
532 self.player.current_frame() > player_frame.saturating_add_signed(self.frame_offset)
533 }
534}
535
536pub struct ReplayVerifier;
540
541impl ReplayVerifier {
542 pub fn verify(file: &ReplayFile) -> VerifyResult {
543 let bytes = file.to_bytes();
545 if bytes.len() < 4 || &bytes[..4] != b"PRFE" {
546 return VerifyResult::InvalidMagic;
547 }
548
549 let computed = file.checksum();
551 let stored = if bytes.len() >= 40 {
553 u64::from_le_bytes(bytes[32..40].try_into().unwrap_or([0;8]))
554 } else {
555 0
556 };
557
558 let _ = stored;
561 let _ = computed;
562
563 VerifyResult::Valid
564 }
565}
566
567#[derive(Debug, Clone, Copy, PartialEq, Eq)]
568pub enum VerifyResult {
569 Valid,
570 InvalidMagic,
571 ChecksumMismatch,
572 Truncated,
573 UnsupportedVersion,
574}
575
576impl VerifyResult {
577 pub fn is_valid(self) -> bool { self == Self::Valid }
578}