Skip to main content

tickwise_cli/
compare.rs

1//! The compare command: the first divergent tick between two recordings.
2
3use std::fs::File;
4use std::io::{BufReader, Read, Seek};
5use std::path::Path;
6use tickwise::compare::{CompareError, Outcome, first_divergence_from};
7use tickwise::format::{FormatError, RecReader};
8
9/// Rendered comparison output plus the verdict for the exit code.
10pub struct CompareOutput {
11    /// Human-readable report text.
12    pub text: String,
13    /// True when the recordings diverge.
14    pub diverged: bool,
15}
16
17fn describe<R: Read + Seek>(reader: &RecReader<R>) -> String {
18    let meta = &reader.header().meta;
19    let game = if meta.game_id.is_empty() {
20        "unset"
21    } else {
22        &meta.game_id
23    };
24    format!(
25        "{} ticks, game {game}, seed {:#x}",
26        reader.tick_count(),
27        meta.rng_seed
28    )
29}
30
31/// Compares the recordings at the two paths.
32pub fn render<A: AsRef<Path>, B: AsRef<Path>>(a: A, b: B) -> Result<CompareOutput, CompareError> {
33    let path_a = a.as_ref();
34    let path_b = b.as_ref();
35    let mut reader_a = RecReader::open(BufReader::new(
36        File::open(path_a).map_err(FormatError::from)?,
37    ))?;
38    let mut reader_b = RecReader::open(BufReader::new(
39        File::open(path_b).map_err(FormatError::from)?,
40    ))?;
41
42    let mut s = String::new();
43    s.push_str(&format!(
44        "comparing {} and {}\n\n",
45        path_a.display(),
46        path_b.display()
47    ));
48    s.push_str(&format!("  first          {}\n", describe(&reader_a)));
49    s.push_str(&format!("  second         {}\n\n", describe(&reader_b)));
50
51    let report = first_divergence_from(&mut reader_a, &mut reader_b)?;
52
53    for warning in &report.warnings {
54        s.push_str(&format!("  warning        {warning}\n"));
55    }
56    if !report.warnings.is_empty() {
57        s.push('\n');
58    }
59
60    s.push_str(&format!("  verdict        {report}\n\n"));
61
62    let diverged = match &report.outcome {
63        Outcome::Identical { .. } => {
64            s.push_str(
65                "  next           the recordings agree. To self-check your own replay\n\
66                 \x20                determinism, replay one session and record it again,\n\
67                 \x20                then compare the two recordings\n",
68            );
69            false
70        }
71        Outcome::Diverged(d) => {
72            s.push_str(&format!(
73                "  next           Pass 2: replay each recording in your own loop with\n\
74                 \x20                dump_at_ticks = [{}] to produce two .dump files, then run\n\
75                 \x20                tickwise diff a.dump b.dump\n",
76                d.tick
77            ));
78            true
79        }
80    };
81
82    Ok(CompareOutput { text: s, diverged })
83}