subtr-actor 1.0.0

Rocket League replay transformer
Documentation
use super::*;

#[test]
fn reports_used_split_mismatch() {
    let stats = BoostStats {
        amount_used: 20.0,
        amount_used_while_grounded: 8.0,
        amount_used_while_airborne: 9.0,
        ..Default::default()
    };

    let violations = boost_invariant_violations(&stats);

    assert!(violations
        .iter()
        .any(|violation| violation.kind == BoostInvariantKind::UsedSplitAmounts));
}

#[test]
fn accepts_matching_used_split() {
    let stats = BoostStats {
        amount_used: 20.0,
        amount_used_while_grounded: 8.0,
        amount_used_while_airborne: 12.0,
        ..Default::default()
    };

    let violations = boost_invariant_violations(&stats);

    assert!(!violations
        .iter()
        .any(|violation| violation.kind == BoostInvariantKind::UsedSplitAmounts));
}

#[test]
fn projected_current_boost_byte_uses_replay_byte_rounding() {
    let stats = BoostStats {
        amount_collected_small: boost_percent_to_amount(12.0),
        ..Default::default()
    };

    assert_eq!(
        projected_current_boost_byte(stats.amount_obtained() - stats.amount_used),
        31
    );
}

#[test]
fn current_amount_drift_warns_only_after_settle_window() {
    let player_id = PlayerId::Steam(1);
    let stats = BoostStats {
        amount_respawned: 85.0,
        ..Default::default()
    };
    let mut tracker = BoostCurrentAmountConsistencyTracker::new();

    for frame in 10..=10 + BOOST_CURRENT_AMOUNT_SETTLE_FRAME_WINDOW {
        tracker.observe(frame, frame as f32, &player_id, &stats, 79);
        assert!(tracker.unresolved_warnings().is_empty());
    }

    tracker.observe(
        11 + BOOST_CURRENT_AMOUNT_SETTLE_FRAME_WINDOW,
        1.0,
        &player_id,
        &stats,
        79,
    );
    let warnings = tracker.unresolved_warnings();
    let [warning] = warnings.as_slice() else {
        panic!("persistent current boost drift should warn");
    };
    assert_eq!(warning.first_frame, 10);
    assert_eq!(warning.ledger_projected_byte, 85);
    assert_eq!(warning.observed_byte, 79);
}

#[test]
fn current_amount_drift_clears_when_byte_resyncs() {
    let player_id = PlayerId::Steam(1);
    let stats = BoostStats {
        amount_respawned: 85.0,
        ..Default::default()
    };
    let mut tracker = BoostCurrentAmountConsistencyTracker::new();

    tracker.observe(10, 1.0, &player_id, &stats, 79);
    tracker.observe(11, 1.1, &player_id, &stats, 85);
    assert!(tracker.unresolved_warnings().is_empty());

    for frame in 20..=25 {
        tracker.observe(frame, frame as f32, &player_id, &stats, 79);
    }
    let warnings = tracker.unresolved_warnings();
    let [warning] = warnings.as_slice() else {
        panic!("second persistent current boost drift should warn");
    };
    assert_eq!(warning.first_frame, 20);
}

#[test]
fn current_amount_drift_ignores_small_byte_residue() {
    let player_id = PlayerId::Steam(1);
    let stats = BoostStats {
        amount_respawned: 85.0,
        ..Default::default()
    };
    let mut tracker = BoostCurrentAmountConsistencyTracker::new();

    for frame in 10..=100 {
        tracker.observe(frame, frame as f32, &player_id, &stats, 82);
        assert!(tracker.unresolved_warnings().is_empty());
    }
}