use super::*;
use crate::event::metrics::{
    RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH, RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN,
};

#[test]
fn parses_save_world_selection_context_probe_from_fixed_world_block() {
    let mut bytes = vec![0u8; 0x8000];
    let chunk_tag_offset = 0x3ceusize;
    let payload_offset = chunk_tag_offset + 4;
    bytes[chunk_tag_offset..chunk_tag_offset + 4]
        .copy_from_slice(&RT3_SAVE_WORLD_BLOCK_CHUNK_TAG.to_le_bytes());
    bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_SELECTED_COMPANY_ID_RELATIVE_OFFSET
        ..payload_offset + RT3_SAVE_WORLD_BLOCK_SELECTED_COMPANY_ID_RELATIVE_OFFSET + 4]
        .copy_from_slice(&7u32.to_le_bytes());
    bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_SELECTED_CHAIRMAN_PROFILE_ID_RELATIVE_OFFSET
        ..payload_offset + RT3_SAVE_WORLD_BLOCK_SELECTED_CHAIRMAN_PROFILE_ID_RELATIVE_OFFSET + 4]
        .copy_from_slice(&9u32.to_le_bytes());
    bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_CHAIRMAN_SLOT_SELECTOR_RELATIVE_OFFSET
        ..payload_offset
            + RT3_SAVE_WORLD_BLOCK_CHAIRMAN_SLOT_SELECTOR_RELATIVE_OFFSET
            + RT3_SAVE_WORLD_BLOCK_CHAIRMAN_SLOT_COUNT]
        .copy_from_slice(&[3, 1, 4, 1, 5, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
    bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_CAMPAIGN_OVERRIDE_FLAG_RELATIVE_OFFSET] = 1;
    for (slot_index, role_gate) in [2u8, 1, 0, 2].into_iter().enumerate() {
        bytes[payload_offset
            + RT3_SAVE_WORLD_BLOCK_CHAIRMAN_ROLE_GATE_RELATIVE_OFFSET
            + slot_index * RT3_SAVE_WORLD_BLOCK_CHAIRMAN_ROLE_GATE_STRIDE] = role_gate;
    }
    let next_chunk_offset = payload_offset + RT3_SAVE_WORLD_BLOCK_LEN;
    bytes[next_chunk_offset..next_chunk_offset + 4]
        .copy_from_slice(&RT3_SAVE_WORLD_BLOCK_NEXT_CHUNK_TAG.to_le_bytes());

    let probe = parse_save_world_selection_context_probe(
        &bytes,
        Some("gms"),
        Some(&SmpContainerProfile {
            profile_family: "rt3-105-save-container-v1".to_string(),
            profile_evidence: vec![],
            is_known_profile: true,
        }),
    )
    .expect("selection-context probe should parse");

    assert_eq!(probe.chunk_tag_offset, chunk_tag_offset);
    assert_eq!(probe.payload_offset, payload_offset);
    assert_eq!(probe.selected_company_id, 7);
    assert_eq!(probe.selected_chairman_profile_id, 9);
    assert_eq!(probe.chairman_slot_selectors[..6], [3, 1, 4, 1, 5, 9]);
    assert_eq!(probe.campaign_override_flag, 1);
    assert_eq!(probe.chairman_role_gate_bytes[..4], [2, 1, 0, 2]);
}

#[test]
fn parses_save_world_economic_tuning_probe_from_fixed_world_block() {
    let mut bytes = vec![0u8; 0x8000];
    let chunk_tag_offset = 0x3ceusize;
    let payload_offset = chunk_tag_offset + 4;
    bytes[chunk_tag_offset..chunk_tag_offset + 4]
        .copy_from_slice(&RT3_SAVE_WORLD_BLOCK_CHUNK_TAG.to_le_bytes());
    bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_ECONOMIC_TUNING_MIRROR_RELATIVE_OFFSET
        ..payload_offset + RT3_SAVE_WORLD_BLOCK_ECONOMIC_TUNING_MIRROR_RELATIVE_OFFSET + 4]
        .copy_from_slice(&1.5f32.to_bits().to_le_bytes());
    for (lane_index, relative_offset) in
        RT3_SAVE_WORLD_BLOCK_ECONOMIC_TUNING_PRIMARY_RELATIVE_OFFSETS
            .iter()
            .copied()
            .enumerate()
    {
        let value = (lane_index as f32) + 10.25f32;
        bytes[payload_offset + relative_offset..payload_offset + relative_offset + 4]
            .copy_from_slice(&value.to_bits().to_le_bytes());
    }
    let next_chunk_offset = payload_offset + RT3_SAVE_WORLD_BLOCK_LEN;
    bytes[next_chunk_offset..next_chunk_offset + 4]
        .copy_from_slice(&RT3_SAVE_WORLD_BLOCK_NEXT_CHUNK_TAG.to_le_bytes());

    let probe = parse_save_world_economic_tuning_probe(
        &bytes,
        Some("gms"),
        Some(&SmpContainerProfile {
            profile_family: "rt3-105-save-container-v1".to_string(),
            profile_evidence: vec![],
            is_known_profile: true,
        }),
    )
    .expect("world economic tuning probe should parse");

    assert_eq!(probe.chunk_tag_offset, chunk_tag_offset);
    assert_eq!(probe.payload_offset, payload_offset);
    assert_eq!(probe.mirror_lane.relative_offset_hex, "0xbda");
    assert_eq!(probe.mirror_lane.value_f32, 1.5);
    assert_eq!(probe.tuning_lanes.len(), 6);
    assert_eq!(probe.tuning_lanes[0].relative_offset_hex, "0xbde");
    assert_eq!(probe.tuning_lanes[0].value_f32, 10.25);
    assert_eq!(probe.tuning_lanes[5].relative_offset_hex, "0xbf2");
    assert_eq!(probe.tuning_lanes[5].value_f32, 15.25);
}

#[test]
fn parses_save_world_issue_37_probe_from_fixed_world_block() {
    let mut bytes = vec![0u8; 0x8000];
    let chunk_tag_offset = 0x3ceusize;
    let payload_offset = chunk_tag_offset + 4;
    bytes[chunk_tag_offset..chunk_tag_offset + 4]
        .copy_from_slice(&RT3_SAVE_WORLD_BLOCK_CHUNK_TAG.to_le_bytes());
    bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_VALUE_RELATIVE_OFFSET
        ..payload_offset + RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_VALUE_RELATIVE_OFFSET + 4]
        .copy_from_slice(&3u32.to_le_bytes());
    bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_MULTIPLIER_RELATIVE_OFFSET
        ..payload_offset + RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_MULTIPLIER_RELATIVE_OFFSET + 4]
        .copy_from_slice(&0.06f32.to_bits().to_le_bytes());
    let next_chunk_offset = payload_offset + RT3_SAVE_WORLD_BLOCK_LEN;
    bytes[next_chunk_offset..next_chunk_offset + 4]
        .copy_from_slice(&RT3_SAVE_WORLD_BLOCK_NEXT_CHUNK_TAG.to_le_bytes());

    let probe = parse_save_world_issue_37_probe(
        &bytes,
        Some("gms"),
        Some(&SmpContainerProfile {
            profile_family: "rt3-105-save-container-v1".to_string(),
            profile_evidence: vec![],
            is_known_profile: true,
        }),
    )
    .expect("world issue-0x37 probe should parse");

    assert_eq!(probe.chunk_tag_offset, chunk_tag_offset);
    assert_eq!(probe.payload_offset, payload_offset);
    assert_eq!(probe.issue_value_lane.relative_offset_hex, "0x29");
    assert_eq!(probe.issue_value_lane.value_i32, 3);
    assert_eq!(probe.issue_37_raw_u8, 3);
    assert_eq!(probe.issue_37_raw_hex, "0x03");
    assert_eq!(probe.issue_38_raw_u8, 0);
    assert_eq!(probe.issue_38_raw_hex, "0x00");
    assert_eq!(probe.issue_39_raw_u8, 0);
    assert_eq!(probe.issue_39_raw_hex, "0x00");
    assert_eq!(probe.issue_3a_raw_u8, 0);
    assert_eq!(probe.issue_3a_raw_hex, "0x00");
    assert_eq!(probe.multiplier_lane.relative_offset_hex, "0x25");
    assert!((probe.multiplier_lane.value_f32 - 0.06).abs() < f32::EPSILON);
}

