subtr-actor 1.0.0

Rocket League replay transformer
Documentation
use super::*;

fn frame(frame_number: usize) -> FrameInfo {
    FrameInfo {
        frame_number,
        time: frame_number as f32 * 0.1,
        dt: 0.1,
        seconds_remaining: None,
    }
}

fn rigid_body(position: glam::Vec3, velocity: glam::Vec3) -> boxcars::RigidBody {
    boxcars::RigidBody {
        sleeping: false,
        location: glam_to_vec(&position),
        rotation: boxcars::Quaternion {
            x: 0.0,
            y: 0.0,
            z: 0.0,
            w: 1.0,
        },
        linear_velocity: Some(glam_to_vec(&velocity)),
        angular_velocity: Some(glam_to_vec(&glam::Vec3::ZERO)),
    }
}

fn ball(position: glam::Vec3, velocity: glam::Vec3) -> BallFrameState {
    BallFrameState::Present(BallSample {
        rigid_body: rigid_body(position, velocity),
    })
}

fn player(player_id: PlayerId, is_team_0: bool, position: glam::Vec3) -> PlayerSample {
    PlayerSample {
        player_id,
        is_team_0,
        hitbox: CarHitbox::octane(),
        rigid_body: Some(rigid_body(position, glam::Vec3::ZERO)),
        boost_amount: None,
        last_boost_amount: None,
        boost_active: false,
        dodge_active: false,
        powerslide_active: false,
        match_goals: None,
        match_assists: None,
        match_saves: None,
        match_shots: None,
        match_score: None,
    }
}

fn players(samples: Vec<PlayerSample>) -> PlayerFrameState {
    PlayerFrameState { players: samples }
}

fn touch(player: PlayerId, is_team_0: bool, gap: f32) -> TouchEvent {
    TouchEvent {
        touch_id: None,
        time: 0.1,
        frame: 1,
        team_is_team_0: is_team_0,
        player: Some(player),
        player_position: None,
        closest_approach_distance: Some(gap),
        dodge_contact: false,
    }
}

#[test]
fn backboard_bounce_uses_primary_touch_not_last_contested_candidate() {
    let primary_player = boxcars::RemoteId::Steam(1);
    let secondary_player = boxcars::RemoteId::Steam(2);
    let primary_touch = touch(primary_player.clone(), true, 0.0);
    let secondary_touch = touch(secondary_player, false, 3.0);
    let touch_state = TouchState {
        touch_events: vec![primary_touch.clone(), secondary_touch],
        last_touch: Some(primary_touch),
        last_touch_player: Some(primary_player.clone()),
        last_touch_team_is_team_0: Some(true),
    };
    let mut calculator = BackboardBounceCalculator::new();

    calculator.update(
        &frame(1),
        &ball(
            glam::Vec3::new(0.0, 4700.0, 650.0),
            glam::Vec3::new(0.0, 500.0, 0.0),
        ),
        &PlayerFrameState::default(),
        &touch_state,
        &LivePlayState::active_play(),
    );
    let state = calculator.update(
        &frame(2),
        &ball(
            glam::Vec3::new(0.0, 4800.0, 650.0),
            glam::Vec3::new(0.0, -300.0, 0.0),
        ),
        &PlayerFrameState::default(),
        &TouchState::default(),
        &LivePlayState::active_play(),
    );

    let [bounce] = state.bounce_events.as_slice() else {
        panic!("expected exactly one backboard bounce");
    };
    assert_eq!(bounce.player, primary_player);
    assert!(bounce.is_team_0);
}

#[test]
fn surface_contact_between_touch_and_bounce_drops_attribution() {
    let shooter = boxcars::RemoteId::Steam(1);
    let touch_state = TouchState {
        touch_events: vec![touch(shooter.clone(), true, 0.0)],
        last_touch: None,
        last_touch_player: None,
        last_touch_team_is_team_0: None,
    };
    let mut calculator = BackboardBounceCalculator::new();

    calculator.update(
        &frame(1),
        &ball(
            glam::Vec3::new(0.0, 4700.0, 650.0),
            glam::Vec3::new(0.0, 500.0, 0.0),
        ),
        &players(vec![player(
            shooter.clone(),
            true,
            glam::Vec3::new(0.0, 3000.0, PLAYER_GROUND_Z_THRESHOLD + 200.0),
        )]),
        &touch_state,
        &LivePlayState::active_play(),
    );
    calculator.update(
        &frame(2),
        &ball(
            glam::Vec3::new(0.0, 4750.0, 650.0),
            glam::Vec3::new(0.0, 500.0, 0.0),
        ),
        &players(vec![player(
            shooter.clone(),
            true,
            glam::Vec3::new(0.0, 3000.0, PLAYER_GROUND_Z_THRESHOLD),
        )]),
        &TouchState::default(),
        &LivePlayState::active_play(),
    );
    let state = calculator.update(
        &frame(3),
        &ball(
            glam::Vec3::new(0.0, 4800.0, 650.0),
            glam::Vec3::new(0.0, -300.0, 0.0),
        ),
        &players(vec![player(
            shooter,
            true,
            glam::Vec3::new(0.0, 3000.0, PLAYER_GROUND_Z_THRESHOLD + 200.0),
        )]),
        &TouchState::default(),
        &LivePlayState::active_play(),
    );

    assert!(state.bounce_events.is_empty());
}