redguard-preservation 0.6.1

A CLI tool to preserve Redguard files
Documentation
use super::*;

fn approx_eq(a: f32, b: f32) {
    let diff = (a - b).abs();
    assert!(diff < 1.0e-6, "expected {b}, got {a}, diff {diff}");
}

#[test]
fn decode_position_applies_fixed_point_scaling_on_all_axes() {
    let pos = positioning::decode_position(1, -2, 3);

    approx_eq(pos[0], -(256.0 / 5120.0));
    approx_eq(pos[1], 512.0 / 5120.0);
    approx_eq(pos[2], -((0x00FF_FFFF_i64 - (3_i64 * 256)) as f32) / 5120.0);
}

#[test]
fn decode_position_does_not_collapse_small_values_to_origin() {
    let pos = positioning::decode_position(1, 1, 1);

    assert!(pos[0].abs() > 0.01, "x too close to origin: {}", pos[0]);
    assert!(pos[1].abs() > 0.01, "y too close to origin: {}", pos[1]);
}

fn pack_cmd_type0(opcode: u8, handle: i16, vertex: i16) -> [u8; 3] {
    let handle_u = (handle as u16) & 0x3FF;
    let vertex_u = (vertex as u16) & 0x3FF;
    let packed: u32 = (opcode as u32 & 0xF) | ((handle_u as u32) << 4) | ((vertex_u as u32) << 14);
    [packed as u8, (packed >> 8) as u8, (packed >> 16) as u8]
}

#[test]
fn ragr_command_decodes_vertex_and_handle_for_opcode_0() {
    let [b0, b1, b2] = pack_cmd_type0(0, 3, 47);
    let cmd = metadata::decode_ragr_command(b0, b1, b2);

    assert_eq!(cmd.opcode, 0);
    assert!(cmd.sets_attachment());
    assert_eq!(cmd.handle_index(), 3);
    assert_eq!(cmd.vertex_index(), 47);
}

#[test]
fn ragr_command_decodes_negative_vertex_index() {
    let [b0, b1, b2] = pack_cmd_type0(0, -1, -5);
    let cmd = metadata::decode_ragr_command(b0, b1, b2);

    assert_eq!(cmd.opcode, 0);
    assert_eq!(cmd.handle_index(), -1);
    assert_eq!(cmd.vertex_index(), -5);
}

#[test]
fn ragr_command_opcode_4_is_sound_trigger_not_attachment() {
    let [b0, b1, b2] = pack_cmd_type0(4, 0, 100);
    let cmd = metadata::decode_ragr_command(b0, b1, b2);

    assert_eq!(cmd.opcode, 4);
    assert!(!cmd.sets_attachment());
}

#[test]
fn ragr_command_opcode_6_does_not_set_attachment() {
    let cmd = metadata::decode_ragr_command(0x06, 0x00, 0x00);
    assert_eq!(cmd.opcode, 6);
    assert!(!cmd.sets_attachment());
}

#[test]
fn marker_metadata_reads_positions_before_auxiliary_bytes() {
    let mut data = 2u32.to_le_bytes().to_vec();
    for coordinate in [20i32, -40, 65536, 100, 60, 65536] {
        data.extend_from_slice(&coordinate.to_le_bytes());
    }
    data.extend_from_slice(&[7, 9]);
    let rgm = RgmFile {
        sections: vec![RgmSection::Mpm(
            RgmSectionHeader {
                name: *b"MPMK",
                data_length: 30,
                record_count: None,
            },
            data,
        )],
    };
    let metadata = export_rgm_metadata_json(&rgm, None);
    let markers = metadata["markers"].as_array().unwrap();
    assert_eq!(markers.len(), 2);
    assert_eq!(markers[0]["position"][0], -1.0);
    assert_eq!(markers[0]["position"][1], 2.0);
    assert_eq!(markers[1]["position"][0], -5.0);
    assert_eq!(markers[1]["position"][1], -3.0);
    assert_eq!(markers[0]["reserved"], 7);
    assert_eq!(markers[1]["reserved"], 9);
}

#[test]
fn marker_metadata_rejects_truncated_tables_before_allocating() {
    let rgm = RgmFile {
        sections: vec![RgmSection::Mpm(
            RgmSectionHeader {
                name: *b"MPMK",
                data_length: 4,
                record_count: None,
            },
            u32::MAX.to_le_bytes().to_vec(),
        )],
    };
    let metadata = export_rgm_metadata_json(&rgm, None);
    assert_eq!(metadata["markers"], serde_json::json!([]));
}