#[test]
fn parses_save_world_finance_neighborhood_probe_from_fixed_world_block() {
    let mut bytes = vec![0u8; 0x8000];
    let chunk_tag_offset = 0x3ceusize;
    let payload_offset = chunk_tag_offset + 4;
    bytes[chunk_tag_offset..chunk_tag_offset + 4]
        .copy_from_slice(&RT3_SAVE_WORLD_BLOCK_CHUNK_TAG.to_le_bytes());
    for index in 0..RT3_SAVE_WORLD_BLOCK_FINANCE_NEIGHBORHOOD_WINDOW_LEN_DWORDS {
        let relative_offset =
            RT3_SAVE_WORLD_BLOCK_FINANCE_NEIGHBORHOOD_ROOT_RELATIVE_OFFSET + index * 4;
        bytes[payload_offset + relative_offset..payload_offset + relative_offset + 4]
            .copy_from_slice(&((index as u32) + 1).to_le_bytes());
    }
    bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_STOCK_POLICY_RELATIVE_OFFSET] = 1;
    bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_BOND_POLICY_RELATIVE_OFFSET] = 2;
    bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_BANKRUPTCY_POLICY_RELATIVE_OFFSET] = 3;
    bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_DIVIDEND_POLICY_RELATIVE_OFFSET] = 4;
    bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_BUILDING_DENSITY_GROWTH_RELATIVE_OFFSET
        ..payload_offset + RT3_SAVE_WORLD_BLOCK_BUILDING_DENSITY_GROWTH_RELATIVE_OFFSET + 4]
        .copy_from_slice(&2u32.to_le_bytes());
    let next_chunk_offset = payload_offset + RT3_SAVE_WORLD_BLOCK_LEN;
    bytes[next_chunk_offset..next_chunk_offset + 4]
        .copy_from_slice(&RT3_SAVE_WORLD_BLOCK_NEXT_CHUNK_TAG.to_le_bytes());

    let probe = parse_save_world_finance_neighborhood_probe(
        &bytes,
        Some("gms"),
        Some(&SmpContainerProfile {
            profile_family: "rt3-105-save-container-v1".to_string(),
            profile_evidence: vec![],
            is_known_profile: true,
        }),
    )
    .expect("world finance neighborhood probe should parse");

    assert_eq!(probe.chunk_tag_offset, chunk_tag_offset);
    assert_eq!(probe.payload_offset, payload_offset);
    assert_eq!(
        probe.dword_candidates.len(),
        RT3_SAVE_WORLD_BLOCK_FINANCE_NEIGHBORHOOD_WINDOW_LEN_DWORDS
    );
    assert_eq!(
        probe.current_calendar_tuple_word_lane.relative_offset_hex,
        "0xd"
    );
    assert_eq!(probe.packed_year_word_raw_u16, 1);
    assert_eq!(probe.packed_year_word_raw_hex, "0x0001");
    assert_eq!(probe.partial_year_progress_raw_u8, 0);
    assert_eq!(probe.partial_year_progress_raw_hex, "0x00");
    assert_eq!(probe.stock_policy_raw_u8, 1);
    assert_eq!(probe.stock_policy_raw_hex, "0x01");
    assert_eq!(probe.bond_policy_raw_u8, 2);
    assert_eq!(probe.bond_policy_raw_hex, "0x02");
    assert_eq!(probe.bankruptcy_policy_raw_u8, 3);
    assert_eq!(probe.bankruptcy_policy_raw_hex, "0x03");
    assert_eq!(probe.dividend_policy_raw_u8, 4);
    assert_eq!(probe.dividend_policy_raw_hex, "0x04");
    assert_eq!(probe.building_density_growth_setting_lane.raw_u32, 2);
    assert_eq!(
        probe
            .building_density_growth_setting_lane
            .relative_offset_hex,
        "0x4c78"
    );
    assert_eq!(probe.current_calendar_tuple_word_lane.value_i32, 1);
    assert_eq!(
        probe.current_calendar_tuple_word_2_lane.relative_offset_hex,
        "0x11"
    );
    assert_eq!(probe.current_calendar_tuple_word_2_lane.value_i32, 2);
    assert_eq!(probe.absolute_counter_lane.relative_offset_hex, "0x15");
    assert_eq!(probe.absolute_counter_lane.value_i32, 3);
    assert_eq!(
        probe.absolute_counter_mirror_lane.relative_offset_hex,
        "0x19"
    );
    assert_eq!(probe.absolute_counter_mirror_lane.value_i32, 4);
    assert_eq!(
        probe.dword_candidates[0].label,
        "current_calendar_tuple_word"
    );
    assert_eq!(probe.dword_candidates[0].relative_offset_hex, "0xd");
    assert_eq!(probe.dword_candidates[0].value_i32, 1);
    assert_eq!(probe.dword_candidates[6].label, "issue_0x37_multiplier");
    assert_eq!(probe.dword_candidates[6].relative_offset_hex, "0x25");
    assert_eq!(
        probe.dword_candidates[10].label,
        "issue_neighbor_candidate_2"
    );
    assert_eq!(probe.dword_candidates[10].relative_offset_hex, "0x35");
    assert_eq!(probe.dword_candidates[10].value_i32, 11);
    assert_eq!(
        probe.dword_candidates[11].label,
        "finance_neighborhood_word_12"
    );
    assert_eq!(probe.dword_candidates[11].relative_offset_hex, "0x39");
    assert_eq!(probe.dword_candidates[11].value_i32, 12);
    assert_eq!(
        probe.dword_candidates[16].label,
        "finance_neighborhood_word_17"
    );
    assert_eq!(probe.dword_candidates[16].relative_offset_hex, "0x4d");
    assert_eq!(probe.dword_candidates[16].value_i32, 17);
}

#[test]
fn loads_selection_only_company_and_chairman_context_from_save_world_probe() {
    let mut report = inspect_smp_bytes(&[]);
    report.save_load_summary = Some(SmpSaveLoadSummary {
        file_extension_hint: Some("gms".to_string()),
        container_profile_family: Some("rt3-105-save-container-v1".to_string()),
        mechanism_family: "rt3-105-save-post-span-bridge-v1".to_string(),
        mechanism_confidence: "mixed".to_string(),
        packed_profile_kind: None,
        packed_profile_family: None,
        packed_profile_offset: None,
        packed_profile_len: None,
        map_path: None,
        display_name: None,
        profile_byte_0x77: None,
        profile_byte_0x77_hex: None,
        profile_byte_0x82: None,
        profile_byte_0x82_hex: None,
        profile_byte_0x97: None,
        profile_byte_0x97_hex: None,
        profile_byte_0xc5: None,
        profile_byte_0xc5_hex: None,
        trailer_family: Some("rt3-105-save-trailer-v1".to_string()),
        bridge_family: Some("rt3-105-save-post-span-bridge-v1".to_string()),
        candidate_table: None,
        notes: vec![],
    });
    report.save_world_selection_context_probe = Some(SmpSaveWorldSelectionContextProbe {
        profile_family: "rt3-105-save-container-v1".to_string(),
        source_kind: "save-direct-world-block".to_string(),
        semantic_family: "scenario-selected-company-and-chairman-context".to_string(),
        chunk_tag_offset: 0x3ce,
        payload_offset: 0x3d2,
        payload_len: RT3_SAVE_WORLD_BLOCK_LEN,
        payload_len_hex: format!("0x{:x}", RT3_SAVE_WORLD_BLOCK_LEN),
        selected_company_id_offset: 0x3ef,
        selected_company_id: 1,
        selected_company_id_hex: "0x00000001".to_string(),
        selected_chairman_profile_id_offset: 0x3f3,
        selected_chairman_profile_id: 9,
        selected_chairman_profile_id_hex: "0x00000009".to_string(),
        chairman_slot_selector_offset: 0x455,
        chairman_slot_selectors: vec![3, 1, 4, 1, 5, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        campaign_override_flag_offset: 0x493,
        campaign_override_flag: 1,
        campaign_override_flag_hex: "0x01".to_string(),
        chairman_role_gate_offset: 0xf91,
        chairman_role_gate_bytes: vec![2, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        evidence: vec![],
    });
    report.save_world_issue_37_probe = Some(SmpSaveWorldIssue37Probe {
        profile_family: "rt3-105-save-container-v1".to_string(),
        source_kind: "save-direct-world-block".to_string(),
        semantic_family: "scenario-save-world-issue-0x37".to_string(),
        chunk_tag_offset: 0x3ce,
        payload_offset: 0x3d2,
        payload_len: RT3_SAVE_WORLD_BLOCK_LEN,
        payload_len_hex: format!("0x{:x}", RT3_SAVE_WORLD_BLOCK_LEN),
        issue_37_raw_u8: 3,
        issue_37_raw_hex: "0x03".to_string(),
        issue_38_raw_u8: 1,
        issue_38_raw_hex: "0x01".to_string(),
        issue_39_raw_u8: 2,
        issue_39_raw_hex: "0x02".to_string(),
        issue_3a_raw_u8: 4,
        issue_3a_raw_hex: "0x04".to_string(),
        issue_value_lane: SmpSaveDwordCandidate {
            label: "issue_0x37_value".to_string(),
            relative_offset: 0x29,
            relative_offset_hex: "0x29".to_string(),
            raw_u32: 3,
            raw_u32_hex: "0x00000003".to_string(),
            value_i32: 3,
            value_f32: f32::from_bits(3),
        },
        multiplier_lane: SmpSaveDwordCandidate {
            label: "issue_0x37_multiplier".to_string(),
            relative_offset: 0x25,
            relative_offset_hex: "0x25".to_string(),
            raw_u32: 0x3d75c28f,
            raw_u32_hex: "0x3d75c28f".to_string(),
            value_i32: 1031127695,
            value_f32: 0.06,
        },
        issue_opinion_base_terms_raw_i32: Vec::new(),
        evidence: vec![],
    });
    report.save_world_economic_tuning_probe = Some(SmpSaveWorldEconomicTuningProbe {
        profile_family: "rt3-105-save-container-v1".to_string(),
        source_kind: "save-direct-world-block".to_string(),
        semantic_family: "scenario-save-world-economic-tuning".to_string(),
        chunk_tag_offset: 0x3ce,
        payload_offset: 0x3d2,
        payload_len: RT3_SAVE_WORLD_BLOCK_LEN,
        payload_len_hex: format!("0x{:x}", RT3_SAVE_WORLD_BLOCK_LEN),
        mirror_lane: SmpSaveDwordCandidate {
            label: "economic_tuning_mirror_lane_0".to_string(),
            relative_offset: 0xbda,
            relative_offset_hex: "0xbda".to_string(),
            raw_u32: 0x3f46d093,
            raw_u32_hex: "0x3f46d093".to_string(),
            value_i32: 1061605523,
            value_f32: 0.7766201,
        },
        tuning_lanes: vec![
            SmpSaveDwordCandidate {
                label: "economic_tuning_lane_0".to_string(),
                relative_offset: 0xbde,
                relative_offset_hex: "0xbde".to_string(),
                raw_u32: 0x3f400000,
                raw_u32_hex: "0x3f400000".to_string(),
                value_i32: 1061158912,
                value_f32: 0.75,
            },
            SmpSaveDwordCandidate {
                label: "economic_tuning_lane_1".to_string(),
                relative_offset: 0xbe2,
                relative_offset_hex: "0xbe2".to_string(),
                raw_u32: 0x3be56042,
                raw_u32_hex: "0x3be56042".to_string(),
                value_i32: 1004888130,
                value_f32: 0.007,
            },
        ],
        evidence: vec![],
    });
    report.save_company_collection_header_probe = Some(SmpSaveTaggedCollectionHeaderProbe {
        profile_family: "rt3-105-save-container-v1".to_string(),
        source_kind: "save-company-tagged-header-counts".to_string(),
        semantic_family: "scenario-save-company-header-counts".to_string(),
        metadata_tag_offset: 0x1000,
        records_tag_offset: 0x1100,
        close_tag_offset: 0x1200,
        direct_collection_flag: 1,
        direct_collection_flag_hex: "0x00000001".to_string(),
        direct_record_stride: 0x7684,
        direct_record_stride_hex: "0x00007684".to_string(),
        live_id_bound: 5,
        live_id_bound_hex: "0x00000005".to_string(),
        live_record_count: 1,
        live_record_count_hex: "0x00000001".to_string(),
        header_words: vec![1, 0x7684, 5, 5, 5, 1],
        header_hex_words: vec![
            "0x00000001".to_string(),
            "0x00007684".to_string(),
            "0x00000005".to_string(),
            "0x00000005".to_string(),
            "0x00000005".to_string(),
            "0x00000001".to_string(),
        ],
        evidence: vec![],
    });
    report.save_chairman_profile_collection_header_probe =
        Some(SmpSaveTaggedCollectionHeaderProbe {
            profile_family: "rt3-105-save-container-v1".to_string(),
            source_kind: "save-chairman-profile-tagged-header-counts".to_string(),
            semantic_family: "scenario-save-chairman-profile-header-counts".to_string(),
            metadata_tag_offset: 0x2000,
            records_tag_offset: 0x2100,
            close_tag_offset: 0x2200,
            direct_collection_flag: 1,
            direct_collection_flag_hex: "0x00000001".to_string(),
            direct_record_stride: 0xcab,
            direct_record_stride_hex: "0x00000cab".to_string(),
            live_id_bound: 8,
            live_id_bound_hex: "0x00000008".to_string(),
            live_record_count: 2,
            live_record_count_hex: "0x00000002".to_string(),
            header_words: vec![1, 0xcab, 8, 6, 8, 2],
            header_hex_words: vec![
                "0x00000001".to_string(),
                "0x00000cab".to_string(),
                "0x00000008".to_string(),
                "0x00000006".to_string(),
                "0x00000008".to_string(),
                "0x00000002".to_string(),
            ],
            evidence: vec![],
        });
    report.save_train_collection_header_probe = Some(SmpSaveTaggedCollectionHeaderProbe {
        profile_family: "rt3-105-save-container-v1".to_string(),
        source_kind: "save-train-tagged-header-counts".to_string(),
        semantic_family: "scenario-save-train-header-counts".to_string(),
        metadata_tag_offset: 0x3000,
        records_tag_offset: 0x3100,
        close_tag_offset: 0x3200,
        direct_collection_flag: 1,
        direct_collection_flag_hex: "0x00000001".to_string(),
        direct_record_stride: 0x1d5,
        direct_record_stride_hex: "0x000001d5".to_string(),
        live_id_bound: 0x32,
        live_id_bound_hex: "0x00000032".to_string(),
        live_record_count: 0x14,
        live_record_count_hex: "0x00000014".to_string(),
        header_words: vec![1, 0x1d5, 0x32, 0x14, 0x32, 0x14],
        header_hex_words: vec![
            "0x00000001".to_string(),
            "0x000001d5".to_string(),
            "0x00000032".to_string(),
            "0x00000014".to_string(),
            "0x00000032".to_string(),
            "0x00000014".to_string(),
        ],
        evidence: vec![],
    });
    report.save_train_collection_directory_probe = Some(SmpSaveTrainCollectionDirectoryProbe {
        profile_family: "rt3-105-save-container-v1".to_string(),
        source_kind: "save-train-live-directory".to_string(),
        semantic_family: "scenario-save-train-live-directory".to_string(),
        metadata_tag_offset: 0x3000,
        records_tag_offset: 0x3100,
        close_tag_offset: 0x3200,
        directory_root_dword_index: 16,
        directory_entry_dword_count: 3,
        live_record_count: 0x14,
        live_id_bound: 0x32,
        chain_head_live_entry_id: Some(1),
        chain_tail_live_entry_id: Some(20),
        entries: vec![
            SmpSaveTrainCollectionDirectoryEntryProbe {
                live_entry_id: 1,
                payload_relative_offset: 0x2af8,
                payload_relative_offset_hex: "0x00002af8".to_string(),
                payload_absolute_offset: 0x5afc,
                previous_live_entry_id: 0,
                previous_live_entry_id_hex: "0x00000000".to_string(),
                next_live_entry_id: 2,
                next_live_entry_id_hex: "0x00000002".to_string(),
            },
            SmpSaveTrainCollectionDirectoryEntryProbe {
                live_entry_id: 2,
                payload_relative_offset: 0x2ee0,
                payload_relative_offset_hex: "0x00002ee0".to_string(),
                payload_absolute_offset: 0x5ee4,
                previous_live_entry_id: 1,
                previous_live_entry_id_hex: "0x00000001".to_string(),
                next_live_entry_id: 0,
                next_live_entry_id_hex: "0x00000000".to_string(),
            },
        ],
        evidence: vec![],
    });
    report.save_region_collection_header_probe = Some(SmpSaveTaggedCollectionHeaderProbe {
        profile_family: "rt3-105-save-container-v1".to_string(),
        source_kind: "save-region-tagged-header-counts".to_string(),
        semantic_family: "scenario-save-region-header-counts".to_string(),
        metadata_tag_offset: 0x5000,
        records_tag_offset: 0x5100,
        close_tag_offset: 0x5200,
        direct_collection_flag: 0,
        direct_collection_flag_hex: "0x00000000".to_string(),
        direct_record_stride: 0x06,
        direct_record_stride_hex: "0x00000006".to_string(),
        live_id_bound: 0x96,
        live_id_bound_hex: "0x00000096".to_string(),
        live_record_count: 0x91,
        live_record_count_hex: "0x00000091".to_string(),
        header_words: vec![0, 6, 0x0a, 0x14, 0x96, 0x91],
        header_hex_words: vec![
            "0x00000000".to_string(),
            "0x00000006".to_string(),
            "0x0000000a".to_string(),
            "0x00000014".to_string(),
            "0x00000096".to_string(),
            "0x00000091".to_string(),
        ],
        evidence: vec![],
    });
    report.save_region_record_triplet_probe = Some(SmpSaveRegionRecordTripletProbe {
        profile_family: "rt3-105-save-container-v1".to_string(),
        source_kind: "save-region-record-triplets".to_string(),
        semantic_family: "scenario-save-region-record-triplets".to_string(),
        records_tag_offset: 0x5100,
        close_tag_offset: 0x5200,
        record_count: 2,
        entries: vec![
            SmpSaveRegionRecordTripletEntryProbe {
                record_index: 0,
                name: "Marker09".to_string(),
                record_payload_relative_offset: 0,
                record_payload_relative_offset_hex: "0x0".to_string(),
                name_tag_relative_offset: 0,
                policy_tag_relative_offset: 0x10,
                profile_tag_relative_offset: 0x2e,
                pre_name_prefix_len: 0,
                pre_name_prefix_hex_bytes: Vec::new(),
                pre_name_prefix_dword_candidates: Vec::new(),
                policy_chunk_len: 0x1a,
                profile_chunk_len: 0x40,
                policy_leading_f32_0: 368.0,
                policy_leading_f32_1: 0.0,
                policy_leading_f32_2: 92.0,
                policy_reserved_dwords: vec![0, 0, 0],
                policy_reserved_dword_candidates: Vec::new(),
                policy_trailing_word: 1,
                policy_trailing_word_hex: "0x0001".to_string(),
                profile_collection: Some(SmpSaveRegionProfileCollectionProbe {
                    direct_collection_flag: 1,
                    entry_stride: 0x22,
                    live_id_bound: 18,
                    live_record_count: 17,
                    entry_start_relative_offset: 0x4d,
                    trailing_padding_len: 2,
                    entries: vec![
                        SmpSaveRegionProfileEntryProbe {
                            entry_index: 0,
                            row_relative_offset: 0x4d,
                            name: "House".to_string(),
                            trailing_weight_f32: 0.2,
                        },
                        SmpSaveRegionProfileEntryProbe {
                            entry_index: 1,
                            row_relative_offset: 0x6f,
                            name: "Farm Corn".to_string(),
                            trailing_weight_f32: 0.2,
                        },
                    ],
                }),
            },
            SmpSaveRegionRecordTripletEntryProbe {
                record_index: 1,
                name: "Marker10".to_string(),
                record_payload_relative_offset: 0x6e,
                record_payload_relative_offset_hex: "0x6e".to_string(),
                name_tag_relative_offset: 0x6e,
                policy_tag_relative_offset: 0x7e,
                profile_tag_relative_offset: 0x9c,
                pre_name_prefix_len: 0,
                pre_name_prefix_hex_bytes: Vec::new(),
                pre_name_prefix_dword_candidates: Vec::new(),
                policy_chunk_len: 0x1a,
                profile_chunk_len: 0x20,
                policy_leading_f32_0: 552.0,
                policy_leading_f32_1: 0.0,
                policy_leading_f32_2: 276.0,
                policy_reserved_dwords: vec![0, 0, 0],
                policy_reserved_dword_candidates: Vec::new(),
                policy_trailing_word: 1,
                policy_trailing_word_hex: "0x0001".to_string(),
                profile_collection: Some(SmpSaveRegionProfileCollectionProbe {
                    direct_collection_flag: 1,
                    entry_stride: 0x22,
                    live_id_bound: 26,
                    live_record_count: 24,
                    entry_start_relative_offset: 0x50,
                    trailing_padding_len: 0,
                    entries: vec![SmpSaveRegionProfileEntryProbe {
                        entry_index: 0,
                        row_relative_offset: 0x50,
                        name: "Farm Corn".to_string(),
                        trailing_weight_f32: 0.2,
                    }],
                }),
            },
        ],
        evidence: vec![],
    });
    report.save_placed_structure_collection_header_probe =
        Some(SmpSaveTaggedCollectionHeaderProbe {
            profile_family: "rt3-105-save-container-v1".to_string(),
            source_kind: "save-placed-structure-tagged-header-counts".to_string(),
            semantic_family: "scenario-save-placed-structure-header-counts".to_string(),
            metadata_tag_offset: 0x4000,
            records_tag_offset: 0x4100,
            close_tag_offset: 0x4200,
            direct_collection_flag: 0,
            direct_collection_flag_hex: "0x00000000".to_string(),
            direct_record_stride: 0x06,
            direct_record_stride_hex: "0x00000006".to_string(),
            live_id_bound: 0x7ee,
            live_id_bound_hex: "0x000007ee".to_string(),
            live_record_count: 0x7ea,
            live_record_count_hex: "0x000007ea".to_string(),
            header_words: vec![0, 6, 0x0a, 0x14, 0x7ee, 0x7ea],
            header_hex_words: vec![
                "0x00000000".to_string(),
                "0x00000006".to_string(),
                "0x0000000a".to_string(),
                "0x00000014".to_string(),
                "0x000007ee".to_string(),
                "0x000007ea".to_string(),
            ],
            evidence: vec![],
        });

    let slice = load_save_slice_from_report(&report).expect("save slice");

    let company_roster = slice.company_roster.expect("selection-only company roster");
    assert_eq!(company_roster.observed_entry_count, 1);
    assert_eq!(company_roster.selected_company_id, Some(1));
    assert!(company_roster.entries.is_empty());

    let chairman_table = slice
        .chairman_profile_table
        .expect("selection-only chairman table");
    assert_eq!(chairman_table.observed_entry_count, 2);
    assert_eq!(chairman_table.selected_chairman_profile_id, Some(9));
    assert!(chairman_table.entries.is_empty());
    let issue_37_state = slice
        .world_issue_37_state
        .expect("world issue-0x37 state should load");
    assert_eq!(issue_37_state.issue_value, 3);
    assert_eq!(issue_37_state.issue_38_value, 1);
    assert_eq!(issue_37_state.issue_39_value, 2);
    assert_eq!(issue_37_state.issue_3a_value, 4);
    assert_eq!(issue_37_state.multiplier_raw_hex, "0x3d75c28f");
    assert_eq!(issue_37_state.multiplier_value_f32_text, "0.060000");
    let tuning_state = slice
        .world_economic_tuning_state
        .expect("world economic tuning state should load");
    assert_eq!(tuning_state.mirror_raw_hex, "0x3f46d093");
    assert_eq!(tuning_state.mirror_value_f32_text, "0.776620");
    assert_eq!(
        tuning_state.lane_value_f32_text,
        vec!["0.750000", "0.007000"]
    );

    assert!(
        slice
            .notes
            .iter()
            .any(|note| note.contains("selected_company_id=1"))
    );
    assert!(
        slice
            .notes
            .iter()
            .any(|note| note.contains("selected_chairman_profile_id=9"))
    );
    assert!(
        slice
            .notes
            .iter()
            .any(|note| note.contains("grounded issue-0x37 pair: value=3"))
    );
    assert!(
        slice
            .notes
            .iter()
            .any(|note| note.contains("campaign_override_flag=1"))
    );
    assert!(
        slice
            .notes
            .iter()
            .any(|note| note.contains("tagged company header reports live_record_count=1"))
    );
    assert!(slice.notes.iter().any(|note| {
        note.contains("tagged chairman/profile header reports live_record_count=2")
    }));
    assert!(
        slice
            .notes
            .iter()
            .any(|note| note.contains("tagged train header reports live_record_count=20"))
    );
    assert!(slice.notes.iter().any(|note| {
        note.contains("tagged train metadata also exposes a live-entry directory")
    }));
    assert!(
        slice
            .notes
            .iter()
            .any(|note| { note.contains("tagged region header reports live_record_count=145") })
    );
    assert!(slice.notes.iter().any(|note| {
        note.contains("tagged region records also expose 2 repeated 0x55f1/0x55f2/0x55f3 triplets")
    }));
    assert!(slice.notes.iter().any(|note| {
        note.contains("tagged placed-structure header reports live_record_count=2026")
    }));
}

#[test]
fn parses_save_company_roster_probe_from_direct_records() {
    let metadata_tag_offset = 0x40usize;
    let stride = 0x7684usize;
    let count = 2usize;
    let start_offset = 0xc6usize;
    let total_len = metadata_tag_offset + 4 + start_offset + stride * count + 0x400;
    let mut bytes = vec![0u8; total_len];
    bytes[metadata_tag_offset..metadata_tag_offset + 4]
        .copy_from_slice(&0x000061a9u32.to_le_bytes());
    let header_words = [
        1u32,
        0x7684,
        5,
        5,
        5,
        count as u32,
        1,
        1,
        0,
        0,
        1,
        1,
        1,
        0,
        0,
        0,
        0,
        0,
        0,
    ];
    for (index, word) in header_words.into_iter().enumerate() {
        let offset = metadata_tag_offset + 4 + index * 4;
        bytes[offset..offset + 4].copy_from_slice(&word.to_le_bytes());
    }
    let records_tag_offset = metadata_tag_offset + 4 + 0x200;
    let close_tag_offset = records_tag_offset + 4;
    bytes[records_tag_offset..records_tag_offset + 4].copy_from_slice(&0x000061aau32.to_le_bytes());
    bytes[close_tag_offset..close_tag_offset + 4].copy_from_slice(&0x000061abu32.to_le_bytes());

    for (
        index,
        (
            name,
            linked,
            merger,
            takeover,
            bond_count,
            _debt,
            track_capacity,
            mutable_support_scalar_raw_u32,
            young_company_support_scalar_raw_u32,
            support_progress_word,
            recent_per_share_subscore_raw_u32,
            cached_share_price_raw_u32,
            chairman_salary_baseline,
            chairman_salary_current,
            chairman_bonus_year,
            chairman_bonus_amount,
            founding_year,
            last_bankruptcy_year,
            last_dividend_year,
            current_issue_calendar_word,
            current_issue_calendar_word_2,
            prior_issue_calendar_word,
            prior_issue_calendar_word_2,
            preferred_locomotive_engine_type_raw_u8,
            city_connection_latch,
            linked_transit_latch,
            linked_transit_route_anchor_entry_id,
            linked_transit_route_anchor_fallback_counts,
        ),
    ) in [
        (
            "Company One",
            1u32,
            1862u32,
            1865u32,
            2u8,
            1_000_000u32,
            Some(603i32),
            0x3f800000u32,
            0x42340000u32,
            17u32,
            0x41f00000u32,
            0x426c0000u32,
            24u32,
            31u32,
            1849u32,
            1250i32,
            1842u32,
            1851u32,
            1848u32,
            7u32,
            8u32,
            6u32,
            7u32,
            2u8,
            true,
            false,
            Some(77u32),
            [3u32, 5u32, 8u32],
        ),
        (
            "Company Two",
            2u32,
            0u32,
            1871u32,
            1u8,
            500_000u32,
            None,
            0x40000000u32,
            0x42700000u32,
            33u32,
            0x42000000u32,
            0x42780000u32,
            28u32,
            36u32,
            0u32,
            0i32,
            1845u32,
            0u32,
            1850u32,
            3u32,
            4u32,
            2u32,
            3u32,
            1u8,
            false,
            true,
            Some(41u32),
            [13u32, 21u32, 34u32],
        ),
    ]
    .into_iter()
    .enumerate()
    {
        let record_offset = metadata_tag_offset + 4 + start_offset + index * stride;
        bytes[record_offset..record_offset + 4]
            .copy_from_slice(&((index + 1) as u32).to_le_bytes());
        bytes[record_offset + 4..record_offset + 4 + name.len()].copy_from_slice(name.as_bytes());
        bytes[record_offset + SAVE_COMPANY_RECORD_LINKED_CHAIRMAN_OFFSET
            ..record_offset + SAVE_COMPANY_RECORD_LINKED_CHAIRMAN_OFFSET + 4]
            .copy_from_slice(&linked.to_le_bytes());
        bytes[record_offset + SAVE_COMPANY_RECORD_ACTIVE_OFFSET] = 1;
        bytes[record_offset + 0x47..record_offset + 0x4b].copy_from_slice(&20000u32.to_le_bytes());
        bytes[record_offset + SAVE_COMPANY_RECORD_SUPPORT_SCALAR_OFFSET
            ..record_offset + SAVE_COMPANY_RECORD_SUPPORT_SCALAR_OFFSET + 4]
            .copy_from_slice(&mutable_support_scalar_raw_u32.to_le_bytes());
        bytes[record_offset + SAVE_COMPANY_RECORD_COMPANY_VALUE_OFFSET
            ..record_offset + SAVE_COMPANY_RECORD_COMPANY_VALUE_OFFSET + 4]
            .copy_from_slice(&young_company_support_scalar_raw_u32.to_le_bytes());
        bytes[record_offset + SAVE_COMPANY_RECORD_BOND_COUNT_OFFSET] = bond_count;
        for slot_index in 0..bond_count as usize {
            let slot_offset = record_offset
                + SAVE_COMPANY_RECORD_BOND_TABLE_OFFSET
                + slot_index * SAVE_COMPANY_RECORD_BOND_SLOT_STRIDE;
            let (principal, coupon_rate) = if index == 0 && slot_index == 0 {
                (900_000i32, 0.08f32)
            } else if index == 0 && slot_index == 1 {
                (650_000i32, 0.12f32)
            } else {
                (500_000i32, 0.10f32)
            };
            bytes[slot_offset..slot_offset + 4].copy_from_slice(&principal.to_le_bytes());
            bytes[slot_offset + 4..slot_offset + 8]
                .copy_from_slice(&(1894u32 + slot_index as u32).to_le_bytes());
            bytes[slot_offset + 8..slot_offset + 12].copy_from_slice(&coupon_rate.to_le_bytes());
        }
        bytes[record_offset + SAVE_COMPANY_RECORD_MERGER_COOLDOWN_OFFSET
            ..record_offset + SAVE_COMPANY_RECORD_MERGER_COOLDOWN_OFFSET + 4]
            .copy_from_slice(&merger.to_le_bytes());
        bytes[record_offset + SAVE_COMPANY_RECORD_TAKEOVER_COOLDOWN_OFFSET
            ..record_offset + SAVE_COMPANY_RECORD_TAKEOVER_COOLDOWN_OFFSET + 4]
            .copy_from_slice(&takeover.to_le_bytes());
        let raw_capacity = track_capacity.unwrap_or(-1);
        bytes[record_offset + SAVE_COMPANY_RECORD_TRACK_LAYING_CAPACITY_OFFSET
            ..record_offset + SAVE_COMPANY_RECORD_TRACK_LAYING_CAPACITY_OFFSET + 4]
            .copy_from_slice(&raw_capacity.to_le_bytes());
        bytes[record_offset + SAVE_COMPANY_RECORD_SUPPORT_PROGRESS_OFFSET
            ..record_offset + SAVE_COMPANY_RECORD_SUPPORT_PROGRESS_OFFSET + 4]
            .copy_from_slice(&support_progress_word.to_le_bytes());
        bytes[record_offset + SAVE_COMPANY_RECORD_RECENT_PER_SHARE_SUBSCORE_OFFSET
            ..record_offset + SAVE_COMPANY_RECORD_RECENT_PER_SHARE_SUBSCORE_OFFSET + 4]
            .copy_from_slice(&recent_per_share_subscore_raw_u32.to_le_bytes());
        bytes[record_offset + SAVE_COMPANY_RECORD_CACHED_SHARE_PRICE_OFFSET
            ..record_offset + SAVE_COMPANY_RECORD_CACHED_SHARE_PRICE_OFFSET + 4]
            .copy_from_slice(&cached_share_price_raw_u32.to_le_bytes());
        bytes[record_offset + SAVE_COMPANY_RECORD_CHAIRMAN_SALARY_BASELINE_OFFSET
            ..record_offset + SAVE_COMPANY_RECORD_CHAIRMAN_SALARY_BASELINE_OFFSET + 4]
            .copy_from_slice(&chairman_salary_baseline.to_le_bytes());
        bytes[record_offset + SAVE_COMPANY_RECORD_CHAIRMAN_SALARY_CURRENT_OFFSET
            ..record_offset + SAVE_COMPANY_RECORD_CHAIRMAN_SALARY_CURRENT_OFFSET + 4]
            .copy_from_slice(&chairman_salary_current.to_le_bytes());
        bytes[record_offset + SAVE_COMPANY_RECORD_CHAIRMAN_BONUS_YEAR_OFFSET
            ..record_offset + SAVE_COMPANY_RECORD_CHAIRMAN_BONUS_YEAR_OFFSET + 4]
            .copy_from_slice(&chairman_bonus_year.to_le_bytes());
        bytes[record_offset + SAVE_COMPANY_RECORD_CHAIRMAN_BONUS_AMOUNT_OFFSET
            ..record_offset + SAVE_COMPANY_RECORD_CHAIRMAN_BONUS_AMOUNT_OFFSET + 4]
            .copy_from_slice(&chairman_bonus_amount.to_le_bytes());
        bytes[record_offset + SAVE_COMPANY_RECORD_FOUNDING_YEAR_OFFSET
            ..record_offset + SAVE_COMPANY_RECORD_FOUNDING_YEAR_OFFSET + 4]
            .copy_from_slice(&founding_year.to_le_bytes());
        bytes[record_offset + SAVE_COMPANY_RECORD_LAST_BANKRUPTCY_YEAR_OFFSET
            ..record_offset + SAVE_COMPANY_RECORD_LAST_BANKRUPTCY_YEAR_OFFSET + 4]
            .copy_from_slice(&last_bankruptcy_year.to_le_bytes());
        bytes[record_offset + SAVE_COMPANY_RECORD_LAST_DIVIDEND_YEAR_OFFSET
            ..record_offset + SAVE_COMPANY_RECORD_LAST_DIVIDEND_YEAR_OFFSET + 4]
            .copy_from_slice(&last_dividend_year.to_le_bytes());
        bytes[record_offset + SAVE_COMPANY_RECORD_CURRENT_ISSUE_CALENDAR_OFFSET
            ..record_offset + SAVE_COMPANY_RECORD_CURRENT_ISSUE_CALENDAR_OFFSET + 4]
            .copy_from_slice(&current_issue_calendar_word.to_le_bytes());
        bytes[record_offset + SAVE_COMPANY_RECORD_CURRENT_ISSUE_CALENDAR_OFFSET_2
            ..record_offset + SAVE_COMPANY_RECORD_CURRENT_ISSUE_CALENDAR_OFFSET_2 + 4]
            .copy_from_slice(&current_issue_calendar_word_2.to_le_bytes());
        bytes[record_offset + SAVE_COMPANY_RECORD_PRIOR_ISSUE_CALENDAR_OFFSET
            ..record_offset + SAVE_COMPANY_RECORD_PRIOR_ISSUE_CALENDAR_OFFSET + 4]
            .copy_from_slice(&prior_issue_calendar_word.to_le_bytes());
        bytes[record_offset + SAVE_COMPANY_RECORD_PRIOR_ISSUE_CALENDAR_OFFSET_2
            ..record_offset + SAVE_COMPANY_RECORD_PRIOR_ISSUE_CALENDAR_OFFSET_2 + 4]
            .copy_from_slice(&prior_issue_calendar_word_2.to_le_bytes());
        bytes[record_offset + SAVE_COMPANY_RECORD_PREFERRED_LOCOMOTIVE_ENGINE_TYPE_OFFSET] =
            preferred_locomotive_engine_type_raw_u8;
        bytes[record_offset + SAVE_COMPANY_RECORD_CITY_CONNECTION_LATCH_OFFSET] =
            u8::from(city_connection_latch);
        bytes[record_offset + SAVE_COMPANY_RECORD_LINKED_TRANSIT_LATCH_OFFSET] =
            u8::from(linked_transit_latch);
        if let Some(anchor_entry_id) = linked_transit_route_anchor_entry_id {
            bytes[record_offset + SAVE_COMPANY_RECORD_LINKED_TRANSIT_ROUTE_ANCHOR_ENTRY_ID_OFFSET
                ..record_offset
                    + SAVE_COMPANY_RECORD_LINKED_TRANSIT_ROUTE_ANCHOR_ENTRY_ID_OFFSET
                    + 4]
                .copy_from_slice(&anchor_entry_id.to_le_bytes());
        }
        for (fallback_index, relative_offset) in
            SAVE_COMPANY_RECORD_LINKED_TRANSIT_ROUTE_ANCHOR_FALLBACK_COUNT_OFFSETS
                .into_iter()
                .enumerate()
        {
            bytes[record_offset + relative_offset..record_offset + relative_offset + 4]
                .copy_from_slice(
                    &linked_transit_route_anchor_fallback_counts[fallback_index].to_le_bytes(),
                );
        }
        let current_cash: f64 = if index == 0 { 125_000.0 } else { -25_000.0 };
        let current_cash_slot_offset = record_offset
            + SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0D7F_OFFSET
            + (RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH as usize
                * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN as usize
                * 8);
        bytes[current_cash_slot_offset..current_cash_slot_offset + 8]
            .copy_from_slice(&current_cash.to_bits().to_le_bytes());
    }

    let header_probe = parse_save_company_collection_header_probe(
        &bytes,
        Some("gms"),
        Some(&SmpContainerProfile {
            profile_family: "rt3-105-save-container-v1".to_string(),
            profile_evidence: vec![],
            is_known_profile: true,
        }),
    )
    .expect("company header probe should parse");
    let roster = parse_save_company_roster_probe(
        &bytes,
        Some(&header_probe),
        Some(&SmpSaveWorldSelectionContextProbe {
            profile_family: "rt3-105-save-container-v1".to_string(),
            source_kind: "save-direct-world-block".to_string(),
            semantic_family: "scenario-selected-company-and-chairman-context".to_string(),
            chunk_tag_offset: 0,
            payload_offset: 0,
            payload_len: 0,
            payload_len_hex: "0x0".to_string(),
            selected_company_id_offset: 0,
            selected_company_id: 2,
            selected_company_id_hex: "0x00000002".to_string(),
            selected_chairman_profile_id_offset: 0,
            selected_chairman_profile_id: 1,
            selected_chairman_profile_id_hex: "0x00000001".to_string(),
            chairman_slot_selector_offset: 0,
            chairman_slot_selectors: vec![],
            campaign_override_flag_offset: 0,
            campaign_override_flag: 0,
            campaign_override_flag_hex: "0x00".to_string(),
            chairman_role_gate_offset: 0,
            chairman_role_gate_bytes: vec![],
            evidence: vec![],
        }),
    )
    .expect("company roster should parse");

    assert_eq!(roster.observed_entry_count, 2);
    assert_eq!(roster.selected_company_id, Some(2));
    assert_eq!(roster.entries.len(), 2);
    assert_eq!(roster.entries[0].company_id, 1);
    assert_eq!(roster.entries[0].current_cash, 125_000);
    assert_eq!(roster.entries[0].linked_chairman_profile_id, Some(1));
    assert_eq!(roster.entries[0].debt, 1_550_000);
    assert_eq!(roster.entries[0].available_track_laying_capacity, Some(603));
    assert_eq!(roster.entries[0].merger_cooldown_year, Some(1862));
    let market_state = roster.entries[0]
        .market_state
        .as_ref()
        .expect("company market state should load");
    assert_eq!(market_state.outstanding_shares, 20_000);
    assert_eq!(market_state.live_bond_slots.len(), 2);
    assert_eq!(market_state.live_bond_slots[0].principal, 900_000);
    assert_eq!(market_state.live_bond_slots[0].maturity_year, 1894);
    assert_eq!(
        market_state.live_bond_slots[1].coupon_rate_raw_u32,
        0.12f32.to_bits()
    );
    assert_eq!(market_state.largest_live_bond_principal, Some(900_000));
    assert_eq!(
        market_state.highest_coupon_live_bond_principal,
        Some(650_000)
    );
    assert_eq!(market_state.mutable_support_scalar_raw_u32, 0x3f800000);
    assert_eq!(
        market_state.young_company_support_scalar_raw_u32,
        0x42340000
    );
    assert_eq!(market_state.support_progress_word, 17);
    assert_eq!(market_state.recent_per_share_subscore_raw_u32, 0x41f00000);
    assert_eq!(market_state.cached_share_price_raw_u32, 0x426c0000);
    assert_eq!(market_state.chairman_salary_baseline, 24);
    assert_eq!(market_state.chairman_salary_current, 31);
    assert_eq!(market_state.chairman_bonus_year, 1849);
    assert_eq!(market_state.chairman_bonus_amount, 1250);
    assert_eq!(market_state.founding_year, 1842);
    assert_eq!(market_state.last_bankruptcy_year, 1851);
    assert_eq!(market_state.last_dividend_year, 1848);
    assert_eq!(market_state.current_issue_calendar_word, 7);
    assert_eq!(market_state.current_issue_calendar_word_2, 8);
    assert_eq!(market_state.prior_issue_calendar_word, 6);
    assert_eq!(market_state.prior_issue_calendar_word_2, 7);
    assert_eq!(market_state.linked_transit_route_anchor_entry_id, Some(77));
    assert_eq!(
        market_state.linked_transit_route_anchor_fallback_counts,
        vec![3, 5, 8]
    );
    assert_eq!(
        roster.entries[0].preferred_locomotive_engine_type_raw_u8,
        Some(2)
    );
    assert!(market_state.city_connection_latch);
    assert!(!market_state.linked_transit_latch);
    assert_eq!(
        market_state.stat_band_root_0cfb_candidates.len(),
        SAVE_COMPANY_RECORD_STAT_BAND_ROOT_WINDOW_LEN_DWORDS
    );
    assert_eq!(
        market_state.stat_band_root_0d7f_candidates.len(),
        SAVE_COMPANY_RECORD_STAT_BAND_ROOT_WINDOW_LEN_DWORDS
    );
    assert_eq!(
        market_state.stat_band_root_1c47_candidates.len(),
        SAVE_COMPANY_RECORD_STAT_BAND_ROOT_WINDOW_LEN_DWORDS
    );
    assert_eq!(
        market_state.stat_band_root_0cfb_candidates[31].label,
        "stat_band_0cfb_word_32"
    );
    assert_eq!(
        market_state.stat_band_root_0cfb_candidates[31].relative_offset_hex,
        "0xd77"
    );
    assert_eq!(roster.entries[1].company_id, 2);
    assert_eq!(roster.entries[1].current_cash, -25_000);
    assert_eq!(roster.entries[1].linked_chairman_profile_id, Some(2));
    assert_eq!(roster.entries[1].debt, 500_000);
    assert_eq!(roster.entries[1].available_track_laying_capacity, None);
    assert_eq!(roster.entries[1].takeover_cooldown_year, Some(1871));
    let second_market_state = roster.entries[1]
        .market_state
        .as_ref()
        .expect("second company market state should load");
    assert_eq!(
        second_market_state.largest_live_bond_principal,
        Some(500_000)
    );
    assert_eq!(
        second_market_state.highest_coupon_live_bond_principal,
        Some(500_000)
    );
    assert_eq!(second_market_state.chairman_bonus_year, 0);
    assert_eq!(second_market_state.chairman_bonus_amount, 0);
    assert_eq!(second_market_state.last_dividend_year, 1850);
    assert_eq!(second_market_state.current_issue_calendar_word, 3);
    assert_eq!(second_market_state.current_issue_calendar_word_2, 4);
    assert_eq!(second_market_state.prior_issue_calendar_word, 2);
    assert_eq!(second_market_state.prior_issue_calendar_word_2, 3);
    assert_eq!(
        second_market_state.linked_transit_route_anchor_entry_id,
        Some(41)
    );
    assert_eq!(
        second_market_state.linked_transit_route_anchor_fallback_counts,
        vec![13, 21, 34]
    );
    assert_eq!(
        roster.entries[1].preferred_locomotive_engine_type_raw_u8,
        Some(1)
    );
    assert!(!second_market_state.city_connection_latch);
    assert!(second_market_state.linked_transit_latch);
}

#[test]
fn parses_save_chairman_profile_table_probe_from_direct_records() {
    let metadata_tag_offset = 0x40usize;
    let stride = 0xcabusize;
    let count = 2usize;
    let start_offset = 0x4eusize;
    let total_len = metadata_tag_offset + 4 + start_offset + stride * count + 0x200;
    let mut bytes = vec![0u8; total_len];
    bytes[metadata_tag_offset..metadata_tag_offset + 4]
        .copy_from_slice(&0x00005209u32.to_le_bytes());
    let header_words = [
        1u32,
        0xcab,
        8,
        6,
        8,
        count as u32,
        1,
        1,
        0,
        0,
        1,
        1,
        1,
        0,
        0,
        0,
        0,
        0,
        0,
    ];
    for (index, word) in header_words.into_iter().enumerate() {
        let offset = metadata_tag_offset + 4 + index * 4;
        bytes[offset..offset + 4].copy_from_slice(&word.to_le_bytes());
    }
    let records_tag_offset = metadata_tag_offset + 4 + 0x100;
    let close_tag_offset = records_tag_offset + 4;
    bytes[records_tag_offset..records_tag_offset + 4].copy_from_slice(&0x0000520au32.to_le_bytes());
    bytes[close_tag_offset..close_tag_offset + 4].copy_from_slice(&0x0000520bu32.to_le_bytes());

    for (index, (name, linked, cash, cache0, cache1, cache4, holdings)) in [
        (
            "Collis Huntington",
            1u32,
            -107644.0f64,
            252508.0f64,
            0.0f64,
            0.0f64,
            vec![(1u32, 6000u32)],
        ),
        (
            "Thomas Durant",
            2u32,
            -382718.0f64,
            -283562.0f64,
            822000.0f64,
            1_392_000.0f64,
            vec![(2u32, 9000u32)],
        ),
    ]
    .into_iter()
    .enumerate()
    {
        let record_offset = metadata_tag_offset + 4 + start_offset + index * stride;
        bytes[record_offset..record_offset + 4]
            .copy_from_slice(&((index + 1) as u32).to_le_bytes());
        bytes[record_offset + 4..record_offset + 8].copy_from_slice(&1u32.to_le_bytes());
        bytes[record_offset + SAVE_CHAIRMAN_RECORD_NAME_OFFSET
            ..record_offset + SAVE_CHAIRMAN_RECORD_NAME_OFFSET + name.len()]
            .copy_from_slice(name.as_bytes());
        bytes[record_offset + SAVE_CHAIRMAN_RECORD_CASH_OFFSET
            ..record_offset + SAVE_CHAIRMAN_RECORD_CASH_OFFSET + 8]
            .copy_from_slice(&cash.to_le_bytes());
        bytes[record_offset + SAVE_CHAIRMAN_RECORD_LINKED_COMPANY_OFFSET
            ..record_offset + SAVE_CHAIRMAN_RECORD_LINKED_COMPANY_OFFSET + 4]
            .copy_from_slice(&linked.to_le_bytes());
        bytes[record_offset + SAVE_CHAIRMAN_RECORD_PERSONALITY_BYTE_0X291_OFFSET] =
            (index as u8) + 10;
        bytes[record_offset + SAVE_CHAIRMAN_RECORD_CACHE_0_OFFSET
            ..record_offset + SAVE_CHAIRMAN_RECORD_CACHE_0_OFFSET + 8]
            .copy_from_slice(&cache0.to_le_bytes());
        bytes[record_offset + SAVE_CHAIRMAN_RECORD_CACHE_1_OFFSET
            ..record_offset + SAVE_CHAIRMAN_RECORD_CACHE_1_OFFSET + 8]
            .copy_from_slice(&cache1.to_le_bytes());
        bytes[record_offset + 0x211..record_offset + 0x211 + 8]
            .copy_from_slice(&cache4.to_le_bytes());
        for (company_id, units) in holdings {
            let slot_offset =
                record_offset + SAVE_CHAIRMAN_RECORD_HOLDINGS_BASE_OFFSET + company_id as usize * 4;
            bytes[slot_offset..slot_offset + 4].copy_from_slice(&units.to_le_bytes());
        }
    }

    let header_probe = parse_save_chairman_profile_collection_header_probe(
        &bytes,
        Some("gms"),
        Some(&SmpContainerProfile {
            profile_family: "rt3-105-save-container-v1".to_string(),
            profile_evidence: vec![],
            is_known_profile: true,
        }),
    )
    .expect("chairman header probe should parse");
    let table = parse_save_chairman_profile_table_probe(
        &bytes,
        Some(&header_probe),
        Some(&SmpSaveWorldSelectionContextProbe {
            profile_family: "rt3-105-save-container-v1".to_string(),
            source_kind: "save-direct-world-block".to_string(),
            semantic_family: "scenario-selected-company-and-chairman-context".to_string(),
            chunk_tag_offset: 0,
            payload_offset: 0,
            payload_len: 0,
            payload_len_hex: "0x0".to_string(),
            selected_company_id_offset: 0,
            selected_company_id: 2,
            selected_company_id_hex: "0x00000002".to_string(),
            selected_chairman_profile_id_offset: 0,
            selected_chairman_profile_id: 2,
            selected_chairman_profile_id_hex: "0x00000002".to_string(),
            chairman_slot_selector_offset: 0,
            chairman_slot_selectors: vec![],
            campaign_override_flag_offset: 0,
            campaign_override_flag: 0,
            campaign_override_flag_hex: "0x00".to_string(),
            chairman_role_gate_offset: 0,
            chairman_role_gate_bytes: vec![],
            evidence: vec![],
        }),
        Some(&SmpSaveTaggedCollectionHeaderProbe {
            profile_family: "rt3-105-save-container-v1".to_string(),
            source_kind: "save-company-tagged-header-counts".to_string(),
            semantic_family: "scenario-save-company-header-counts".to_string(),
            metadata_tag_offset: 0,
            records_tag_offset: 0,
            close_tag_offset: 0,
            direct_collection_flag: 1,
            direct_collection_flag_hex: "0x00000001".to_string(),
            direct_record_stride: 0x7684,
            direct_record_stride_hex: "0x00007684".to_string(),
            live_id_bound: 5,
            live_id_bound_hex: "0x00000005".to_string(),
            live_record_count: 2,
            live_record_count_hex: "0x00000002".to_string(),
            header_words: vec![],
            header_hex_words: vec![],
            evidence: vec![],
        }),
    )
    .expect("chairman profile table should parse");

    assert_eq!(table.observed_entry_count, 2);
    assert_eq!(table.selected_chairman_profile_id, Some(2));
    assert_eq!(table.entries.len(), 2);
    assert_eq!(table.entries[0].profile_id, 1);
    assert_eq!(table.entries[0].name, "Collis Huntington");
    assert_eq!(table.entries[0].linked_company_id, Some(1));
    assert_eq!(table.entries[0].company_holdings.get(&1), Some(&6000));
    assert_eq!(table.entries[0].current_cash, -107644);
    assert_eq!(table.entries[0].holdings_value_total, 252508);
    assert_eq!(table.entries[0].purchasing_power_total, 144864);
    assert_eq!(table.entries[0].personality_byte_0x291, Some(10));
    assert_eq!(table.entries[1].profile_id, 2);
    assert_eq!(table.entries[1].company_holdings.get(&2), Some(&9000));
    assert_eq!(table.entries[1].holdings_value_total, 822000);
    assert_eq!(table.entries[1].purchasing_power_total, 1_009_282);
    assert_eq!(table.entries[1].personality_byte_0x291, Some(11));
}

#[test]
fn builds_save_world_selection_role_analysis_from_probe() {
    let probe = SmpSaveWorldSelectionContextProbe {
        profile_family: "rt3-105-save-container-v1".to_string(),
        source_kind: "save-direct-world-block".to_string(),
        semantic_family: "scenario-selected-company-and-chairman-context".to_string(),
        chunk_tag_offset: 0,
        payload_offset: 0,
        payload_len: 0x4f2c,
        payload_len_hex: "0x4f2c".to_string(),
        selected_company_id_offset: 0x21,
        selected_company_id: 3,
        selected_company_id_hex: "0x00000003".to_string(),
        selected_chairman_profile_id_offset: 0x25,
        selected_chairman_profile_id: 7,
        selected_chairman_profile_id_hex: "0x00000007".to_string(),
        chairman_slot_selector_offset: 0x87,
        chairman_slot_selectors: vec![1, 0, 2, 0],
        campaign_override_flag_offset: 0xc5,
        campaign_override_flag: 1,
        campaign_override_flag_hex: "0x01".to_string(),
        chairman_role_gate_offset: 0x0bc3,
        chairman_role_gate_bytes: vec![2, 0, 1, 0],
        evidence: vec![],
    };

    let analysis = build_save_world_selection_role_analysis(&probe);

    assert_eq!(analysis.selected_company_id, 3);
    assert_eq!(analysis.selected_chairman_profile_id, 7);
    assert_eq!(analysis.campaign_override_flag_hex, "0x01");
    assert_eq!(analysis.chairman_slots.len(), 4);
    assert_eq!(analysis.chairman_slots[0].selector_byte_hex, "0x01");
    assert_eq!(analysis.chairman_slots[2].role_gate_byte_hex, "0x01");
}

#[test]
fn builds_save_candidate_views_with_raw_bits() {
    let mut bytes = vec![0u8; 0x40];
    bytes[0x08..0x0c].copy_from_slice(&0x3f800000u32.to_le_bytes());
    bytes[0x10..0x18].copy_from_slice(&(-2458.0f64).to_le_bytes());

    let dword = build_save_dword_candidate(&bytes, 0, "unit_float", 0x08)
        .expect("dword candidate should build");
    let qword = build_save_qword_candidate(&bytes, 0, 0x10).expect("qword candidate should build");

    assert_eq!(dword.raw_u32_hex, "0x3f800000");
    assert_eq!(dword.value_i32, 1_065_353_216);
    assert_eq!(dword.value_f32, 1.0);
    assert_eq!(qword.raw_u64, (-2458.0f64).to_bits());
    assert_eq!(qword.value_i64, (-2458.0f64).to_bits() as i64);
    assert_eq!(qword.value_f64, -2458.0);
}

#[test]
fn derives_chairman_holdings_share_price_total_from_grounded_company_prices() {
    let holdings_by_company =
        BTreeMap::from([(2u32, 19_000u32), (4u32, 1_000u32), (6u32, 2_000u32)]);
    let company_share_prices = BTreeMap::from([(2u32, 66i64), (4u32, 69i64), (6u32, 27i64)]);

    let total =
        derive_chairman_holdings_share_price_total(&holdings_by_company, &company_share_prices)
            .expect("derived holdings total should compute");

    assert_eq!(total, 1_377_000);
}

#[test]
fn derives_chairman_cached_purchasing_power_from_strongest_nonnegative_cache() {
    let cached_scalar_candidates = vec![
        SmpSaveScalarCandidate {
            relative_offset: 0x1e9,
            relative_offset_hex: "0x1e9".to_string(),
            raw_u64: (-343_508.0f64).to_bits(),
            raw_u64_hex: format!("0x{:016x}", (-343_508.0f64).to_bits()),
            value_i64: round_f64_to_i64(-343_508.0).expect("i64"),
            value_f64: -343_508.0,
        },
        SmpSaveScalarCandidate {
            relative_offset: 0x201,
            relative_offset_hex: "0x201".to_string(),
            raw_u64: 1_386_000.0f64.to_bits(),
            raw_u64_hex: format!("0x{:016x}", 1_386_000.0f64.to_bits()),
            value_i64: round_f64_to_i64(1_386_000.0).expect("i64"),
            value_f64: 1_386_000.0,
        },
        SmpSaveScalarCandidate {
            relative_offset: 0x211,
            relative_offset_hex: "0x211".to_string(),
            raw_u64: 1_392_000.0f64.to_bits(),
            raw_u64_hex: format!("0x{:016x}", 1_392_000.0f64.to_bits()),
            value_i64: round_f64_to_i64(1_392_000.0).expect("i64"),
            value_f64: 1_392_000.0,
        },
    ];

    let total = derive_chairman_cached_purchasing_power_total(-463_436, &cached_scalar_candidates)
        .expect("derived purchasing power should compute");

    assert_eq!(total, 928_564);
}
