use std::collections::{BTreeMap, BTreeSet}; use std::path::{Path, PathBuf}; use serde::{Deserialize, Serialize}; use crate::persistence::{load_runtime_snapshot_document, validate_runtime_snapshot_document}; use crate::{ CalendarPoint, RuntimeCargoCatalogEntry, RuntimeCargoPriceTarget, RuntimeCargoProductionTarget, RuntimeChairmanProfile, RuntimeChairmanTarget, RuntimeCompany, RuntimeCompanyConditionTestScope, RuntimeCompanyControllerKind, RuntimeCompanyMarketState, RuntimeCompanyTarget, RuntimeCondition, RuntimeEffect, RuntimeEventRecord, RuntimeEventRecordTemplate, RuntimeLocomotiveCatalogEntry, RuntimePackedEventCollectionSummary, RuntimePackedEventCompactControlSummary, RuntimePackedEventConditionRowSummary, RuntimePackedEventGroupedEffectRowSummary, RuntimePackedEventNegativeSentinelScopeSummary, RuntimePackedEventRecordSummary, RuntimePackedEventTextBandSummary, RuntimePlayerConditionTestScope, RuntimePlayerTarget, RuntimeSaveProfileState, RuntimeServiceState, RuntimeState, RuntimeTerritoryTarget, RuntimeWorldFinanceNeighborhoodCandidate, RuntimeWorldRestoreState, SmpLoadedPackedEventConditionRowSummary, SmpLoadedPackedEventGroupedEffectRowSummary, SmpLoadedPackedEventNegativeSentinelScopeSummary, SmpLoadedPackedEventRecordSummary, SmpLoadedPackedEventTextBandSummary, SmpLoadedSaveSlice, }; pub const STATE_DUMP_FORMAT_VERSION: u32 = 1; pub const SAVE_SLICE_DOCUMENT_FORMAT_VERSION: u32 = 1; pub const OVERLAY_IMPORT_DOCUMENT_FORMAT_VERSION: u32 = 1; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] pub struct RuntimeStateDumpSource { #[serde(default)] pub description: Option, #[serde(default)] pub source_binary: Option, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RuntimeStateDumpDocument { pub format_version: u32, pub dump_id: String, #[serde(default)] pub source: RuntimeStateDumpSource, pub state: RuntimeState, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] pub struct RuntimeSaveSliceDocumentSource { #[serde(default)] pub description: Option, #[serde(default)] pub original_save_filename: Option, #[serde(default)] pub original_save_sha256: Option, #[serde(default)] pub notes: Vec, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RuntimeSaveSliceDocument { pub format_version: u32, pub save_slice_id: String, #[serde(default)] pub source: RuntimeSaveSliceDocumentSource, pub save_slice: SmpLoadedSaveSlice, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] pub struct RuntimeOverlayImportDocumentSource { #[serde(default)] pub description: Option, #[serde(default)] pub notes: Vec, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RuntimeOverlayImportDocument { pub format_version: u32, pub import_id: String, #[serde(default)] pub source: RuntimeOverlayImportDocumentSource, pub base_snapshot_path: String, pub save_slice_path: String, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct RuntimeStateImport { pub import_id: String, pub description: Option, pub state: RuntimeState, } #[derive(Debug)] struct SaveSliceProjection { world_flags: BTreeMap, save_profile: RuntimeSaveProfileState, world_restore: RuntimeWorldRestoreState, metadata: BTreeMap, packed_event_collection: Option, event_runtime_records: Vec, companies: Vec, has_company_projection: bool, has_company_selection_override: bool, selected_company_id: Option, company_market_state: BTreeMap, has_company_market_projection: bool, world_issue_opinion_base_terms_raw_i32: Vec, chairman_profiles: Vec, has_chairman_projection: bool, has_chairman_selection_override: bool, selected_chairman_profile_id: Option, chairman_issue_opinion_terms_raw_i32: BTreeMap>, chairman_personality_raw_u8: BTreeMap, candidate_availability: BTreeMap, named_locomotive_availability: BTreeMap, locomotive_catalog: Option>, cargo_catalog: Option>, named_locomotive_cost: BTreeMap, all_cargo_price_override: Option, named_cargo_price_overrides: BTreeMap, all_cargo_production_override: Option, factory_cargo_production_override: Option, farm_mine_cargo_production_override: Option, named_cargo_production_overrides: BTreeMap, cargo_production_overrides: BTreeMap, world_scalar_overrides: BTreeMap, special_conditions: BTreeMap, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum SaveSliceProjectionMode { Standalone, Overlay, } #[derive(Debug, Clone, PartialEq, Eq)] struct ImportRuntimeContext { known_company_ids: BTreeSet, selected_company_id: Option, has_complete_company_controller_context: bool, known_player_ids: BTreeSet, selected_player_id: Option, has_complete_player_controller_context: bool, known_chairman_profile_ids: BTreeSet, selected_chairman_profile_id: Option, known_territory_ids: BTreeSet, has_territory_context: bool, territory_name_to_id: BTreeMap, has_train_context: bool, has_train_territory_context: bool, locomotive_catalog_names_by_id: BTreeMap, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum ImportBlocker { MissingCompanyContext, MissingSelectionContext, MissingCompanyRoleContext, MissingPlayerContext, MissingPlayerSelectionContext, MissingPlayerRoleContext, MissingChairmanContext, ChairmanTargetScope, MissingConditionContext, MissingPlayerConditionContext, CompanyConditionScopeDisabled, MissingTerritoryContext, NamedTerritoryBinding, UnmappedOrdinaryCondition, UnmappedWorldCondition, EvidenceBlockedDescriptor, MissingTrainContext, MissingTrainTerritoryContext, MissingLocomotiveCatalogContext, } impl ImportRuntimeContext { fn standalone() -> Self { Self { known_company_ids: BTreeSet::new(), selected_company_id: None, has_complete_company_controller_context: false, known_player_ids: BTreeSet::new(), selected_player_id: None, has_complete_player_controller_context: false, known_chairman_profile_ids: BTreeSet::new(), selected_chairman_profile_id: None, known_territory_ids: BTreeSet::new(), has_territory_context: false, territory_name_to_id: BTreeMap::new(), has_train_context: false, has_train_territory_context: false, locomotive_catalog_names_by_id: BTreeMap::new(), } } fn from_runtime_state(state: &RuntimeState) -> Self { Self { known_company_ids: state .companies .iter() .map(|company| company.company_id) .collect(), selected_company_id: state.selected_company_id, has_complete_company_controller_context: !state.companies.is_empty() && state.companies.iter().all(|company| { company.controller_kind != RuntimeCompanyControllerKind::Unknown }), known_player_ids: state .players .iter() .map(|player| player.player_id) .collect(), selected_player_id: state.selected_player_id, has_complete_player_controller_context: !state.players.is_empty() && state .players .iter() .all(|player| player.controller_kind != RuntimeCompanyControllerKind::Unknown), known_chairman_profile_ids: state .chairman_profiles .iter() .map(|profile| profile.profile_id) .collect(), selected_chairman_profile_id: state.selected_chairman_profile_id, known_territory_ids: state .territories .iter() .map(|territory| territory.territory_id) .collect(), has_territory_context: !state.territories.is_empty(), territory_name_to_id: state .territories .iter() .filter_map(|territory| { territory .name .as_ref() .map(|name| (name.clone(), territory.territory_id)) }) .collect(), has_train_context: !state.trains.is_empty(), has_train_territory_context: state .trains .iter() .any(|train| train.territory_id.is_some()), locomotive_catalog_names_by_id: state .locomotive_catalog .iter() .map(|entry| (entry.locomotive_id, entry.name.clone())) .collect(), } } } pub fn project_save_slice_to_runtime_state_import( save_slice: &SmpLoadedSaveSlice, import_id: &str, description: Option, ) -> Result { if import_id.trim().is_empty() { return Err("import_id must not be empty".to_string()); } let projection = project_save_slice_components( save_slice, &ImportRuntimeContext::standalone(), SaveSliceProjectionMode::Standalone, )?; let state = RuntimeState { calendar: CalendarPoint { year: 1830, month_slot: 0, phase_slot: 0, tick_slot: 0, }, world_flags: projection.world_flags, save_profile: projection.save_profile, world_restore: projection.world_restore, metadata: projection.metadata, companies: projection.companies, selected_company_id: if projection.has_company_projection { projection.selected_company_id } else { None }, players: Vec::new(), selected_player_id: None, chairman_profiles: projection.chairman_profiles, selected_chairman_profile_id: if projection.has_chairman_projection { projection.selected_chairman_profile_id } else { None }, trains: Vec::new(), locomotive_catalog: projection.locomotive_catalog.unwrap_or_default(), cargo_catalog: projection.cargo_catalog.unwrap_or_default(), territories: Vec::new(), company_territory_track_piece_counts: Vec::new(), company_territory_access: Vec::new(), packed_event_collection: projection.packed_event_collection, event_runtime_records: projection.event_runtime_records, candidate_availability: projection.candidate_availability, named_locomotive_availability: projection.named_locomotive_availability, named_locomotive_cost: projection.named_locomotive_cost, all_cargo_price_override: projection.all_cargo_price_override, named_cargo_price_overrides: projection.named_cargo_price_overrides, all_cargo_production_override: projection.all_cargo_production_override, factory_cargo_production_override: projection.factory_cargo_production_override, farm_mine_cargo_production_override: projection.farm_mine_cargo_production_override, named_cargo_production_overrides: projection.named_cargo_production_overrides, cargo_production_overrides: projection.cargo_production_overrides, world_runtime_variables: BTreeMap::new(), company_runtime_variables: BTreeMap::new(), player_runtime_variables: BTreeMap::new(), territory_runtime_variables: BTreeMap::new(), world_scalar_overrides: projection.world_scalar_overrides, special_conditions: projection.special_conditions, service_state: RuntimeServiceState { world_issue_opinion_base_terms_raw_i32: projection .world_issue_opinion_base_terms_raw_i32, company_market_state: projection.company_market_state, chairman_issue_opinion_terms_raw_i32: projection.chairman_issue_opinion_terms_raw_i32, chairman_personality_raw_u8: projection.chairman_personality_raw_u8, ..RuntimeServiceState::default() }, }; let mut state = state; state.refresh_derived_world_state(); state.refresh_derived_market_state(); state.validate()?; Ok(RuntimeStateImport { import_id: import_id.to_string(), description, state, }) } pub fn project_save_slice_overlay_to_runtime_state_import( base_state: &RuntimeState, save_slice: &SmpLoadedSaveSlice, import_id: &str, description: Option, ) -> Result { if import_id.trim().is_empty() { return Err("import_id must not be empty".to_string()); } base_state.validate()?; let company_context = ImportRuntimeContext::from_runtime_state(base_state); let projection = project_save_slice_components( save_slice, &company_context, SaveSliceProjectionMode::Overlay, )?; let mut world_flags = base_state.world_flags.clone(); world_flags.retain(|key, _| !key.starts_with("save_slice.")); world_flags.extend(projection.world_flags); let mut metadata = base_state.metadata.clone(); metadata.retain(|key, _| !key.starts_with("save_slice.")); metadata.extend(projection.metadata); let state = RuntimeState { calendar: base_state.calendar, world_flags, save_profile: projection.save_profile, world_restore: RuntimeWorldRestoreState { territory_access_cost: base_state.world_restore.territory_access_cost, ..projection.world_restore }, metadata, companies: if projection.has_company_projection { projection.companies } else { base_state.companies.clone() }, selected_company_id: if projection.has_company_projection || projection.has_company_selection_override { projection.selected_company_id } else { base_state.selected_company_id }, players: base_state.players.clone(), selected_player_id: base_state.selected_player_id, chairman_profiles: if projection.has_chairman_projection { projection.chairman_profiles } else { base_state.chairman_profiles.clone() }, selected_chairman_profile_id: if projection.has_chairman_projection || projection.has_chairman_selection_override { projection.selected_chairman_profile_id } else { base_state.selected_chairman_profile_id }, trains: base_state.trains.clone(), locomotive_catalog: projection .locomotive_catalog .unwrap_or_else(|| base_state.locomotive_catalog.clone()), cargo_catalog: projection .cargo_catalog .unwrap_or_else(|| base_state.cargo_catalog.clone()), territories: base_state.territories.clone(), company_territory_track_piece_counts: base_state .company_territory_track_piece_counts .clone(), company_territory_access: base_state.company_territory_access.clone(), packed_event_collection: projection.packed_event_collection, event_runtime_records: projection.event_runtime_records, candidate_availability: projection.candidate_availability, named_locomotive_availability: projection.named_locomotive_availability, named_locomotive_cost: base_state.named_locomotive_cost.clone(), all_cargo_price_override: base_state.all_cargo_price_override, named_cargo_price_overrides: base_state.named_cargo_price_overrides.clone(), all_cargo_production_override: base_state.all_cargo_production_override, factory_cargo_production_override: base_state.factory_cargo_production_override, farm_mine_cargo_production_override: base_state.farm_mine_cargo_production_override, named_cargo_production_overrides: base_state.named_cargo_production_overrides.clone(), cargo_production_overrides: base_state.cargo_production_overrides.clone(), world_runtime_variables: base_state.world_runtime_variables.clone(), company_runtime_variables: base_state.company_runtime_variables.clone(), player_runtime_variables: base_state.player_runtime_variables.clone(), territory_runtime_variables: base_state.territory_runtime_variables.clone(), world_scalar_overrides: base_state.world_scalar_overrides.clone(), special_conditions: projection.special_conditions, service_state: RuntimeServiceState { world_issue_opinion_base_terms_raw_i32: if projection .world_issue_opinion_base_terms_raw_i32 .is_empty() { base_state .service_state .world_issue_opinion_base_terms_raw_i32 .clone() } else { projection.world_issue_opinion_base_terms_raw_i32 }, company_market_state: if projection.has_company_market_projection { projection.company_market_state } else { base_state.service_state.company_market_state.clone() }, chairman_issue_opinion_terms_raw_i32: if projection.has_chairman_projection { projection.chairman_issue_opinion_terms_raw_i32 } else { base_state .service_state .chairman_issue_opinion_terms_raw_i32 .clone() }, chairman_personality_raw_u8: if projection.has_chairman_projection { projection.chairman_personality_raw_u8 } else { base_state.service_state.chairman_personality_raw_u8.clone() }, ..base_state.service_state.clone() }, }; let mut state = state; state.refresh_derived_world_state(); state.refresh_derived_market_state(); state.validate()?; Ok(RuntimeStateImport { import_id: import_id.to_string(), description, state, }) } fn project_save_slice_components( save_slice: &SmpLoadedSaveSlice, company_context: &ImportRuntimeContext, mode: SaveSliceProjectionMode, ) -> Result { let mut world_flags = BTreeMap::new(); world_flags.insert( "save_slice.profile_present".to_string(), save_slice.profile.is_some(), ); world_flags.insert( "save_slice.candidate_availability_present".to_string(), save_slice.candidate_availability_table.is_some(), ); world_flags.insert( "save_slice.special_conditions_present".to_string(), save_slice.special_conditions_table.is_some(), ); world_flags.insert( "save_slice.named_locomotive_availability_present".to_string(), save_slice.named_locomotive_availability_table.is_some(), ); world_flags.insert( "save_slice.locomotive_catalog_present".to_string(), save_slice.locomotive_catalog.is_some() || save_slice.named_locomotive_availability_table.is_some(), ); world_flags.insert( "save_slice.cargo_catalog_present".to_string(), save_slice.cargo_catalog.is_some(), ); world_flags.insert( "save_slice.world_issue_37_state_present".to_string(), save_slice.world_issue_37_state.is_some(), ); world_flags.insert( "save_slice.world_economic_tuning_state_present".to_string(), save_slice.world_economic_tuning_state.is_some(), ); world_flags.insert( "save_slice.world_finance_neighborhood_state_present".to_string(), save_slice.world_finance_neighborhood_state.is_some(), ); world_flags.insert( "save_slice.event_runtime_collection_present".to_string(), save_slice.event_runtime_collection.is_some(), ); world_flags.insert( "save_slice.mechanism_confidence_grounded".to_string(), save_slice.mechanism_confidence == "grounded", ); if let Some(profile) = &save_slice.profile { world_flags.insert( "save_slice.profile_byte_0x82_nonzero".to_string(), profile.profile_byte_0x82 != 0, ); world_flags.insert( "save_slice.profile_byte_0x97_nonzero".to_string(), profile.profile_byte_0x97 != 0, ); world_flags.insert( "save_slice.profile_byte_0xc5_nonzero".to_string(), profile.profile_byte_0xc5 != 0, ); } let mut metadata = BTreeMap::new(); metadata.insert( "save_slice.import_projection".to_string(), match mode { SaveSliceProjectionMode::Standalone => "partial-runtime-restore-v1", SaveSliceProjectionMode::Overlay => "overlay-runtime-restore-v1", } .to_string(), ); metadata.insert( "save_slice.calendar_source".to_string(), match mode { SaveSliceProjectionMode::Standalone => "default-1830-placeholder", SaveSliceProjectionMode::Overlay => "base-snapshot-preserved", } .to_string(), ); metadata.insert( "save_slice.selected_year_seed_tuple_source".to_string(), "raw-lane-via-0x51d3f0".to_string(), ); metadata.insert( "save_slice.selected_year_absolute_counter_source".to_string(), if save_slice.world_finance_neighborhood_state.is_some() { "save-direct-world-block-absolute-counter".to_string() } else { "mode-adjusted-lane-via-0x51d390-0x409e80".to_string() }, ); metadata.insert( "save_slice.selected_year_absolute_counter_reconstructible_from_save".to_string(), save_slice .world_finance_neighborhood_state .is_some() .to_string(), ); metadata.insert( "save_slice.disable_cargo_economy_special_condition_slot".to_string(), "30".to_string(), ); metadata.insert( "save_slice.disable_cargo_economy_special_condition_reconstructible_from_save".to_string(), "true".to_string(), ); metadata.insert( "save_slice.disable_cargo_economy_special_condition_write_side_grounded".to_string(), "true".to_string(), ); metadata.insert( "save_slice.selected_year_absolute_counter_adjustment_context".to_string(), if save_slice.world_finance_neighborhood_state.is_some() { "save-direct-world-block-0x32c8".to_string() } else { "editor-map-mode,shell-selected-year-adjust-policy-0x9d26-0x9d28,save-special-condition-disable-cargo-economy-slot-30" .to_string() }, ); metadata.insert( "save_slice.mechanism_family".to_string(), save_slice.mechanism_family.clone(), ); metadata.insert( "save_slice.mechanism_confidence".to_string(), save_slice.mechanism_confidence.clone(), ); if let Some(family) = &save_slice.container_profile_family { metadata.insert( "save_slice.container_profile_family".to_string(), family.clone(), ); } if let Some(family) = &save_slice.trailer_family { metadata.insert("save_slice.trailer_family".to_string(), family.clone()); } if let Some(family) = &save_slice.bridge_family { metadata.insert("save_slice.bridge_family".to_string(), family.clone()); } if let Some(issue_state) = &save_slice.world_issue_37_state { metadata.insert( "save_slice.world_issue_37_source_kind".to_string(), issue_state.source_kind.clone(), ); metadata.insert( "save_slice.world_issue_37_semantic_family".to_string(), issue_state.semantic_family.clone(), ); metadata.insert( "save_slice.world_issue_37_value".to_string(), issue_state.issue_value.to_string(), ); metadata.insert( "save_slice.world_issue_37_value_hex".to_string(), issue_state.issue_value_hex.clone(), ); metadata.insert( "save_slice.world_issue_38_value".to_string(), issue_state.issue_38_value.to_string(), ); metadata.insert( "save_slice.world_issue_38_value_hex".to_string(), issue_state.issue_38_value_hex.clone(), ); metadata.insert( "save_slice.world_issue_39_value".to_string(), issue_state.issue_39_value.to_string(), ); metadata.insert( "save_slice.world_issue_39_value_hex".to_string(), issue_state.issue_39_value_hex.clone(), ); metadata.insert( "save_slice.world_issue_3a_value".to_string(), issue_state.issue_3a_value.to_string(), ); metadata.insert( "save_slice.world_issue_3a_value_hex".to_string(), issue_state.issue_3a_value_hex.clone(), ); metadata.insert( "save_slice.world_issue_37_multiplier_raw_hex".to_string(), issue_state.multiplier_raw_hex.clone(), ); metadata.insert( "save_slice.world_issue_37_multiplier_value_f32".to_string(), issue_state.multiplier_value_f32_text.clone(), ); } if let Some(tuning_state) = &save_slice.world_economic_tuning_state { metadata.insert( "save_slice.world_economic_tuning_source_kind".to_string(), tuning_state.source_kind.clone(), ); metadata.insert( "save_slice.world_economic_tuning_semantic_family".to_string(), tuning_state.semantic_family.clone(), ); metadata.insert( "save_slice.world_economic_tuning_mirror_raw_hex".to_string(), tuning_state.mirror_raw_hex.clone(), ); metadata.insert( "save_slice.world_economic_tuning_mirror_value_f32".to_string(), tuning_state.mirror_value_f32_text.clone(), ); metadata.insert( "save_slice.world_economic_tuning_lane_count".to_string(), tuning_state.lane_raw_u32.len().to_string(), ); for (index, value) in tuning_state.lane_value_f32_text.iter().enumerate() { metadata.insert( format!("save_slice.world_economic_tuning_lane_{index}_f32"), value.clone(), ); } } if let Some(finance_state) = &save_slice.world_finance_neighborhood_state { metadata.insert( "save_slice.world_finance_neighborhood_source_kind".to_string(), finance_state.source_kind.clone(), ); metadata.insert( "save_slice.world_finance_neighborhood_semantic_family".to_string(), finance_state.semantic_family.clone(), ); metadata.insert( "save_slice.world_finance_neighborhood_candidate_count".to_string(), finance_state.raw_u32.len().to_string(), ); for (index, label) in finance_state.labels.iter().enumerate() { metadata.insert( format!("save_slice.world_finance_neighborhood_label_{index}"), label.clone(), ); } } let save_profile = if let Some(profile) = &save_slice.profile { metadata.insert( "save_slice.profile_kind".to_string(), profile.profile_kind.clone(), ); metadata.insert( "save_slice.profile_family".to_string(), profile.profile_family.clone(), ); metadata.insert( "save_slice.packed_profile_offset".to_string(), profile.packed_profile_offset.to_string(), ); metadata.insert( "save_slice.packed_profile_len".to_string(), profile.packed_profile_len.to_string(), ); metadata.insert( "save_slice.leading_word_0_hex".to_string(), profile.leading_word_0_hex.clone(), ); metadata.insert( "save_slice.profile_byte_0x77_hex".to_string(), profile.profile_byte_0x77_hex.clone(), ); metadata.insert( "save_slice.profile_byte_0x82_hex".to_string(), profile.profile_byte_0x82_hex.clone(), ); metadata.insert( "save_slice.profile_byte_0x97_hex".to_string(), profile.profile_byte_0x97_hex.clone(), ); metadata.insert( "save_slice.profile_byte_0xc5_hex".to_string(), profile.profile_byte_0xc5_hex.clone(), ); if let Some(header_flag_word_3_hex) = &profile.header_flag_word_3_hex { metadata.insert( "save_slice.header_flag_word_3_hex".to_string(), header_flag_word_3_hex.clone(), ); } if let Some(map_path) = &profile.map_path { metadata.insert("save_slice.map_path".to_string(), map_path.clone()); } if let Some(display_name) = &profile.display_name { metadata.insert("save_slice.display_name".to_string(), display_name.clone()); } RuntimeSaveProfileState { profile_kind: Some(profile.profile_kind.clone()), profile_family: Some(profile.profile_family.clone()), map_path: profile.map_path.clone(), display_name: profile.display_name.clone(), selected_year_profile_lane: Some(profile.profile_byte_0x77), sandbox_enabled: Some(profile.profile_byte_0x82 != 0), campaign_scenario_enabled: Some(profile.profile_byte_0xc5 != 0), staged_profile_copy_on_restore: Some(profile.profile_byte_0x97 != 0), } } else { RuntimeSaveProfileState::default() }; let special_condition_enabled = |slot_index: u8| { save_slice.special_conditions_table.as_ref().map(|table| { table .entries .iter() .find(|entry| entry.slot_index == slot_index) .map(|entry| entry.value != 0) .unwrap_or(false) }) }; let world_restore = if let Some(profile) = &save_slice.profile { let disable_cargo_economy_special_condition_enabled = special_condition_enabled(30); RuntimeWorldRestoreState { selected_year_profile_lane: Some(profile.profile_byte_0x77), campaign_scenario_enabled: Some(profile.profile_byte_0xc5 != 0), sandbox_enabled: Some(profile.profile_byte_0x82 != 0), seed_tuple_written_from_raw_lane: Some(true), absolute_counter_requires_shell_context: Some( save_slice.world_finance_neighborhood_state.is_none(), ), absolute_counter_reconstructible_from_save: Some( save_slice.world_finance_neighborhood_state.is_some(), ), packed_year_word_raw_u16: save_slice .world_finance_neighborhood_state .as_ref() .map(|state| state.packed_year_word_raw_u16), partial_year_progress_raw_u8: save_slice .world_finance_neighborhood_state .as_ref() .map(|state| state.partial_year_progress_raw_u8), current_calendar_tuple_word_raw_u32: save_slice .world_finance_neighborhood_state .as_ref() .map(|state| state.current_calendar_tuple_word_raw_u32), current_calendar_tuple_word_2_raw_u32: save_slice .world_finance_neighborhood_state .as_ref() .map(|state| state.current_calendar_tuple_word_2_raw_u32), absolute_counter_raw_u32: save_slice .world_finance_neighborhood_state .as_ref() .map(|state| state.absolute_counter_raw_u32), absolute_counter_mirror_raw_u32: save_slice .world_finance_neighborhood_state .as_ref() .map(|state| state.absolute_counter_mirror_raw_u32), disable_cargo_economy_special_condition_slot: Some(30), disable_cargo_economy_special_condition_reconstructible_from_save: Some(true), disable_cargo_economy_special_condition_write_side_grounded: Some(true), disable_cargo_economy_special_condition_enabled, use_bio_accelerator_cars_enabled: special_condition_enabled(29), use_wartime_cargos_enabled: special_condition_enabled(31), disable_train_crashes_enabled: special_condition_enabled(32), disable_train_crashes_and_breakdowns_enabled: special_condition_enabled(33), ai_ignore_territories_at_startup_enabled: special_condition_enabled(34), limited_track_building_amount: None, economic_status_code: None, territory_access_cost: None, issue_37_value: save_slice .world_issue_37_state .as_ref() .map(|state| state.issue_value), issue_38_value: save_slice .world_issue_37_state .as_ref() .map(|state| state.issue_38_value), issue_39_value: save_slice .world_issue_37_state .as_ref() .map(|state| state.issue_39_value), issue_3a_value: save_slice .world_issue_37_state .as_ref() .map(|state| state.issue_3a_value), issue_37_multiplier_raw_u32: save_slice .world_issue_37_state .as_ref() .map(|state| state.multiplier_raw_u32), issue_37_multiplier_value_f32_text: save_slice .world_issue_37_state .as_ref() .map(|state| state.multiplier_value_f32_text.clone()), stock_issue_and_buyback_policy_raw_u8: save_slice .world_finance_neighborhood_state .as_ref() .map(|state| state.stock_policy_raw_u8), bond_issue_and_repayment_policy_raw_u8: save_slice .world_finance_neighborhood_state .as_ref() .map(|state| state.bond_policy_raw_u8), bankruptcy_policy_raw_u8: save_slice .world_finance_neighborhood_state .as_ref() .map(|state| state.bankruptcy_policy_raw_u8), dividend_policy_raw_u8: save_slice .world_finance_neighborhood_state .as_ref() .map(|state| state.dividend_policy_raw_u8), building_density_growth_setting_raw_u32: save_slice .world_finance_neighborhood_state .as_ref() .map(|state| state.building_density_growth_setting_raw_u32), stock_issue_and_buyback_allowed: save_slice .world_finance_neighborhood_state .as_ref() .map(|state| state.stock_policy_raw_u8 == 0), bond_issue_and_repayment_allowed: save_slice .world_finance_neighborhood_state .as_ref() .map(|state| state.bond_policy_raw_u8 == 0), bankruptcy_allowed: save_slice .world_finance_neighborhood_state .as_ref() .map(|state| state.bankruptcy_policy_raw_u8 == 0), dividend_adjustment_allowed: save_slice .world_finance_neighborhood_state .as_ref() .map(|state| state.dividend_policy_raw_u8 == 0), finance_neighborhood_candidates: save_slice .world_finance_neighborhood_state .as_ref() .map(|state| { state .labels .iter() .enumerate() .map(|(index, label)| RuntimeWorldFinanceNeighborhoodCandidate { label: label.clone(), relative_offset: state.relative_offsets[index], relative_offset_hex: state.relative_offset_hex[index].clone(), raw_u32: state.raw_u32[index], raw_u32_hex: state.raw_hex[index].clone(), value_i32: state.value_i32[index], value_f32_text: state.value_f32_text[index].clone(), }) .collect::>() }) .unwrap_or_default(), economic_tuning_mirror_raw_u32: save_slice .world_economic_tuning_state .as_ref() .map(|state| state.mirror_raw_u32), economic_tuning_mirror_value_f32_text: save_slice .world_economic_tuning_state .as_ref() .map(|state| state.mirror_value_f32_text.clone()), economic_tuning_lane_raw_u32: save_slice .world_economic_tuning_state .as_ref() .map(|state| state.lane_raw_u32.clone()) .unwrap_or_default(), economic_tuning_lane_value_f32_text: save_slice .world_economic_tuning_state .as_ref() .map(|state| state.lane_value_f32_text.clone()) .unwrap_or_default(), selected_year_gap_scalar_raw_u32: None, selected_year_gap_scalar_value_f32_text: None, absolute_counter_restore_kind: Some( if save_slice.world_finance_neighborhood_state.is_some() { "save-direct-world-absolute-counter".to_string() } else { "mode-adjusted-selected-year-lane".to_string() }, ), absolute_counter_adjustment_context: Some( if save_slice.world_finance_neighborhood_state.is_some() { "save-direct-world-block-0x32c8".to_string() } else { "editor-map-mode,shell-selected-year-adjust-policy-0x9d26-0x9d28,save-special-condition-disable-cargo-economy-slot-30" .to_string() }, ), } } else { RuntimeWorldRestoreState::default() }; let mut candidate_availability = BTreeMap::new(); if let Some(table) = &save_slice.candidate_availability_table { metadata.insert( "save_slice.candidate_table_source_kind".to_string(), table.source_kind.clone(), ); metadata.insert( "save_slice.candidate_table_semantic_family".to_string(), table.semantic_family.clone(), ); metadata.insert( "save_slice.candidate_table_entry_count".to_string(), table.observed_entry_count.to_string(), ); metadata.insert( "save_slice.candidate_table_zero_count".to_string(), table.zero_availability_count.to_string(), ); for entry in &table.entries { candidate_availability.insert(entry.text.clone(), entry.availability_dword); } } let mut special_conditions = BTreeMap::new(); if let Some(table) = &save_slice.special_conditions_table { metadata.insert( "save_slice.special_conditions_source_kind".to_string(), table.source_kind.clone(), ); metadata.insert( "save_slice.special_conditions_table_offset".to_string(), table.table_offset.to_string(), ); metadata.insert( "save_slice.special_conditions_enabled_visible_count".to_string(), table.enabled_visible_count.to_string(), ); for entry in &table.entries { if !entry.hidden { special_conditions.insert(entry.label.clone(), entry.value); } } } let mut named_locomotive_availability = BTreeMap::new(); if let Some(table) = &save_slice.named_locomotive_availability_table { metadata.insert( "save_slice.named_locomotive_availability_source_kind".to_string(), table.source_kind.clone(), ); metadata.insert( "save_slice.named_locomotive_availability_semantic_family".to_string(), table.semantic_family.clone(), ); metadata.insert( "save_slice.named_locomotive_availability_entry_count".to_string(), table.observed_entry_count.to_string(), ); metadata.insert( "save_slice.named_locomotive_availability_zero_count".to_string(), table.zero_availability_count.to_string(), ); if let Some(header_offset) = table.header_offset { metadata.insert( "save_slice.named_locomotive_availability_header_offset".to_string(), header_offset.to_string(), ); } for entry in &table.entries { named_locomotive_availability.insert(entry.text.clone(), entry.availability_dword); } } let locomotive_catalog = if let Some(catalog) = &save_slice.locomotive_catalog { metadata.insert( "save_slice.locomotive_catalog_source_kind".to_string(), catalog.source_kind.clone(), ); metadata.insert( "save_slice.locomotive_catalog_semantic_family".to_string(), catalog.semantic_family.clone(), ); metadata.insert( "save_slice.locomotive_catalog_entry_count".to_string(), catalog.observed_entry_count.to_string(), ); if let Some(entries_offset) = catalog.entries_offset { metadata.insert( "save_slice.locomotive_catalog_entries_offset".to_string(), entries_offset.to_string(), ); } Some( catalog .entries .iter() .map(|entry| RuntimeLocomotiveCatalogEntry { locomotive_id: entry.locomotive_id, name: entry.name.clone(), }) .collect::>(), ) } else if let Some(table) = &save_slice.named_locomotive_availability_table { metadata.insert( "save_slice.locomotive_catalog_source_kind".to_string(), "derived-from-named-locomotive-availability-table".to_string(), ); metadata.insert( "save_slice.locomotive_catalog_semantic_family".to_string(), "scenario-save-derived-locomotive-catalog".to_string(), ); metadata.insert( "save_slice.locomotive_catalog_entry_count".to_string(), table.observed_entry_count.to_string(), ); if let Some(entries_offset) = table.entries_offset { metadata.insert( "save_slice.locomotive_catalog_entries_offset".to_string(), entries_offset.to_string(), ); } Some( table .entries .iter() .enumerate() .map(|(index, entry)| RuntimeLocomotiveCatalogEntry { locomotive_id: (index + 1) as u32, name: entry.text.clone(), }) .collect::>(), ) } else { None }; let cargo_catalog = if let Some(catalog) = &save_slice.cargo_catalog { metadata.insert( "save_slice.cargo_catalog_source_kind".to_string(), catalog.source_kind.clone(), ); metadata.insert( "save_slice.cargo_catalog_semantic_family".to_string(), catalog.semantic_family.clone(), ); metadata.insert( "save_slice.cargo_catalog_entry_count".to_string(), catalog.observed_entry_count.to_string(), ); if let Some(root_offset) = catalog.root_offset { metadata.insert( "save_slice.cargo_catalog_root_offset".to_string(), root_offset.to_string(), ); } Some( catalog .entries .iter() .map(|entry| RuntimeCargoCatalogEntry { slot_id: entry.slot_id, label: entry.label.clone(), cargo_class: entry.cargo_class, supplied_token_stem: entry .supplied_cargo_token_probable_high16_ascii_stem .clone(), demanded_token_stem: entry .demanded_cargo_token_probable_high16_ascii_stem .clone(), }) .collect::>(), ) } else { None }; let ( companies, has_company_projection, has_company_selection_override, selected_company_id, company_market_state, world_issue_opinion_base_terms_raw_i32, has_company_market_projection, ) = if let Some(roster) = &save_slice.company_roster { metadata.insert( "save_slice.company_roster_source_kind".to_string(), roster.source_kind.clone(), ); metadata.insert( "save_slice.company_roster_semantic_family".to_string(), roster.semantic_family.clone(), ); metadata.insert( "save_slice.company_roster_entry_count".to_string(), roster.observed_entry_count.to_string(), ); let market_state = roster .entries .iter() .filter_map(|entry| { entry .market_state .as_ref() .map(|state| (entry.company_id, state.clone())) }) .collect::>(); metadata.insert( "save_slice.company_market_state_owner_count".to_string(), market_state.len().to_string(), ); if let Some(selected_company_id) = roster.selected_company_id { metadata.insert( "save_slice.selected_company_id".to_string(), selected_company_id.to_string(), ); } if roster.entries.is_empty() { ( Vec::new(), false, roster.selected_company_id.is_some(), roster.selected_company_id, BTreeMap::new(), save_slice .world_issue_37_state .as_ref() .map(|state| state.issue_opinion_base_terms_raw_i32.clone()) .unwrap_or_default(), false, ) } else { ( roster .entries .iter() .map(|entry| RuntimeCompany { company_id: entry.company_id, current_cash: entry.current_cash, debt: entry.debt, credit_rating_score: entry.credit_rating_score, prime_rate: entry.prime_rate, active: entry.active, available_track_laying_capacity: entry.available_track_laying_capacity, controller_kind: entry.controller_kind, linked_chairman_profile_id: entry.linked_chairman_profile_id, book_value_per_share: entry.book_value_per_share, investor_confidence: entry.investor_confidence, management_attitude: entry.management_attitude, takeover_cooldown_year: entry.takeover_cooldown_year, merger_cooldown_year: entry.merger_cooldown_year, track_piece_counts: entry.track_piece_counts, }) .collect::>(), true, roster.selected_company_id.is_some(), roster.selected_company_id, market_state, save_slice .world_issue_37_state .as_ref() .map(|state| state.issue_opinion_base_terms_raw_i32.clone()) .unwrap_or_default(), true, ) } } else { ( Vec::new(), false, false, None, BTreeMap::new(), save_slice .world_issue_37_state .as_ref() .map(|state| state.issue_opinion_base_terms_raw_i32.clone()) .unwrap_or_default(), false, ) }; let ( chairman_profiles, has_chairman_projection, has_chairman_selection_override, selected_chairman_profile_id, chairman_issue_opinion_terms_raw_i32, chairman_personality_raw_u8, ) = if let Some(table) = &save_slice.chairman_profile_table { metadata.insert( "save_slice.chairman_profile_table_source_kind".to_string(), table.source_kind.clone(), ); metadata.insert( "save_slice.chairman_profile_table_semantic_family".to_string(), table.semantic_family.clone(), ); metadata.insert( "save_slice.chairman_profile_table_entry_count".to_string(), table.observed_entry_count.to_string(), ); if let Some(selected_chairman_profile_id) = table.selected_chairman_profile_id { metadata.insert( "save_slice.selected_chairman_profile_id".to_string(), selected_chairman_profile_id.to_string(), ); } if table.entries.is_empty() { ( Vec::new(), false, table.selected_chairman_profile_id.is_some(), table.selected_chairman_profile_id, BTreeMap::new(), BTreeMap::new(), ) } else { ( table .entries .iter() .map(|entry| RuntimeChairmanProfile { profile_id: entry.profile_id, name: entry.name.clone(), active: entry.active, current_cash: entry.current_cash, linked_company_id: entry.linked_company_id, company_holdings: entry.company_holdings.clone(), holdings_value_total: entry.holdings_value_total, net_worth_total: entry.net_worth_total, purchasing_power_total: entry.purchasing_power_total, }) .collect::>(), true, table.selected_chairman_profile_id.is_some(), table.selected_chairman_profile_id, table .entries .iter() .map(|entry| (entry.profile_id, entry.issue_opinion_terms_raw_i32.clone())) .collect::>(), table .entries .iter() .filter_map(|entry| { entry .personality_byte_0x291 .map(|value| (entry.profile_id, value)) }) .collect::>(), ) } } else { ( Vec::new(), false, false, None, BTreeMap::new(), BTreeMap::new(), ) }; let named_locomotive_cost = BTreeMap::new(); let all_cargo_price_override = None; let named_cargo_price_overrides = BTreeMap::new(); let all_cargo_production_override = None; let factory_cargo_production_override = None; let farm_mine_cargo_production_override = None; let named_cargo_production_overrides = BTreeMap::new(); let cargo_production_overrides = BTreeMap::new(); let world_scalar_overrides = BTreeMap::new(); let mut packed_event_context = company_context.clone(); if has_company_projection { packed_event_context.known_company_ids = companies.iter().map(|company| company.company_id).collect(); packed_event_context.selected_company_id = selected_company_id; packed_event_context.has_complete_company_controller_context = !companies.is_empty() && companies .iter() .all(|company| company.controller_kind != RuntimeCompanyControllerKind::Unknown); } else if has_company_selection_override { packed_event_context.selected_company_id = selected_company_id; } if has_chairman_projection { packed_event_context.known_chairman_profile_ids = chairman_profiles .iter() .map(|profile| profile.profile_id) .collect(); packed_event_context.selected_chairman_profile_id = selected_chairman_profile_id; } else if has_chairman_selection_override { packed_event_context.selected_chairman_profile_id = selected_chairman_profile_id; } if let Some(catalog) = &locomotive_catalog { packed_event_context.locomotive_catalog_names_by_id = catalog .iter() .map(|entry| (entry.locomotive_id, entry.name.clone())) .collect(); } let (packed_event_collection, event_runtime_records) = project_packed_event_collection( save_slice, &packed_event_context, cargo_catalog.as_deref().unwrap_or(&[]), )?; if let Some(summary) = &save_slice.event_runtime_collection { metadata.insert( "save_slice.event_runtime_collection_source_kind".to_string(), summary.source_kind.clone(), ); metadata.insert( "save_slice.event_runtime_collection_version_hex".to_string(), summary.packed_state_version_hex.clone(), ); metadata.insert( "save_slice.event_runtime_collection_record_count".to_string(), summary.live_record_count.to_string(), ); metadata.insert( "save_slice.event_runtime_collection_decoded_record_count".to_string(), summary.decoded_record_count.to_string(), ); metadata.insert( "save_slice.event_runtime_collection_imported_runtime_record_count".to_string(), event_runtime_records.len().to_string(), ); } for (index, note) in save_slice.notes.iter().enumerate() { metadata.insert(format!("save_slice.note.{index}"), note.clone()); } Ok(SaveSliceProjection { world_flags, save_profile, world_restore, metadata, packed_event_collection, event_runtime_records, companies, has_company_projection, has_company_selection_override, selected_company_id, company_market_state, has_company_market_projection, world_issue_opinion_base_terms_raw_i32, chairman_profiles, has_chairman_projection, has_chairman_selection_override, selected_chairman_profile_id, chairman_issue_opinion_terms_raw_i32, chairman_personality_raw_u8, candidate_availability, named_locomotive_availability, locomotive_catalog, cargo_catalog, named_locomotive_cost, all_cargo_price_override, named_cargo_price_overrides, all_cargo_production_override, factory_cargo_production_override, farm_mine_cargo_production_override, named_cargo_production_overrides, cargo_production_overrides, world_scalar_overrides, special_conditions, }) } fn project_packed_event_collection( save_slice: &SmpLoadedSaveSlice, company_context: &ImportRuntimeContext, cargo_catalog: &[RuntimeCargoCatalogEntry], ) -> Result< ( Option, Vec, ), String, > { let Some(summary) = save_slice.event_runtime_collection.as_ref() else { return Ok((None, Vec::new())); }; let mut imported_runtime_records = Vec::new(); let mut imported_record_ids = BTreeSet::new(); for record in &summary.records { if let Some(import_result) = smp_packed_record_to_runtime_event_record(record, company_context) { let runtime_record = import_result?; imported_record_ids.insert(record.live_entry_id); imported_runtime_records.push(runtime_record); } } let records = summary .records .iter() .map(|record| { runtime_packed_event_record_summary_from_smp( record, company_context, cargo_catalog, imported_record_ids.contains(&record.live_entry_id), ) }) .collect::>(); Ok(( Some(RuntimePackedEventCollectionSummary { source_kind: summary.source_kind.clone(), mechanism_family: summary.mechanism_family.clone(), mechanism_confidence: summary.mechanism_confidence.clone(), container_profile_family: summary.container_profile_family.clone(), packed_state_version: summary.packed_state_version, packed_state_version_hex: summary.packed_state_version_hex.clone(), live_id_bound: summary.live_id_bound, live_record_count: summary.live_record_count, live_entry_ids: summary.live_entry_ids.clone(), decoded_record_count: records .iter() .filter(|record| record.decode_status != "unsupported_framing") .count(), imported_runtime_record_count: imported_runtime_records.len(), records, }), imported_runtime_records, )) } fn runtime_packed_event_record_summary_from_smp( record: &SmpLoadedPackedEventRecordSummary, company_context: &ImportRuntimeContext, cargo_catalog: &[RuntimeCargoCatalogEntry], imported: bool, ) -> RuntimePackedEventRecordSummary { let lowered_decoded_conditions = lowered_record_decoded_conditions(record, company_context) .unwrap_or_else(|_| record.decoded_conditions.clone()); let lowered_decoded_actions = lowered_record_decoded_actions(record, company_context) .unwrap_or_else(|_| record.decoded_actions.clone()); RuntimePackedEventRecordSummary { record_index: record.record_index, live_entry_id: record.live_entry_id, payload_offset: record.payload_offset, payload_len: record.payload_len, decode_status: record.decode_status.clone(), payload_family: record.payload_family.clone(), trigger_kind: record.trigger_kind, active: record.active, marks_collection_dirty: record.marks_collection_dirty, one_shot: record.one_shot, compact_control: record .compact_control .as_ref() .map(runtime_packed_event_compact_control_summary_from_smp), text_bands: record .text_bands .iter() .map(runtime_packed_event_text_band_summary_from_smp) .collect(), standalone_condition_row_count: record.standalone_condition_row_count, standalone_condition_rows: record .standalone_condition_rows .iter() .map(|row| runtime_packed_event_condition_row_summary_from_smp(row, cargo_catalog)) .collect(), negative_sentinel_scope: record .negative_sentinel_scope .as_ref() .map(runtime_packed_event_negative_sentinel_scope_summary_from_smp), grouped_effect_row_counts: record.grouped_effect_row_counts.clone(), grouped_effect_rows: record .grouped_effect_rows .iter() .map(|row| runtime_packed_event_grouped_effect_row_summary_from_smp(row, cargo_catalog)) .collect(), grouped_company_targets: classify_real_grouped_company_targets(record), decoded_conditions: lowered_decoded_conditions, decoded_actions: lowered_decoded_actions, executable_import_ready: record.executable_import_ready, import_outcome: Some(determine_packed_event_import_outcome( record, company_context, imported, )), notes: record.notes.clone(), } } fn runtime_packed_event_negative_sentinel_scope_summary_from_smp( scope: &SmpLoadedPackedEventNegativeSentinelScopeSummary, ) -> RuntimePackedEventNegativeSentinelScopeSummary { RuntimePackedEventNegativeSentinelScopeSummary { company_test_scope: scope.company_test_scope, player_test_scope: scope.player_test_scope, territory_scope_selector_is_0x63: scope.territory_scope_selector_is_0x63, source_row_indexes: scope.source_row_indexes.clone(), } } fn runtime_packed_event_compact_control_summary_from_smp( control: &crate::SmpLoadedPackedEventCompactControlSummary, ) -> RuntimePackedEventCompactControlSummary { RuntimePackedEventCompactControlSummary { mode_byte_0x7ef: control.mode_byte_0x7ef, primary_selector_0x7f0: control.primary_selector_0x7f0, grouped_mode_0x7f4: control.grouped_mode_0x7f4, one_shot_header_0x7f5: control.one_shot_header_0x7f5, modifier_flag_0x7f9: control.modifier_flag_0x7f9, modifier_flag_0x7fa: control.modifier_flag_0x7fa, grouped_target_scope_ordinals_0x7fb: control.grouped_target_scope_ordinals_0x7fb.clone(), grouped_scope_checkboxes_0x7ff: control.grouped_scope_checkboxes_0x7ff.clone(), summary_toggle_0x800: control.summary_toggle_0x800, grouped_territory_selectors_0x80f: control.grouped_territory_selectors_0x80f.clone(), } } fn runtime_packed_event_text_band_summary_from_smp( band: &SmpLoadedPackedEventTextBandSummary, ) -> RuntimePackedEventTextBandSummary { RuntimePackedEventTextBandSummary { label: band.label.clone(), packed_len: band.packed_len, present: band.present, preview: band.preview.clone(), } } fn runtime_packed_event_condition_row_summary_from_smp( row: &crate::SmpLoadedPackedEventConditionRowSummary, cargo_catalog: &[RuntimeCargoCatalogEntry], ) -> RuntimePackedEventConditionRowSummary { let cargo_entry = row .recovered_cargo_slot .and_then(|slot| cargo_catalog.iter().find(|entry| entry.slot_id == slot)); RuntimePackedEventConditionRowSummary { row_index: row.row_index, raw_condition_id: row.raw_condition_id, subtype: row.subtype, flag_bytes: row.flag_bytes.clone(), candidate_name: row.candidate_name.clone(), comparator: row.comparator.clone(), metric: row.metric.clone(), semantic_family: row.semantic_family.clone(), semantic_preview: row.semantic_preview.clone(), requires_candidate_name_binding: row.requires_candidate_name_binding, recovered_cargo_slot: row.recovered_cargo_slot, recovered_cargo_class: cargo_entry .map(|entry| format!("{:?}", entry.cargo_class).to_ascii_lowercase()) .map(|value| value.replace("farmmine", "farm_mine")) .or_else(|| row.recovered_cargo_class.clone()), recovered_cargo_label: cargo_entry .map(|entry| entry.label.clone()) .or_else(|| row.recovered_cargo_slot.map(default_cargo_slot_label)), recovered_cargo_supplied_token_stem: cargo_entry .and_then(|entry| entry.supplied_token_stem.clone()), recovered_cargo_demanded_token_stem: cargo_entry .and_then(|entry| entry.demanded_token_stem.clone()), notes: row.notes.clone(), } } fn runtime_packed_event_grouped_effect_row_summary_from_smp( row: &crate::SmpLoadedPackedEventGroupedEffectRowSummary, cargo_catalog: &[RuntimeCargoCatalogEntry], ) -> RuntimePackedEventGroupedEffectRowSummary { let cargo_entry = row .recovered_cargo_slot .and_then(|slot| cargo_catalog.iter().find(|entry| entry.slot_id == slot)); RuntimePackedEventGroupedEffectRowSummary { group_index: row.group_index, row_index: row.row_index, descriptor_id: row.descriptor_id, descriptor_label: row.descriptor_label.clone(), target_mask_bits: row.target_mask_bits, parameter_family: row.parameter_family.clone(), grouped_target_subject: row.grouped_target_subject.clone(), grouped_target_scope: row.grouped_target_scope.clone(), opcode: row.opcode, raw_scalar_value: row.raw_scalar_value, value_byte_0x09: row.value_byte_0x09, value_dword_0x0d: row.value_dword_0x0d, value_byte_0x11: row.value_byte_0x11, value_byte_0x12: row.value_byte_0x12, value_word_0x14: row.value_word_0x14, value_word_0x16: row.value_word_0x16, row_shape: row.row_shape.clone(), semantic_family: row.semantic_family.clone(), semantic_preview: row.semantic_preview.clone(), recovered_cargo_slot: row.recovered_cargo_slot, recovered_cargo_class: cargo_entry .map(|entry| format!("{:?}", entry.cargo_class).to_ascii_lowercase()) .map(|value| value.replace("farmmine", "farm_mine")) .or_else(|| row.recovered_cargo_class.clone()), recovered_cargo_label: cargo_entry .map(|entry| entry.label.clone()) .or_else(|| row.recovered_cargo_label.clone()) .or_else(|| row.recovered_cargo_slot.map(default_cargo_slot_label)), recovered_cargo_supplied_token_stem: cargo_entry .and_then(|entry| entry.supplied_token_stem.clone()), recovered_cargo_demanded_token_stem: cargo_entry .and_then(|entry| entry.demanded_token_stem.clone()), recovered_locomotive_id: row.recovered_locomotive_id, locomotive_name: row.locomotive_name.clone(), notes: row.notes.clone(), } } fn default_cargo_slot_label(slot: u32) -> String { format!("Cargo Production Slot {slot}") } fn smp_packed_record_to_runtime_event_record( record: &SmpLoadedPackedEventRecordSummary, company_context: &ImportRuntimeContext, ) -> Option> { if record.decode_status == "unsupported_framing" { return None; } if record.payload_family == "real_packed_v1" && record.compact_control.is_none() { return None; } let lowered_conditions = match lowered_record_decoded_conditions(record, company_context) { Ok(conditions) => conditions, Err(_) => return None, }; let lowered_effects = match lowered_record_decoded_actions(record, company_context) { Ok(effects) => effects, Err(_) => return None, }; let effects = match smp_runtime_effects_to_runtime_effects( &lowered_effects, company_context, conditions_provide_company_context(&lowered_conditions), false, ) { Ok(effects) => effects, Err(_) => return None, }; Some((|| { let trigger_kind = record.trigger_kind.ok_or_else(|| { format!( "packed event record {} is missing trigger_kind", record.live_entry_id ) })?; let active = record.active.unwrap_or(true); let marks_collection_dirty = record.marks_collection_dirty.unwrap_or(false); let one_shot = record.one_shot.unwrap_or(false); Ok(RuntimeEventRecordTemplate { record_id: record.live_entry_id, trigger_kind, active, marks_collection_dirty, one_shot, conditions: lowered_conditions, effects, } .into_runtime_record()) })()) } fn lowered_record_decoded_conditions( record: &SmpLoadedPackedEventRecordSummary, company_context: &ImportRuntimeContext, ) -> Result, ImportBlocker> { if let Some(blocker) = packed_record_condition_scope_import_blocker(record, company_context) { return Err(blocker); } let lowered_company_target = lowered_condition_true_company_target(record)?; let lowered_player_target = lowered_condition_true_player_target(record)?; let ordinary_rows = record .standalone_condition_rows .iter() .filter(|row| row.raw_condition_id >= 0); ordinary_rows .zip(record.decoded_conditions.iter()) .map(|(row, condition)| { lower_condition_targets_in_condition( condition, row, lowered_company_target.as_ref(), lowered_player_target.as_ref(), company_context, ) }) .collect() } fn lowered_record_decoded_actions( record: &SmpLoadedPackedEventRecordSummary, company_context: &ImportRuntimeContext, ) -> Result, ImportBlocker> { if let Some(blocker) = packed_record_condition_scope_import_blocker(record, company_context) { return Err(blocker); } ensure_condition_true_chairman_context(record)?; let lowered_company_target = lowered_condition_true_company_target(record)?; let lowered_player_target = lowered_condition_true_player_target(record)?; let base_effects = if record.payload_family != "real_packed_v1" || record.decoded_actions.len() == record.grouped_effect_rows.len() { record.decoded_actions.clone() } else { lower_contextual_real_grouped_effects(record, company_context)? }; base_effects .iter() .map(|effect| { lower_condition_targets_in_effect( effect, lowered_company_target.as_ref(), lowered_player_target.as_ref(), ) }) .collect() } fn lower_contextual_real_grouped_effects( record: &SmpLoadedPackedEventRecordSummary, company_context: &ImportRuntimeContext, ) -> Result, ImportBlocker> { if record.payload_family != "real_packed_v1" || record.compact_control.is_none() { return Err(ImportBlocker::UnmappedWorldCondition); } let mut effects = Vec::with_capacity(record.grouped_effect_rows.len()); for row in &record.grouped_effect_rows { if real_grouped_row_is_unsupported_chairman_target_scope(row) { return Err(ImportBlocker::ChairmanTargetScope); } if let Some(effect) = lower_contextual_cargo_price_effect(row)? { effects.push(effect); continue; } if let Some(effect) = lower_contextual_world_scalar_override_effect(row)? { effects.push(effect); continue; } if let Some(effect) = lower_contextual_runtime_variable_effect(row)? { effects.push(effect); continue; } if let Some(effect) = lower_contextual_cargo_production_effect(row)? { effects.push(effect); continue; } if let Some(effect) = lower_contextual_territory_access_cost_effect(row)? { effects.push(effect); continue; } if let Some(effect) = lower_contextual_locomotive_cost_effect(row, company_context)? { effects.push(effect); continue; } if let Some(effect) = lower_contextual_locomotive_availability_effect(row, company_context)? { effects.push(effect); continue; } return Err(if real_grouped_row_is_world_state_family(row) { ImportBlocker::UnmappedWorldCondition } else { ImportBlocker::UnmappedOrdinaryCondition }); } if effects.is_empty() { return Err(ImportBlocker::UnmappedWorldCondition); } Ok(effects) } fn lower_contextual_cargo_price_effect( row: &SmpLoadedPackedEventGroupedEffectRowSummary, ) -> Result, ImportBlocker> { if row.parameter_family.as_deref() != Some("cargo_price_scalar") { return Ok(None); } if row.row_shape != "scalar_assignment" { return Ok(None); } let Some(value) = u32::try_from(row.raw_scalar_value).ok() else { return Ok(None); }; let target = if row.descriptor_id == 105 { RuntimeCargoPriceTarget::All } else if let Some(name) = row.recovered_cargo_label.as_deref() { RuntimeCargoPriceTarget::Named { name: name.to_string(), } } else { return Ok(None); }; Ok(Some(RuntimeEffect::SetCargoPriceOverride { target, value })) } fn lower_contextual_world_scalar_override_effect( row: &SmpLoadedPackedEventGroupedEffectRowSummary, ) -> Result, ImportBlocker> { if row.parameter_family.as_deref() != Some("world_scalar_override") { return Ok(None); } if row.row_shape != "scalar_assignment" { return Ok(None); } let Some(key) = row .descriptor_label .as_deref() .map(crate::smp::runtime_world_scalar_key_from_label) else { return Ok(None); }; Ok(Some(RuntimeEffect::SetWorldScalarOverride { key, value: i64::from(row.raw_scalar_value), })) } fn lower_contextual_runtime_variable_effect( row: &SmpLoadedPackedEventGroupedEffectRowSummary, ) -> Result, ImportBlocker> { if row.parameter_family.as_deref() != Some("runtime_variable_scalar") { return Ok(None); } if row.row_shape != "scalar_assignment" { return Ok(None); } let value = i64::from(row.raw_scalar_value); Ok(match row.descriptor_id { 39..=42 => Some(RuntimeEffect::SetWorldVariable { index: row.descriptor_id - 38, value, }), _ => None, }) } fn lower_contextual_locomotive_availability_effect( row: &SmpLoadedPackedEventGroupedEffectRowSummary, company_context: &ImportRuntimeContext, ) -> Result, ImportBlocker> { if row.parameter_family.as_deref() != Some("locomotive_availability_scalar") { return Ok(None); } if row.row_shape != "scalar_assignment" { return Ok(None); } let Some(value) = u32::try_from(row.raw_scalar_value).ok() else { return Ok(None); }; let Some(locomotive_id) = row.recovered_locomotive_id else { return Ok(None); }; let Some(name) = company_context .locomotive_catalog_names_by_id .get(&locomotive_id) .cloned() else { return Err(ImportBlocker::MissingLocomotiveCatalogContext); }; Ok(Some(RuntimeEffect::SetNamedLocomotiveAvailabilityValue { name, value, })) } fn lower_contextual_cargo_production_effect( row: &SmpLoadedPackedEventGroupedEffectRowSummary, ) -> Result, ImportBlocker> { if row.parameter_family.as_deref() != Some("cargo_production_scalar") { return Ok(None); } if row.row_shape != "scalar_assignment" { return Ok(None); } let Some(value) = u32::try_from(row.raw_scalar_value).ok() else { return Ok(None); }; match row.descriptor_id { 177 => Ok(Some(RuntimeEffect::SetCargoProductionOverride { target: RuntimeCargoProductionTarget::All, value, })), 178 => Ok(Some(RuntimeEffect::SetCargoProductionOverride { target: RuntimeCargoProductionTarget::Factory, value, })), 179 => Ok(Some(RuntimeEffect::SetCargoProductionOverride { target: RuntimeCargoProductionTarget::FarmMine, value, })), 180..=229 => { let Some(name) = row.recovered_cargo_label.clone() else { return Err(ImportBlocker::EvidenceBlockedDescriptor); }; Ok(Some(RuntimeEffect::SetCargoProductionOverride { target: RuntimeCargoProductionTarget::Named { name }, value, })) } 230..=240 => { let Some(slot) = row.descriptor_id.checked_sub(229) else { return Ok(None); }; Ok(Some(RuntimeEffect::SetCargoProductionSlot { slot, value })) } _ => Ok(None), } } fn lower_contextual_territory_access_cost_effect( row: &SmpLoadedPackedEventGroupedEffectRowSummary, ) -> Result, ImportBlocker> { if row.parameter_family.as_deref() != Some("territory_access_cost_scalar") { return Ok(None); } if row.row_shape != "scalar_assignment" { return Ok(None); } let Some(value) = u32::try_from(row.raw_scalar_value).ok() else { return Ok(None); }; Ok(Some(RuntimeEffect::SetTerritoryAccessCost { value })) } fn lower_contextual_locomotive_cost_effect( row: &SmpLoadedPackedEventGroupedEffectRowSummary, company_context: &ImportRuntimeContext, ) -> Result, ImportBlocker> { if row.parameter_family.as_deref() != Some("locomotive_cost_scalar") { return Ok(None); } if row.row_shape != "scalar_assignment" { return Ok(None); } let value = u32::try_from(row.raw_scalar_value).ok(); let Some(value) = value else { return Ok(None); }; let Some(locomotive_id) = row.recovered_locomotive_id else { return Ok(None); }; let Some(name) = company_context .locomotive_catalog_names_by_id .get(&locomotive_id) .cloned() else { return Err(ImportBlocker::MissingLocomotiveCatalogContext); }; Ok(Some(RuntimeEffect::SetNamedLocomotiveCost { name, value })) } fn packed_record_condition_scope_import_blocker( record: &SmpLoadedPackedEventRecordSummary, company_context: &ImportRuntimeContext, ) -> Option { if record.standalone_condition_rows.is_empty() { return None; } let ordinary_condition_row_count = record .standalone_condition_rows .iter() .filter(|row| row.raw_condition_id >= 0) .count(); if ordinary_condition_row_count != 0 { if ordinary_condition_row_count != record.decoded_conditions.len() { return Some(if record_has_world_state_condition_rows(record) { ImportBlocker::UnmappedWorldCondition } else { ImportBlocker::UnmappedOrdinaryCondition }); } if (!company_context.has_territory_context) && (record .standalone_condition_rows .iter() .any(|row| row.requires_candidate_name_binding) || record.decoded_conditions.iter().any(|condition| { matches!( condition, RuntimeCondition::TerritoryNumericThreshold { .. } | RuntimeCondition::CompanyTerritoryNumericThreshold { .. } ) })) { return Some(ImportBlocker::MissingTerritoryContext); } } let negative_sentinel_row_count = record .standalone_condition_rows .iter() .filter(|row| row.raw_condition_id == -1) .count(); if negative_sentinel_row_count == 0 { return if ordinary_condition_row_count == 0 { Some(ImportBlocker::MissingConditionContext) } else { None }; } if ordinary_condition_row_count == 0 && negative_sentinel_row_count != record.standalone_condition_rows.len() { return Some(ImportBlocker::MissingConditionContext); } if record.negative_sentinel_scope.is_none() { return Some(ImportBlocker::MissingConditionContext); } None } fn lowered_condition_true_company_target( record: &SmpLoadedPackedEventRecordSummary, ) -> Result, ImportBlocker> { if !record_uses_condition_true_company(record) { return Ok(None); } let scope = record .negative_sentinel_scope .as_ref() .ok_or(ImportBlocker::MissingConditionContext)?; match scope.company_test_scope { RuntimeCompanyConditionTestScope::Disabled => { Err(ImportBlocker::CompanyConditionScopeDisabled) } RuntimeCompanyConditionTestScope::AllCompanies => Ok(Some(RuntimeCompanyTarget::AllActive)), RuntimeCompanyConditionTestScope::SelectedCompanyOnly => { Ok(Some(RuntimeCompanyTarget::SelectedCompany)) } RuntimeCompanyConditionTestScope::AiCompaniesOnly => { Ok(Some(RuntimeCompanyTarget::AiCompanies)) } RuntimeCompanyConditionTestScope::HumanCompaniesOnly => { Ok(Some(RuntimeCompanyTarget::HumanCompanies)) } } } fn lowered_condition_true_player_target( record: &SmpLoadedPackedEventRecordSummary, ) -> Result, ImportBlocker> { if !record_uses_condition_true_player(record) { return Ok(None); } let scope = record .negative_sentinel_scope .as_ref() .ok_or(ImportBlocker::MissingPlayerConditionContext)?; match scope.player_test_scope { RuntimePlayerConditionTestScope::Disabled => { Err(ImportBlocker::MissingPlayerConditionContext) } RuntimePlayerConditionTestScope::AllPlayers => Ok(Some(RuntimePlayerTarget::AllActive)), RuntimePlayerConditionTestScope::SelectedPlayerOnly => { Ok(Some(RuntimePlayerTarget::SelectedPlayer)) } RuntimePlayerConditionTestScope::AiPlayersOnly => Ok(Some(RuntimePlayerTarget::AiPlayers)), RuntimePlayerConditionTestScope::HumanPlayersOnly => { Ok(Some(RuntimePlayerTarget::HumanPlayers)) } } } fn lower_condition_targets_in_effect( effect: &RuntimeEffect, lowered_company_target: Option<&RuntimeCompanyTarget>, lowered_player_target: Option<&RuntimePlayerTarget>, ) -> Result { Ok(match effect { RuntimeEffect::SetWorldFlag { key, value } => RuntimeEffect::SetWorldFlag { key: key.clone(), value: *value, }, RuntimeEffect::SetWorldScalarOverride { key, value } => { RuntimeEffect::SetWorldScalarOverride { key: key.clone(), value: *value, } } RuntimeEffect::SetWorldVariable { index, value } => RuntimeEffect::SetWorldVariable { index: *index, value: *value, }, RuntimeEffect::SetLimitedTrackBuildingAmount { value } => { RuntimeEffect::SetLimitedTrackBuildingAmount { value: *value } } RuntimeEffect::SetEconomicStatusCode { value } => { RuntimeEffect::SetEconomicStatusCode { value: *value } } RuntimeEffect::SetCompanyVariable { target, index, value, } => RuntimeEffect::SetCompanyVariable { target: lower_condition_true_company_target_in_company_target( target, lowered_company_target, )?, index: *index, value: *value, }, RuntimeEffect::SetCompanyCash { target, value } => RuntimeEffect::SetCompanyCash { target: lower_condition_true_company_target_in_company_target( target, lowered_company_target, )?, value: *value, }, RuntimeEffect::SetPlayerVariable { target, index, value, } => RuntimeEffect::SetPlayerVariable { target: lower_condition_true_player_target_in_player_target( target, lowered_player_target, )?, index: *index, value: *value, }, RuntimeEffect::SetPlayerCash { target, value } => RuntimeEffect::SetPlayerCash { target: lower_condition_true_player_target_in_player_target( target, lowered_player_target, )?, value: *value, }, RuntimeEffect::SetCompanyGovernanceScalar { target, metric, value, } => RuntimeEffect::SetCompanyGovernanceScalar { target: lower_condition_true_company_target_in_company_target( target, lowered_company_target, )?, metric: *metric, value: *value, }, RuntimeEffect::SetChairmanCash { target, value } => RuntimeEffect::SetChairmanCash { target: target.clone(), value: *value, }, RuntimeEffect::DeactivatePlayer { target } => RuntimeEffect::DeactivatePlayer { target: lower_condition_true_player_target_in_player_target( target, lowered_player_target, )?, }, RuntimeEffect::DeactivateChairman { target } => RuntimeEffect::DeactivateChairman { target: target.clone(), }, RuntimeEffect::SetCompanyTerritoryAccess { target, territory, value, } => RuntimeEffect::SetCompanyTerritoryAccess { target: lower_condition_true_company_target_in_company_target( target, lowered_company_target, )?, territory: territory.clone(), value: *value, }, RuntimeEffect::ConfiscateCompanyAssets { target } => { RuntimeEffect::ConfiscateCompanyAssets { target: lower_condition_true_company_target_in_company_target( target, lowered_company_target, )?, } } RuntimeEffect::DeactivateCompany { target } => RuntimeEffect::DeactivateCompany { target: lower_condition_true_company_target_in_company_target( target, lowered_company_target, )?, }, RuntimeEffect::SetCompanyTrackLayingCapacity { target, value } => { RuntimeEffect::SetCompanyTrackLayingCapacity { target: lower_condition_true_company_target_in_company_target( target, lowered_company_target, )?, value: *value, } } RuntimeEffect::RetireTrains { company_target, territory_target, locomotive_name, } => RuntimeEffect::RetireTrains { company_target: company_target .as_ref() .map(|target| { lower_condition_true_company_target_in_company_target( target, lowered_company_target, ) }) .transpose()?, territory_target: territory_target.clone(), locomotive_name: locomotive_name.clone(), }, RuntimeEffect::AdjustCompanyCash { target, delta } => RuntimeEffect::AdjustCompanyCash { target: lower_condition_true_company_target_in_company_target( target, lowered_company_target, )?, delta: *delta, }, RuntimeEffect::AdjustCompanyDebt { target, delta } => RuntimeEffect::AdjustCompanyDebt { target: lower_condition_true_company_target_in_company_target( target, lowered_company_target, )?, delta: *delta, }, RuntimeEffect::SetCandidateAvailability { name, value } => { RuntimeEffect::SetCandidateAvailability { name: name.clone(), value: *value, } } RuntimeEffect::SetNamedLocomotiveAvailability { name, value } => { RuntimeEffect::SetNamedLocomotiveAvailability { name: name.clone(), value: *value, } } RuntimeEffect::SetNamedLocomotiveAvailabilityValue { name, value } => { RuntimeEffect::SetNamedLocomotiveAvailabilityValue { name: name.clone(), value: *value, } } RuntimeEffect::SetNamedLocomotiveCost { name, value } => { RuntimeEffect::SetNamedLocomotiveCost { name: name.clone(), value: *value, } } RuntimeEffect::SetCargoPriceOverride { target, value } => { RuntimeEffect::SetCargoPriceOverride { target: target.clone(), value: *value, } } RuntimeEffect::SetTerritoryVariable { target, index, value, } => RuntimeEffect::SetTerritoryVariable { target: target.clone(), index: *index, value: *value, }, RuntimeEffect::SetCargoProductionOverride { target, value } => { RuntimeEffect::SetCargoProductionOverride { target: target.clone(), value: *value, } } RuntimeEffect::SetCargoProductionSlot { slot, value } => { RuntimeEffect::SetCargoProductionSlot { slot: *slot, value: *value, } } RuntimeEffect::SetTerritoryAccessCost { value } => { RuntimeEffect::SetTerritoryAccessCost { value: *value } } RuntimeEffect::SetSpecialCondition { label, value } => RuntimeEffect::SetSpecialCondition { label: label.clone(), value: *value, }, RuntimeEffect::AppendEventRecord { record } => RuntimeEffect::AppendEventRecord { record: Box::new(RuntimeEventRecordTemplate { record_id: record.record_id, trigger_kind: record.trigger_kind, active: record.active, marks_collection_dirty: record.marks_collection_dirty, one_shot: record.one_shot, conditions: record.conditions.clone(), effects: record .effects .iter() .map(|nested| { lower_condition_targets_in_effect( nested, lowered_company_target, lowered_player_target, ) }) .collect::, _>>()?, }), }, RuntimeEffect::ActivateEventRecord { record_id } => RuntimeEffect::ActivateEventRecord { record_id: *record_id, }, RuntimeEffect::DeactivateEventRecord { record_id } => { RuntimeEffect::DeactivateEventRecord { record_id: *record_id, } } RuntimeEffect::RemoveEventRecord { record_id } => RuntimeEffect::RemoveEventRecord { record_id: *record_id, }, }) } fn lower_condition_targets_in_condition( condition: &RuntimeCondition, row: &SmpLoadedPackedEventConditionRowSummary, lowered_company_target: Option<&RuntimeCompanyTarget>, lowered_player_target: Option<&RuntimePlayerTarget>, company_context: &ImportRuntimeContext, ) -> Result { Ok(match condition { RuntimeCondition::WorldVariableThreshold { index, comparator, value, } => RuntimeCondition::WorldVariableThreshold { index: *index, comparator: *comparator, value: *value, }, RuntimeCondition::CompanyNumericThreshold { target, metric, comparator, value, } => RuntimeCondition::CompanyNumericThreshold { target: lower_condition_true_company_target_in_company_target( target, lowered_company_target, )?, metric: *metric, comparator: *comparator, value: *value, }, RuntimeCondition::CompanyVariableThreshold { target, index, comparator, value, } => RuntimeCondition::CompanyVariableThreshold { target: lower_condition_true_company_target_in_company_target( target, lowered_company_target, )?, index: *index, comparator: *comparator, value: *value, }, RuntimeCondition::ChairmanNumericThreshold { target, metric, comparator, value, } => RuntimeCondition::ChairmanNumericThreshold { target: target.clone(), metric: *metric, comparator: *comparator, value: *value, }, RuntimeCondition::PlayerVariableThreshold { target, index, comparator, value, } => RuntimeCondition::PlayerVariableThreshold { target: lower_condition_true_player_target_in_player_target( target, lowered_player_target, )?, index: *index, comparator: *comparator, value: *value, }, RuntimeCondition::TerritoryNumericThreshold { target, metric, comparator, value, } => RuntimeCondition::TerritoryNumericThreshold { target: lower_territory_target_in_condition(target, row, company_context)?, metric: *metric, comparator: *comparator, value: *value, }, RuntimeCondition::TerritoryVariableThreshold { target, index, comparator, value, } => RuntimeCondition::TerritoryVariableThreshold { target: lower_territory_target_in_condition(target, row, company_context)?, index: *index, comparator: *comparator, value: *value, }, RuntimeCondition::CompanyTerritoryNumericThreshold { target, territory, metric, comparator, value, } => RuntimeCondition::CompanyTerritoryNumericThreshold { target: lower_condition_true_company_target_in_company_target( target, lowered_company_target, )?, territory: lower_territory_target_in_condition(territory, row, company_context)?, metric: *metric, comparator: *comparator, value: *value, }, RuntimeCondition::SpecialConditionThreshold { label, comparator, value, } => RuntimeCondition::SpecialConditionThreshold { label: label.clone(), comparator: *comparator, value: *value, }, RuntimeCondition::CandidateAvailabilityThreshold { name, comparator, value, } => RuntimeCondition::CandidateAvailabilityThreshold { name: name.clone(), comparator: *comparator, value: *value, }, RuntimeCondition::NamedLocomotiveAvailabilityThreshold { name, comparator, value, } => RuntimeCondition::NamedLocomotiveAvailabilityThreshold { name: name.clone(), comparator: *comparator, value: *value, }, RuntimeCondition::NamedLocomotiveCostThreshold { name, comparator, value, } => RuntimeCondition::NamedLocomotiveCostThreshold { name: name.clone(), comparator: *comparator, value: *value, }, RuntimeCondition::CargoProductionSlotThreshold { slot, label, comparator, value, } => RuntimeCondition::CargoProductionSlotThreshold { slot: *slot, label: label.clone(), comparator: *comparator, value: *value, }, RuntimeCondition::CargoProductionTotalThreshold { comparator, value } => { RuntimeCondition::CargoProductionTotalThreshold { comparator: *comparator, value: *value, } } RuntimeCondition::FactoryProductionTotalThreshold { comparator, value } => { RuntimeCondition::FactoryProductionTotalThreshold { comparator: *comparator, value: *value, } } RuntimeCondition::FarmMineProductionTotalThreshold { comparator, value } => { RuntimeCondition::FarmMineProductionTotalThreshold { comparator: *comparator, value: *value, } } RuntimeCondition::OtherCargoProductionTotalThreshold { comparator, value } => { RuntimeCondition::OtherCargoProductionTotalThreshold { comparator: *comparator, value: *value, } } RuntimeCondition::LimitedTrackBuildingAmountThreshold { comparator, value } => { RuntimeCondition::LimitedTrackBuildingAmountThreshold { comparator: *comparator, value: *value, } } RuntimeCondition::TerritoryAccessCostThreshold { comparator, value } => { RuntimeCondition::TerritoryAccessCostThreshold { comparator: *comparator, value: *value, } } RuntimeCondition::EconomicStatusCodeThreshold { comparator, value } => { RuntimeCondition::EconomicStatusCodeThreshold { comparator: *comparator, value: *value, } } RuntimeCondition::WorldFlagEquals { key, value } => RuntimeCondition::WorldFlagEquals { key: key.clone(), value: *value, }, }) } fn lower_condition_true_company_target_in_company_target( target: &RuntimeCompanyTarget, lowered_target: Option<&RuntimeCompanyTarget>, ) -> Result { match target { RuntimeCompanyTarget::ConditionTrueCompany => lowered_target .cloned() .ok_or(ImportBlocker::MissingConditionContext), _ => Ok(target.clone()), } } fn lower_condition_true_player_target_in_player_target( target: &RuntimePlayerTarget, lowered_target: Option<&RuntimePlayerTarget>, ) -> Result { match target { RuntimePlayerTarget::ConditionTruePlayer => lowered_target .cloned() .ok_or(ImportBlocker::MissingPlayerConditionContext), _ => Ok(target.clone()), } } fn ensure_condition_true_chairman_context( record: &SmpLoadedPackedEventRecordSummary, ) -> Result<(), ImportBlocker> { if !record_uses_condition_true_chairman(record) { return Ok(()); } if record .decoded_conditions .iter() .any(|condition| matches!(condition, RuntimeCondition::ChairmanNumericThreshold { .. })) { Ok(()) } else { Err(ImportBlocker::MissingConditionContext) } } fn lower_territory_target_in_condition( target: &RuntimeTerritoryTarget, row: &SmpLoadedPackedEventConditionRowSummary, company_context: &ImportRuntimeContext, ) -> Result { if !company_context.has_territory_context { return Err(ImportBlocker::MissingTerritoryContext); } if !row.requires_candidate_name_binding { return Ok(target.clone()); } let candidate_name = row .candidate_name .as_ref() .ok_or(ImportBlocker::NamedTerritoryBinding)?; let territory_id = company_context .territory_name_to_id .get(candidate_name) .copied() .ok_or(ImportBlocker::NamedTerritoryBinding)?; Ok(RuntimeTerritoryTarget::Ids { ids: vec![territory_id], }) } fn record_uses_condition_true_company(record: &SmpLoadedPackedEventRecordSummary) -> bool { record .decoded_conditions .iter() .any(condition_uses_condition_true_company) || record .decoded_actions .iter() .any(runtime_effect_uses_condition_true_company) } fn record_uses_condition_true_player(record: &SmpLoadedPackedEventRecordSummary) -> bool { record .decoded_actions .iter() .any(runtime_effect_uses_condition_true_player) } fn condition_uses_condition_true_company(condition: &RuntimeCondition) -> bool { match condition { RuntimeCondition::CompanyNumericThreshold { target, .. } | RuntimeCondition::CompanyVariableThreshold { target, .. } | RuntimeCondition::CompanyTerritoryNumericThreshold { target, .. } => { matches!(target, RuntimeCompanyTarget::ConditionTrueCompany) } RuntimeCondition::PlayerVariableThreshold { target, .. } => { matches!(target, RuntimePlayerTarget::ConditionTruePlayer) } RuntimeCondition::ChairmanNumericThreshold { .. } => false, RuntimeCondition::TerritoryNumericThreshold { .. } | RuntimeCondition::TerritoryVariableThreshold { .. } | RuntimeCondition::WorldVariableThreshold { .. } | RuntimeCondition::SpecialConditionThreshold { .. } | RuntimeCondition::CandidateAvailabilityThreshold { .. } | RuntimeCondition::NamedLocomotiveAvailabilityThreshold { .. } | RuntimeCondition::NamedLocomotiveCostThreshold { .. } | RuntimeCondition::CargoProductionSlotThreshold { .. } | RuntimeCondition::CargoProductionTotalThreshold { .. } | RuntimeCondition::FactoryProductionTotalThreshold { .. } | RuntimeCondition::FarmMineProductionTotalThreshold { .. } | RuntimeCondition::OtherCargoProductionTotalThreshold { .. } | RuntimeCondition::LimitedTrackBuildingAmountThreshold { .. } | RuntimeCondition::TerritoryAccessCostThreshold { .. } | RuntimeCondition::EconomicStatusCodeThreshold { .. } | RuntimeCondition::WorldFlagEquals { .. } => false, } } fn chairman_target_import_blocker( target: &RuntimeChairmanTarget, company_context: &ImportRuntimeContext, ) -> Option { match target { RuntimeChairmanTarget::AllActive => { if company_context.known_chairman_profile_ids.is_empty() { Some(ImportBlocker::MissingChairmanContext) } else { None } } RuntimeChairmanTarget::HumanChairmen | RuntimeChairmanTarget::AiChairmen => { if company_context.known_chairman_profile_ids.is_empty() { Some(ImportBlocker::MissingChairmanContext) } else if !company_context.has_complete_company_controller_context { Some(ImportBlocker::MissingCompanyRoleContext) } else { None } } RuntimeChairmanTarget::SelectedChairman => { if company_context.selected_chairman_profile_id.is_some() { None } else { Some(ImportBlocker::MissingChairmanContext) } } RuntimeChairmanTarget::ConditionTrueChairman => { if company_context.known_chairman_profile_ids.is_empty() { Some(ImportBlocker::MissingChairmanContext) } else { None } } RuntimeChairmanTarget::Ids { ids } => { if company_context.known_chairman_profile_ids.is_empty() { Some(ImportBlocker::MissingChairmanContext) } else if ids .iter() .all(|id| company_context.known_chairman_profile_ids.contains(id)) { None } else { Some(ImportBlocker::MissingChairmanContext) } } } } fn smp_runtime_effects_to_runtime_effects( effects: &[RuntimeEffect], company_context: &ImportRuntimeContext, allow_condition_true_company: bool, allow_condition_true_player: bool, ) -> Result, String> { effects .iter() .map(|effect| { smp_runtime_effect_to_runtime_effect( effect, company_context, allow_condition_true_company, allow_condition_true_player, ) }) .collect() } fn smp_runtime_effect_to_runtime_effect( effect: &RuntimeEffect, company_context: &ImportRuntimeContext, allow_condition_true_company: bool, allow_condition_true_player: bool, ) -> Result { match effect { RuntimeEffect::SetWorldFlag { key, value } => Ok(RuntimeEffect::SetWorldFlag { key: key.clone(), value: *value, }), RuntimeEffect::SetWorldVariable { index, value } => Ok(RuntimeEffect::SetWorldVariable { index: *index, value: *value, }), RuntimeEffect::SetLimitedTrackBuildingAmount { value } => { Ok(RuntimeEffect::SetLimitedTrackBuildingAmount { value: *value }) } RuntimeEffect::SetEconomicStatusCode { value } => { Ok(RuntimeEffect::SetEconomicStatusCode { value: *value }) } RuntimeEffect::SetCompanyVariable { target, index, value, } => { if company_target_allowed_for_import( target, company_context, allow_condition_true_company, ) { Ok(RuntimeEffect::SetCompanyVariable { target: target.clone(), index: *index, value: *value, }) } else { Err(company_target_import_error_message(target, company_context)) } } RuntimeEffect::SetCompanyCash { target, value } => { if company_target_allowed_for_import( target, company_context, allow_condition_true_company, ) { Ok(RuntimeEffect::SetCompanyCash { target: target.clone(), value: *value, }) } else { Err(company_target_import_error_message(target, company_context)) } } RuntimeEffect::SetPlayerVariable { target, index, value, } => { if player_target_allowed_for_import( target, company_context, allow_condition_true_player, ) { Ok(RuntimeEffect::SetPlayerVariable { target: target.clone(), index: *index, value: *value, }) } else { Err(player_target_import_error_message(target, company_context)) } } RuntimeEffect::SetPlayerCash { target, value } => { if player_target_allowed_for_import( target, company_context, allow_condition_true_player, ) { Ok(RuntimeEffect::SetPlayerCash { target: target.clone(), value: *value, }) } else { Err(player_target_import_error_message(target, company_context)) } } RuntimeEffect::SetTerritoryVariable { target, index, value, } => { if territory_target_import_blocker(target, company_context).is_none() { Ok(RuntimeEffect::SetTerritoryVariable { target: target.clone(), index: *index, value: *value, }) } else { Err("packed effect requires territory runtime context".to_string()) } } RuntimeEffect::SetChairmanCash { target, value } => { if chairman_target_import_blocker(target, company_context).is_none() { Ok(RuntimeEffect::SetChairmanCash { target: target.clone(), value: *value, }) } else { Err("packed effect requires chairman runtime context".to_string()) } } RuntimeEffect::DeactivatePlayer { target } => { if player_target_allowed_for_import( target, company_context, allow_condition_true_player, ) { Ok(RuntimeEffect::DeactivatePlayer { target: target.clone(), }) } else { Err(player_target_import_error_message(target, company_context)) } } RuntimeEffect::DeactivateChairman { target } => { if chairman_target_import_blocker(target, company_context).is_none() { Ok(RuntimeEffect::DeactivateChairman { target: target.clone(), }) } else { Err("packed effect requires chairman runtime context".to_string()) } } RuntimeEffect::SetCompanyTerritoryAccess { target, territory, value, } => { if !company_target_allowed_for_import( target, company_context, allow_condition_true_company, ) { Err(company_target_import_error_message(target, company_context)) } else if territory_target_import_blocker(territory, company_context).is_some() { Err("packed effect requires territory runtime context".to_string()) } else { Ok(RuntimeEffect::SetCompanyTerritoryAccess { target: target.clone(), territory: territory.clone(), value: *value, }) } } RuntimeEffect::ConfiscateCompanyAssets { target } => { if company_target_allowed_for_import( target, company_context, allow_condition_true_company, ) && company_context.has_train_context { Ok(RuntimeEffect::ConfiscateCompanyAssets { target: target.clone(), }) } else if !company_context.has_train_context { Err("packed effect requires runtime train context".to_string()) } else { Err(company_target_import_error_message(target, company_context)) } } RuntimeEffect::DeactivateCompany { target } => { if company_target_allowed_for_import( target, company_context, allow_condition_true_company, ) { Ok(RuntimeEffect::DeactivateCompany { target: target.clone(), }) } else { Err(company_target_import_error_message(target, company_context)) } } RuntimeEffect::SetCompanyTrackLayingCapacity { target, value } => { if company_target_allowed_for_import( target, company_context, allow_condition_true_company, ) { Ok(RuntimeEffect::SetCompanyTrackLayingCapacity { target: target.clone(), value: *value, }) } else { Err(company_target_import_error_message(target, company_context)) } } RuntimeEffect::SetCompanyGovernanceScalar { target, metric, value, } => { if company_target_allowed_for_import( target, company_context, allow_condition_true_company, ) { Ok(RuntimeEffect::SetCompanyGovernanceScalar { target: target.clone(), metric: *metric, value: *value, }) } else { Err(company_target_import_error_message(target, company_context)) } } RuntimeEffect::RetireTrains { company_target, territory_target, locomotive_name, } => { if !company_context.has_train_context { Err("packed effect requires runtime train context".to_string()) } else if territory_target.is_some() && !company_context.has_train_territory_context { Err("packed train effect requires runtime train territory context".to_string()) } else if let Some(company_target) = company_target { if !company_target_allowed_for_import( company_target, company_context, allow_condition_true_company, ) { Err(company_target_import_error_message( company_target, company_context, )) } else if let Some(territory_target) = territory_target { if territory_target_import_blocker(territory_target, company_context).is_some() { Err("packed condition requires territory runtime context".to_string()) } else { Ok(RuntimeEffect::RetireTrains { company_target: Some(company_target.clone()), territory_target: Some(territory_target.clone()), locomotive_name: locomotive_name.clone(), }) } } else { Ok(RuntimeEffect::RetireTrains { company_target: Some(company_target.clone()), territory_target: None, locomotive_name: locomotive_name.clone(), }) } } else if let Some(territory_target) = territory_target { if territory_target_import_blocker(territory_target, company_context).is_some() { Err("packed condition requires territory runtime context".to_string()) } else { Ok(RuntimeEffect::RetireTrains { company_target: None, territory_target: Some(territory_target.clone()), locomotive_name: locomotive_name.clone(), }) } } else { Ok(RuntimeEffect::RetireTrains { company_target: None, territory_target: None, locomotive_name: locomotive_name.clone(), }) } } RuntimeEffect::AdjustCompanyCash { target, delta } => { if company_target_allowed_for_import( target, company_context, allow_condition_true_company, ) { Ok(RuntimeEffect::AdjustCompanyCash { target: target.clone(), delta: *delta, }) } else { Err(company_target_import_error_message(target, company_context)) } } RuntimeEffect::AdjustCompanyDebt { target, delta } => { if company_target_allowed_for_import( target, company_context, allow_condition_true_company, ) { Ok(RuntimeEffect::AdjustCompanyDebt { target: target.clone(), delta: *delta, }) } else { Err(company_target_import_error_message(target, company_context)) } } RuntimeEffect::SetCandidateAvailability { name, value } => { Ok(RuntimeEffect::SetCandidateAvailability { name: name.clone(), value: *value, }) } RuntimeEffect::SetNamedLocomotiveAvailability { name, value } => { Ok(RuntimeEffect::SetNamedLocomotiveAvailability { name: name.clone(), value: *value, }) } RuntimeEffect::SetNamedLocomotiveAvailabilityValue { name, value } => { Ok(RuntimeEffect::SetNamedLocomotiveAvailabilityValue { name: name.clone(), value: *value, }) } RuntimeEffect::SetNamedLocomotiveCost { name, value } => { Ok(RuntimeEffect::SetNamedLocomotiveCost { name: name.clone(), value: *value, }) } RuntimeEffect::SetCargoPriceOverride { target, value } => { Ok(RuntimeEffect::SetCargoPriceOverride { target: target.clone(), value: *value, }) } RuntimeEffect::SetCargoProductionOverride { target, value } => { Ok(RuntimeEffect::SetCargoProductionOverride { target: target.clone(), value: *value, }) } RuntimeEffect::SetWorldScalarOverride { key, value } => { Ok(RuntimeEffect::SetWorldScalarOverride { key: key.clone(), value: *value, }) } RuntimeEffect::SetCargoProductionSlot { slot, value } => { Ok(RuntimeEffect::SetCargoProductionSlot { slot: *slot, value: *value, }) } RuntimeEffect::SetTerritoryAccessCost { value } => { Ok(RuntimeEffect::SetTerritoryAccessCost { value: *value }) } RuntimeEffect::SetSpecialCondition { label, value } => { Ok(RuntimeEffect::SetSpecialCondition { label: label.clone(), value: *value, }) } RuntimeEffect::AppendEventRecord { record } => Ok(RuntimeEffect::AppendEventRecord { record: Box::new(smp_runtime_record_template_to_runtime( record, company_context, allow_condition_true_company, allow_condition_true_player, )?), }), RuntimeEffect::ActivateEventRecord { record_id } => { Ok(RuntimeEffect::ActivateEventRecord { record_id: *record_id, }) } RuntimeEffect::DeactivateEventRecord { record_id } => { Ok(RuntimeEffect::DeactivateEventRecord { record_id: *record_id, }) } RuntimeEffect::RemoveEventRecord { record_id } => Ok(RuntimeEffect::RemoveEventRecord { record_id: *record_id, }), } } fn smp_runtime_record_template_to_runtime( record: &RuntimeEventRecordTemplate, company_context: &ImportRuntimeContext, allow_condition_true_company: bool, allow_condition_true_player: bool, ) -> Result { Ok(RuntimeEventRecordTemplate { record_id: record.record_id, trigger_kind: record.trigger_kind, active: record.active, marks_collection_dirty: record.marks_collection_dirty, one_shot: record.one_shot, conditions: record.conditions.clone(), effects: smp_runtime_effects_to_runtime_effects( &record.effects, company_context, allow_condition_true_company, allow_condition_true_player, )?, }) } fn company_target_allowed_for_import( target: &RuntimeCompanyTarget, company_context: &ImportRuntimeContext, allow_condition_true_company: bool, ) -> bool { match company_target_import_blocker(target, company_context) { None => true, Some(ImportBlocker::MissingConditionContext) if allow_condition_true_company && matches!(target, RuntimeCompanyTarget::ConditionTrueCompany) => { true } Some(_) => false, } } fn player_target_allowed_for_import( target: &RuntimePlayerTarget, company_context: &ImportRuntimeContext, allow_condition_true_player: bool, ) -> bool { match player_target_import_blocker(target, company_context) { None => true, Some(ImportBlocker::MissingPlayerConditionContext) if allow_condition_true_player && matches!(target, RuntimePlayerTarget::ConditionTruePlayer) => { true } Some(_) => false, } } fn conditions_provide_company_context(conditions: &[RuntimeCondition]) -> bool { conditions.iter().any(|condition| { matches!( condition, RuntimeCondition::CompanyNumericThreshold { .. } | RuntimeCondition::CompanyVariableThreshold { .. } | RuntimeCondition::CompanyTerritoryNumericThreshold { .. } ) }) } fn company_target_import_blocker( target: &RuntimeCompanyTarget, company_context: &ImportRuntimeContext, ) -> Option { match target { RuntimeCompanyTarget::AllActive => None, RuntimeCompanyTarget::Ids { ids } => { if ids.is_empty() || ids .iter() .any(|company_id| !company_context.known_company_ids.contains(company_id)) { Some(ImportBlocker::MissingCompanyContext) } else { None } } RuntimeCompanyTarget::HumanCompanies | RuntimeCompanyTarget::AiCompanies => { if !company_context.has_complete_company_controller_context { Some(ImportBlocker::MissingCompanyRoleContext) } else { None } } RuntimeCompanyTarget::SelectedCompany => { if company_context.selected_company_id.is_some() { None } else { Some(ImportBlocker::MissingSelectionContext) } } RuntimeCompanyTarget::ConditionTrueCompany => Some(ImportBlocker::MissingConditionContext), } } fn company_target_import_error_message( target: &RuntimeCompanyTarget, company_context: &ImportRuntimeContext, ) -> String { match company_target_import_blocker(target, company_context) { Some(ImportBlocker::MissingCompanyContext) => { "packed company effect requires resolved company ids".to_string() } Some(ImportBlocker::MissingSelectionContext) => { "packed company effect requires selected_company_id context".to_string() } Some(ImportBlocker::MissingCompanyRoleContext) => { "packed company effect requires company controller role context".to_string() } Some(ImportBlocker::MissingConditionContext) => { "packed company effect requires condition-relative context".to_string() } Some(ImportBlocker::CompanyConditionScopeDisabled) => { "packed company effect disables company-side negative-sentinel condition scope" .to_string() } Some(ImportBlocker::MissingTerritoryContext) => { "packed condition requires territory runtime context".to_string() } Some(ImportBlocker::NamedTerritoryBinding) => { "packed condition requires named territory binding".to_string() } Some(ImportBlocker::EvidenceBlockedDescriptor) => { "packed descriptor is still evidence-blocked".to_string() } Some(ImportBlocker::UnmappedOrdinaryCondition) => { "packed ordinary condition is not yet mapped".to_string() } Some(ImportBlocker::UnmappedWorldCondition) => { "packed whole-game condition is not yet mapped".to_string() } Some(ImportBlocker::MissingTrainContext) => { "packed effect requires runtime train context".to_string() } Some(ImportBlocker::MissingTrainTerritoryContext) => { "packed train effect requires runtime train territory context".to_string() } Some(ImportBlocker::MissingLocomotiveCatalogContext) => { "packed locomotive availability row requires locomotive catalog context".to_string() } Some(ImportBlocker::MissingPlayerContext) | Some(ImportBlocker::MissingPlayerSelectionContext) | Some(ImportBlocker::MissingPlayerRoleContext) | Some(ImportBlocker::MissingChairmanContext) | Some(ImportBlocker::ChairmanTargetScope) | Some(ImportBlocker::MissingPlayerConditionContext) => { "packed company effect is blocked by non-company import context".to_string() } None => "packed company effect is importable".to_string(), } } fn player_target_import_blocker( target: &RuntimePlayerTarget, company_context: &ImportRuntimeContext, ) -> Option { match target { RuntimePlayerTarget::AllActive => { if company_context.known_player_ids.is_empty() { Some(ImportBlocker::MissingPlayerContext) } else { None } } RuntimePlayerTarget::Ids { ids } => { if ids.is_empty() || ids .iter() .any(|player_id| !company_context.known_player_ids.contains(player_id)) { Some(ImportBlocker::MissingPlayerContext) } else { None } } RuntimePlayerTarget::HumanPlayers | RuntimePlayerTarget::AiPlayers => { if !company_context.has_complete_player_controller_context { Some(ImportBlocker::MissingPlayerRoleContext) } else { None } } RuntimePlayerTarget::SelectedPlayer => { if company_context.selected_player_id.is_some() { None } else { Some(ImportBlocker::MissingPlayerSelectionContext) } } RuntimePlayerTarget::ConditionTruePlayer => { Some(ImportBlocker::MissingPlayerConditionContext) } } } fn player_target_import_error_message( target: &RuntimePlayerTarget, company_context: &ImportRuntimeContext, ) -> String { match player_target_import_blocker(target, company_context) { Some(ImportBlocker::MissingPlayerContext) => { "packed player effect requires resolved player ids".to_string() } Some(ImportBlocker::MissingPlayerSelectionContext) => { "packed player effect requires selected_player_id context".to_string() } Some(ImportBlocker::MissingPlayerRoleContext) => { "packed player effect requires player controller role context".to_string() } Some(ImportBlocker::MissingPlayerConditionContext) => { "packed player effect requires player condition-relative context".to_string() } _ => "packed player effect is importable".to_string(), } } fn determine_packed_event_import_outcome( record: &SmpLoadedPackedEventRecordSummary, company_context: &ImportRuntimeContext, imported: bool, ) -> String { if imported { return "imported".to_string(); } if record.decode_status == "unsupported_framing" { return "blocked_unsupported_decode".to_string(); } if record.payload_family == "real_packed_v1" { if record.compact_control.is_none() { return "blocked_missing_compact_control".to_string(); } if !record.executable_import_ready { if let Err(blocker) = lowered_record_decoded_actions(record, company_context) { if matches!( blocker, ImportBlocker::MissingLocomotiveCatalogContext | ImportBlocker::MissingChairmanContext | ImportBlocker::ChairmanTargetScope ) { return company_target_import_outcome(blocker).to_string(); } } if record .grouped_effect_rows .iter() .any(real_grouped_row_is_unsupported_chairman_target_scope) { return "blocked_chairman_target_scope".to_string(); } if record .grouped_effect_rows .iter() .any(real_grouped_row_is_unsupported_territory_access_scope) { return "blocked_territory_access_scope".to_string(); } if record .grouped_effect_rows .iter() .any(real_grouped_row_is_unsupported_territory_access_variant) { return "blocked_territory_access_variant".to_string(); } if record .grouped_effect_rows .iter() .any(real_grouped_row_is_unsupported_confiscation_variant) { return "blocked_confiscation_variant".to_string(); } if record .grouped_effect_rows .iter() .any(real_grouped_row_is_unsupported_retire_train_scope) { return "blocked_retire_train_scope".to_string(); } if record .grouped_effect_rows .iter() .any(real_grouped_row_is_unsupported_retire_train_variant) { return "blocked_retire_train_variant".to_string(); } if record .standalone_condition_rows .iter() .any(|row| row.raw_condition_id >= 0) { if record_has_world_state_condition_rows(record) { return "blocked_unmapped_world_condition".to_string(); } else { return "blocked_unmapped_ordinary_condition".to_string(); } } if record .grouped_effect_rows .iter() .any(real_grouped_row_is_shell_owned_descriptor_family) { return "blocked_shell_owned_descriptor".to_string(); } if record .grouped_effect_rows .iter() .any(real_grouped_row_is_evidence_blocked_descriptor_family) { return "blocked_evidence_blocked_descriptor".to_string(); } if record .grouped_effect_rows .iter() .any(real_grouped_row_is_variant_or_scope_blocked_descriptor_family) { return "blocked_variant_or_scope_blocked_descriptor".to_string(); } if record .grouped_effect_rows .iter() .any(real_grouped_row_is_unsupported_executable_descriptor_variant) { return "blocked_variant_or_scope_blocked_descriptor".to_string(); } if record .grouped_effect_rows .iter() .any(real_grouped_row_is_world_state_family) { return "blocked_unmapped_world_descriptor".to_string(); } return "blocked_unmapped_real_descriptor".to_string(); } if let Some(blocker) = packed_record_condition_scope_import_blocker(record, company_context) { return company_target_import_outcome(blocker).to_string(); } if let Some(blocker) = packed_record_company_target_import_blocker(record, company_context) { return company_target_import_outcome(blocker).to_string(); } return "blocked_unsupported_decode".to_string(); } if let Some(blocker) = packed_record_company_target_import_blocker(record, company_context) { return company_target_import_outcome(blocker).to_string(); } "blocked_unsupported_decode".to_string() } fn record_has_world_state_condition_rows(record: &SmpLoadedPackedEventRecordSummary) -> bool { record .decoded_conditions .iter() .any(runtime_condition_is_world_state) || record .standalone_condition_rows .iter() .any(ordinary_condition_row_is_world_state_family) } fn runtime_condition_is_world_state(condition: &RuntimeCondition) -> bool { matches!( condition, RuntimeCondition::WorldVariableThreshold { .. } | RuntimeCondition::SpecialConditionThreshold { .. } | RuntimeCondition::CandidateAvailabilityThreshold { .. } | RuntimeCondition::NamedLocomotiveAvailabilityThreshold { .. } | RuntimeCondition::NamedLocomotiveCostThreshold { .. } | RuntimeCondition::CargoProductionSlotThreshold { .. } | RuntimeCondition::CargoProductionTotalThreshold { .. } | RuntimeCondition::FactoryProductionTotalThreshold { .. } | RuntimeCondition::FarmMineProductionTotalThreshold { .. } | RuntimeCondition::OtherCargoProductionTotalThreshold { .. } | RuntimeCondition::LimitedTrackBuildingAmountThreshold { .. } | RuntimeCondition::TerritoryAccessCostThreshold { .. } | RuntimeCondition::EconomicStatusCodeThreshold { .. } | RuntimeCondition::WorldFlagEquals { .. } ) } fn ordinary_condition_row_is_world_state_family( row: &SmpLoadedPackedEventConditionRowSummary, ) -> bool { row.metric.as_deref().is_some_and(|metric| { metric.contains("Special Condition") || metric.contains("Candidate Availability") || metric.contains("Named Locomotive") || metric.contains("Cargo Production") || metric.contains("Factory Production") || metric.contains("Farm/Mine Production") || metric.contains("Other Cargo Production") || metric.contains("Limited Track Building Amount") || metric.contains("Territory Access Cost") || metric.contains("Economic Status") || metric.contains("World Flag") }) || row.semantic_family.as_deref().is_some_and(|family| { matches!( family, "world_state_threshold" | "world_scalar_threshold" | "world_flag_equals" ) }) } fn real_grouped_row_is_world_state_family( row: &SmpLoadedPackedEventGroupedEffectRowSummary, ) -> bool { row.target_mask_bits == Some(0x08) || row.parameter_family.as_deref().is_some_and(|family| { family.starts_with("whole_game_") || family.starts_with("special_condition") || family.starts_with("candidate_availability") || family.starts_with("world_flag") }) || row.descriptor_label.as_deref().is_some_and(|label| { label.contains("Special Condition") || label.contains("Candidate Availability") || label.contains("World Flag") || label == "Economic Status" }) } fn real_grouped_row_is_shell_owned_descriptor_family( row: &SmpLoadedPackedEventGroupedEffectRowSummary, ) -> bool { real_grouped_row_has_runtime_status(row, "shell_owned") } fn real_grouped_row_is_evidence_blocked_descriptor_family( row: &SmpLoadedPackedEventGroupedEffectRowSummary, ) -> bool { real_grouped_row_has_runtime_status(row, "evidence_blocked") } fn real_grouped_row_is_variant_or_scope_blocked_descriptor_family( row: &SmpLoadedPackedEventGroupedEffectRowSummary, ) -> bool { real_grouped_row_has_runtime_status(row, "variant_or_scope_blocked") } fn real_grouped_row_has_runtime_status( row: &SmpLoadedPackedEventGroupedEffectRowSummary, status: &str, ) -> bool { crate::smp::grouped_effect_descriptor_runtime_status_name(row.descriptor_id) .is_some_and(|runtime_status| runtime_status == status) } fn real_grouped_row_is_unsupported_executable_descriptor_variant( row: &SmpLoadedPackedEventGroupedEffectRowSummary, ) -> bool { if !real_grouped_row_has_runtime_status(row, "executable") { return false; } match row.descriptor_id { 1 => !(row.opcode == 8 && row.row_shape == "multivalue_scalar"), 2 => !(row.opcode == 8 && row.row_shape == "multivalue_scalar"), 8 | 108 | 109 | 122 => row.row_shape != "scalar_assignment", 13 | 14 => !(row.row_shape == "bool_toggle" && row.raw_scalar_value != 0), 56 | 57 => row.row_shape != "scalar_assignment", _ => match row.parameter_family.as_deref() { Some("world_scalar_override") => row.row_shape != "scalar_assignment", Some("runtime_variable_scalar") => row.row_shape != "scalar_assignment", Some("locomotive_availability_scalar") => { !(row.row_shape == "scalar_assignment" && row.raw_scalar_value >= 0) } Some("locomotive_cost_scalar") => { !(row.row_shape == "scalar_assignment" && row.raw_scalar_value >= 0) } Some("cargo_price_scalar") => { !(row.row_shape == "scalar_assignment" && row.raw_scalar_value >= 0) } Some("cargo_production_scalar") => { matches!(row.descriptor_id, 177 | 178 | 179 | 230..=240) && !(row.row_shape == "scalar_assignment" && row.raw_scalar_value >= 0) } Some("territory_access_cost_scalar") => { !(row.row_shape == "scalar_assignment" && row.raw_scalar_value >= 0) } _ => false, }, } } fn packed_record_company_target_import_blocker( record: &SmpLoadedPackedEventRecordSummary, company_context: &ImportRuntimeContext, ) -> Option { if record.decoded_actions.iter().any(|effect| { runtime_effect_uses_condition_true_company(effect) || runtime_effect_uses_condition_true_player(effect) }) && record.negative_sentinel_scope.is_none() { return Some(ImportBlocker::MissingConditionContext); } let lowered_conditions = match lowered_record_decoded_conditions(record, company_context) { Ok(conditions) => conditions, Err(blocker) => return Some(blocker), }; if let Some(blocker) = lowered_conditions.iter().find_map(|condition| { runtime_condition_company_target_import_blocker(condition, company_context) }) { return Some(blocker); } let lowered_effects = match lowered_record_decoded_actions(record, company_context) { Ok(effects) => effects, Err(blocker) => return Some(blocker), }; lowered_effects .iter() .find_map(|effect| runtime_effect_company_target_import_blocker(effect, company_context)) } fn runtime_condition_company_target_import_blocker( condition: &RuntimeCondition, company_context: &ImportRuntimeContext, ) -> Option { match condition { RuntimeCondition::WorldVariableThreshold { .. } => None, RuntimeCondition::CompanyNumericThreshold { target, .. } => { company_target_import_blocker(target, company_context) } RuntimeCondition::CompanyVariableThreshold { target, .. } => { company_target_import_blocker(target, company_context) } RuntimeCondition::PlayerVariableThreshold { target, .. } => { player_target_import_blocker(target, company_context) } RuntimeCondition::ChairmanNumericThreshold { target, .. } => { chairman_target_import_blocker(target, company_context) } RuntimeCondition::TerritoryNumericThreshold { target, .. } => { territory_target_import_blocker(target, company_context) } RuntimeCondition::TerritoryVariableThreshold { target, .. } => { territory_target_import_blocker(target, company_context) } RuntimeCondition::CompanyTerritoryNumericThreshold { target, territory, .. } => company_target_import_blocker(target, company_context) .or_else(|| territory_target_import_blocker(territory, company_context)), RuntimeCondition::SpecialConditionThreshold { .. } | RuntimeCondition::CandidateAvailabilityThreshold { .. } | RuntimeCondition::NamedLocomotiveAvailabilityThreshold { .. } | RuntimeCondition::NamedLocomotiveCostThreshold { .. } | RuntimeCondition::CargoProductionSlotThreshold { .. } | RuntimeCondition::CargoProductionTotalThreshold { .. } | RuntimeCondition::FactoryProductionTotalThreshold { .. } | RuntimeCondition::FarmMineProductionTotalThreshold { .. } | RuntimeCondition::OtherCargoProductionTotalThreshold { .. } | RuntimeCondition::LimitedTrackBuildingAmountThreshold { .. } | RuntimeCondition::TerritoryAccessCostThreshold { .. } | RuntimeCondition::EconomicStatusCodeThreshold { .. } | RuntimeCondition::WorldFlagEquals { .. } => None, } } fn territory_target_import_blocker( target: &RuntimeTerritoryTarget, company_context: &ImportRuntimeContext, ) -> Option { if !company_context.has_territory_context { return Some(ImportBlocker::MissingTerritoryContext); } match target { RuntimeTerritoryTarget::AllTerritories => None, RuntimeTerritoryTarget::Ids { ids } => { if ids.is_empty() { Some(ImportBlocker::NamedTerritoryBinding) } else if !territory_ids_match_known_context(ids, company_context) { Some(ImportBlocker::NamedTerritoryBinding) } else { None } } } } fn company_target_import_outcome(blocker: ImportBlocker) -> &'static str { match blocker { ImportBlocker::MissingCompanyContext => "blocked_missing_company_context", ImportBlocker::MissingSelectionContext => "blocked_missing_selection_context", ImportBlocker::MissingCompanyRoleContext => "blocked_missing_company_role_context", ImportBlocker::MissingPlayerContext => "blocked_missing_player_context", ImportBlocker::MissingPlayerSelectionContext => "blocked_missing_player_selection_context", ImportBlocker::MissingPlayerRoleContext => "blocked_missing_player_role_context", ImportBlocker::MissingChairmanContext => "blocked_missing_chairman_context", ImportBlocker::ChairmanTargetScope => "blocked_chairman_target_scope", ImportBlocker::MissingConditionContext => "blocked_missing_condition_context", ImportBlocker::MissingPlayerConditionContext => "blocked_missing_player_condition_context", ImportBlocker::CompanyConditionScopeDisabled => "blocked_company_condition_scope_disabled", ImportBlocker::MissingTerritoryContext => "blocked_missing_territory_context", ImportBlocker::NamedTerritoryBinding => "blocked_named_territory_binding", ImportBlocker::UnmappedOrdinaryCondition => "blocked_unmapped_ordinary_condition", ImportBlocker::UnmappedWorldCondition => "blocked_unmapped_world_condition", ImportBlocker::EvidenceBlockedDescriptor => "blocked_evidence_blocked_descriptor", ImportBlocker::MissingTrainContext => "blocked_missing_train_context", ImportBlocker::MissingTrainTerritoryContext => "blocked_missing_train_territory_context", ImportBlocker::MissingLocomotiveCatalogContext => { "blocked_missing_locomotive_catalog_context" } } } fn territory_ids_match_known_context(ids: &[u32], company_context: &ImportRuntimeContext) -> bool { ids.iter() .all(|territory_id| company_context.known_territory_ids.contains(territory_id)) } fn real_grouped_row_is_unsupported_territory_access_variant( row: &SmpLoadedPackedEventGroupedEffectRowSummary, ) -> bool { row.descriptor_id == 3 && !(row.row_shape == "bool_toggle" && row.raw_scalar_value != 0) } fn real_grouped_row_is_unsupported_chairman_target_scope( row: &SmpLoadedPackedEventGroupedEffectRowSummary, ) -> bool { matches!(row.grouped_target_subject.as_deref(), Some("chairman")) && matches!(row.descriptor_id, 1 | 14) && row.notes.iter().any(|note| { note.starts_with("chairman row uses unsupported grouped target scope ordinal ") }) } fn real_grouped_row_is_unsupported_territory_access_scope( row: &SmpLoadedPackedEventGroupedEffectRowSummary, ) -> bool { row.descriptor_id == 3 && row.row_shape == "bool_toggle" && row.raw_scalar_value != 0 && row .notes .iter() .any(|note| note == "territory access row is missing company or territory scope") } fn real_grouped_row_is_unsupported_confiscation_variant( row: &SmpLoadedPackedEventGroupedEffectRowSummary, ) -> bool { row.descriptor_id == 9 && !(row.row_shape == "bool_toggle" && row.raw_scalar_value != 0) } fn real_grouped_row_is_unsupported_retire_train_variant( row: &SmpLoadedPackedEventGroupedEffectRowSummary, ) -> bool { row.descriptor_id == 15 && !(row.row_shape == "bool_toggle" && row.raw_scalar_value != 0) } fn real_grouped_row_is_unsupported_retire_train_scope( row: &SmpLoadedPackedEventGroupedEffectRowSummary, ) -> bool { row.descriptor_id == 15 && row.row_shape == "bool_toggle" && row.raw_scalar_value != 0 && row .notes .iter() .any(|note| note == "retire train row is missing company and territory scope") } fn runtime_effect_uses_condition_true_company(effect: &RuntimeEffect) -> bool { match effect { RuntimeEffect::SetCompanyCash { target, .. } | RuntimeEffect::SetCompanyVariable { target, .. } | RuntimeEffect::SetCompanyGovernanceScalar { target, .. } | RuntimeEffect::SetCompanyTerritoryAccess { target, .. } | RuntimeEffect::ConfiscateCompanyAssets { target } | RuntimeEffect::DeactivateCompany { target } | RuntimeEffect::SetCompanyTrackLayingCapacity { target, .. } | RuntimeEffect::AdjustCompanyCash { target, .. } | RuntimeEffect::AdjustCompanyDebt { target, .. } => { matches!(target, RuntimeCompanyTarget::ConditionTrueCompany) } RuntimeEffect::RetireTrains { company_target, .. } => matches!( company_target, Some(RuntimeCompanyTarget::ConditionTrueCompany) ), RuntimeEffect::AppendEventRecord { record } => record .effects .iter() .any(runtime_effect_uses_condition_true_company), RuntimeEffect::SetWorldFlag { .. } | RuntimeEffect::SetWorldVariable { .. } | RuntimeEffect::SetWorldScalarOverride { .. } | RuntimeEffect::SetLimitedTrackBuildingAmount { .. } | RuntimeEffect::SetEconomicStatusCode { .. } | RuntimeEffect::SetPlayerCash { .. } | RuntimeEffect::SetPlayerVariable { .. } | RuntimeEffect::SetChairmanCash { .. } | RuntimeEffect::DeactivatePlayer { .. } | RuntimeEffect::DeactivateChairman { .. } | RuntimeEffect::SetCandidateAvailability { .. } | RuntimeEffect::SetNamedLocomotiveAvailability { .. } | RuntimeEffect::SetNamedLocomotiveAvailabilityValue { .. } | RuntimeEffect::SetNamedLocomotiveCost { .. } | RuntimeEffect::SetCargoPriceOverride { .. } | RuntimeEffect::SetCargoProductionOverride { .. } | RuntimeEffect::SetCargoProductionSlot { .. } | RuntimeEffect::SetTerritoryVariable { .. } | RuntimeEffect::SetTerritoryAccessCost { .. } | RuntimeEffect::SetSpecialCondition { .. } | RuntimeEffect::ActivateEventRecord { .. } | RuntimeEffect::DeactivateEventRecord { .. } | RuntimeEffect::RemoveEventRecord { .. } => false, } } fn runtime_effect_uses_condition_true_player(effect: &RuntimeEffect) -> bool { match effect { RuntimeEffect::SetPlayerCash { target, .. } | RuntimeEffect::SetPlayerVariable { target, .. } => { matches!(target, RuntimePlayerTarget::ConditionTruePlayer) } RuntimeEffect::DeactivatePlayer { target } => { matches!(target, RuntimePlayerTarget::ConditionTruePlayer) } RuntimeEffect::SetChairmanCash { .. } | RuntimeEffect::DeactivateChairman { .. } => false, RuntimeEffect::AppendEventRecord { record } => record .effects .iter() .any(runtime_effect_uses_condition_true_player), _ => false, } } fn record_uses_condition_true_chairman(record: &SmpLoadedPackedEventRecordSummary) -> bool { record .decoded_actions .iter() .any(runtime_effect_uses_condition_true_chairman) } fn runtime_effect_uses_condition_true_chairman(effect: &RuntimeEffect) -> bool { match effect { RuntimeEffect::SetChairmanCash { target, .. } | RuntimeEffect::DeactivateChairman { target } => { matches!(target, RuntimeChairmanTarget::ConditionTrueChairman) } RuntimeEffect::AppendEventRecord { record } => record .effects .iter() .any(runtime_effect_uses_condition_true_chairman), _ => false, } } fn runtime_effect_company_target_import_blocker( effect: &RuntimeEffect, company_context: &ImportRuntimeContext, ) -> Option { match effect { RuntimeEffect::SetCompanyCash { target, .. } | RuntimeEffect::SetCompanyVariable { target, .. } | RuntimeEffect::SetCompanyGovernanceScalar { target, .. } | RuntimeEffect::SetCompanyTerritoryAccess { target, .. } | RuntimeEffect::ConfiscateCompanyAssets { target } | RuntimeEffect::DeactivateCompany { target } | RuntimeEffect::SetCompanyTrackLayingCapacity { target, .. } | RuntimeEffect::AdjustCompanyCash { target, .. } | RuntimeEffect::AdjustCompanyDebt { target, .. } => { if matches!(effect, RuntimeEffect::ConfiscateCompanyAssets { .. }) && !company_context.has_train_context { Some(ImportBlocker::MissingTrainContext) } else if let RuntimeEffect::SetCompanyTerritoryAccess { territory, .. } = effect { company_target_import_blocker(target, company_context) .or_else(|| territory_target_import_blocker(territory, company_context)) } else { company_target_import_blocker(target, company_context) } } RuntimeEffect::SetPlayerCash { target, .. } | RuntimeEffect::SetPlayerVariable { target, .. } | RuntimeEffect::DeactivatePlayer { target } => { player_target_import_blocker(target, company_context) } RuntimeEffect::SetTerritoryVariable { target, .. } => { territory_target_import_blocker(target, company_context) } RuntimeEffect::SetChairmanCash { target, .. } | RuntimeEffect::DeactivateChairman { target } => { chairman_target_import_blocker(target, company_context) } RuntimeEffect::RetireTrains { company_target, territory_target, .. } => { if !company_context.has_train_context { return Some(ImportBlocker::MissingTrainContext); } if territory_target.is_some() && !company_context.has_train_territory_context { return Some(ImportBlocker::MissingTrainTerritoryContext); } company_target .as_ref() .and_then(|target| company_target_import_blocker(target, company_context)) .or_else(|| { territory_target .as_ref() .and_then(|target| territory_target_import_blocker(target, company_context)) }) } RuntimeEffect::AppendEventRecord { record } => record.effects.iter().find_map(|nested| { runtime_effect_company_target_import_blocker(nested, company_context) }), RuntimeEffect::SetWorldFlag { .. } | RuntimeEffect::SetWorldVariable { .. } | RuntimeEffect::SetWorldScalarOverride { .. } | RuntimeEffect::SetLimitedTrackBuildingAmount { .. } | RuntimeEffect::SetEconomicStatusCode { .. } | RuntimeEffect::SetCandidateAvailability { .. } | RuntimeEffect::SetNamedLocomotiveAvailability { .. } | RuntimeEffect::SetNamedLocomotiveAvailabilityValue { .. } | RuntimeEffect::SetNamedLocomotiveCost { .. } | RuntimeEffect::SetCargoPriceOverride { .. } | RuntimeEffect::SetCargoProductionOverride { .. } | RuntimeEffect::SetCargoProductionSlot { .. } | RuntimeEffect::SetTerritoryAccessCost { .. } | RuntimeEffect::SetSpecialCondition { .. } | RuntimeEffect::ActivateEventRecord { .. } | RuntimeEffect::DeactivateEventRecord { .. } | RuntimeEffect::RemoveEventRecord { .. } => None, } } fn classify_real_grouped_company_targets( record: &SmpLoadedPackedEventRecordSummary, ) -> Vec> { let Some(control) = &record.compact_control else { return Vec::new(); }; control .grouped_target_scope_ordinals_0x7fb .iter() .enumerate() .map(|(group_index, ordinal)| { if !record .grouped_effect_rows .iter() .any(|row| row.group_index == group_index) { return None; } classify_real_grouped_company_target(*ordinal) }) .collect() } fn classify_real_grouped_company_target(ordinal: u8) -> Option { match ordinal { 0 => Some(RuntimeCompanyTarget::ConditionTrueCompany), 1 => Some(RuntimeCompanyTarget::SelectedCompany), 2 => Some(RuntimeCompanyTarget::HumanCompanies), 3 => Some(RuntimeCompanyTarget::AiCompanies), _ => None, } } pub fn validate_runtime_state_dump_document( document: &RuntimeStateDumpDocument, ) -> Result<(), String> { if document.format_version != STATE_DUMP_FORMAT_VERSION { return Err(format!( "unsupported state dump format_version {} (expected {})", document.format_version, STATE_DUMP_FORMAT_VERSION )); } if document.dump_id.trim().is_empty() { return Err("dump_id must not be empty".to_string()); } document.state.validate() } pub fn validate_runtime_save_slice_document( document: &RuntimeSaveSliceDocument, ) -> Result<(), String> { if document.format_version != SAVE_SLICE_DOCUMENT_FORMAT_VERSION { return Err(format!( "unsupported save slice document format_version {} (expected {})", document.format_version, SAVE_SLICE_DOCUMENT_FORMAT_VERSION )); } if document.save_slice_id.trim().is_empty() { return Err("save_slice_id must not be empty".to_string()); } if document .source .description .as_deref() .is_some_and(|text| text.trim().is_empty()) { return Err("save slice source.description must not be empty".to_string()); } if document .source .original_save_filename .as_deref() .is_some_and(|text| text.trim().is_empty()) { return Err("save slice source.original_save_filename must not be empty".to_string()); } if document .source .original_save_sha256 .as_deref() .is_some_and(|text| text.trim().is_empty()) { return Err("save slice source.original_save_sha256 must not be empty".to_string()); } for (index, note) in document.source.notes.iter().enumerate() { if note.trim().is_empty() { return Err(format!( "save slice source.notes[{index}] must not be empty" )); } } if document.save_slice.mechanism_family.trim().is_empty() { return Err("save_slice.mechanism_family must not be empty".to_string()); } if document.save_slice.mechanism_confidence.trim().is_empty() { return Err("save_slice.mechanism_confidence must not be empty".to_string()); } Ok(()) } pub fn validate_runtime_overlay_import_document( document: &RuntimeOverlayImportDocument, ) -> Result<(), String> { if document.format_version != OVERLAY_IMPORT_DOCUMENT_FORMAT_VERSION { return Err(format!( "unsupported overlay import document format_version {} (expected {})", document.format_version, OVERLAY_IMPORT_DOCUMENT_FORMAT_VERSION )); } if document.import_id.trim().is_empty() { return Err("import_id must not be empty".to_string()); } if document .source .description .as_deref() .is_some_and(|text| text.trim().is_empty()) { return Err("overlay import source.description must not be empty".to_string()); } for (index, note) in document.source.notes.iter().enumerate() { if note.trim().is_empty() { return Err(format!( "overlay import source.notes[{index}] must not be empty" )); } } if document.base_snapshot_path.trim().is_empty() { return Err("base_snapshot_path must not be empty".to_string()); } if document.save_slice_path.trim().is_empty() { return Err("save_slice_path must not be empty".to_string()); } Ok(()) } pub fn load_runtime_save_slice_document( path: &Path, ) -> Result> { let text = std::fs::read_to_string(path)?; let document: RuntimeSaveSliceDocument = serde_json::from_str(&text)?; Ok(document) } pub fn load_runtime_overlay_import_document( path: &Path, ) -> Result> { let text = std::fs::read_to_string(path)?; let document: RuntimeOverlayImportDocument = serde_json::from_str(&text)?; Ok(document) } pub fn save_runtime_save_slice_document( path: &Path, document: &RuntimeSaveSliceDocument, ) -> Result<(), Box> { validate_runtime_save_slice_document(document) .map_err(|err| format!("invalid runtime save slice document: {err}"))?; let bytes = serde_json::to_vec_pretty(document)?; if let Some(parent) = path.parent() { std::fs::create_dir_all(parent)?; } std::fs::write(path, bytes)?; Ok(()) } pub fn save_runtime_overlay_import_document( path: &Path, document: &RuntimeOverlayImportDocument, ) -> Result<(), Box> { validate_runtime_overlay_import_document(document) .map_err(|err| format!("invalid runtime overlay import document: {err}"))?; let bytes = serde_json::to_vec_pretty(document)?; if let Some(parent) = path.parent() { std::fs::create_dir_all(parent)?; } std::fs::write(path, bytes)?; Ok(()) } pub fn load_runtime_state_import( path: &Path, ) -> Result> { let text = std::fs::read_to_string(path)?; load_runtime_state_import_from_str_with_base( &text, path.file_stem() .and_then(|stem| stem.to_str()) .unwrap_or("runtime-state"), path.parent().unwrap_or_else(|| Path::new(".")), ) } pub fn load_runtime_state_import_from_str( text: &str, fallback_id: &str, ) -> Result> { load_runtime_state_import_from_str_with_base(text, fallback_id, Path::new(".")) } fn load_runtime_state_import_from_str_with_base( text: &str, fallback_id: &str, base_dir: &Path, ) -> Result> { if let Ok(document) = serde_json::from_str::(text) { validate_runtime_state_dump_document(&document) .map_err(|err| format!("invalid runtime state dump document: {err}"))?; return Ok(RuntimeStateImport { import_id: document.dump_id, description: document.source.description, state: document.state, }); } if let Ok(document) = serde_json::from_str::(text) { validate_runtime_save_slice_document(&document) .map_err(|err| format!("invalid runtime save slice document: {err}"))?; let mut description_parts = Vec::new(); if let Some(description) = document.source.description { description_parts.push(description); } if let Some(filename) = document.source.original_save_filename { description_parts.push(format!("source save {filename}")); } let import = project_save_slice_to_runtime_state_import( &document.save_slice, &document.save_slice_id, if description_parts.is_empty() { None } else { Some(description_parts.join(" | ")) }, )?; return Ok(import); } if let Ok(document) = serde_json::from_str::(text) { validate_runtime_overlay_import_document(&document) .map_err(|err| format!("invalid runtime overlay import document: {err}"))?; let base_snapshot_path = resolve_document_path(base_dir, &document.base_snapshot_path); let save_slice_path = resolve_document_path(base_dir, &document.save_slice_path); let snapshot = load_runtime_snapshot_document(&base_snapshot_path)?; validate_runtime_snapshot_document(&snapshot).map_err(|err| { format!( "invalid runtime snapshot {}: {err}", base_snapshot_path.display() ) })?; let save_slice_document = load_runtime_save_slice_document(&save_slice_path)?; validate_runtime_save_slice_document(&save_slice_document).map_err(|err| { format!( "invalid runtime save slice document {}: {err}", save_slice_path.display() ) })?; let mut description_parts = Vec::new(); if let Some(description) = document.source.description { description_parts.push(description); } if let Some(description) = snapshot.source.description { description_parts.push(format!("base snapshot {description}")); } if let Some(description) = save_slice_document.source.description { description_parts.push(format!("save slice {description}")); } return project_save_slice_overlay_to_runtime_state_import( &snapshot.state, &save_slice_document.save_slice, &document.import_id, if description_parts.is_empty() { None } else { Some(description_parts.join(" | ")) }, ) .map_err(Into::into); } let state: RuntimeState = serde_json::from_str(text)?; state .validate() .map_err(|err| format!("invalid runtime state: {err}"))?; Ok(RuntimeStateImport { import_id: fallback_id.to_string(), description: None, state, }) } fn resolve_document_path(base_dir: &Path, path: &str) -> PathBuf { let candidate = PathBuf::from(path); if candidate.is_absolute() { candidate } else { base_dir.join(candidate) } } #[cfg(test)] mod tests { use super::*; use crate::{ RuntimeConditionComparator, RuntimeTrackPieceCounts, RuntimeTrain, StepCommand, execute_step_command, }; fn state() -> RuntimeState { RuntimeState { calendar: CalendarPoint { year: 1830, month_slot: 0, phase_slot: 0, tick_slot: 0, }, world_flags: BTreeMap::new(), save_profile: RuntimeSaveProfileState::default(), world_restore: RuntimeWorldRestoreState::default(), metadata: BTreeMap::new(), companies: Vec::new(), selected_company_id: None, players: Vec::new(), selected_player_id: None, chairman_profiles: Vec::new(), selected_chairman_profile_id: None, trains: Vec::new(), locomotive_catalog: Vec::new(), cargo_catalog: Vec::new(), territories: Vec::new(), company_territory_track_piece_counts: Vec::new(), company_territory_access: Vec::new(), packed_event_collection: None, event_runtime_records: Vec::new(), candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), all_cargo_price_override: None, named_cargo_price_overrides: BTreeMap::new(), all_cargo_production_override: None, factory_cargo_production_override: None, farm_mine_cargo_production_override: None, named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_runtime_variables: BTreeMap::new(), company_runtime_variables: BTreeMap::new(), player_runtime_variables: BTreeMap::new(), territory_runtime_variables: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), } } fn packed_text_bands() -> Vec { vec![ crate::SmpLoadedPackedEventTextBandSummary { label: "primary_text_band".to_string(), packed_len: 5, present: true, preview: "Alpha".to_string(), }, crate::SmpLoadedPackedEventTextBandSummary { label: "secondary_text_band_0".to_string(), packed_len: 0, present: false, preview: "".to_string(), }, crate::SmpLoadedPackedEventTextBandSummary { label: "secondary_text_band_1".to_string(), packed_len: 0, present: false, preview: "".to_string(), }, crate::SmpLoadedPackedEventTextBandSummary { label: "secondary_text_band_2".to_string(), packed_len: 0, present: false, preview: "".to_string(), }, crate::SmpLoadedPackedEventTextBandSummary { label: "secondary_text_band_3".to_string(), packed_len: 0, present: false, preview: "".to_string(), }, crate::SmpLoadedPackedEventTextBandSummary { label: "secondary_text_band_4".to_string(), packed_len: 0, present: false, preview: "".to_string(), }, ] } fn real_condition_rows() -> Vec { vec![crate::SmpLoadedPackedEventConditionRowSummary { row_index: 0, raw_condition_id: -1, subtype: 4, flag_bytes: vec![0x30; 25], candidate_name: Some("AutoPlant".to_string()), comparator: None, metric: None, semantic_family: None, semantic_preview: None, recovered_cargo_slot: None, recovered_cargo_class: None, requires_candidate_name_binding: false, notes: vec!["negative sentinel-style condition row id".to_string()], }] } fn synthetic_packed_record( record_index: usize, live_entry_id: u32, effect: RuntimeEffect, ) -> crate::SmpLoadedPackedEventRecordSummary { crate::SmpLoadedPackedEventRecordSummary { record_index, live_entry_id, payload_offset: Some(0x7200 + (live_entry_id as usize * 0x20)), payload_len: Some(64), decode_status: "parity_only".to_string(), payload_family: "synthetic_harness".to_string(), trigger_kind: Some(7), active: Some(true), marks_collection_dirty: Some(false), one_shot: Some(false), compact_control: None, text_bands: packed_text_bands(), standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![0, 0, 0, 0], grouped_effect_rows: vec![], decoded_conditions: Vec::new(), decoded_actions: vec![effect], executable_import_ready: false, notes: vec!["synthetic test record".to_string()], } } fn company_negative_sentinel_scope( company_test_scope: RuntimeCompanyConditionTestScope, ) -> crate::SmpLoadedPackedEventNegativeSentinelScopeSummary { crate::SmpLoadedPackedEventNegativeSentinelScopeSummary { company_test_scope, player_test_scope: RuntimePlayerConditionTestScope::Disabled, territory_scope_selector_is_0x63: false, source_row_indexes: vec![0], } } fn territory_negative_sentinel_scope() -> crate::SmpLoadedPackedEventNegativeSentinelScopeSummary { crate::SmpLoadedPackedEventNegativeSentinelScopeSummary { company_test_scope: RuntimeCompanyConditionTestScope::AllCompanies, player_test_scope: RuntimePlayerConditionTestScope::Disabled, territory_scope_selector_is_0x63: true, source_row_indexes: vec![0], } } fn player_negative_sentinel_scope() -> crate::SmpLoadedPackedEventNegativeSentinelScopeSummary { crate::SmpLoadedPackedEventNegativeSentinelScopeSummary { company_test_scope: RuntimeCompanyConditionTestScope::AllCompanies, player_test_scope: RuntimePlayerConditionTestScope::AllPlayers, territory_scope_selector_is_0x63: false, source_row_indexes: vec![0], } } fn selected_chairman_negative_sentinel_scope() -> crate::SmpLoadedPackedEventNegativeSentinelScopeSummary { crate::SmpLoadedPackedEventNegativeSentinelScopeSummary { company_test_scope: RuntimeCompanyConditionTestScope::Disabled, player_test_scope: RuntimePlayerConditionTestScope::SelectedPlayerOnly, territory_scope_selector_is_0x63: false, source_row_indexes: vec![0], } } fn real_grouped_rows() -> Vec { vec![crate::SmpLoadedPackedEventGroupedEffectRowSummary { group_index: 0, row_index: 0, descriptor_id: 2, descriptor_label: Some("Company Cash".to_string()), target_mask_bits: Some(0x01), parameter_family: Some("company_finance_scalar".to_string()), grouped_target_subject: None, grouped_target_scope: None, opcode: 8, raw_scalar_value: 7, value_byte_0x09: 1, value_dword_0x0d: 12, value_byte_0x11: 2, value_byte_0x12: 3, value_word_0x14: 24, value_word_0x16: 36, row_shape: "multivalue_scalar".to_string(), semantic_family: Some("multivalue_scalar".to_string()), semantic_preview: Some("Set Company Cash to 7 with aux [2, 3, 24, 36]".to_string()), recovered_cargo_slot: None, recovered_cargo_class: None, recovered_cargo_label: None, recovered_locomotive_id: None, locomotive_name: Some("Mikado".to_string()), notes: vec!["grouped effect row carries locomotive-name side string".to_string()], }] } fn real_deactivate_company_row( enabled: bool, ) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary { crate::SmpLoadedPackedEventGroupedEffectRowSummary { group_index: 0, row_index: 0, descriptor_id: 13, descriptor_label: Some("Deactivate Company".to_string()), target_mask_bits: Some(0x01), parameter_family: Some("company_lifecycle_toggle".to_string()), grouped_target_subject: None, grouped_target_scope: None, opcode: 1, raw_scalar_value: if enabled { 1 } else { 0 }, value_byte_0x09: 0, value_dword_0x0d: 0, value_byte_0x11: 0, value_byte_0x12: 0, value_word_0x14: 0, value_word_0x16: 0, row_shape: "bool_toggle".to_string(), semantic_family: Some("bool_toggle".to_string()), semantic_preview: Some(format!( "Set Deactivate Company to {}", if enabled { "TRUE" } else { "FALSE" } )), recovered_cargo_slot: None, recovered_cargo_class: None, recovered_cargo_label: None, recovered_locomotive_id: None, locomotive_name: None, notes: vec![], } } fn real_track_capacity_row(value: i32) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary { crate::SmpLoadedPackedEventGroupedEffectRowSummary { group_index: 0, row_index: 0, descriptor_id: 16, descriptor_label: Some("Company Track Pieces Buildable".to_string()), target_mask_bits: Some(0x01), parameter_family: Some("company_build_limit_scalar".to_string()), grouped_target_subject: None, grouped_target_scope: None, opcode: 3, raw_scalar_value: value, value_byte_0x09: 0, value_dword_0x0d: 0, value_byte_0x11: 0, value_byte_0x12: 0, value_word_0x14: 0, value_word_0x16: 0, row_shape: "scalar_assignment".to_string(), semantic_family: Some("scalar_assignment".to_string()), semantic_preview: Some(format!("Set Company Track Pieces Buildable to {value}")), recovered_cargo_slot: None, recovered_cargo_class: None, recovered_cargo_label: None, recovered_locomotive_id: None, locomotive_name: None, notes: vec![], } } fn real_credit_rating_row(value: i32) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary { crate::SmpLoadedPackedEventGroupedEffectRowSummary { group_index: 0, row_index: 0, descriptor_id: 56, descriptor_label: Some("Credit Rating".to_string()), target_mask_bits: Some(0x0b), parameter_family: Some("company_governance_scalar".to_string()), grouped_target_subject: Some("company".to_string()), grouped_target_scope: Some("selected_company".to_string()), opcode: 3, raw_scalar_value: value, value_byte_0x09: 0, value_dword_0x0d: 0, value_byte_0x11: 0, value_byte_0x12: 0, value_word_0x14: 0, value_word_0x16: 0, row_shape: "scalar_assignment".to_string(), semantic_family: Some("scalar_assignment".to_string()), semantic_preview: Some(format!("Set Credit Rating to {value}")), recovered_cargo_slot: None, recovered_cargo_class: None, recovered_cargo_label: None, recovered_locomotive_id: None, locomotive_name: None, notes: vec![], } } fn real_merger_premium_shell_row( value: i32, ) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary { crate::SmpLoadedPackedEventGroupedEffectRowSummary { group_index: 0, row_index: 0, descriptor_id: 58, descriptor_label: Some("Merger Premium".to_string()), target_mask_bits: Some(0x0b), parameter_family: Some("company_finance_shell_scalar".to_string()), grouped_target_subject: Some("company".to_string()), grouped_target_scope: Some("selected_company".to_string()), opcode: 3, raw_scalar_value: value, value_byte_0x09: 0, value_dword_0x0d: 0, value_byte_0x11: 0, value_byte_0x12: 0, value_word_0x14: 0, value_word_0x16: 0, row_shape: "scalar_assignment".to_string(), semantic_family: Some("scalar_assignment".to_string()), semantic_preview: Some(format!("Set Merger Premium to {value}")), recovered_cargo_slot: None, recovered_cargo_class: None, recovered_cargo_label: None, recovered_locomotive_id: None, locomotive_name: None, notes: vec![ "descriptor is recovered in the checked-in effect table as shell_owned parity" .to_string(), ], } } fn real_stock_prices_shell_row( value: i32, ) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary { crate::SmpLoadedPackedEventGroupedEffectRowSummary { group_index: 0, row_index: 0, descriptor_id: 55, descriptor_label: Some("Stock Prices".to_string()), target_mask_bits: Some(0x0b), parameter_family: Some("company_finance_shell_scalar".to_string()), grouped_target_subject: Some("company".to_string()), grouped_target_scope: Some("selected_company".to_string()), opcode: 3, raw_scalar_value: value, value_byte_0x09: 0, value_dword_0x0d: 0, value_byte_0x11: 0, value_byte_0x12: 0, value_word_0x14: 0, value_word_0x16: 0, row_shape: "scalar_assignment".to_string(), semantic_family: Some("scalar_assignment".to_string()), semantic_preview: Some(format!("Set Stock Prices to {value}")), recovered_cargo_slot: None, recovered_cargo_class: None, recovered_cargo_label: None, recovered_locomotive_id: None, locomotive_name: None, notes: vec![ "descriptor is recovered in the checked-in effect table as shell_owned parity" .to_string(), ], } } fn real_deactivate_player_row( enabled: bool, ) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary { crate::SmpLoadedPackedEventGroupedEffectRowSummary { group_index: 0, row_index: 0, descriptor_id: 14, descriptor_label: Some("Deactivate Player".to_string()), target_mask_bits: Some(0x02), parameter_family: Some("player_lifecycle_toggle".to_string()), grouped_target_subject: None, grouped_target_scope: None, opcode: 1, raw_scalar_value: if enabled { 1 } else { 0 }, value_byte_0x09: 0, value_dword_0x0d: 0, value_byte_0x11: 0, value_byte_0x12: 0, value_word_0x14: 0, value_word_0x16: 0, row_shape: "bool_toggle".to_string(), semantic_family: Some("bool_toggle".to_string()), semantic_preview: Some(format!( "Set Deactivate Player to {}", if enabled { "TRUE" } else { "FALSE" } )), recovered_cargo_slot: None, recovered_cargo_class: None, recovered_cargo_label: None, recovered_locomotive_id: None, locomotive_name: None, notes: vec![], } } fn real_territory_access_row( enabled: bool, notes: Vec, ) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary { crate::SmpLoadedPackedEventGroupedEffectRowSummary { group_index: 0, row_index: 0, descriptor_id: 3, descriptor_label: Some("Territory - Allow All".to_string()), target_mask_bits: Some(0x05), parameter_family: Some("territory_access_toggle".to_string()), grouped_target_subject: None, grouped_target_scope: None, opcode: 1, raw_scalar_value: if enabled { 1 } else { 0 }, value_byte_0x09: 0, value_dword_0x0d: 0, value_byte_0x11: 0, value_byte_0x12: 0, value_word_0x14: 0, value_word_0x16: 0, row_shape: "bool_toggle".to_string(), semantic_family: Some("bool_toggle".to_string()), semantic_preview: Some(format!( "Set Territory - Allow All to {}", if enabled { "TRUE" } else { "FALSE" } )), recovered_cargo_slot: None, recovered_cargo_class: None, recovered_cargo_label: None, recovered_locomotive_id: None, locomotive_name: None, notes, } } fn real_economic_status_row(value: i32) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary { crate::SmpLoadedPackedEventGroupedEffectRowSummary { group_index: 0, row_index: 0, descriptor_id: 8, descriptor_label: Some("Economic Status".to_string()), target_mask_bits: Some(0x08), parameter_family: Some("whole_game_state_enum".to_string()), grouped_target_subject: None, grouped_target_scope: None, opcode: 3, raw_scalar_value: value, value_byte_0x09: 0, value_dword_0x0d: 0, value_byte_0x11: 0, value_byte_0x12: 0, value_word_0x14: 0, value_word_0x16: 0, row_shape: "scalar_assignment".to_string(), semantic_family: Some("scalar_assignment".to_string()), semantic_preview: Some(format!("Set Economic Status to {value}")), recovered_cargo_slot: None, recovered_cargo_class: None, recovered_cargo_label: None, recovered_locomotive_id: None, locomotive_name: None, notes: vec![], } } fn real_limited_track_building_amount_row( value: i32, ) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary { crate::SmpLoadedPackedEventGroupedEffectRowSummary { group_index: 0, row_index: 0, descriptor_id: 122, descriptor_label: Some("Limited Track Building Amount".to_string()), target_mask_bits: Some(0x08), parameter_family: Some("world_track_build_limit_scalar".to_string()), grouped_target_subject: None, grouped_target_scope: None, opcode: 3, raw_scalar_value: value, value_byte_0x09: 0, value_dword_0x0d: 0, value_byte_0x11: 0, value_byte_0x12: 0, value_word_0x14: 0, value_word_0x16: 0, row_shape: "scalar_assignment".to_string(), semantic_family: Some("scalar_assignment".to_string()), semantic_preview: Some(format!("Set Limited Track Building Amount to {value}")), recovered_cargo_slot: None, recovered_cargo_class: None, recovered_cargo_label: None, recovered_locomotive_id: None, locomotive_name: None, notes: vec![], } } fn real_special_condition_row( value: i32, ) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary { crate::SmpLoadedPackedEventGroupedEffectRowSummary { group_index: 0, row_index: 0, descriptor_id: 108, descriptor_label: Some("Use Wartime Cargos".to_string()), target_mask_bits: Some(0x08), parameter_family: Some("special_condition_scalar".to_string()), grouped_target_subject: None, grouped_target_scope: None, opcode: 3, raw_scalar_value: value, value_byte_0x09: 0, value_dword_0x0d: 0, value_byte_0x11: 0, value_byte_0x12: 0, value_word_0x14: 0, value_word_0x16: 0, row_shape: "scalar_assignment".to_string(), semantic_family: Some("scalar_assignment".to_string()), semantic_preview: Some(format!("Set Use Wartime Cargos to {value}")), recovered_cargo_slot: None, recovered_cargo_class: None, recovered_cargo_label: None, recovered_locomotive_id: None, locomotive_name: None, notes: vec![], } } fn real_candidate_availability_row( value: i32, ) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary { crate::SmpLoadedPackedEventGroupedEffectRowSummary { group_index: 0, row_index: 0, descriptor_id: 109, descriptor_label: Some("Turbo Diesel Availability".to_string()), target_mask_bits: Some(0x08), parameter_family: Some("candidate_availability_scalar".to_string()), grouped_target_subject: None, grouped_target_scope: None, opcode: 3, raw_scalar_value: value, value_byte_0x09: 0, value_dword_0x0d: 0, value_byte_0x11: 0, value_byte_0x12: 0, value_word_0x14: 0, value_word_0x16: 0, row_shape: "scalar_assignment".to_string(), semantic_family: Some("scalar_assignment".to_string()), semantic_preview: Some(format!("Set Turbo Diesel Availability to {value}")), recovered_cargo_slot: None, recovered_cargo_class: None, recovered_cargo_label: None, recovered_locomotive_id: None, locomotive_name: None, notes: vec![], } } fn real_locomotive_availability_row( descriptor_id: u32, value: i32, ) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary { fn grounded_locomotive_name(locomotive_id: u32) -> Option<&'static str> { match locomotive_id { 1 => Some("2-D-2"), 2 => Some("E-88"), 3 => Some("Adler 2-2-2"), 4 => Some("USA 103"), 5 => Some("American 4-4-0"), 6 => Some("Atlantic 4-4-2"), 7 => Some("Baldwin 0-6-0"), 8 => Some("Be 5/7"), 9 => Some("Beuth 2-2-2"), 10 => Some("Big Boy 4-8-8-4"), 11 => Some("C55 Deltic"), 12 => Some("Camelback 0-6-0"), 13 => Some("Challenger 4-6-6-4"), 14 => Some("Class 01 4-6-2"), 15 => Some("Class 103"), 16 => Some("Class 132"), 17 => Some("Class 500 4-6-0"), 18 => Some("Class 9100"), 19 => Some("Class EF 66"), 20 => Some("Class 6E"), 21 => Some("Consolidation 2-8-0"), 22 => Some("Crampton 4-2-0"), 23 => Some("DD 080-X"), 24 => Some("DD40AX"), 25 => Some("Duke Class 4-4-0"), 26 => Some("E18"), 27 => Some("E428"), 28 => Some("Brenner E412"), 29 => Some("E60CP"), 30 => Some("Eight Wheeler 4-4-0"), 31 => Some("EP-2 Bipolar"), 32 => Some("ET22"), 33 => Some("F3"), 34 => Some("Fairlie 0-6-6-0"), 35 => Some("Firefly 2-2-2"), 36 => Some("FP45"), 37 => Some("Ge 6/6 Crocodile"), 38 => Some("GG1"), 39 => Some("GP7"), 40 => Some("H10 2-8-2"), 41 => Some("HST 125"), 42 => Some("Kriegslok 2-10-0"), 43 => Some("Mallard 4-6-2"), 44 => Some("Norris 4-2-0"), 45 => Some("Northern 4-8-4"), 46 => Some("Orca NX462"), 47 => Some("Pacific 4-6-2"), 48 => Some("Planet 2-2-0"), 49 => Some("Re 6/6"), 50 => Some("Red Devil 4-8-4"), 51 => Some("S3 4-4-0"), 52 => Some("NA-90D"), 53 => Some("Shay (2-Truck)"), 54 => Some("Shinkansen Series 0"), 55 => Some("Stirling 4-2-2"), 56 => Some("Trans-Euro"), 57 => Some("V200"), 58 => Some("VL80T"), 59 => Some("GP 35"), 60 => Some("U1"), 61 => Some("Zephyr"), _ => None, } } let recovered_locomotive_id = match descriptor_id { 241..=351 => Some(descriptor_id - 240), _ => None, }; let descriptor_label = match descriptor_id { 457..=474 => { format!( "Upper-Band Locomotive Availability Slot {}", descriptor_id - 456 ) } _ => recovered_locomotive_id .map(|loco_id| { grounded_locomotive_name(loco_id) .map(|name| format!("{name} Availability")) .unwrap_or_else(|| format!("Locomotive {loco_id} Availability")) }) .unwrap_or_else(|| "Locomotive Availability".to_string()), }; crate::SmpLoadedPackedEventGroupedEffectRowSummary { group_index: 0, row_index: 0, descriptor_id, descriptor_label: Some(descriptor_label.clone()), target_mask_bits: Some(0x08), parameter_family: Some("locomotive_availability_scalar".to_string()), grouped_target_subject: None, grouped_target_scope: None, opcode: 3, raw_scalar_value: value, value_byte_0x09: 0, value_dword_0x0d: 0, value_byte_0x11: 0, value_byte_0x12: 0, value_word_0x14: 0, value_word_0x16: 0, row_shape: "scalar_assignment".to_string(), semantic_family: Some("scalar_assignment".to_string()), semantic_preview: Some(format!("Set {descriptor_label} to {value}")), recovered_cargo_slot: None, recovered_cargo_class: None, recovered_cargo_label: None, recovered_locomotive_id, locomotive_name: None, notes: vec![], } } fn real_locomotive_cost_row( descriptor_id: u32, value: i32, ) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary { fn grounded_locomotive_name(locomotive_id: u32) -> Option<&'static str> { match locomotive_id { 1 => Some("2-D-2"), 2 => Some("E-88"), 3 => Some("Adler 2-2-2"), 4 => Some("USA 103"), 5 => Some("American 4-4-0"), 6 => Some("Atlantic 4-4-2"), 7 => Some("Baldwin 0-6-0"), 8 => Some("Be 5/7"), 9 => Some("Beuth 2-2-2"), 10 => Some("Big Boy 4-8-8-4"), 11 => Some("C55 Deltic"), 12 => Some("Camelback 0-6-0"), 13 => Some("Challenger 4-6-6-4"), 14 => Some("Class 01 4-6-2"), 15 => Some("Class 103"), 16 => Some("Class 132"), 17 => Some("Class 500 4-6-0"), 18 => Some("Class 9100"), 19 => Some("Class EF 66"), 20 => Some("Class 6E"), 21 => Some("Consolidation 2-8-0"), 22 => Some("Crampton 4-2-0"), 23 => Some("DD 080-X"), 24 => Some("DD40AX"), 25 => Some("Duke Class 4-4-0"), 26 => Some("E18"), 27 => Some("E428"), 28 => Some("Brenner E412"), 29 => Some("E60CP"), 30 => Some("Eight Wheeler 4-4-0"), 31 => Some("EP-2 Bipolar"), 32 => Some("ET22"), 33 => Some("F3"), 34 => Some("Fairlie 0-6-6-0"), 35 => Some("Firefly 2-2-2"), 36 => Some("FP45"), 37 => Some("Ge 6/6 Crocodile"), 38 => Some("GG1"), 39 => Some("GP7"), 40 => Some("H10 2-8-2"), 41 => Some("HST 125"), 42 => Some("Kriegslok 2-10-0"), 43 => Some("Mallard 4-6-2"), 44 => Some("Norris 4-2-0"), 45 => Some("Northern 4-8-4"), 46 => Some("Orca NX462"), 47 => Some("Pacific 4-6-2"), 48 => Some("Planet 2-2-0"), 49 => Some("Re 6/6"), 50 => Some("Red Devil 4-8-4"), 51 => Some("S3 4-4-0"), 52 => Some("NA-90D"), 53 => Some("Shay (2-Truck)"), 54 => Some("Shinkansen Series 0"), 55 => Some("Stirling 4-2-2"), 56 => Some("Trans-Euro"), 57 => Some("V200"), 58 => Some("VL80T"), 59 => Some("GP 35"), 60 => Some("U1"), 61 => Some("Zephyr"), _ => None, } } let recovered_locomotive_id = match descriptor_id { 352..=451 => Some(descriptor_id - 351), _ => None, }; let descriptor_label = match descriptor_id { 475..=502 => format!("Upper-Band Locomotive Cost Slot {}", descriptor_id - 474), _ => recovered_locomotive_id .map(|loco_id| { grounded_locomotive_name(loco_id) .map(|name| format!("{name} Cost")) .unwrap_or_else(|| format!("Locomotive {loco_id} Cost")) }) .unwrap_or_else(|| "Locomotive Cost".to_string()), }; crate::SmpLoadedPackedEventGroupedEffectRowSummary { group_index: 0, row_index: 0, descriptor_id, descriptor_label: Some(descriptor_label.clone()), target_mask_bits: Some(0x08), parameter_family: Some("locomotive_cost_scalar".to_string()), grouped_target_subject: None, grouped_target_scope: None, opcode: 3, raw_scalar_value: value, value_byte_0x09: 0, value_dword_0x0d: 0, value_byte_0x11: 0, value_byte_0x12: 0, value_word_0x14: 0, value_word_0x16: 0, row_shape: "scalar_assignment".to_string(), semantic_family: Some("scalar_assignment".to_string()), semantic_preview: Some(format!("Set {descriptor_label} to {value}")), recovered_cargo_slot: None, recovered_cargo_class: None, recovered_cargo_label: None, recovered_locomotive_id, locomotive_name: None, notes: vec![], } } fn save_named_locomotive_table( count: usize, ) -> crate::SmpLoadedNamedLocomotiveAvailabilityTable { fn grounded_locomotive_name(index: usize) -> String { match index { 0 => "2-D-2", 1 => "E-88", 2 => "Adler 2-2-2", 3 => "USA 103", 4 => "American 4-4-0", 5 => "Atlantic 4-4-2", 6 => "Baldwin 0-6-0", 7 => "Be 5/7", 8 => "Beuth 2-2-2", 9 => "Big Boy 4-8-8-4", 10 => "C55 Deltic", 11 => "Camelback 0-6-0", 12 => "Challenger 4-6-6-4", 13 => "Class 01 4-6-2", 14 => "Class 103", 15 => "Class 132", 16 => "Class 500 4-6-0", 17 => "Class 9100", 18 => "Class EF 66", 19 => "Class 6E", 20 => "Consolidation 2-8-0", 21 => "Crampton 4-2-0", 22 => "DD 080-X", 23 => "DD40AX", 24 => "Duke Class 4-4-0", 25 => "E18", 26 => "E428", 27 => "Brenner E412", 28 => "E60CP", 29 => "Eight Wheeler 4-4-0", 30 => "EP-2 Bipolar", 31 => "ET22", 32 => "F3", 33 => "Fairlie 0-6-6-0", 34 => "Firefly 2-2-2", 35 => "FP45", 36 => "Ge 6/6 Crocodile", 37 => "GG1", 38 => "GP7", 39 => "H10 2-8-2", 40 => "HST 125", 41 => "Kriegslok 2-10-0", 42 => "Mallard 4-6-2", 43 => "Norris 4-2-0", 44 => "Northern 4-8-4", 45 => "Orca NX462", 46 => "Pacific 4-6-2", 47 => "Planet 2-2-0", 48 => "Re 6/6", 49 => "Red Devil 4-8-4", 50 => "S3 4-4-0", 51 => "NA-90D", 52 => "Shay (2-Truck)", 53 => "Shinkansen Series 0", 54 => "Stirling 4-2-2", 55 => "Trans-Euro", 56 => "V200", 57 => "VL80T", 58 => "GP 35", 59 => "U1", 60 => "Zephyr", _ => return format!("Locomotive {}", index + 1), } .to_string() } crate::SmpLoadedNamedLocomotiveAvailabilityTable { source_kind: "runtime-save-direct-serializer".to_string(), semantic_family: "scenario-named-locomotive-availability-table".to_string(), header_offset: None, entries_offset: Some(0x7c78), entries_end_offset: Some(0x7c78 + count * 0x41), observed_entry_count: count, zero_availability_count: 0, zero_availability_names: vec![], entries: (0..count) .map(|index| crate::SmpRt3105SaveNameTableEntry { index, offset: 0x7c78 + index * 0x41, text: grounded_locomotive_name(index), availability_dword: 1, availability_dword_hex: "0x00000001".to_string(), trailer_word: 1, trailer_word_hex: "0x00000001".to_string(), }) .collect(), } } fn save_cargo_catalog( entries: &[(u32, crate::RuntimeCargoClass)], ) -> crate::SmpLoadedCargoCatalog { crate::SmpLoadedCargoCatalog { source_kind: "recipe-book-summary-slot-catalog".to_string(), semantic_family: "scenario-save-derived-cargo-catalog".to_string(), root_offset: Some(0x0fe7), observed_entry_count: entries.len(), entries: entries .iter() .enumerate() .map( |(index, (slot_id, cargo_class))| crate::SmpLoadedCargoCatalogEntry { slot_id: *slot_id, label: format!("Cargo Production Slot {slot_id}"), cargo_class: *cargo_class, book_index: index, max_annual_production_word: 0, mode_word: 0, runtime_import_branch_kind: "zero-mode-skipped".to_string(), annual_amount_word: 0, supplied_cargo_token_word: 0, supplied_cargo_token_probable_high16_ascii_stem: None, demanded_cargo_token_word: 0, demanded_cargo_token_probable_high16_ascii_stem: None, }, ) .collect(), } } fn save_company_roster() -> crate::SmpLoadedCompanyRoster { crate::SmpLoadedCompanyRoster { source_kind: "tracked-save-slice-company-roster".to_string(), semantic_family: "save-slice-runtime-company-context".to_string(), observed_entry_count: 2, selected_company_id: Some(1), entries: vec![ crate::SmpLoadedCompanyRosterEntry { company_id: 1, active: true, controller_kind: RuntimeCompanyControllerKind::Human, current_cash: 150, debt: 80, credit_rating_score: Some(650), prime_rate: Some(5), available_track_laying_capacity: Some(6), track_piece_counts: RuntimeTrackPieceCounts { total: 20, single: 5, double: 8, transition: 1, electric: 3, non_electric: 17, }, linked_chairman_profile_id: Some(1), book_value_per_share: 2620, investor_confidence: 37, management_attitude: 58, takeover_cooldown_year: Some(1839), merger_cooldown_year: Some(1838), market_state: Some(crate::RuntimeCompanyMarketState { outstanding_shares: 20_000, bond_count: 2, live_bond_slots: Vec::new(), largest_live_bond_principal: Some(500_000), highest_coupon_live_bond_principal: Some(350_000), mutable_support_scalar_raw_u32: 0x3f99999a, young_company_support_scalar_raw_u32: 0x42700000, support_progress_word: 12, recent_per_share_cache_absolute_counter: 0, recent_per_share_cached_value_bits: 0, recent_per_share_subscore_raw_u32: 0x420c0000, cached_share_price_raw_u32: 0x42200000, chairman_salary_baseline: 24, chairman_salary_current: 30, chairman_bonus_year: 1832, chairman_bonus_amount: 900, founding_year: 1831, last_bankruptcy_year: 0, last_dividend_year: 1837, current_issue_calendar_word: 5, current_issue_calendar_word_2: 6, prior_issue_calendar_word: 4, prior_issue_calendar_word_2: 5, city_connection_latch: true, linked_transit_latch: false, stat_band_root_0cfb_candidates: Vec::new(), stat_band_root_0d7f_candidates: Vec::new(), stat_band_root_1c47_candidates: Vec::new(), year_stat_family_qword_bits: Vec::new(), special_stat_family_232a_qword_bits: Vec::new(), issue_opinion_terms_raw_i32: Vec::new(), direct_control_transfer_float_fields_raw_u32: BTreeMap::new(), direct_control_transfer_int_fields_raw_u32: BTreeMap::new(), }), }, crate::SmpLoadedCompanyRosterEntry { company_id: 2, active: true, controller_kind: RuntimeCompanyControllerKind::Ai, current_cash: 90, debt: 40, credit_rating_score: Some(480), prime_rate: Some(6), available_track_laying_capacity: Some(2), track_piece_counts: RuntimeTrackPieceCounts { total: 8, single: 2, double: 2, transition: 0, electric: 1, non_electric: 7, }, linked_chairman_profile_id: Some(2), book_value_per_share: 1400, investor_confidence: 22, management_attitude: 31, takeover_cooldown_year: None, merger_cooldown_year: None, market_state: Some(crate::RuntimeCompanyMarketState { outstanding_shares: 18_000, bond_count: 1, live_bond_slots: Vec::new(), largest_live_bond_principal: Some(300_000), highest_coupon_live_bond_principal: Some(300_000), mutable_support_scalar_raw_u32: 0x3f4ccccd, young_company_support_scalar_raw_u32: 0x42580000, support_progress_word: 9, recent_per_share_cache_absolute_counter: 0, recent_per_share_cached_value_bits: 0, recent_per_share_subscore_raw_u32: 0x41f00000, cached_share_price_raw_u32: 0x41f80000, chairman_salary_baseline: 20, chairman_salary_current: 22, chairman_bonus_year: 0, chairman_bonus_amount: 0, founding_year: 1833, last_bankruptcy_year: 0, last_dividend_year: 0, current_issue_calendar_word: 3, current_issue_calendar_word_2: 4, prior_issue_calendar_word: 2, prior_issue_calendar_word_2: 3, city_connection_latch: false, linked_transit_latch: true, stat_band_root_0cfb_candidates: Vec::new(), stat_band_root_0d7f_candidates: Vec::new(), stat_band_root_1c47_candidates: Vec::new(), year_stat_family_qword_bits: Vec::new(), special_stat_family_232a_qword_bits: Vec::new(), issue_opinion_terms_raw_i32: Vec::new(), direct_control_transfer_float_fields_raw_u32: BTreeMap::new(), direct_control_transfer_int_fields_raw_u32: BTreeMap::new(), }), }, ], } } fn save_chairman_profile_table() -> crate::SmpLoadedChairmanProfileTable { crate::SmpLoadedChairmanProfileTable { source_kind: "tracked-save-slice-chairman-profile-table".to_string(), semantic_family: "save-slice-runtime-chairman-context".to_string(), observed_entry_count: 2, selected_chairman_profile_id: Some(1), entries: vec![ crate::SmpLoadedChairmanProfileEntry { profile_id: 1, name: "Chairman One".to_string(), active: true, current_cash: 500, linked_company_id: Some(1), company_holdings: BTreeMap::from([(1, 1000)]), holdings_value_total: 700, net_worth_total: 1200, purchasing_power_total: 1500, personality_byte_0x291: Some(12), issue_opinion_terms_raw_i32: Vec::new(), }, crate::SmpLoadedChairmanProfileEntry { profile_id: 2, name: "Chairman Two".to_string(), active: true, current_cash: 250, linked_company_id: Some(2), company_holdings: BTreeMap::from([(2, 900)]), holdings_value_total: 600, net_worth_total: 900, purchasing_power_total: 1100, personality_byte_0x291: Some(20), issue_opinion_terms_raw_i32: Vec::new(), }, ], } } fn real_cargo_production_row( descriptor_id: u32, value: i32, ) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary { let slot = descriptor_id.saturating_sub(229); let descriptor_label = format!("Cargo Production Slot {slot}"); let recovered_cargo_class = match slot { 1..=4 => Some("factory".to_string()), 5..=8 => Some("farm_mine".to_string()), 9..=11 => Some("other".to_string()), _ => None, }; crate::SmpLoadedPackedEventGroupedEffectRowSummary { group_index: 0, row_index: 0, descriptor_id, descriptor_label: Some(descriptor_label.clone()), target_mask_bits: Some(0x08), parameter_family: Some("cargo_production_scalar".to_string()), grouped_target_subject: None, grouped_target_scope: None, opcode: 3, raw_scalar_value: value, value_byte_0x09: 0, value_dword_0x0d: 0, value_byte_0x11: 0, value_byte_0x12: 0, value_word_0x14: 0, value_word_0x16: 0, row_shape: "scalar_assignment".to_string(), semantic_family: Some("scalar_assignment".to_string()), semantic_preview: Some(format!("Set {descriptor_label} to {value}")), recovered_cargo_slot: Some(slot), recovered_cargo_class, recovered_cargo_label: None, recovered_locomotive_id: None, locomotive_name: None, notes: vec![], } } fn real_all_cargo_price_row(value: i32) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary { crate::SmpLoadedPackedEventGroupedEffectRowSummary { group_index: 0, row_index: 0, descriptor_id: 105, descriptor_label: Some("All Cargo Prices".to_string()), target_mask_bits: Some(0x08), parameter_family: Some("cargo_price_scalar".to_string()), grouped_target_subject: None, grouped_target_scope: None, opcode: 3, raw_scalar_value: value, value_byte_0x09: 0, value_dword_0x0d: 0, value_byte_0x11: 0, value_byte_0x12: 0, value_word_0x14: 0, value_word_0x16: 0, row_shape: "scalar_assignment".to_string(), semantic_family: Some("scalar_assignment".to_string()), semantic_preview: Some(format!("Set All Cargo Prices to {value}")), recovered_cargo_slot: None, recovered_cargo_class: None, recovered_cargo_label: None, recovered_locomotive_id: None, locomotive_name: None, notes: vec![ "descriptor recovered from checked-in EventEffects semantic catalog".to_string(), ], } } fn real_named_cargo_price_row( descriptor_id: u32, value: i32, ) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary { let cargo_label = crate::smp::grounded_named_cargo_price_label(descriptor_id).map(ToString::to_string); let descriptor_label = cargo_label .as_deref() .map(|label| format!("{label} Price")) .unwrap_or_else(|| { format!( "Named Cargo Price Slot {}", descriptor_id.saturating_sub(105) ) }); crate::SmpLoadedPackedEventGroupedEffectRowSummary { group_index: 0, row_index: 0, descriptor_id, descriptor_label: Some(descriptor_label.clone()), target_mask_bits: Some(0x08), parameter_family: Some("cargo_price_scalar".to_string()), grouped_target_subject: None, grouped_target_scope: None, opcode: 3, raw_scalar_value: value, value_byte_0x09: 0, value_dword_0x0d: 0, value_byte_0x11: 0, value_byte_0x12: 0, value_word_0x14: 0, value_word_0x16: 0, row_shape: "scalar_assignment".to_string(), semantic_family: Some("scalar_assignment".to_string()), semantic_preview: Some(format!("Set {descriptor_label} to {value}")), recovered_cargo_slot: None, recovered_cargo_class: None, recovered_cargo_label: cargo_label, recovered_locomotive_id: None, locomotive_name: None, notes: vec![ "descriptor recovered from checked-in EventEffects semantic catalog".to_string(), ], } } fn real_aggregate_cargo_production_row( descriptor_id: u32, value: i32, ) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary { let (label, recovered_cargo_class) = match descriptor_id { 177 => ("All Cargo Production", None), 178 => ("All Factory Production", Some("factory".to_string())), 179 => ("All Farm/Mine Production", Some("farm_mine".to_string())), _ => ("Unknown Cargo Production", None), }; crate::SmpLoadedPackedEventGroupedEffectRowSummary { group_index: 0, row_index: 0, descriptor_id, descriptor_label: Some(label.to_string()), target_mask_bits: Some(0x08), parameter_family: Some("cargo_production_scalar".to_string()), grouped_target_subject: None, grouped_target_scope: None, opcode: 3, raw_scalar_value: value, value_byte_0x09: 0, value_dword_0x0d: 0, value_byte_0x11: 0, value_byte_0x12: 0, value_word_0x14: 0, value_word_0x16: 0, row_shape: "scalar_assignment".to_string(), semantic_family: Some("scalar_assignment".to_string()), semantic_preview: Some(format!("Set {label} to {value}")), recovered_cargo_slot: None, recovered_cargo_class, recovered_cargo_label: None, recovered_locomotive_id: None, locomotive_name: None, notes: vec![ "descriptor recovered from checked-in EventEffects semantic catalog".to_string(), ], } } fn real_named_cargo_production_row( descriptor_id: u32, value: i32, ) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary { let cargo_label = match descriptor_id { 180 => Some("Alcohol".to_string()), _ => None, }; let descriptor_label = cargo_label .as_ref() .map(|label| format!("{label} Production")) .unwrap_or_else(|| "Unknown Cargo Production".to_string()); crate::SmpLoadedPackedEventGroupedEffectRowSummary { group_index: 0, row_index: 0, descriptor_id, descriptor_label: Some(descriptor_label.clone()), target_mask_bits: Some(0x08), parameter_family: Some("cargo_production_scalar".to_string()), grouped_target_subject: None, grouped_target_scope: None, opcode: 3, raw_scalar_value: value, value_byte_0x09: 0, value_dword_0x0d: 0, value_byte_0x11: 0, value_byte_0x12: 0, value_word_0x14: 0, value_word_0x16: 0, row_shape: "scalar_assignment".to_string(), semantic_family: Some("scalar_assignment".to_string()), semantic_preview: Some(format!("Set {descriptor_label} to {value}")), recovered_cargo_slot: None, recovered_cargo_class: None, recovered_cargo_label: cargo_label, recovered_locomotive_id: None, locomotive_name: None, notes: vec![ "descriptor recovered from checked-in EventEffects semantic catalog".to_string(), ], } } fn real_territory_access_cost_row( value: i32, ) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary { crate::SmpLoadedPackedEventGroupedEffectRowSummary { group_index: 0, row_index: 0, descriptor_id: 453, descriptor_label: Some("Territory Access Cost".to_string()), target_mask_bits: Some(0x08), parameter_family: Some("territory_access_cost_scalar".to_string()), grouped_target_subject: None, grouped_target_scope: None, opcode: 3, raw_scalar_value: value, value_byte_0x09: 0, value_dword_0x0d: 0, value_byte_0x11: 0, value_byte_0x12: 0, value_word_0x14: 0, value_word_0x16: 0, row_shape: "scalar_assignment".to_string(), semantic_family: Some("scalar_assignment".to_string()), semantic_preview: Some(format!("Set Territory Access Cost to {value}")), recovered_cargo_slot: None, recovered_cargo_class: None, recovered_cargo_label: None, recovered_locomotive_id: None, locomotive_name: None, notes: vec![], } } fn real_world_flag_row( descriptor_id: u32, label: &str, enabled: bool, ) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary { crate::SmpLoadedPackedEventGroupedEffectRowSummary { group_index: 0, row_index: 0, descriptor_id, descriptor_label: Some(label.to_string()), target_mask_bits: Some(0x08), parameter_family: Some("world_flag_toggle".to_string()), grouped_target_subject: None, grouped_target_scope: None, opcode: 0, raw_scalar_value: if enabled { 1 } else { 0 }, value_byte_0x09: 0, value_dword_0x0d: 0, value_byte_0x11: 0, value_byte_0x12: 0, value_word_0x14: 0, value_word_0x16: 0, row_shape: "bool_toggle".to_string(), semantic_family: Some("bool_toggle".to_string()), semantic_preview: Some(format!( "Set {label} to {}", if enabled { "TRUE" } else { "FALSE" } )), recovered_cargo_slot: None, recovered_cargo_class: None, recovered_cargo_label: None, recovered_locomotive_id: None, locomotive_name: None, notes: vec![], } } fn real_confiscate_all_row( enabled: bool, ) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary { crate::SmpLoadedPackedEventGroupedEffectRowSummary { group_index: 0, row_index: 0, descriptor_id: 9, descriptor_label: Some("Confiscate All".to_string()), target_mask_bits: Some(0x01), parameter_family: Some("company_confiscation_variant".to_string()), grouped_target_subject: None, grouped_target_scope: None, opcode: 1, raw_scalar_value: if enabled { 1 } else { 0 }, value_byte_0x09: 0, value_dword_0x0d: 0, value_byte_0x11: 0, value_byte_0x12: 0, value_word_0x14: 0, value_word_0x16: 0, row_shape: "bool_toggle".to_string(), semantic_family: Some("bool_toggle".to_string()), semantic_preview: Some(format!( "Set Confiscate All to {}", if enabled { "TRUE" } else { "FALSE" } )), recovered_cargo_slot: None, recovered_cargo_class: None, recovered_cargo_label: None, recovered_locomotive_id: None, locomotive_name: None, notes: vec![], } } fn real_retire_train_row( enabled: bool, locomotive_name: Option<&str>, notes: Vec, ) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary { crate::SmpLoadedPackedEventGroupedEffectRowSummary { group_index: 0, row_index: 0, descriptor_id: 15, descriptor_label: Some("Retire Train".to_string()), target_mask_bits: Some(0x0d), parameter_family: Some("company_or_territory_asset_toggle".to_string()), grouped_target_subject: None, grouped_target_scope: None, opcode: 1, raw_scalar_value: if enabled { 1 } else { 0 }, value_byte_0x09: 0, value_dword_0x0d: 0, value_byte_0x11: 0, value_byte_0x12: 0, value_word_0x14: 0, value_word_0x16: 0, row_shape: "bool_toggle".to_string(), semantic_family: Some("bool_toggle".to_string()), semantic_preview: Some(format!( "Set Retire Train to {}", if enabled { "TRUE" } else { "FALSE" } )), recovered_cargo_slot: None, recovered_cargo_class: None, recovered_cargo_label: None, recovered_locomotive_id: None, locomotive_name: locomotive_name.map(ToString::to_string), notes, } } fn unsupported_real_grouped_row() -> crate::SmpLoadedPackedEventGroupedEffectRowSummary { crate::SmpLoadedPackedEventGroupedEffectRowSummary { group_index: 1, row_index: 0, descriptor_id: 9, descriptor_label: Some("Confiscate All".to_string()), target_mask_bits: Some(0x01), parameter_family: Some("company_confiscation_variant".to_string()), grouped_target_subject: None, grouped_target_scope: None, opcode: 1, raw_scalar_value: 0, value_byte_0x09: 0, value_dword_0x0d: 0, value_byte_0x11: 0, value_byte_0x12: 0, value_word_0x14: 0, value_word_0x16: 0, row_shape: "bool_toggle".to_string(), semantic_family: Some("bool_toggle".to_string()), semantic_preview: Some("Set Confiscate All to FALSE".to_string()), recovered_cargo_slot: None, recovered_cargo_class: None, recovered_cargo_label: None, recovered_locomotive_id: None, locomotive_name: None, notes: vec![], } } fn real_compact_control() -> crate::SmpLoadedPackedEventCompactControlSummary { crate::SmpLoadedPackedEventCompactControlSummary { mode_byte_0x7ef: 6, primary_selector_0x7f0: 0x63, grouped_mode_0x7f4: 2, one_shot_header_0x7f5: 1, modifier_flag_0x7f9: 1, modifier_flag_0x7fa: 0, grouped_target_scope_ordinals_0x7fb: vec![0, 1, 2, 3], grouped_scope_checkboxes_0x7ff: vec![1, 0, 1, 0], summary_toggle_0x800: 1, grouped_territory_selectors_0x80f: vec![-1, 10, -1, 22], } } fn real_compact_control_without_symbolic_company_scope() -> crate::SmpLoadedPackedEventCompactControlSummary { crate::SmpLoadedPackedEventCompactControlSummary { mode_byte_0x7ef: 6, primary_selector_0x7f0: 0x63, grouped_mode_0x7f4: 2, one_shot_header_0x7f5: 1, modifier_flag_0x7f9: 1, modifier_flag_0x7fa: 0, grouped_target_scope_ordinals_0x7fb: vec![8, 9, 10, 11], grouped_scope_checkboxes_0x7ff: vec![1, 0, 1, 0], summary_toggle_0x800: 1, grouped_territory_selectors_0x80f: vec![-1, 10, -1, 22], } } #[test] fn loads_dump_document() { let text = serde_json::to_string(&RuntimeStateDumpDocument { format_version: STATE_DUMP_FORMAT_VERSION, dump_id: "dump-smoke".to_string(), source: RuntimeStateDumpSource { description: Some("test dump".to_string()), source_binary: None, }, state: state(), }) .expect("dump should serialize"); let import = load_runtime_state_import_from_str(&text, "fallback").expect("dump should load"); assert_eq!(import.import_id, "dump-smoke"); assert_eq!(import.description.as_deref(), Some("test dump")); } #[test] fn loads_bare_runtime_state() { let text = serde_json::to_string(&state()).expect("state should serialize"); let import = load_runtime_state_import_from_str(&text, "fallback").expect("state should load"); assert_eq!(import.import_id, "fallback"); assert!(import.description.is_none()); } #[test] fn validates_and_roundtrips_save_slice_document() { let document = RuntimeSaveSliceDocument { format_version: SAVE_SLICE_DOCUMENT_FORMAT_VERSION, save_slice_id: "save-slice-smoke".to_string(), source: RuntimeSaveSliceDocumentSource { description: Some("test save slice".to_string()), original_save_filename: Some("smoke.gms".to_string()), original_save_sha256: Some("deadbeef".to_string()), notes: vec!["captured fixture".to_string()], }, save_slice: crate::SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: None, notes: vec![], }, }; assert!(validate_runtime_save_slice_document(&document).is_ok()); let nonce = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .expect("system time should be after epoch") .as_nanos(); let path = std::env::temp_dir().join(format!("rrt-save-slice-doc-{nonce}.json")); save_runtime_save_slice_document(&path, &document).expect("save slice doc should save"); let loaded = load_runtime_save_slice_document(&path).expect("save slice doc should load"); assert_eq!(document, loaded); let _ = std::fs::remove_file(path); } #[test] fn loads_save_slice_document_as_runtime_state_import() { let text = serde_json::to_string(&RuntimeSaveSliceDocument { format_version: SAVE_SLICE_DOCUMENT_FORMAT_VERSION, save_slice_id: "save-slice-import".to_string(), source: RuntimeSaveSliceDocumentSource { description: Some("test save slice import".to_string()), original_save_filename: Some("import.gms".to_string()), original_save_sha256: None, notes: vec![], }, save_slice: crate::SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: None, notes: vec![], }, }) .expect("save slice doc should serialize"); let import = load_runtime_state_import_from_str(&text, "fallback") .expect("save slice document should load as runtime import"); assert_eq!(import.import_id, "save-slice-import"); assert_eq!( import .state .metadata .get("save_slice.import_projection") .map(String::as_str), Some("partial-runtime-restore-v1") ); } #[test] fn projects_save_slice_into_runtime_state_import() { let save_slice = SmpLoadedSaveSlice { 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(), trailer_family: Some("rt3-105-save-trailer-v1".to_string()), bridge_family: Some("rt3-105-save-post-span-bridge-v1".to_string()), profile: Some(crate::SmpLoadedProfile { profile_kind: "rt3-105-packed-profile".to_string(), profile_family: "rt3-105-save-container-v1".to_string(), packed_profile_offset: 0x73c0, packed_profile_len: 0x108, packed_profile_len_hex: "0x108".to_string(), leading_word_0: 3, leading_word_0_hex: "0x00000003".to_string(), header_flag_word_3: Some(0x01000000), header_flag_word_3_hex: Some("0x01000000".to_string()), map_path: Some("Alternate USA.gmp".to_string()), display_name: Some("Alternate USA".to_string()), profile_byte_0x77: 0x07, profile_byte_0x77_hex: "0x07".to_string(), profile_byte_0x82: 0x4d, profile_byte_0x82_hex: "0x4d".to_string(), profile_byte_0x97: 0x00, profile_byte_0x97_hex: "0x00".to_string(), profile_byte_0xc5: 0x00, profile_byte_0xc5_hex: "0x00".to_string(), }), candidate_availability_table: Some(crate::SmpLoadedCandidateAvailabilityTable { source_kind: "save-bridge-secondary-block".to_string(), semantic_family: "scenario-named-candidate-availability-table".to_string(), header_offset: 0x6a70, entries_offset: 0x6ad1, entries_end_offset: 0x73b7, observed_entry_count: 2, zero_availability_count: 1, zero_availability_names: vec!["Uranium Mine".to_string()], footer_progress_hex_words: vec!["0x000032dc".to_string(), "0x00003714".to_string()], entries: vec![ crate::SmpRt3105SaveNameTableEntry { index: 0, offset: 0x6ad1, text: "AutoPlant".to_string(), availability_dword: 1, availability_dword_hex: "0x00000001".to_string(), trailer_word: 1, trailer_word_hex: "0x00000001".to_string(), }, crate::SmpRt3105SaveNameTableEntry { index: 1, offset: 0x6af3, text: "Uranium Mine".to_string(), availability_dword: 0, availability_dword_hex: "0x00000000".to_string(), trailer_word: 0, trailer_word_hex: "0x00000000".to_string(), }, ], }), named_locomotive_availability_table: Some( crate::SmpLoadedNamedLocomotiveAvailabilityTable { source_kind: "runtime-save-direct-serializer".to_string(), semantic_family: "scenario-named-locomotive-availability-table".to_string(), header_offset: None, entries_offset: None, entries_end_offset: None, observed_entry_count: 2, zero_availability_count: 1, zero_availability_names: vec!["Big Boy".to_string()], entries: vec![ crate::SmpRt3105SaveNameTableEntry { index: 0, offset: 0, text: "Big Boy".to_string(), availability_dword: 0, availability_dword_hex: "0x00000000".to_string(), trailer_word: 0, trailer_word_hex: "0x00000000".to_string(), }, crate::SmpRt3105SaveNameTableEntry { index: 1, offset: 0x41, text: "GP7".to_string(), availability_dword: 1, availability_dword_hex: "0x00000001".to_string(), trailer_word: 1, trailer_word_hex: "0x00000001".to_string(), }, ], }, ), locomotive_catalog: None, cargo_catalog: Some(save_cargo_catalog(&[ (1, crate::RuntimeCargoClass::Factory), (5, crate::RuntimeCargoClass::FarmMine), (9, crate::RuntimeCargoClass::Other), ])), world_issue_37_state: Some(crate::SmpLoadedWorldIssue37State { source_kind: "save-fixed-world-block".to_string(), semantic_family: "world-issue-0x37".to_string(), issue_value: 3, issue_value_hex: "0x00000003".to_string(), issue_38_value: 1, issue_38_value_hex: "0x01".to_string(), issue_39_value: 2, issue_39_value_hex: "0x02".to_string(), issue_3a_value: 4, issue_3a_value_hex: "0x04".to_string(), multiplier_raw_u32: 0x3d75c28f, multiplier_raw_hex: "0x3d75c28f".to_string(), multiplier_value_f32_text: "0.060000".to_string(), issue_opinion_base_terms_raw_i32: Vec::new(), }), world_economic_tuning_state: Some(crate::SmpLoadedWorldEconomicTuningState { source_kind: "save-fixed-world-block".to_string(), semantic_family: "world-economic-tuning".to_string(), mirror_raw_u32: 0x3f46d093, mirror_raw_hex: "0x3f46d093".to_string(), mirror_value_f32_text: "0.776620".to_string(), lane_raw_u32: vec![0x3f400000, 0x3be56042], lane_raw_hex: vec!["0x3f400000".to_string(), "0x3be56042".to_string()], lane_value_f32_text: vec!["0.750000".to_string(), "0.007000".to_string()], }), world_finance_neighborhood_state: Some(crate::SmpLoadedWorldFinanceNeighborhoodState { source_kind: "save-fixed-world-block".to_string(), semantic_family: "world-finance-neighborhood".to_string(), packed_year_word_raw_u16: 0x0201, packed_year_word_raw_hex: "0x0201".to_string(), partial_year_progress_raw_u8: 3, partial_year_progress_raw_hex: "0x03".to_string(), current_calendar_tuple_word_raw_u32: 1, current_calendar_tuple_word_raw_hex: "0x00000001".to_string(), current_calendar_tuple_word_2_raw_u32: 2, current_calendar_tuple_word_2_raw_hex: "0x00000002".to_string(), absolute_counter_raw_u32: 3, absolute_counter_raw_hex: "0x00000003".to_string(), absolute_counter_mirror_raw_u32: 4, absolute_counter_mirror_raw_hex: "0x00000004".to_string(), stock_policy_raw_u8: 0, stock_policy_raw_hex: "0x00".to_string(), bond_policy_raw_u8: 1, bond_policy_raw_hex: "0x01".to_string(), bankruptcy_policy_raw_u8: 0, bankruptcy_policy_raw_hex: "0x00".to_string(), dividend_policy_raw_u8: 1, dividend_policy_raw_hex: "0x01".to_string(), building_density_growth_setting_raw_u32: 1, building_density_growth_setting_raw_hex: "0x00000001".to_string(), labels: vec![ "current_calendar_tuple_word".to_string(), "current_calendar_tuple_word_2".to_string(), "absolute_calendar_counter".to_string(), "absolute_calendar_counter_mirror".to_string(), ], relative_offsets: vec![0x0d, 0x11, 0x15, 0x19], relative_offset_hex: vec![ "0xd".to_string(), "0x11".to_string(), "0x15".to_string(), "0x19".to_string(), ], raw_u32: vec![1, 2, 3, 4], raw_hex: vec![ "0x00000001".to_string(), "0x00000002".to_string(), "0x00000003".to_string(), "0x00000004".to_string(), ], value_i32: vec![1, 2, 3, 4], value_f32_text: vec![ "0.000000".to_string(), "0.000000".to_string(), "0.000000".to_string(), "0.000000".to_string(), ], }), company_roster: None, chairman_profile_table: None, special_conditions_table: Some(crate::SmpLoadedSpecialConditionsTable { source_kind: "save-fixed-special-conditions-range".to_string(), table_offset: 0x0d64, table_len: 36 * 4, enabled_visible_count: 0, enabled_visible_labels: vec![], entries: vec![ crate::SmpSpecialConditionEntry { slot_index: 30, hidden: false, label_id: 3722, help_id: 3723, label: "Disable Cargo Economy".to_string(), value: 0, value_hex: "0x00000000".to_string(), }, crate::SmpSpecialConditionEntry { slot_index: 35, hidden: true, label_id: 3, help_id: 3, label: "Hidden sentinel".to_string(), value: 1, value_hex: "0x00000001".to_string(), }, ], }), event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "rt3-105-save-post-span-bridge-v1".to_string(), mechanism_confidence: "mixed".to_string(), container_profile_family: Some("rt3-105-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 5, live_record_count: 3, live_entry_ids: vec![1, 3, 5], decoded_record_count: 0, imported_runtime_record_count: 0, records: vec![ crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 1, payload_offset: None, payload_len: None, decode_status: "unsupported_framing".to_string(), payload_family: "unsupported_framing".to_string(), trigger_kind: None, active: None, marks_collection_dirty: None, one_shot: None, compact_control: None, text_bands: Vec::new(), standalone_condition_row_count: 0, standalone_condition_rows: Vec::new(), negative_sentinel_scope: None, grouped_effect_row_counts: vec![0, 0, 0, 0], grouped_effect_rows: Vec::new(), decoded_conditions: Vec::new(), decoded_actions: Vec::new(), executable_import_ready: false, notes: vec!["test".to_string()], }, crate::SmpLoadedPackedEventRecordSummary { record_index: 1, live_entry_id: 3, payload_offset: None, payload_len: None, decode_status: "unsupported_framing".to_string(), payload_family: "unsupported_framing".to_string(), trigger_kind: None, active: None, marks_collection_dirty: None, one_shot: None, compact_control: None, text_bands: Vec::new(), standalone_condition_row_count: 0, standalone_condition_rows: Vec::new(), negative_sentinel_scope: None, grouped_effect_row_counts: vec![0, 0, 0, 0], grouped_effect_rows: Vec::new(), decoded_conditions: Vec::new(), decoded_actions: Vec::new(), executable_import_ready: false, notes: vec!["test".to_string()], }, crate::SmpLoadedPackedEventRecordSummary { record_index: 2, live_entry_id: 5, payload_offset: None, payload_len: None, decode_status: "unsupported_framing".to_string(), payload_family: "unsupported_framing".to_string(), trigger_kind: None, active: None, marks_collection_dirty: None, one_shot: None, compact_control: None, text_bands: Vec::new(), standalone_condition_row_count: 0, standalone_condition_rows: Vec::new(), negative_sentinel_scope: None, grouped_effect_row_counts: vec![0, 0, 0, 0], grouped_effect_rows: Vec::new(), decoded_conditions: Vec::new(), decoded_actions: Vec::new(), executable_import_ready: false, notes: vec!["test".to_string()], }, ], }), notes: vec!["packed profile recovered".to_string()], }; let import = project_save_slice_to_runtime_state_import( &save_slice, "save-import-smoke", Some("test save import".to_string()), ) .expect("save slice should project"); assert_eq!(import.import_id, "save-import-smoke"); assert_eq!( import .state .metadata .get("save_slice.map_path") .map(String::as_str), Some("Alternate USA.gmp") ); assert_eq!( import.state.save_profile.selected_year_profile_lane, Some(0x07) ); assert_eq!(import.state.save_profile.sandbox_enabled, Some(true)); assert_eq!( import.state.world_restore.selected_year_profile_lane, Some(0x07) ); assert_eq!(import.state.world_restore.sandbox_enabled, Some(true)); assert_eq!( import.state.world_restore.campaign_scenario_enabled, Some(false) ); assert_eq!( import.state.world_restore.seed_tuple_written_from_raw_lane, Some(true) ); assert_eq!( import .state .world_restore .absolute_counter_requires_shell_context, Some(false) ); assert_eq!( import .state .world_restore .absolute_counter_reconstructible_from_save, Some(true) ); assert_eq!( import.state.world_restore.packed_year_word_raw_u16, Some(0x0201) ); assert_eq!( import.state.world_restore.partial_year_progress_raw_u8, Some(3) ); assert_eq!( import .state .world_restore .current_calendar_tuple_word_raw_u32, Some(1) ); assert_eq!( import .state .world_restore .current_calendar_tuple_word_2_raw_u32, Some(2) ); assert_eq!(import.state.world_restore.absolute_counter_raw_u32, Some(3)); assert_eq!( import.state.world_restore.absolute_counter_mirror_raw_u32, Some(4) ); assert_eq!( import .state .world_restore .disable_cargo_economy_special_condition_slot, Some(30) ); assert_eq!( import .state .world_restore .disable_cargo_economy_special_condition_reconstructible_from_save, Some(true) ); assert_eq!( import .state .world_restore .disable_cargo_economy_special_condition_write_side_grounded, Some(true) ); assert_eq!( import .state .world_restore .disable_cargo_economy_special_condition_enabled, Some(false) ); assert_eq!( import.state.world_restore.use_bio_accelerator_cars_enabled, Some(false) ); assert_eq!( import.state.world_restore.use_wartime_cargos_enabled, Some(false) ); assert_eq!( import.state.world_restore.disable_train_crashes_enabled, Some(false) ); assert_eq!( import .state .world_restore .disable_train_crashes_and_breakdowns_enabled, Some(false) ); assert_eq!( import .state .world_restore .ai_ignore_territories_at_startup_enabled, Some(false) ); assert_eq!(import.state.world_restore.issue_37_value, Some(3)); assert_eq!(import.state.world_restore.issue_38_value, Some(1)); assert_eq!(import.state.world_restore.issue_39_value, Some(2)); assert_eq!(import.state.world_restore.issue_3a_value, Some(4)); assert_eq!( import.state.world_restore.issue_37_multiplier_raw_u32, Some(0x3d75c28f) ); assert_eq!( import .state .world_restore .stock_issue_and_buyback_policy_raw_u8, Some(0) ); assert_eq!( import .state .world_restore .bond_issue_and_repayment_policy_raw_u8, Some(1) ); assert_eq!(import.state.world_restore.bankruptcy_policy_raw_u8, Some(0)); assert_eq!(import.state.world_restore.dividend_policy_raw_u8, Some(1)); assert_eq!( import.state.world_restore.stock_issue_and_buyback_allowed, Some(true) ); assert_eq!( import.state.world_restore.bond_issue_and_repayment_allowed, Some(false) ); assert_eq!(import.state.world_restore.bankruptcy_allowed, Some(true)); assert_eq!( import.state.world_restore.dividend_adjustment_allowed, Some(false) ); assert_eq!( import .state .world_restore .issue_37_multiplier_value_f32_text .as_deref(), Some("0.060000") ); assert_eq!( import.state.world_restore.economic_tuning_mirror_raw_u32, Some(0x3f46d093) ); assert_eq!( import .state .world_restore .economic_tuning_mirror_value_f32_text .as_deref(), Some("0.776620") ); assert_eq!( import.state.world_restore.economic_tuning_lane_raw_u32, vec![0x3f400000, 0x3be56042] ); assert_eq!( import .state .world_restore .economic_tuning_lane_value_f32_text, vec!["0.750000".to_string(), "0.007000".to_string()] ); assert_eq!( import .state .world_restore .absolute_counter_restore_kind .as_deref(), Some("save-direct-world-absolute-counter") ); assert_eq!( import .state .world_restore .absolute_counter_adjustment_context .as_deref(), Some("save-direct-world-block-0x32c8") ); assert_eq!( import.state.save_profile.map_path.as_deref(), Some("Alternate USA.gmp") ); assert_eq!( import.state.candidate_availability.get("Uranium Mine"), Some(&0) ); assert_eq!( import.state.named_locomotive_availability.get("Big Boy"), Some(&0) ); assert_eq!( import.state.named_locomotive_availability.get("GP7"), Some(&1) ); assert_eq!(import.state.locomotive_catalog.len(), 2); assert_eq!(import.state.locomotive_catalog[0].locomotive_id, 1); assert_eq!(import.state.locomotive_catalog[0].name, "Big Boy"); assert_eq!(import.state.locomotive_catalog[1].locomotive_id, 2); assert_eq!(import.state.locomotive_catalog[1].name, "GP7"); assert_eq!( import.state.special_conditions.get("Disable Cargo Economy"), Some(&0) ); assert_eq!( import .state .metadata .get("save_slice.world_issue_37_value") .map(String::as_str), Some("3") ); assert_eq!( import .state .metadata .get("save_slice.world_issue_39_value") .map(String::as_str), Some("2") ); assert_eq!( import .state .metadata .get("save_slice.world_economic_tuning_lane_count") .map(String::as_str), Some("2") ); assert_eq!( import .state .world_flags .get("save_slice.world_issue_37_state_present"), Some(&true) ); assert_eq!( import .state .world_flags .get("save_slice.world_economic_tuning_state_present"), Some(&true) ); assert_eq!( import .state .metadata .get("save_slice.named_locomotive_availability_source_kind") .map(String::as_str), Some("runtime-save-direct-serializer") ); assert_eq!( import .state .metadata .get("save_slice.named_locomotive_availability_entry_count") .map(String::as_str), Some("2") ); assert_eq!( import .state .metadata .get("save_slice.locomotive_catalog_source_kind") .map(String::as_str), Some("derived-from-named-locomotive-availability-table") ); assert_eq!( import .state .world_flags .get("save_slice.profile_byte_0x82_nonzero"), Some(&true) ); assert_eq!( import .state .packed_event_collection .as_ref() .map(|summary| summary.live_record_count), Some(3) ); assert_eq!( import .state .packed_event_collection .as_ref() .map(|summary| summary.live_entry_ids.clone()), Some(vec![1, 3, 5]) ); assert!(import.state.event_runtime_records.is_empty()); } #[test] fn projects_company_and_chairman_context_from_save_slice() { let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: Some(save_company_roster()), chairman_profile_table: Some(save_chairman_profile_table()), special_conditions_table: None, event_runtime_collection: None, notes: vec![], }; let import = project_save_slice_to_runtime_state_import( &save_slice, "save-native-company-chairman", None, ) .expect("save slice should project"); assert_eq!(import.state.companies.len(), 2); assert_eq!(import.state.selected_company_id, Some(1)); assert_eq!(import.state.chairman_profiles.len(), 2); assert_eq!(import.state.selected_chairman_profile_id, Some(1)); assert_eq!(import.state.companies[0].book_value_per_share, 2620); assert_eq!(import.state.chairman_profiles[0].current_cash, 500); assert_eq!(import.state.service_state.company_market_state.len(), 2); assert_eq!( import .state .service_state .company_market_state .get(&1) .map(|state| state.cached_share_price_raw_u32), Some(0x42200000) ); } #[test] fn overlay_replaces_base_company_and_chairman_context_from_save_slice() { let base_state = RuntimeState { companies: vec![crate::RuntimeCompany { company_id: 42, current_cash: 5, debt: 1, credit_rating_score: None, prime_rate: None, active: true, available_track_laying_capacity: Some(1), controller_kind: RuntimeCompanyControllerKind::Unknown, linked_chairman_profile_id: None, book_value_per_share: 10, investor_confidence: 0, management_attitude: 0, takeover_cooldown_year: None, merger_cooldown_year: None, track_piece_counts: RuntimeTrackPieceCounts::default(), }], selected_company_id: Some(42), chairman_profiles: vec![crate::RuntimeChairmanProfile { profile_id: 9, name: "Base Chairman".to_string(), active: true, current_cash: 10, linked_company_id: None, company_holdings: BTreeMap::new(), holdings_value_total: 0, net_worth_total: 0, purchasing_power_total: 0, }], selected_chairman_profile_id: Some(9), territories: vec![crate::RuntimeTerritory { territory_id: 7, name: Some("Base Territory".to_string()), track_piece_counts: RuntimeTrackPieceCounts::default(), }], ..state() }; let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: Some(save_company_roster()), chairman_profile_table: Some(save_chairman_profile_table()), special_conditions_table: None, event_runtime_collection: None, notes: vec![], }; let import = project_save_slice_overlay_to_runtime_state_import( &base_state, &save_slice, "overlay-save-native-company-chairman", None, ) .expect("overlay import should project"); assert_eq!(import.state.companies.len(), 2); assert_eq!(import.state.selected_company_id, Some(1)); assert_eq!(import.state.chairman_profiles.len(), 2); assert_eq!(import.state.selected_chairman_profile_id, Some(1)); assert_eq!(import.state.territories, base_state.territories); assert_eq!( import .state .service_state .company_market_state .get(&2) .map(|state| state.linked_transit_latch), Some(true) ); } #[test] fn overlay_applies_selection_only_company_and_chairman_context_from_save_slice() { let base_state = RuntimeState { companies: vec![ crate::RuntimeCompany { company_id: 1, current_cash: 100, debt: 0, credit_rating_score: None, prime_rate: None, active: true, available_track_laying_capacity: None, controller_kind: RuntimeCompanyControllerKind::Human, linked_chairman_profile_id: Some(1), book_value_per_share: 0, investor_confidence: 0, management_attitude: 0, takeover_cooldown_year: None, merger_cooldown_year: None, track_piece_counts: RuntimeTrackPieceCounts::default(), }, crate::RuntimeCompany { company_id: 42, current_cash: 200, debt: 0, credit_rating_score: None, prime_rate: None, active: true, available_track_laying_capacity: None, controller_kind: RuntimeCompanyControllerKind::Human, linked_chairman_profile_id: Some(9), book_value_per_share: 0, investor_confidence: 0, management_attitude: 0, takeover_cooldown_year: None, merger_cooldown_year: None, track_piece_counts: RuntimeTrackPieceCounts::default(), }, ], selected_company_id: Some(42), chairman_profiles: vec![ crate::RuntimeChairmanProfile { profile_id: 1, name: "Selected".to_string(), active: true, current_cash: 0, linked_company_id: Some(1), company_holdings: BTreeMap::new(), holdings_value_total: 0, net_worth_total: 0, purchasing_power_total: 0, }, crate::RuntimeChairmanProfile { profile_id: 9, name: "Base".to_string(), active: true, current_cash: 0, linked_company_id: Some(42), company_holdings: BTreeMap::new(), holdings_value_total: 0, net_worth_total: 0, purchasing_power_total: 0, }, ], selected_chairman_profile_id: Some(9), service_state: RuntimeServiceState { company_market_state: BTreeMap::from([( 42, crate::RuntimeCompanyMarketState { outstanding_shares: 30_000, bond_count: 3, live_bond_slots: Vec::new(), largest_live_bond_principal: Some(750_000), highest_coupon_live_bond_principal: Some(500_000), mutable_support_scalar_raw_u32: 0x3f19999a, young_company_support_scalar_raw_u32: 0x42580000, support_progress_word: 8, recent_per_share_cache_absolute_counter: 0, recent_per_share_cached_value_bits: 0, recent_per_share_subscore_raw_u32: 0x42000000, cached_share_price_raw_u32: 0x42180000, chairman_salary_baseline: 21, chairman_salary_current: 24, chairman_bonus_year: 1836, chairman_bonus_amount: 600, founding_year: 1834, last_bankruptcy_year: 0, last_dividend_year: 1838, current_issue_calendar_word: 4, current_issue_calendar_word_2: 5, prior_issue_calendar_word: 3, prior_issue_calendar_word_2: 4, city_connection_latch: false, linked_transit_latch: true, stat_band_root_0cfb_candidates: Vec::new(), stat_band_root_0d7f_candidates: Vec::new(), stat_band_root_1c47_candidates: Vec::new(), year_stat_family_qword_bits: Vec::new(), special_stat_family_232a_qword_bits: Vec::new(), issue_opinion_terms_raw_i32: Vec::new(), direct_control_transfer_float_fields_raw_u32: BTreeMap::new(), direct_control_transfer_int_fields_raw_u32: BTreeMap::new(), }, )]), world_issue_opinion_base_terms_raw_i32: Vec::new(), chairman_issue_opinion_terms_raw_i32: BTreeMap::new(), ..RuntimeServiceState::default() }, ..state() }; let save_slice = SmpLoadedSaveSlice { 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(), trailer_family: Some("rt3-105-save-trailer-v1".to_string()), bridge_family: Some("rt3-105-save-post-span-bridge-v1".to_string()), profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: Some(crate::SmpLoadedCompanyRoster { source_kind: "save-direct-world-block-company-selection-only".to_string(), semantic_family: "scenario-selected-company-context".to_string(), observed_entry_count: 0, selected_company_id: Some(1), entries: Vec::new(), }), chairman_profile_table: Some(crate::SmpLoadedChairmanProfileTable { source_kind: "save-direct-world-block-chairman-selection-only".to_string(), semantic_family: "scenario-selected-chairman-context".to_string(), observed_entry_count: 0, selected_chairman_profile_id: Some(1), entries: Vec::new(), }), special_conditions_table: None, event_runtime_collection: None, notes: vec![], }; let import = project_save_slice_overlay_to_runtime_state_import( &base_state, &save_slice, "overlay-save-selection-only-context", None, ) .expect("overlay import should project"); let mut expected_companies = base_state.companies.clone(); expected_companies[1].investor_confidence = 38; assert_eq!(import.state.companies, expected_companies); assert_eq!(import.state.selected_company_id, Some(1)); assert_eq!(import.state.chairman_profiles, base_state.chairman_profiles); assert_eq!(import.state.selected_chairman_profile_id, Some(1)); assert_eq!( import.state.service_state.company_market_state, base_state.service_state.company_market_state ); } #[test] fn projects_executable_packed_records_into_runtime_and_services_follow_on() { let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: Some(save_cargo_catalog(&[ (1, crate::RuntimeCargoClass::Factory), (5, crate::RuntimeCargoClass::FarmMine), (9, crate::RuntimeCargoClass::Other), ])), world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 7, live_record_count: 1, live_entry_ids: vec![7], decoded_record_count: 1, imported_runtime_record_count: 1, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 7, payload_offset: Some(0x7202), payload_len: Some(64), decode_status: "executable".to_string(), payload_family: "synthetic_harness".to_string(), trigger_kind: Some(7), active: Some(true), marks_collection_dirty: Some(true), one_shot: Some(false), compact_control: None, text_bands: packed_text_bands(), standalone_condition_row_count: 1, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![0, 1, 0, 0], grouped_effect_rows: vec![], decoded_conditions: Vec::new(), decoded_actions: vec![ RuntimeEffect::SetWorldFlag { key: "from_packed_root".to_string(), value: true, }, RuntimeEffect::AppendEventRecord { record: Box::new(RuntimeEventRecordTemplate { record_id: 99, trigger_kind: 0x0a, active: true, marks_collection_dirty: false, one_shot: false, conditions: Vec::new(), effects: vec![RuntimeEffect::SetSpecialCondition { label: "Imported Follow-On".to_string(), value: 1, }], }), }, ], executable_import_ready: true, notes: vec!["decoded test record".to_string()], }], }), notes: vec![], }; let mut import = project_save_slice_to_runtime_state_import( &save_slice, "packed-events-exec", Some("test packed event import".to_string()), ) .expect("save slice should project"); assert_eq!(import.state.event_runtime_records.len(), 1); assert_eq!( import .state .packed_event_collection .as_ref() .map(|summary| summary.imported_runtime_record_count), Some(1) ); let result = execute_step_command( &mut import.state, &StepCommand::ServiceTriggerKind { trigger_kind: 7 }, ) .expect("trigger service should succeed"); assert_eq!(result.final_summary.event_runtime_record_count, 2); assert_eq!(result.final_summary.total_event_record_service_count, 2); assert_eq!(result.final_summary.total_trigger_dispatch_count, 2); assert_eq!(result.final_summary.dirty_rerun_count, 1); assert_eq!( import.state.world_flags.get("from_packed_root"), Some(&true) ); assert_eq!( import.state.special_conditions.get("Imported Follow-On"), Some(&1) ); assert_eq!(import.state.event_runtime_records[0].service_count, 1); assert_eq!(import.state.event_runtime_records[1].record_id, 99); assert_eq!(import.state.event_runtime_records[1].service_count, 1); } #[test] fn leaves_parity_only_packed_records_out_of_runtime_event_records() { let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: Some(save_cargo_catalog(&[ (1, crate::RuntimeCargoClass::Factory), (5, crate::RuntimeCargoClass::FarmMine), (9, crate::RuntimeCargoClass::Other), ])), world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 7, live_record_count: 1, live_entry_ids: vec![7], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 7, payload_offset: Some(0x7202), payload_len: Some(48), decode_status: "parity_only".to_string(), payload_family: "synthetic_harness".to_string(), trigger_kind: Some(7), active: Some(true), marks_collection_dirty: Some(false), one_shot: Some(false), compact_control: None, text_bands: packed_text_bands(), standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![0, 0, 0, 0], grouped_effect_rows: vec![], decoded_conditions: Vec::new(), decoded_actions: vec![RuntimeEffect::AdjustCompanyCash { target: crate::RuntimeCompanyTarget::Ids { ids: vec![42] }, delta: 50, }], executable_import_ready: false, notes: vec!["decoded but not importable".to_string()], }], }), notes: vec![], }; let import = project_save_slice_to_runtime_state_import( &save_slice, "packed-events-parity-only", None, ) .expect("save slice should project"); assert!(import.state.event_runtime_records.is_empty()); assert_eq!( import .state .packed_event_collection .as_ref() .map(|summary| summary.decoded_record_count), Some(1) ); assert_eq!( import .state .packed_event_collection .as_ref() .map(|summary| summary.imported_runtime_record_count), Some(0) ); assert_eq!( import .state .packed_event_collection .as_ref() .and_then(|summary| summary.records[0].import_outcome.as_deref()), Some("blocked_missing_company_context") ); } #[test] fn classifies_symbolic_company_target_blockers_for_standalone_import() { let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: Some(save_cargo_catalog(&[ (1, crate::RuntimeCargoClass::Factory), (5, crate::RuntimeCargoClass::FarmMine), (9, crate::RuntimeCargoClass::Other), ])), world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 12, live_record_count: 3, live_entry_ids: vec![10, 11, 12], decoded_record_count: 3, imported_runtime_record_count: 0, records: vec![ synthetic_packed_record( 0, 10, RuntimeEffect::AdjustCompanyCash { target: RuntimeCompanyTarget::SelectedCompany, delta: 1, }, ), synthetic_packed_record( 1, 11, RuntimeEffect::AdjustCompanyDebt { target: RuntimeCompanyTarget::HumanCompanies, delta: 2, }, ), synthetic_packed_record( 2, 12, RuntimeEffect::AdjustCompanyDebt { target: RuntimeCompanyTarget::ConditionTrueCompany, delta: 3, }, ), ], }), notes: vec![], }; let import = project_save_slice_to_runtime_state_import(&save_slice, "symbolic-blockers", None) .expect("standalone projection should succeed"); assert!(import.state.event_runtime_records.is_empty()); let outcomes = import .state .packed_event_collection .as_ref() .expect("packed event collection should be present") .records .iter() .map(|record| record.import_outcome.clone()) .collect::>(); assert_eq!( outcomes, vec![ Some("blocked_missing_selection_context".to_string()), Some("blocked_missing_company_role_context".to_string()), Some("blocked_missing_condition_context".to_string()), ] ); } #[test] fn overlays_symbolic_company_targets_into_executable_runtime_records() { let base_state = RuntimeState { companies: vec![ crate::RuntimeCompany { company_id: 1, controller_kind: RuntimeCompanyControllerKind::Human, current_cash: 100, debt: 10, credit_rating_score: None, prime_rate: None, track_piece_counts: RuntimeTrackPieceCounts::default(), active: true, available_track_laying_capacity: None, linked_chairman_profile_id: None, book_value_per_share: 0, investor_confidence: 0, management_attitude: 0, takeover_cooldown_year: None, merger_cooldown_year: None, }, crate::RuntimeCompany { company_id: 2, controller_kind: RuntimeCompanyControllerKind::Ai, current_cash: 50, debt: 20, credit_rating_score: None, prime_rate: None, track_piece_counts: RuntimeTrackPieceCounts::default(), active: true, available_track_laying_capacity: None, linked_chairman_profile_id: None, book_value_per_share: 0, investor_confidence: 0, management_attitude: 0, takeover_cooldown_year: None, merger_cooldown_year: None, }, ], selected_company_id: Some(1), ..state() }; let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 22, live_record_count: 2, live_entry_ids: vec![21, 22], decoded_record_count: 2, imported_runtime_record_count: 0, records: vec![ synthetic_packed_record( 0, 21, RuntimeEffect::AdjustCompanyCash { target: RuntimeCompanyTarget::SelectedCompany, delta: 15, }, ), synthetic_packed_record( 1, 22, RuntimeEffect::AdjustCompanyDebt { target: RuntimeCompanyTarget::AiCompanies, delta: 4, }, ), ], }), notes: vec![], }; let mut import = project_save_slice_overlay_to_runtime_state_import( &base_state, &save_slice, "symbolic-overlay", None, ) .expect("overlay projection should succeed"); assert_eq!(import.state.event_runtime_records.len(), 2); let outcomes = import .state .packed_event_collection .as_ref() .expect("packed event collection should be present") .records .iter() .map(|record| record.import_outcome.clone()) .collect::>(); assert_eq!( outcomes, vec![Some("imported".to_string()), Some("imported".to_string())] ); execute_step_command( &mut import.state, &StepCommand::ServiceTriggerKind { trigger_kind: 7 }, ) .expect("symbolic overlay dispatch should succeed"); assert_eq!(import.state.companies[0].current_cash, 115); assert_eq!(import.state.companies[1].debt, 24); } #[test] fn leaves_real_records_without_compact_control_blocked_missing_compact_control() { let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 7, live_record_count: 1, live_entry_ids: vec![7], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 7, payload_offset: Some(0x7202), payload_len: Some(96), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: None, active: None, marks_collection_dirty: None, one_shot: None, compact_control: None, text_bands: packed_text_bands(), standalone_condition_row_count: 1, standalone_condition_rows: real_condition_rows(), negative_sentinel_scope: Some(company_negative_sentinel_scope( RuntimeCompanyConditionTestScope::AllCompanies, )), grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: real_grouped_rows(), decoded_conditions: Vec::new(), decoded_actions: vec![RuntimeEffect::SetCompanyCash { target: RuntimeCompanyTarget::ConditionTrueCompany, value: 7, }], executable_import_ready: true, notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()], }], }), notes: vec![], }; let import = project_save_slice_to_runtime_state_import( &save_slice, "packed-events-structural-only", None, ) .expect("save slice should project"); assert!(import.state.event_runtime_records.is_empty()); assert_eq!( import .state .packed_event_collection .as_ref() .and_then(|summary| summary.records[0].import_outcome.as_deref()), Some("blocked_missing_compact_control") ); assert_eq!( import .state .packed_event_collection .as_ref() .map(|summary| summary.records[0].standalone_condition_rows.len()), Some(1) ); assert_eq!( import .state .packed_event_collection .as_ref() .map(|summary| summary.records[0].grouped_effect_rows.len()), Some(1) ); } #[test] fn lowers_negative_sentinel_company_scopes_into_runtime_company_targets() { let base_state = RuntimeState { companies: vec![ crate::RuntimeCompany { company_id: 1, controller_kind: RuntimeCompanyControllerKind::Human, current_cash: 100, debt: 10, credit_rating_score: None, prime_rate: None, track_piece_counts: RuntimeTrackPieceCounts::default(), active: true, available_track_laying_capacity: None, linked_chairman_profile_id: None, book_value_per_share: 0, investor_confidence: 0, management_attitude: 0, takeover_cooldown_year: None, merger_cooldown_year: None, }, crate::RuntimeCompany { company_id: 2, controller_kind: RuntimeCompanyControllerKind::Ai, current_cash: 50, debt: 20, credit_rating_score: None, prime_rate: None, track_piece_counts: RuntimeTrackPieceCounts::default(), active: true, available_track_laying_capacity: None, linked_chairman_profile_id: None, book_value_per_share: 0, investor_confidence: 0, management_attitude: 0, takeover_cooldown_year: None, merger_cooldown_year: None, }, crate::RuntimeCompany { company_id: 3, controller_kind: RuntimeCompanyControllerKind::Human, current_cash: 70, debt: 30, credit_rating_score: None, prime_rate: None, track_piece_counts: RuntimeTrackPieceCounts::default(), active: true, available_track_laying_capacity: None, linked_chairman_profile_id: None, book_value_per_share: 0, investor_confidence: 0, management_attitude: 0, takeover_cooldown_year: None, merger_cooldown_year: None, }, ], selected_company_id: Some(3), ..state() }; let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 11, live_record_count: 5, live_entry_ids: vec![7, 8, 9, 10, 11], decoded_record_count: 5, imported_runtime_record_count: 0, records: vec![ crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 7, payload_offset: Some(0x7202), payload_len: Some(133), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(6), active: None, marks_collection_dirty: None, one_shot: Some(true), compact_control: Some(real_compact_control()), text_bands: packed_text_bands(), standalone_condition_row_count: 1, standalone_condition_rows: real_condition_rows(), negative_sentinel_scope: Some(company_negative_sentinel_scope( RuntimeCompanyConditionTestScope::AllCompanies, )), grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: real_grouped_rows(), decoded_conditions: Vec::new(), decoded_actions: vec![RuntimeEffect::SetCompanyCash { target: RuntimeCompanyTarget::ConditionTrueCompany, value: 7, }], executable_import_ready: true, notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()], }, crate::SmpLoadedPackedEventRecordSummary { record_index: 1, live_entry_id: 8, payload_offset: Some(0x7282), payload_len: Some(133), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(6), active: None, marks_collection_dirty: None, one_shot: Some(true), compact_control: Some(real_compact_control()), text_bands: packed_text_bands(), standalone_condition_row_count: 1, standalone_condition_rows: real_condition_rows(), negative_sentinel_scope: Some(company_negative_sentinel_scope( RuntimeCompanyConditionTestScope::SelectedCompanyOnly, )), grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: real_grouped_rows(), decoded_conditions: Vec::new(), decoded_actions: vec![RuntimeEffect::SetCompanyCash { target: RuntimeCompanyTarget::ConditionTrueCompany, value: 8, }], executable_import_ready: true, notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()], }, crate::SmpLoadedPackedEventRecordSummary { record_index: 2, live_entry_id: 9, payload_offset: Some(0x7302), payload_len: Some(133), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(6), active: None, marks_collection_dirty: None, one_shot: Some(true), compact_control: Some(real_compact_control()), text_bands: packed_text_bands(), standalone_condition_row_count: 1, standalone_condition_rows: real_condition_rows(), negative_sentinel_scope: Some(company_negative_sentinel_scope( RuntimeCompanyConditionTestScope::AiCompaniesOnly, )), grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: real_grouped_rows(), decoded_conditions: Vec::new(), decoded_actions: vec![RuntimeEffect::SetCompanyCash { target: RuntimeCompanyTarget::ConditionTrueCompany, value: 9, }], executable_import_ready: true, notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()], }, crate::SmpLoadedPackedEventRecordSummary { record_index: 3, live_entry_id: 10, payload_offset: Some(0x7382), payload_len: Some(133), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(6), active: None, marks_collection_dirty: None, one_shot: Some(true), compact_control: Some(real_compact_control()), text_bands: packed_text_bands(), standalone_condition_row_count: 1, standalone_condition_rows: real_condition_rows(), negative_sentinel_scope: Some(company_negative_sentinel_scope( RuntimeCompanyConditionTestScope::HumanCompaniesOnly, )), grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: real_grouped_rows(), decoded_conditions: Vec::new(), decoded_actions: vec![RuntimeEffect::SetCompanyCash { target: RuntimeCompanyTarget::ConditionTrueCompany, value: 10, }], executable_import_ready: true, notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()], }, crate::SmpLoadedPackedEventRecordSummary { record_index: 4, live_entry_id: 11, payload_offset: Some(0x7402), payload_len: Some(133), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(6), active: None, marks_collection_dirty: None, one_shot: Some(true), compact_control: Some(real_compact_control()), text_bands: packed_text_bands(), standalone_condition_row_count: 1, standalone_condition_rows: real_condition_rows(), negative_sentinel_scope: Some(company_negative_sentinel_scope( RuntimeCompanyConditionTestScope::Disabled, )), grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: real_grouped_rows(), decoded_conditions: Vec::new(), decoded_actions: vec![RuntimeEffect::SetCompanyCash { target: RuntimeCompanyTarget::ConditionTrueCompany, value: 11, }], executable_import_ready: true, notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()], }, ], }), notes: vec![], }; let import = project_save_slice_overlay_to_runtime_state_import( &base_state, &save_slice, "packed-events-real-descriptor-frontier", None, ) .expect("save slice should project"); assert_eq!(import.state.event_runtime_records.len(), 4); assert_eq!( import .state .packed_event_collection .as_ref() .and_then(|summary| summary.records[0].compact_control.as_ref()) .map(|control| control.mode_byte_0x7ef), Some(6) ); let effects = import .state .event_runtime_records .iter() .map(|record| record.effects[0].clone()) .collect::>(); assert_eq!( effects, vec![ RuntimeEffect::SetCompanyCash { target: RuntimeCompanyTarget::AllActive, value: 7, }, RuntimeEffect::SetCompanyCash { target: RuntimeCompanyTarget::SelectedCompany, value: 8, }, RuntimeEffect::SetCompanyCash { target: RuntimeCompanyTarget::AiCompanies, value: 9, }, RuntimeEffect::SetCompanyCash { target: RuntimeCompanyTarget::HumanCompanies, value: 10, }, ] ); assert_eq!( import .state .packed_event_collection .as_ref() .map(|summary| { summary .records .iter() .map(|record| record.import_outcome.clone()) .collect::>() }), Some(vec![ Some("imported".to_string()), Some("imported".to_string()), Some("imported".to_string()), Some("imported".to_string()), Some("blocked_company_condition_scope_disabled".to_string()), ]) ); } #[test] fn blocks_player_scoped_effects_without_player_runtime_context() { let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 7, live_record_count: 1, live_entry_ids: vec![7], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 7, payload_offset: Some(0x7202), payload_len: Some(133), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(6), active: None, marks_collection_dirty: None, one_shot: Some(true), compact_control: Some(real_compact_control()), text_bands: packed_text_bands(), standalone_condition_row_count: 1, standalone_condition_rows: real_condition_rows(), negative_sentinel_scope: Some(player_negative_sentinel_scope()), grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: real_grouped_rows(), decoded_conditions: Vec::new(), decoded_actions: vec![RuntimeEffect::SetPlayerCash { target: RuntimePlayerTarget::ConditionTruePlayer, value: 7, }], executable_import_ready: true, notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()], }], }), notes: vec![], }; let import = project_save_slice_to_runtime_state_import( &save_slice, "negative-sentinel-player-scope", None, ) .expect("save slice should project"); assert!(import.state.event_runtime_records.is_empty()); assert_eq!( import .state .packed_event_collection .as_ref() .and_then(|summary| summary.records[0].import_outcome.as_deref()), Some("blocked_missing_player_context") ); } #[test] fn blocks_named_or_aggregate_territory_conditions_without_territory_context() { let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 7, live_record_count: 1, live_entry_ids: vec![7], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 7, payload_offset: Some(0x7202), payload_len: Some(133), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(6), active: None, marks_collection_dirty: None, one_shot: Some(true), compact_control: Some(real_compact_control()), text_bands: packed_text_bands(), standalone_condition_row_count: 1, standalone_condition_rows: vec![ crate::SmpLoadedPackedEventConditionRowSummary { row_index: 0, raw_condition_id: 2313, subtype: 0, flag_bytes: vec![ 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ], candidate_name: None, comparator: Some("ge".to_string()), metric: Some("Territory Track Pieces".to_string()), semantic_family: Some("numeric_threshold".to_string()), semantic_preview: Some("Test Territory Track Pieces >= 10".to_string()), recovered_cargo_slot: None, recovered_cargo_class: None, requires_candidate_name_binding: false, notes: Vec::new(), }, ], negative_sentinel_scope: Some(territory_negative_sentinel_scope()), grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: real_grouped_rows(), decoded_conditions: vec![RuntimeCondition::TerritoryNumericThreshold { target: RuntimeTerritoryTarget::AllTerritories, metric: crate::RuntimeTerritoryMetric::TrackPiecesTotal, comparator: crate::RuntimeConditionComparator::Ge, value: 10, }], decoded_actions: vec![RuntimeEffect::SetCompanyCash { target: RuntimeCompanyTarget::ConditionTrueCompany, value: 7, }], executable_import_ready: true, notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()], }], }), notes: vec![], }; let import = project_save_slice_to_runtime_state_import( &save_slice, "negative-sentinel-territory-scope", None, ) .expect("save slice should project"); assert!(import.state.event_runtime_records.is_empty()); assert_eq!( import .state .packed_event_collection .as_ref() .and_then(|summary| summary.records[0].import_outcome.as_deref()), Some("blocked_missing_territory_context") ); } #[test] fn leaves_real_records_with_unclassified_scope_blocked_unmapped_real_descriptor() { let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 7, live_record_count: 1, live_entry_ids: vec![7], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 7, payload_offset: Some(0x7202), payload_len: Some(133), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(6), active: None, marks_collection_dirty: None, one_shot: Some(true), compact_control: Some(real_compact_control_without_symbolic_company_scope()), text_bands: packed_text_bands(), standalone_condition_row_count: 1, standalone_condition_rows: real_condition_rows(), negative_sentinel_scope: None, grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: real_grouped_rows(), decoded_conditions: Vec::new(), decoded_actions: vec![], executable_import_ready: false, notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()], }], }), notes: vec![], }; let import = project_save_slice_to_runtime_state_import( &save_slice, "packed-events-real-descriptor-frontier", None, ) .expect("save slice should project"); assert!(import.state.event_runtime_records.is_empty()); assert_eq!( import .state .packed_event_collection .as_ref() .and_then(|summary| summary.records[0].import_outcome.as_deref()), Some("blocked_unmapped_real_descriptor") ); } #[test] fn leaves_recovered_shell_owned_descriptor_rows_on_explicit_shell_owned_frontier() { let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: Some(save_company_roster()), chairman_profile_table: Some(save_chairman_profile_table()), special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 8, live_record_count: 1, live_entry_ids: vec![8], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 8, payload_offset: Some(0x7202), payload_len: Some(120), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(true), compact_control: Some(real_compact_control_without_symbolic_company_scope()), text_bands: packed_text_bands(), standalone_condition_row_count: 0, standalone_condition_rows: Vec::new(), negative_sentinel_scope: None, grouped_effect_row_counts: vec![2, 0, 0, 0], grouped_effect_rows: vec![ real_stock_prices_shell_row(120), real_merger_premium_shell_row(25), ], decoded_conditions: Vec::new(), decoded_actions: Vec::new(), executable_import_ready: false, notes: vec!["synthetic shell-owned descriptor test record".to_string()], }], }), notes: vec![], }; let import = project_save_slice_to_runtime_state_import( &save_slice, "packed-events-shell-owned-descriptor-frontier", None, ) .expect("save slice should project"); assert!(import.state.event_runtime_records.is_empty()); assert_eq!( import .state .packed_event_collection .as_ref() .and_then(|summary| summary.records[0].import_outcome.as_deref()), Some("blocked_shell_owned_descriptor") ); } #[test] fn imports_credit_rating_descriptor_from_save_slice_company_context() { let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: Some(save_company_roster()), chairman_profile_table: Some(save_chairman_profile_table()), special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 9, live_record_count: 1, live_entry_ids: vec![9], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 9, payload_offset: Some(0x7202), payload_len: Some(120), decode_status: "executable".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(true), compact_control: Some(real_compact_control_without_symbolic_company_scope()), text_bands: packed_text_bands(), standalone_condition_row_count: 0, standalone_condition_rows: Vec::new(), negative_sentinel_scope: None, grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: vec![real_credit_rating_row(640)], decoded_conditions: Vec::new(), decoded_actions: vec![RuntimeEffect::SetCompanyGovernanceScalar { target: RuntimeCompanyTarget::SelectedCompany, metric: crate::RuntimeCompanyMetric::CreditRating, value: 640, }], executable_import_ready: true, notes: vec!["synthetic governance descriptor test record".to_string()], }], }), notes: vec![], }; let import = project_save_slice_to_runtime_state_import( &save_slice, "packed-events-credit-rating-descriptor", None, ) .expect("save slice should project"); assert_eq!(import.state.event_runtime_records.len(), 1); assert_eq!( import .state .event_runtime_records .first() .map(|record| record.effects.clone()), Some(vec![RuntimeEffect::SetCompanyGovernanceScalar { target: RuntimeCompanyTarget::SelectedCompany, metric: crate::RuntimeCompanyMetric::CreditRating, value: 640, }]) ); assert_eq!( import .state .packed_event_collection .as_ref() .and_then(|summary| summary.records[0].import_outcome.as_deref()), Some("imported") ); } #[test] fn blocks_scalar_locomotive_availability_rows_without_catalog_context() { let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 31, live_record_count: 1, live_entry_ids: vec![31], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 31, payload_offset: Some(0x7202), payload_len: Some(96), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(6), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(real_compact_control()), text_bands: vec![], standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: vec![crate::SmpLoadedPackedEventGroupedEffectRowSummary { group_index: 0, row_index: 0, descriptor_id: 250, descriptor_label: Some("Big Boy 4-8-8-4 Availability".to_string()), target_mask_bits: Some(0x08), parameter_family: Some("locomotive_availability_scalar".to_string()), grouped_target_subject: None, grouped_target_scope: None, opcode: 3, raw_scalar_value: 42, value_byte_0x09: 0, value_dword_0x0d: 0, value_byte_0x11: 0, value_byte_0x12: 0, value_word_0x14: 0, value_word_0x16: 0, row_shape: "scalar_assignment".to_string(), semantic_family: Some("scalar_assignment".to_string()), semantic_preview: Some( "Set Big Boy 4-8-8-4 Availability to 42".to_string(), ), recovered_cargo_slot: None, recovered_cargo_class: None, recovered_cargo_label: None, recovered_locomotive_id: Some(10), locomotive_name: None, notes: vec![], }], decoded_conditions: Vec::new(), decoded_actions: vec![], executable_import_ready: false, notes: vec![ "decoded from grounded real 0x4e9a row framing".to_string(), "scalar locomotive availability rows still need catalog context" .to_string(), ], }], }), notes: vec![], }; let import = project_save_slice_to_runtime_state_import( &save_slice, "packed-events-recovered-locomotive-availability-frontier", None, ) .expect("save slice should project"); assert!(import.state.event_runtime_records.is_empty()); assert_eq!( import .state .packed_event_collection .as_ref() .and_then(|summary| summary.records[0].import_outcome.as_deref()), Some("blocked_missing_locomotive_catalog_context") ); } #[test] fn blocks_boolean_locomotive_availability_rows_without_catalog_context() { let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 32, live_record_count: 1, live_entry_ids: vec![32], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 32, payload_offset: Some(0x7202), payload_len: Some(96), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(real_compact_control()), text_bands: vec![], standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: vec![real_locomotive_availability_row(250, 1)], decoded_conditions: Vec::new(), decoded_actions: vec![], executable_import_ready: false, notes: vec![ "boolean locomotive availability row still needs catalog context" .to_string(), ], }], }), notes: vec![], }; let import = project_save_slice_to_runtime_state_import( &save_slice, "packed-events-locomotive-availability-missing-catalog", None, ) .expect("save slice should project"); assert!(import.state.event_runtime_records.is_empty()); assert_eq!( import .state .packed_event_collection .as_ref() .and_then(|summary| summary.records[0].import_outcome.as_deref()), Some("blocked_missing_locomotive_catalog_context") ); } #[test] fn imports_scalar_locomotive_availability_rows_with_save_derived_catalog_context() { let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: Some(save_named_locomotive_table(61)), locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 33, live_record_count: 1, live_entry_ids: vec![33], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 33, payload_offset: Some(0x7202), payload_len: Some(120), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(real_compact_control()), text_bands: vec![], standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![2, 0, 0, 0], grouped_effect_rows: vec![ real_locomotive_availability_row(250, 42), real_locomotive_availability_row(301, 7), ], decoded_conditions: Vec::new(), decoded_actions: vec![], executable_import_ready: false, notes: vec![ "scalar locomotive availability rows use save-derived catalog context" .to_string(), ], }], }), notes: vec![], }; let mut import = project_save_slice_to_runtime_state_import( &save_slice, "save-derived-locomotive-availability", None, ) .expect("save slice should project"); assert_eq!(import.state.locomotive_catalog.len(), 61); assert_eq!(import.state.event_runtime_records.len(), 1); assert_eq!( import .state .packed_event_collection .as_ref() .and_then(|summary| summary.records[0].import_outcome.as_deref()), Some("imported") ); execute_step_command( &mut import.state, &StepCommand::ServiceTriggerKind { trigger_kind: 7 }, ) .expect("save-derived locomotive availability record should run"); assert_eq!( import .state .named_locomotive_availability .get("Big Boy 4-8-8-4"), Some(&42) ); assert_eq!( import.state.named_locomotive_availability.get("Zephyr"), Some(&7) ); } #[test] fn overlays_scalar_locomotive_availability_rows_into_named_availability_effects() { let base_state = RuntimeState { calendar: CalendarPoint { year: 1845, month_slot: 2, phase_slot: 1, tick_slot: 3, }, world_flags: BTreeMap::new(), save_profile: RuntimeSaveProfileState::default(), world_restore: RuntimeWorldRestoreState::default(), metadata: BTreeMap::new(), companies: Vec::new(), selected_company_id: None, players: Vec::new(), selected_player_id: None, chairman_profiles: Vec::new(), selected_chairman_profile_id: None, trains: Vec::new(), locomotive_catalog: vec![ crate::RuntimeLocomotiveCatalogEntry { locomotive_id: 10, name: "Big Boy 4-8-8-4".to_string(), }, crate::RuntimeLocomotiveCatalogEntry { locomotive_id: 61, name: "Zephyr".to_string(), }, ], cargo_catalog: Vec::new(), territories: Vec::new(), company_territory_track_piece_counts: Vec::new(), company_territory_access: Vec::new(), packed_event_collection: None, event_runtime_records: Vec::new(), candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::from([ ("Big Boy 4-8-8-4".to_string(), 0), ("Zephyr".to_string(), 1), ]), named_locomotive_cost: BTreeMap::new(), all_cargo_price_override: None, named_cargo_price_overrides: BTreeMap::new(), all_cargo_production_override: None, factory_cargo_production_override: None, farm_mine_cargo_production_override: None, named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_runtime_variables: BTreeMap::new(), company_runtime_variables: BTreeMap::new(), player_runtime_variables: BTreeMap::new(), territory_runtime_variables: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 33, live_record_count: 1, live_entry_ids: vec![33], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 33, payload_offset: Some(0x7202), payload_len: Some(120), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(real_compact_control()), text_bands: vec![], standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![2, 0, 0, 0], grouped_effect_rows: vec![ real_locomotive_availability_row(250, 42), real_locomotive_availability_row(301, 7), ], decoded_conditions: Vec::new(), decoded_actions: vec![], executable_import_ready: false, notes: vec![ "scalar locomotive availability rows use overlay catalog context" .to_string(), ], }], }), notes: vec![], }; let mut import = project_save_slice_overlay_to_runtime_state_import( &base_state, &save_slice, "overlay-locomotive-availability", None, ) .expect("overlay import should project"); assert_eq!(import.state.event_runtime_records.len(), 1); assert_eq!( import .state .packed_event_collection .as_ref() .and_then(|summary| summary.records[0].import_outcome.as_deref()), Some("imported") ); execute_step_command( &mut import.state, &StepCommand::ServiceTriggerKind { trigger_kind: 7 }, ) .expect("overlay-imported locomotive availability record should run"); assert_eq!( import .state .named_locomotive_availability .get("Big Boy 4-8-8-4"), Some(&42) ); assert_eq!( import.state.named_locomotive_availability.get("Zephyr"), Some(&7) ); } #[test] fn blocks_recovered_locomotive_cost_rows_without_catalog_context_lower_band() { let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 34, live_record_count: 1, live_entry_ids: vec![34], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 34, payload_offset: Some(0x7202), payload_len: Some(96), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(real_compact_control()), text_bands: vec![], standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: vec![real_locomotive_cost_row(352, 250000)], decoded_conditions: Vec::new(), decoded_actions: vec![], executable_import_ready: false, notes: vec![ "scalar locomotive cost row still needs catalog context".to_string(), ], }], }), notes: vec![], }; let import = project_save_slice_to_runtime_state_import( &save_slice, "packed-events-locomotive-cost-frontier-lower-band", None, ) .expect("save slice should project"); assert!(import.state.event_runtime_records.is_empty()); assert_eq!( import .state .packed_event_collection .as_ref() .and_then(|summary| summary.records[0].import_outcome.as_deref()), Some("blocked_missing_locomotive_catalog_context") ); } #[test] fn blocks_recovered_locomotive_cost_rows_without_catalog_context() { let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 35, live_record_count: 1, live_entry_ids: vec![35], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 35, payload_offset: Some(0x7202), payload_len: Some(96), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(real_compact_control()), text_bands: vec![], standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: vec![real_locomotive_cost_row(352, 250000)], decoded_conditions: Vec::new(), decoded_actions: vec![], executable_import_ready: false, notes: vec![ "scalar locomotive cost row still needs catalog context".to_string(), ], }], }), notes: vec![], }; let import = project_save_slice_to_runtime_state_import( &save_slice, "packed-events-locomotive-cost-missing-catalog", None, ) .expect("save slice should project"); assert!(import.state.event_runtime_records.is_empty()); assert_eq!( import .state .packed_event_collection .as_ref() .and_then(|summary| summary.records[0].import_outcome.as_deref()), Some("blocked_missing_locomotive_catalog_context") ); } #[test] fn imports_scalar_locomotive_cost_rows_with_save_derived_catalog_context() { let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: Some(save_named_locomotive_table(61)), locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 41, live_record_count: 1, live_entry_ids: vec![41], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 41, payload_offset: Some(0x7202), payload_len: Some(120), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(real_compact_control()), text_bands: vec![], standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![2, 0, 0, 0], grouped_effect_rows: vec![ real_locomotive_cost_row(352, 250000), real_locomotive_cost_row(412, 325000), ], decoded_conditions: Vec::new(), decoded_actions: vec![], executable_import_ready: false, notes: vec![ "scalar locomotive cost rows use save-derived catalog context".to_string(), ], }], }), notes: vec![], }; let mut import = project_save_slice_to_runtime_state_import( &save_slice, "save-derived-locomotive-cost", None, ) .expect("save slice should project"); assert_eq!(import.state.locomotive_catalog.len(), 61); assert_eq!(import.state.event_runtime_records.len(), 1); assert_eq!( import .state .packed_event_collection .as_ref() .and_then(|summary| summary.records[0].import_outcome.as_deref()), Some("imported") ); execute_step_command( &mut import.state, &StepCommand::ServiceTriggerKind { trigger_kind: 7 }, ) .expect("save-derived locomotive cost record should run"); assert_eq!( import.state.named_locomotive_cost.get("2-D-2"), Some(&250000) ); assert_eq!( import.state.named_locomotive_cost.get("Zephyr"), Some(&325000) ); } #[test] fn overlays_scalar_locomotive_cost_rows_into_named_cost_effects() { let base_state = RuntimeState { calendar: CalendarPoint { year: 1845, month_slot: 2, phase_slot: 1, tick_slot: 3, }, world_flags: BTreeMap::new(), save_profile: RuntimeSaveProfileState::default(), world_restore: RuntimeWorldRestoreState::default(), metadata: BTreeMap::new(), companies: Vec::new(), selected_company_id: None, players: Vec::new(), selected_player_id: None, chairman_profiles: Vec::new(), selected_chairman_profile_id: None, trains: Vec::new(), locomotive_catalog: vec![ crate::RuntimeLocomotiveCatalogEntry { locomotive_id: 1, name: "2-D-2".to_string(), }, crate::RuntimeLocomotiveCatalogEntry { locomotive_id: 61, name: "Zephyr".to_string(), }, ], cargo_catalog: Vec::new(), territories: Vec::new(), company_territory_track_piece_counts: Vec::new(), company_territory_access: Vec::new(), packed_event_collection: None, event_runtime_records: Vec::new(), candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::from([ ("2-D-2".to_string(), 100000), ("Zephyr".to_string(), 200000), ]), all_cargo_price_override: None, named_cargo_price_overrides: BTreeMap::new(), all_cargo_production_override: None, factory_cargo_production_override: None, farm_mine_cargo_production_override: None, named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_runtime_variables: BTreeMap::new(), company_runtime_variables: BTreeMap::new(), player_runtime_variables: BTreeMap::new(), territory_runtime_variables: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 36, live_record_count: 1, live_entry_ids: vec![36], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 36, payload_offset: Some(0x7202), payload_len: Some(120), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(real_compact_control()), text_bands: vec![], standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![2, 0, 0, 0], grouped_effect_rows: vec![ real_locomotive_cost_row(352, 250000), real_locomotive_cost_row(412, 325000), ], decoded_conditions: Vec::new(), decoded_actions: vec![], executable_import_ready: false, notes: vec![ "scalar locomotive cost rows use overlay catalog context".to_string(), ], }], }), notes: vec![], }; let mut import = project_save_slice_overlay_to_runtime_state_import( &base_state, &save_slice, "overlay-locomotive-cost", None, ) .expect("overlay import should project"); assert_eq!(import.state.event_runtime_records.len(), 1); assert_eq!( import .state .packed_event_collection .as_ref() .and_then(|summary| summary.records[0].import_outcome.as_deref()), Some("imported") ); execute_step_command( &mut import.state, &StepCommand::ServiceTriggerKind { trigger_kind: 7 }, ) .expect("overlay-imported locomotive cost record should run"); assert_eq!( import.state.named_locomotive_cost.get("2-D-2"), Some(&250000) ); assert_eq!( import.state.named_locomotive_cost.get("Zephyr"), Some(&325000) ); } #[test] fn keeps_negative_locomotive_cost_rows_parity_only() { let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 37, live_record_count: 1, live_entry_ids: vec![37], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 37, payload_offset: Some(0x7202), payload_len: Some(96), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(real_compact_control()), text_bands: vec![], standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: vec![real_locomotive_cost_row(352, -1)], decoded_conditions: Vec::new(), decoded_actions: vec![], executable_import_ready: false, notes: vec!["negative locomotive cost rows remain parity-only".to_string()], }], }), notes: vec![], }; let import = project_save_slice_to_runtime_state_import( &save_slice, "packed-events-negative-locomotive-cost-frontier", None, ) .expect("save slice should project"); assert!(import.state.event_runtime_records.is_empty()); assert_eq!( import .state .packed_event_collection .as_ref() .and_then(|summary| summary.records[0].import_outcome.as_deref()), Some("blocked_variant_or_scope_blocked_descriptor") ); } #[test] fn imports_recovered_cargo_production_rows_into_runtime_records() { let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 35, live_record_count: 1, live_entry_ids: vec![35], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 35, payload_offset: Some(0x7202), payload_len: Some(96), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(real_compact_control()), text_bands: vec![], standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: vec![real_cargo_production_row(230, 125)], decoded_conditions: Vec::new(), decoded_actions: vec![], executable_import_ready: false, notes: vec![ "cargo production rows now import through world overrides".to_string(), ], }], }), notes: vec![], }; let mut import = project_save_slice_to_runtime_state_import( &save_slice, "packed-events-cargo-production-frontier", None, ) .expect("save slice should project"); assert_eq!(import.state.event_runtime_records.len(), 1); assert_eq!( import .state .packed_event_collection .as_ref() .and_then(|summary| summary.records[0].import_outcome.as_deref()), Some("imported") ); execute_step_command( &mut import.state, &StepCommand::ServiceTriggerKind { trigger_kind: 7 }, ) .expect("cargo production runtime record should run"); assert_eq!(import.state.cargo_production_overrides.get(&1), Some(&125)); } #[test] fn imports_aggregate_cargo_economics_rows_into_runtime_records() { let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 38, live_record_count: 1, live_entry_ids: vec![38], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 38, payload_offset: Some(0x7202), payload_len: Some(144), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(real_compact_control()), text_bands: vec![], standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![4, 0, 0, 0], grouped_effect_rows: vec![ real_all_cargo_price_row(180), real_aggregate_cargo_production_row(177, 210), real_aggregate_cargo_production_row(178, 225), real_aggregate_cargo_production_row(179, 175), ], decoded_conditions: Vec::new(), decoded_actions: vec![], executable_import_ready: false, notes: vec![ "grounded aggregate cargo economics descriptors import through bounded override surfaces" .to_string(), ], }], }), notes: vec![], }; let mut import = project_save_slice_to_runtime_state_import( &save_slice, "packed-events-aggregate-cargo-economics", None, ) .expect("save slice should project"); assert_eq!(import.state.event_runtime_records.len(), 1); assert_eq!( import .state .packed_event_collection .as_ref() .and_then(|summary| summary.records[0].import_outcome.as_deref()), Some("imported") ); execute_step_command( &mut import.state, &StepCommand::ServiceTriggerKind { trigger_kind: 7 }, ) .expect("aggregate cargo economics runtime record should run"); assert_eq!(import.state.all_cargo_price_override, Some(180)); assert_eq!(import.state.all_cargo_production_override, Some(210)); assert_eq!(import.state.factory_cargo_production_override, Some(225)); assert_eq!(import.state.farm_mine_cargo_production_override, Some(175)); } #[test] fn imports_named_cargo_price_rows_when_binding_is_grounded() { let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 39, live_record_count: 1, live_entry_ids: vec![39], decoded_record_count: 1, imported_runtime_record_count: 1, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 39, payload_offset: Some(0x7202), payload_len: Some(96), decode_status: "executable".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(real_compact_control()), text_bands: vec![], standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: vec![real_named_cargo_price_row(106, 140)], decoded_conditions: Vec::new(), decoded_actions: vec![RuntimeEffect::SetCargoPriceOverride { target: RuntimeCargoPriceTarget::Named { name: "Alcohol".to_string(), }, value: 140, }], executable_import_ready: true, notes: vec![ "named cargo price descriptors now import through named cargo overrides" .to_string(), ], }], }), notes: vec![], }; let mut import = project_save_slice_to_runtime_state_import( &save_slice, "packed-events-named-cargo-price-import", None, ) .expect("save slice should project"); assert_eq!(import.state.event_runtime_records.len(), 1); execute_step_command( &mut import.state, &StepCommand::ServiceTriggerKind { trigger_kind: 7 }, ) .expect("named cargo price runtime record should run"); assert_eq!( import.state.named_cargo_price_overrides.get("Alcohol"), Some(&140) ); assert_eq!( import .state .packed_event_collection .as_ref() .and_then(|summary| summary.records[0].import_outcome.as_deref()), Some("imported") ); assert_eq!( import .state .packed_event_collection .as_ref() .and_then(|summary| summary.records[0].grouped_effect_rows.first()) .and_then(|row| row.descriptor_label.as_deref()), Some("Alcohol Price") ); } #[test] fn imports_named_cargo_production_rows_when_binding_is_grounded() { let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 40, live_record_count: 1, live_entry_ids: vec![40], decoded_record_count: 1, imported_runtime_record_count: 1, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 40, payload_offset: Some(0x7202), payload_len: Some(96), decode_status: "executable".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(real_compact_control()), text_bands: vec![], standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: vec![real_named_cargo_production_row(180, 160)], decoded_conditions: Vec::new(), decoded_actions: vec![RuntimeEffect::SetCargoProductionOverride { target: RuntimeCargoProductionTarget::Named { name: "Alcohol".to_string(), }, value: 160, }], executable_import_ready: true, notes: vec!["named cargo production descriptors now import through named cargo overrides" .to_string()], }], }), notes: vec![], }; let mut import = project_save_slice_to_runtime_state_import( &save_slice, "packed-events-named-cargo-production-parity", None, ) .expect("save slice should project"); assert_eq!(import.state.event_runtime_records.len(), 1); execute_step_command( &mut import.state, &StepCommand::ServiceTriggerKind { trigger_kind: 7 }, ) .expect("named cargo production runtime record should run"); assert_eq!( import.state.named_cargo_production_overrides.get("Alcohol"), Some(&160) ); assert_eq!( import .state .packed_event_collection .as_ref() .and_then(|summary| summary.records[0].import_outcome.as_deref()), Some("imported") ); } #[test] fn keeps_negative_all_cargo_price_rows_variant_blocked() { let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 41, live_record_count: 1, live_entry_ids: vec![41], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 41, payload_offset: Some(0x7202), payload_len: Some(96), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(real_compact_control()), text_bands: vec![], standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: vec![real_all_cargo_price_row(-1)], decoded_conditions: Vec::new(), decoded_actions: vec![], executable_import_ready: false, notes: vec![ "negative aggregate cargo price variants remain parity-only".to_string(), ], }], }), notes: vec![], }; let import = project_save_slice_to_runtime_state_import( &save_slice, "packed-events-negative-all-cargo-price", None, ) .expect("save slice should project"); assert!(import.state.event_runtime_records.is_empty()); assert_eq!( import .state .packed_event_collection .as_ref() .and_then(|summary| summary.records[0].import_outcome.as_deref()), Some("blocked_variant_or_scope_blocked_descriptor") ); } #[test] fn imports_recovered_territory_access_cost_rows_into_runtime_records() { let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 36, live_record_count: 1, live_entry_ids: vec![36], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 36, payload_offset: Some(0x7202), payload_len: Some(96), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(real_compact_control()), text_bands: vec![], standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: vec![real_territory_access_cost_row(750000)], decoded_conditions: Vec::new(), decoded_actions: vec![], executable_import_ready: false, notes: vec![ "territory access cost rows now import through world restore".to_string(), ], }], }), notes: vec![], }; let mut import = project_save_slice_to_runtime_state_import( &save_slice, "packed-events-territory-access-cost-frontier", None, ) .expect("save slice should project"); assert_eq!(import.state.event_runtime_records.len(), 1); assert_eq!( import .state .packed_event_collection .as_ref() .and_then(|summary| summary.records[0].import_outcome.as_deref()), Some("imported") ); execute_step_command( &mut import.state, &StepCommand::ServiceTriggerKind { trigger_kind: 7 }, ) .expect("territory access cost runtime record should run"); assert_eq!( import.state.world_restore.territory_access_cost, Some(750000) ); } #[test] fn overlays_real_company_cash_descriptor_into_executable_runtime_record() { let base_state = RuntimeState { calendar: CalendarPoint { year: 1845, month_slot: 2, phase_slot: 1, tick_slot: 3, }, world_flags: BTreeMap::new(), save_profile: RuntimeSaveProfileState::default(), world_restore: RuntimeWorldRestoreState::default(), metadata: BTreeMap::new(), companies: vec![crate::RuntimeCompany { company_id: 42, controller_kind: RuntimeCompanyControllerKind::Human, current_cash: 500, debt: 20, credit_rating_score: None, prime_rate: None, track_piece_counts: RuntimeTrackPieceCounts::default(), active: true, available_track_laying_capacity: None, linked_chairman_profile_id: None, book_value_per_share: 0, investor_confidence: 0, management_attitude: 0, takeover_cooldown_year: None, merger_cooldown_year: None, }], selected_company_id: Some(42), players: Vec::new(), selected_player_id: None, chairman_profiles: Vec::new(), selected_chairman_profile_id: None, trains: Vec::new(), locomotive_catalog: Vec::new(), cargo_catalog: Vec::new(), territories: Vec::new(), company_territory_track_piece_counts: Vec::new(), company_territory_access: Vec::new(), packed_event_collection: None, event_runtime_records: Vec::new(), candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), all_cargo_price_override: None, named_cargo_price_overrides: BTreeMap::new(), all_cargo_production_override: None, factory_cargo_production_override: None, farm_mine_cargo_production_override: None, named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_runtime_variables: BTreeMap::new(), company_runtime_variables: BTreeMap::new(), player_runtime_variables: BTreeMap::new(), territory_runtime_variables: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 9, live_record_count: 1, live_entry_ids: vec![9], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 9, payload_offset: Some(0x7202), payload_len: Some(133), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(crate::SmpLoadedPackedEventCompactControlSummary { mode_byte_0x7ef: 7, primary_selector_0x7f0: 0x63, grouped_mode_0x7f4: 2, one_shot_header_0x7f5: 0, modifier_flag_0x7f9: 1, modifier_flag_0x7fa: 0, grouped_target_scope_ordinals_0x7fb: vec![1, 1, 1, 1], grouped_scope_checkboxes_0x7ff: vec![1, 0, 0, 0], summary_toggle_0x800: 1, grouped_territory_selectors_0x80f: vec![-1, -1, -1, -1], }), text_bands: packed_text_bands(), standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: vec![crate::SmpLoadedPackedEventGroupedEffectRowSummary { group_index: 0, row_index: 0, descriptor_id: 2, descriptor_label: Some("Company Cash".to_string()), target_mask_bits: Some(0x01), parameter_family: Some("company_finance_scalar".to_string()), grouped_target_subject: None, grouped_target_scope: None, opcode: 8, raw_scalar_value: 250, value_byte_0x09: 1, value_dword_0x0d: 12, value_byte_0x11: 2, value_byte_0x12: 3, value_word_0x14: 24, value_word_0x16: 36, row_shape: "multivalue_scalar".to_string(), semantic_family: Some("multivalue_scalar".to_string()), semantic_preview: Some( "Set Company Cash to 250 with aux [2, 3, 24, 36]".to_string(), ), recovered_cargo_slot: None, recovered_cargo_class: None, recovered_cargo_label: None, recovered_locomotive_id: None, locomotive_name: Some("Mikado".to_string()), notes: vec![ "grouped effect row carries locomotive-name side string".to_string(), ], }], decoded_conditions: Vec::new(), decoded_actions: vec![RuntimeEffect::SetCompanyCash { target: RuntimeCompanyTarget::SelectedCompany, value: 250, }], executable_import_ready: true, notes: vec![ "decoded from grounded real 0x4e9a row framing".to_string(), "grouped descriptor labels and target masks come from the checked-in effect table recovered around 0x006103a0".to_string(), ], }], }), notes: vec![], }; let mut import = project_save_slice_overlay_to_runtime_state_import( &base_state, &save_slice, "real-company-cash-overlay", None, ) .expect("overlay import should project"); assert_eq!(import.state.event_runtime_records.len(), 1); assert_eq!( import .state .packed_event_collection .as_ref() .and_then(|summary| summary.records[0].import_outcome.as_deref()), Some("imported") ); execute_step_command( &mut import.state, &StepCommand::ServiceTriggerKind { trigger_kind: 7 }, ) .expect("real company-cash descriptor should execute through the normal trigger path"); assert_eq!(import.state.companies[0].current_cash, 250); } #[test] fn overlays_real_territory_access_descriptor_into_executable_runtime_record() { let base_state = RuntimeState { companies: vec![crate::RuntimeCompany { company_id: 42, controller_kind: RuntimeCompanyControllerKind::Human, current_cash: 500, debt: 20, credit_rating_score: None, prime_rate: None, track_piece_counts: RuntimeTrackPieceCounts::default(), active: true, available_track_laying_capacity: None, linked_chairman_profile_id: None, book_value_per_share: 0, investor_confidence: 0, management_attitude: 0, takeover_cooldown_year: None, merger_cooldown_year: None, }], selected_company_id: Some(42), territories: vec![crate::RuntimeTerritory { territory_id: 7, name: Some("Appalachia".to_string()), track_piece_counts: RuntimeTrackPieceCounts::default(), }], ..state() }; let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 11, live_record_count: 1, live_entry_ids: vec![11], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 11, payload_offset: Some(0x7202), payload_len: Some(120), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(6), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(crate::SmpLoadedPackedEventCompactControlSummary { mode_byte_0x7ef: 6, primary_selector_0x7f0: 12, grouped_mode_0x7f4: 2, one_shot_header_0x7f5: 0, modifier_flag_0x7f9: 0, modifier_flag_0x7fa: 0, grouped_target_scope_ordinals_0x7fb: vec![1, 1, 1, 1], grouped_scope_checkboxes_0x7ff: vec![1, 0, 0, 0], summary_toggle_0x800: 1, grouped_territory_selectors_0x80f: vec![7, -1, -1, -1], }), text_bands: packed_text_bands(), standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: vec![real_territory_access_row(true, vec![])], decoded_conditions: Vec::new(), decoded_actions: vec![RuntimeEffect::SetCompanyTerritoryAccess { target: RuntimeCompanyTarget::SelectedCompany, territory: RuntimeTerritoryTarget::Ids { ids: vec![7] }, value: true, }], executable_import_ready: true, notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()], }], }), notes: vec![], }; let mut import = project_save_slice_overlay_to_runtime_state_import( &base_state, &save_slice, "real-territory-access-overlay", None, ) .expect("overlay import should project"); assert_eq!(import.state.event_runtime_records.len(), 1); assert_eq!( import .state .packed_event_collection .as_ref() .and_then(|summary| summary.records[0].import_outcome.as_deref()), Some("imported") ); execute_step_command( &mut import.state, &StepCommand::ServiceTriggerKind { trigger_kind: 6 }, ) .expect("real territory-access descriptor should execute"); assert_eq!( import.state.company_territory_access, vec![crate::RuntimeCompanyTerritoryAccess { company_id: 42, territory_id: 7, }] ); } #[test] fn keeps_real_territory_access_false_row_parity_only() { let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 12, live_record_count: 1, live_entry_ids: vec![12], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 12, payload_offset: Some(0x7202), payload_len: Some(120), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(6), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(crate::SmpLoadedPackedEventCompactControlSummary { mode_byte_0x7ef: 6, primary_selector_0x7f0: 12, grouped_mode_0x7f4: 2, one_shot_header_0x7f5: 0, modifier_flag_0x7f9: 0, modifier_flag_0x7fa: 0, grouped_target_scope_ordinals_0x7fb: vec![1, 1, 1, 1], grouped_scope_checkboxes_0x7ff: vec![1, 0, 0, 0], summary_toggle_0x800: 1, grouped_territory_selectors_0x80f: vec![7, -1, -1, -1], }), text_bands: packed_text_bands(), standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: vec![real_territory_access_row(false, vec![])], decoded_conditions: Vec::new(), decoded_actions: vec![], executable_import_ready: false, notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()], }], }), notes: vec![], }; let import = project_save_slice_to_runtime_state_import( &save_slice, "real-territory-access-false", None, ) .expect("save slice should project"); assert!(import.state.event_runtime_records.is_empty()); assert_eq!( import .state .packed_event_collection .as_ref() .and_then(|summary| summary.records[0].import_outcome.as_deref()), Some("blocked_territory_access_variant") ); } #[test] fn keeps_real_territory_access_missing_scope_row_parity_only() { let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 13, live_record_count: 1, live_entry_ids: vec![13], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 13, payload_offset: Some(0x7202), payload_len: Some(120), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(6), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(crate::SmpLoadedPackedEventCompactControlSummary { mode_byte_0x7ef: 6, primary_selector_0x7f0: 12, grouped_mode_0x7f4: 2, one_shot_header_0x7f5: 0, modifier_flag_0x7f9: 0, modifier_flag_0x7fa: 0, grouped_target_scope_ordinals_0x7fb: vec![9, 1, 1, 1], grouped_scope_checkboxes_0x7ff: vec![1, 0, 0, 0], summary_toggle_0x800: 1, grouped_territory_selectors_0x80f: vec![-1, -1, -1, -1], }), text_bands: packed_text_bands(), standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: vec![real_territory_access_row( true, vec![ "territory access row is missing company or territory scope" .to_string(), ], )], decoded_conditions: Vec::new(), decoded_actions: vec![], executable_import_ready: false, notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()], }], }), notes: vec![], }; let import = project_save_slice_to_runtime_state_import( &save_slice, "real-territory-access-missing-scope", None, ) .expect("save slice should project"); assert!(import.state.event_runtime_records.is_empty()); assert_eq!( import .state .packed_event_collection .as_ref() .and_then(|summary| summary.records[0].import_outcome.as_deref()), Some("blocked_territory_access_scope") ); } #[test] fn overlays_real_deactivate_company_descriptor_into_executable_runtime_record() { let base_state = RuntimeState { companies: vec![crate::RuntimeCompany { company_id: 42, controller_kind: RuntimeCompanyControllerKind::Human, current_cash: 500, debt: 20, credit_rating_score: None, prime_rate: None, track_piece_counts: RuntimeTrackPieceCounts::default(), active: true, available_track_laying_capacity: None, linked_chairman_profile_id: None, book_value_per_share: 0, investor_confidence: 0, management_attitude: 0, takeover_cooldown_year: None, merger_cooldown_year: None, }], selected_company_id: Some(42), ..state() }; let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 13, live_record_count: 1, live_entry_ids: vec![13], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 13, payload_offset: Some(0x7202), payload_len: Some(120), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(crate::SmpLoadedPackedEventCompactControlSummary { mode_byte_0x7ef: 7, primary_selector_0x7f0: 0x63, grouped_mode_0x7f4: 2, one_shot_header_0x7f5: 0, modifier_flag_0x7f9: 1, modifier_flag_0x7fa: 0, grouped_target_scope_ordinals_0x7fb: vec![1, 1, 1, 1], grouped_scope_checkboxes_0x7ff: vec![1, 0, 0, 0], summary_toggle_0x800: 1, grouped_territory_selectors_0x80f: vec![-1, -1, -1, -1], }), text_bands: packed_text_bands(), standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: vec![real_deactivate_company_row(true)], decoded_conditions: Vec::new(), decoded_actions: vec![RuntimeEffect::DeactivateCompany { target: RuntimeCompanyTarget::SelectedCompany, }], executable_import_ready: true, notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()], }], }), notes: vec![], }; let mut import = project_save_slice_overlay_to_runtime_state_import( &base_state, &save_slice, "real-deactivate-company-overlay", None, ) .expect("overlay import should project"); assert_eq!(import.state.event_runtime_records.len(), 1); execute_step_command( &mut import.state, &StepCommand::ServiceTriggerKind { trigger_kind: 7 }, ) .expect("real deactivate-company descriptor should execute"); assert!(!import.state.companies[0].active); assert_eq!(import.state.selected_company_id, None); } #[test] fn overlays_real_deactivate_player_descriptor_into_executable_runtime_record() { let base_state = RuntimeState { players: vec![ crate::RuntimePlayer { player_id: 7, current_cash: 500, active: true, controller_kind: RuntimeCompanyControllerKind::Human, }, crate::RuntimePlayer { player_id: 8, current_cash: 250, active: true, controller_kind: RuntimeCompanyControllerKind::Ai, }, ], selected_player_id: Some(7), ..state() }; let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 18, live_record_count: 1, live_entry_ids: vec![18], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 18, payload_offset: Some(0x7202), payload_len: Some(120), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(crate::SmpLoadedPackedEventCompactControlSummary { mode_byte_0x7ef: 7, primary_selector_0x7f0: 0x63, grouped_mode_0x7f4: 2, one_shot_header_0x7f5: 0, modifier_flag_0x7f9: 0, modifier_flag_0x7fa: 2, grouped_target_scope_ordinals_0x7fb: vec![0, 0, 0, 0], grouped_scope_checkboxes_0x7ff: vec![1, 0, 0, 0], summary_toggle_0x800: 1, grouped_territory_selectors_0x80f: vec![-1, -1, -1, -1], }), text_bands: packed_text_bands(), standalone_condition_row_count: 1, standalone_condition_rows: real_condition_rows(), negative_sentinel_scope: Some( crate::SmpLoadedPackedEventNegativeSentinelScopeSummary { company_test_scope: RuntimeCompanyConditionTestScope::Disabled, player_test_scope: RuntimePlayerConditionTestScope::SelectedPlayerOnly, territory_scope_selector_is_0x63: false, source_row_indexes: vec![0], }, ), grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: vec![real_deactivate_player_row(true)], decoded_conditions: Vec::new(), decoded_actions: vec![RuntimeEffect::DeactivatePlayer { target: RuntimePlayerTarget::ConditionTruePlayer, }], executable_import_ready: true, notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()], }], }), notes: vec![], }; let mut import = project_save_slice_overlay_to_runtime_state_import( &base_state, &save_slice, "real-deactivate-player-overlay", None, ) .expect("overlay import should project"); assert_eq!(import.state.event_runtime_records.len(), 1); execute_step_command( &mut import.state, &StepCommand::ServiceTriggerKind { trigger_kind: 7 }, ) .expect("real deactivate-player descriptor should execute"); assert!(!import.state.players[0].active); assert!(import.state.players[1].active); assert_eq!(import.state.selected_player_id, None); } #[test] fn keeps_real_deactivate_player_false_row_parity_only() { let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 19, live_record_count: 1, live_entry_ids: vec![19], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 19, payload_offset: Some(0x7202), payload_len: Some(120), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(crate::SmpLoadedPackedEventCompactControlSummary { mode_byte_0x7ef: 7, primary_selector_0x7f0: 0x63, grouped_mode_0x7f4: 2, one_shot_header_0x7f5: 0, modifier_flag_0x7f9: 0, modifier_flag_0x7fa: 2, grouped_target_scope_ordinals_0x7fb: vec![0, 0, 0, 0], grouped_scope_checkboxes_0x7ff: vec![1, 0, 0, 0], summary_toggle_0x800: 1, grouped_territory_selectors_0x80f: vec![-1, -1, -1, -1], }), text_bands: packed_text_bands(), standalone_condition_row_count: 1, standalone_condition_rows: real_condition_rows(), negative_sentinel_scope: Some( crate::SmpLoadedPackedEventNegativeSentinelScopeSummary { company_test_scope: RuntimeCompanyConditionTestScope::Disabled, player_test_scope: RuntimePlayerConditionTestScope::SelectedPlayerOnly, territory_scope_selector_is_0x63: false, source_row_indexes: vec![0], }, ), grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: vec![real_deactivate_player_row(false)], decoded_conditions: Vec::new(), decoded_actions: vec![], executable_import_ready: false, notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()], }], }), notes: vec![], }; let import = project_save_slice_to_runtime_state_import( &save_slice, "real-deactivate-player-false", None, ) .expect("save slice should project"); assert!(import.state.event_runtime_records.is_empty()); assert_eq!( import .state .packed_event_collection .as_ref() .and_then(|summary| summary.records[0].import_outcome.as_deref()), Some("blocked_variant_or_scope_blocked_descriptor") ); } #[test] fn keeps_real_deactivate_company_false_row_parity_only() { let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 14, live_record_count: 1, live_entry_ids: vec![14], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 14, payload_offset: Some(0x7202), payload_len: Some(120), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(crate::SmpLoadedPackedEventCompactControlSummary { mode_byte_0x7ef: 7, primary_selector_0x7f0: 0x63, grouped_mode_0x7f4: 2, one_shot_header_0x7f5: 0, modifier_flag_0x7f9: 1, modifier_flag_0x7fa: 0, grouped_target_scope_ordinals_0x7fb: vec![1, 1, 1, 1], grouped_scope_checkboxes_0x7ff: vec![1, 0, 0, 0], summary_toggle_0x800: 1, grouped_territory_selectors_0x80f: vec![-1, -1, -1, -1], }), text_bands: packed_text_bands(), standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: vec![real_deactivate_company_row(false)], decoded_conditions: Vec::new(), decoded_actions: vec![], executable_import_ready: false, notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()], }], }), notes: vec![], }; let import = project_save_slice_to_runtime_state_import( &save_slice, "real-deactivate-company-false", None, ) .expect("save slice should project"); assert!(import.state.event_runtime_records.is_empty()); assert_eq!( import .state .packed_event_collection .as_ref() .and_then(|summary| summary.records[0].import_outcome.as_deref()), Some("blocked_variant_or_scope_blocked_descriptor") ); } #[test] fn overlays_real_track_capacity_descriptor_into_executable_runtime_record() { let base_state = RuntimeState { companies: vec![crate::RuntimeCompany { company_id: 42, controller_kind: RuntimeCompanyControllerKind::Human, current_cash: 500, debt: 20, credit_rating_score: None, prime_rate: None, track_piece_counts: RuntimeTrackPieceCounts::default(), active: true, available_track_laying_capacity: None, linked_chairman_profile_id: None, book_value_per_share: 0, investor_confidence: 0, management_attitude: 0, takeover_cooldown_year: None, merger_cooldown_year: None, }], selected_company_id: Some(42), ..state() }; let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 16, live_record_count: 1, live_entry_ids: vec![16], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 16, payload_offset: Some(0x7202), payload_len: Some(120), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(crate::SmpLoadedPackedEventCompactControlSummary { mode_byte_0x7ef: 7, primary_selector_0x7f0: 0x63, grouped_mode_0x7f4: 2, one_shot_header_0x7f5: 0, modifier_flag_0x7f9: 1, modifier_flag_0x7fa: 0, grouped_target_scope_ordinals_0x7fb: vec![1, 1, 1, 1], grouped_scope_checkboxes_0x7ff: vec![1, 0, 0, 0], summary_toggle_0x800: 1, grouped_territory_selectors_0x80f: vec![-1, -1, -1, -1], }), text_bands: packed_text_bands(), standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: vec![real_track_capacity_row(18)], decoded_conditions: Vec::new(), decoded_actions: vec![RuntimeEffect::SetCompanyTrackLayingCapacity { target: RuntimeCompanyTarget::SelectedCompany, value: Some(18), }], executable_import_ready: true, notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()], }], }), notes: vec![], }; let mut import = project_save_slice_overlay_to_runtime_state_import( &base_state, &save_slice, "real-track-capacity-overlay", None, ) .expect("overlay import should project"); execute_step_command( &mut import.state, &StepCommand::ServiceTriggerKind { trigger_kind: 7 }, ) .expect("real track-capacity descriptor should execute"); assert_eq!( import.state.companies[0].available_track_laying_capacity, Some(18) ); } #[test] fn overlays_real_economic_status_descriptor_into_executable_runtime_record() { let base_state = state(); let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 18, live_record_count: 1, live_entry_ids: vec![18], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 18, payload_offset: Some(0x7202), payload_len: Some(120), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(real_compact_control()), text_bands: packed_text_bands(), standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: vec![real_economic_status_row(2)], decoded_conditions: Vec::new(), decoded_actions: vec![RuntimeEffect::SetEconomicStatusCode { value: 2 }], executable_import_ready: true, notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()], }], }), notes: vec![], }; let mut import = project_save_slice_overlay_to_runtime_state_import( &base_state, &save_slice, "real-economic-status-overlay", None, ) .expect("overlay import should project"); execute_step_command( &mut import.state, &StepCommand::ServiceTriggerKind { trigger_kind: 7 }, ) .expect("real economic-status descriptor should execute"); assert_eq!(import.state.world_restore.economic_status_code, Some(2)); } #[test] fn imports_real_limited_track_building_amount_descriptor_into_executable_runtime_record() { let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 52, live_record_count: 1, live_entry_ids: vec![52], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 52, payload_offset: Some(0x7202), payload_len: Some(120), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(6), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(real_compact_control()), text_bands: packed_text_bands(), standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: vec![real_limited_track_building_amount_row(18)], decoded_conditions: Vec::new(), decoded_actions: vec![RuntimeEffect::SetLimitedTrackBuildingAmount { value: 18, }], executable_import_ready: true, notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()], }], }), notes: vec![], }; let mut import = project_save_slice_to_runtime_state_import( &save_slice, "real-limited-track-building-amount-save-slice", None, ) .expect("save-slice import should project"); execute_step_command( &mut import.state, &StepCommand::ServiceTriggerKind { trigger_kind: 6 }, ) .expect("real limited-track-building-amount descriptor should execute"); assert_eq!( import.state.world_restore.limited_track_building_amount, Some(18) ); } #[test] fn overlays_real_special_condition_descriptor_into_executable_runtime_record() { let base_state = state(); let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 21, live_record_count: 1, live_entry_ids: vec![21], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 21, payload_offset: Some(0x7202), payload_len: Some(120), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(real_compact_control()), text_bands: packed_text_bands(), standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: vec![real_special_condition_row(1)], decoded_conditions: Vec::new(), decoded_actions: vec![RuntimeEffect::SetSpecialCondition { label: "Use Wartime Cargos".to_string(), value: 1, }], executable_import_ready: true, notes: vec![ "decoded from grounded real 0x4e9a row framing".to_string(), "whole-game descriptor labels and parameter families come from the checked-in effect table".to_string(), ], }], }), notes: vec![], }; let mut import = project_save_slice_overlay_to_runtime_state_import( &base_state, &save_slice, "real-special-condition-overlay", None, ) .expect("overlay import should project"); execute_step_command( &mut import.state, &StepCommand::ServiceTriggerKind { trigger_kind: 7 }, ) .expect("real special-condition descriptor should execute"); assert_eq!( import.state.special_conditions.get("Use Wartime Cargos"), Some(&1) ); } #[test] fn overlays_real_candidate_availability_descriptor_into_executable_runtime_record() { let base_state = state(); let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 22, live_record_count: 1, live_entry_ids: vec![22], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 22, payload_offset: Some(0x7202), payload_len: Some(120), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(real_compact_control()), text_bands: packed_text_bands(), standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: vec![real_candidate_availability_row(1)], decoded_conditions: Vec::new(), decoded_actions: vec![RuntimeEffect::SetCandidateAvailability { name: "Turbo Diesel".to_string(), value: 1, }], executable_import_ready: true, notes: vec![ "decoded from grounded real 0x4e9a row framing".to_string(), "whole-game descriptor labels and parameter families come from the checked-in effect table".to_string(), ], }], }), notes: vec![], }; let mut import = project_save_slice_overlay_to_runtime_state_import( &base_state, &save_slice, "real-candidate-availability-overlay", None, ) .expect("overlay import should project"); execute_step_command( &mut import.state, &StepCommand::ServiceTriggerKind { trigger_kind: 7 }, ) .expect("real candidate-availability descriptor should execute"); assert_eq!( import.state.candidate_availability.get("Turbo Diesel"), Some(&1) ); } #[test] fn overlays_real_world_flag_descriptor_into_executable_runtime_record() { let base_state = state(); let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 23, live_record_count: 1, live_entry_ids: vec![23], decoded_record_count: 1, imported_runtime_record_count: 1, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 23, payload_offset: Some(0x7200), payload_len: Some(120), decode_status: "executable".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(real_compact_control()), text_bands: packed_text_bands(), standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: vec![real_world_flag_row( 110, "Disable Stock Buying and Selling", true, )], decoded_conditions: Vec::new(), decoded_actions: vec![RuntimeEffect::SetWorldFlag { key: "world.disable_stock_buying_and_selling".to_string(), value: true, }], executable_import_ready: true, notes: vec![ "decoded from grounded real 0x4e9a row framing".to_string(), "world-flag descriptor identity and keyed runtime mapping are checked in" .to_string(), ], }], }), notes: vec![], }; let mut import = project_save_slice_overlay_to_runtime_state_import( &base_state, &save_slice, "real-world-flag-overlay", None, ) .expect("overlay import should project"); assert_eq!(import.state.event_runtime_records.len(), 1); assert_eq!( import .state .packed_event_collection .as_ref() .and_then(|summary| summary.records[0].import_outcome.as_deref()), Some("imported") ); crate::execute_step_command( &mut import.state, &crate::StepCommand::ServiceTriggerKind { trigger_kind: 7 }, ) .expect("trigger service should execute"); assert_eq!( import .state .world_flags .get("world.disable_stock_buying_and_selling"), Some(&true) ); } #[test] fn overlays_real_world_flag_false_variant_into_executable_runtime_record() { let base_state = state(); let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 24, live_record_count: 1, live_entry_ids: vec![24], decoded_record_count: 1, imported_runtime_record_count: 1, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 24, payload_offset: Some(0x7200), payload_len: Some(120), decode_status: "executable".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(real_compact_control()), text_bands: packed_text_bands(), standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: vec![real_world_flag_row( 110, "Disable Stock Buying and Selling", false, )], decoded_conditions: Vec::new(), decoded_actions: vec![RuntimeEffect::SetWorldFlag { key: "world.disable_stock_buying_and_selling".to_string(), value: false, }], executable_import_ready: true, notes: vec![ "decoded from grounded real 0x4e9a row framing".to_string(), "world-flag descriptor identity and keyed runtime mapping are checked in" .to_string(), ], }], }), notes: vec![], }; let mut import = project_save_slice_overlay_to_runtime_state_import( &base_state, &save_slice, "real-world-flag-false-overlay", None, ) .expect("overlay import should project"); crate::execute_step_command( &mut import.state, &crate::StepCommand::ServiceTriggerKind { trigger_kind: 7 }, ) .expect("trigger service should execute"); assert_eq!( import .state .world_flags .get("world.disable_stock_buying_and_selling"), Some(&false) ); } #[test] fn overlays_recovered_locomotive_policy_descriptors_into_executable_runtime_record() { let base_state = state(); let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 29, live_record_count: 1, live_entry_ids: vec![29], decoded_record_count: 1, imported_runtime_record_count: 1, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 29, payload_offset: Some(0x7200), payload_len: Some(160), decode_status: "executable".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(real_compact_control()), text_bands: packed_text_bands(), standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![3, 0, 0, 0], grouped_effect_rows: vec![ real_world_flag_row(454, "All Steam Locos Avail.", true), real_world_flag_row(455, "All Diesel Locos Avail.", false), real_world_flag_row(456, "All Electric Locos Avail.", true), ], decoded_conditions: Vec::new(), decoded_actions: vec![ RuntimeEffect::SetWorldFlag { key: "world.all_steam_locos_available".to_string(), value: true, }, RuntimeEffect::SetWorldFlag { key: "world.all_diesel_locos_available".to_string(), value: false, }, RuntimeEffect::SetWorldFlag { key: "world.all_electric_locos_available".to_string(), value: true, }, ], executable_import_ready: true, notes: vec![ "recovered locomotive policy descriptor band now imports as keyed world flags" .to_string(), ], }], }), notes: vec![], }; let mut import = project_save_slice_overlay_to_runtime_state_import( &base_state, &save_slice, "real-locomotive-policy-overlay", None, ) .expect("overlay import should project"); crate::execute_step_command( &mut import.state, &crate::StepCommand::ServiceTriggerKind { trigger_kind: 7 }, ) .expect("trigger service should execute"); assert_eq!( import .state .world_flags .get("world.all_steam_locos_available"), Some(&true) ); assert_eq!( import .state .world_flags .get("world.all_diesel_locos_available"), Some(&false) ); assert_eq!( import .state .world_flags .get("world.all_electric_locos_available"), Some(&true) ); } #[test] fn overlays_real_world_flag_condition_into_executable_runtime_record() { let mut base_state = state(); base_state .world_flags .insert("world.disable_stock_buying_and_selling".to_string(), true); let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 27, live_record_count: 1, live_entry_ids: vec![27], decoded_record_count: 1, imported_runtime_record_count: 1, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 27, payload_offset: Some(0x7200), payload_len: Some(152), decode_status: "executable".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(real_compact_control()), text_bands: packed_text_bands(), standalone_condition_row_count: 1, standalone_condition_rows: vec![ crate::SmpLoadedPackedEventConditionRowSummary { row_index: 0, raw_condition_id: 2535, subtype: 0, flag_bytes: vec![ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ], candidate_name: None, comparator: Some("eq".to_string()), metric: Some( "World Flag: Disable Stock Buying and Selling".to_string(), ), semantic_family: Some("world_flag_equals".to_string()), semantic_preview: Some( "Test Disable Stock Buying and Selling == TRUE".to_string(), ), recovered_cargo_slot: None, recovered_cargo_class: None, requires_candidate_name_binding: false, notes: vec![ "checked-in whole-game condition metadata sample".to_string(), ], }, ], negative_sentinel_scope: None, grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: vec![crate::SmpLoadedPackedEventGroupedEffectRowSummary { group_index: 0, row_index: 0, descriptor_id: 109, descriptor_label: Some("Turbo Diesel Availability".to_string()), target_mask_bits: Some(0x08), parameter_family: Some("candidate_availability_scalar".to_string()), grouped_target_subject: None, grouped_target_scope: None, opcode: 3, raw_scalar_value: 1, value_byte_0x09: 0, value_dword_0x0d: 0, value_byte_0x11: 0, value_byte_0x12: 0, value_word_0x14: 0, value_word_0x16: 0, row_shape: "scalar_assignment".to_string(), semantic_family: Some("scalar_assignment".to_string()), semantic_preview: Some("Set Turbo Diesel Availability to 1".to_string()), recovered_cargo_slot: None, recovered_cargo_class: None, recovered_cargo_label: None, recovered_locomotive_id: None, locomotive_name: None, notes: vec!["checked-in whole-game grouped-effect sample".to_string()], }], decoded_conditions: vec![RuntimeCondition::WorldFlagEquals { key: "world.disable_stock_buying_and_selling".to_string(), value: true, }], decoded_actions: vec![RuntimeEffect::SetCandidateAvailability { name: "Turbo Diesel".to_string(), value: 1, }], executable_import_ready: true, notes: vec!["world-flag condition gates a world-side effect".to_string()], }], }), notes: vec![], }; let mut import = project_save_slice_overlay_to_runtime_state_import( &base_state, &save_slice, "real-world-flag-condition-overlay", None, ) .expect("overlay import should project"); crate::execute_step_command( &mut import.state, &crate::StepCommand::ServiceTriggerKind { trigger_kind: 7 }, ) .expect("trigger service should execute"); assert_eq!( import.state.candidate_availability.get("Turbo Diesel"), Some(&1) ); } #[test] fn imports_and_executes_world_scalar_conditions_through_runtime_state() { let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: Some(save_cargo_catalog(&[ (1, crate::RuntimeCargoClass::Factory), (5, crate::RuntimeCargoClass::FarmMine), (9, crate::RuntimeCargoClass::Other), ])), world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7800, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 45, live_record_count: 2, live_entry_ids: vec![41, 45], decoded_record_count: 2, imported_runtime_record_count: 2, records: vec![ crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 41, payload_offset: Some(0x7200), payload_len: Some(192), decode_status: "executable".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(6), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(real_compact_control()), text_bands: packed_text_bands(), standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![7, 0, 0, 0], grouped_effect_rows: vec![ real_locomotive_availability_row(250, 42), real_locomotive_cost_row(352, 250000), real_cargo_production_row(230, 125), real_cargo_production_row(234, 75), real_cargo_production_row(238, 30), real_limited_track_building_amount_row(18), real_territory_access_cost_row(750000), ], decoded_conditions: vec![], decoded_actions: vec![ RuntimeEffect::SetNamedLocomotiveAvailabilityValue { name: "Big Boy".to_string(), value: 42, }, RuntimeEffect::SetNamedLocomotiveCost { name: "Locomotive 1".to_string(), value: 250000, }, RuntimeEffect::SetCargoProductionSlot { slot: 1, value: 125, }, RuntimeEffect::SetCargoProductionSlot { slot: 5, value: 75 }, RuntimeEffect::SetCargoProductionSlot { slot: 9, value: 30 }, RuntimeEffect::SetLimitedTrackBuildingAmount { value: 18 }, RuntimeEffect::SetTerritoryAccessCost { value: 750000 }, ], executable_import_ready: true, notes: vec!["world-scalar setup record".to_string()], }, crate::SmpLoadedPackedEventRecordSummary { record_index: 1, live_entry_id: 45, payload_offset: Some(0x7300), payload_len: Some(184), decode_status: "executable".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(real_compact_control()), text_bands: packed_text_bands(), standalone_condition_row_count: 9, standalone_condition_rows: vec![ crate::SmpLoadedPackedEventConditionRowSummary { row_index: 0, raw_condition_id: 2422, subtype: 4, flag_bytes: { let mut bytes = vec![0; 25]; bytes[0..4].copy_from_slice(&42_i32.to_le_bytes()); bytes }, candidate_name: Some("Big Boy".to_string()), comparator: Some("eq".to_string()), metric: Some("Named Locomotive Availability: Big Boy".to_string()), semantic_family: Some("world_scalar_threshold".to_string()), semantic_preview: Some( "Test Named Locomotive Availability: Big Boy == 42".to_string(), ), recovered_cargo_slot: None, recovered_cargo_class: None, requires_candidate_name_binding: false, notes: vec![], }, crate::SmpLoadedPackedEventConditionRowSummary { row_index: 1, raw_condition_id: 2423, subtype: 4, flag_bytes: { let mut bytes = vec![0; 25]; bytes[0..4].copy_from_slice(&250000_i32.to_le_bytes()); bytes }, candidate_name: Some("Locomotive 1".to_string()), comparator: Some("eq".to_string()), metric: Some("Named Locomotive Cost: Locomotive 1".to_string()), semantic_family: Some("world_scalar_threshold".to_string()), semantic_preview: Some( "Test Named Locomotive Cost: Locomotive 1 == 250000" .to_string(), ), recovered_cargo_slot: None, recovered_cargo_class: None, requires_candidate_name_binding: false, notes: vec![], }, crate::SmpLoadedPackedEventConditionRowSummary { row_index: 2, raw_condition_id: 200, subtype: 4, flag_bytes: { let mut bytes = vec![0; 25]; bytes[0..4].copy_from_slice(&125_i32.to_le_bytes()); bytes }, candidate_name: Some("Cargo Production Slot 1".to_string()), comparator: Some("eq".to_string()), metric: Some( "Cargo Production: Cargo Production Slot 1".to_string(), ), semantic_family: Some("world_scalar_threshold".to_string()), semantic_preview: Some( "Test Cargo Production: Cargo Production Slot 1 == 125" .to_string(), ), recovered_cargo_slot: Some(1), recovered_cargo_class: Some("factory".to_string()), requires_candidate_name_binding: false, notes: vec![], }, crate::SmpLoadedPackedEventConditionRowSummary { row_index: 3, raw_condition_id: 2418, subtype: 4, flag_bytes: { let mut bytes = vec![0; 25]; bytes[0..4].copy_from_slice(&125_i32.to_le_bytes()); bytes }, candidate_name: None, comparator: Some("eq".to_string()), metric: Some("Cargo Production Total".to_string()), semantic_family: Some("world_scalar_threshold".to_string()), semantic_preview: Some( "Test Cargo Production Total == 230".to_string(), ), recovered_cargo_slot: None, recovered_cargo_class: None, requires_candidate_name_binding: false, notes: vec![], }, crate::SmpLoadedPackedEventConditionRowSummary { row_index: 4, raw_condition_id: 2419, subtype: 4, flag_bytes: { let mut bytes = vec![0; 25]; bytes[0..4].copy_from_slice(&125_i32.to_le_bytes()); bytes }, candidate_name: None, comparator: Some("eq".to_string()), metric: Some("Factory Production Total".to_string()), semantic_family: Some("world_scalar_threshold".to_string()), semantic_preview: Some( "Test Factory Production Total == 125".to_string(), ), recovered_cargo_slot: None, recovered_cargo_class: Some("factory".to_string()), requires_candidate_name_binding: false, notes: vec![], }, crate::SmpLoadedPackedEventConditionRowSummary { row_index: 5, raw_condition_id: 2420, subtype: 4, flag_bytes: { let mut bytes = vec![0; 25]; bytes[0..4].copy_from_slice(&75_i32.to_le_bytes()); bytes }, candidate_name: None, comparator: Some("eq".to_string()), metric: Some("Farm/Mine Production Total".to_string()), semantic_family: Some("world_scalar_threshold".to_string()), semantic_preview: Some( "Test Farm/Mine Production Total == 75".to_string(), ), recovered_cargo_slot: None, recovered_cargo_class: Some("farm_mine".to_string()), requires_candidate_name_binding: false, notes: vec![], }, crate::SmpLoadedPackedEventConditionRowSummary { row_index: 6, raw_condition_id: 2421, subtype: 4, flag_bytes: { let mut bytes = vec![0; 25]; bytes[0..4].copy_from_slice(&30_i32.to_le_bytes()); bytes }, candidate_name: None, comparator: Some("eq".to_string()), metric: Some("Other Cargo Production Total".to_string()), semantic_family: Some("world_scalar_threshold".to_string()), semantic_preview: Some( "Test Other Cargo Production Total == 30".to_string(), ), recovered_cargo_slot: None, recovered_cargo_class: Some("other".to_string()), requires_candidate_name_binding: false, notes: vec![], }, crate::SmpLoadedPackedEventConditionRowSummary { row_index: 7, raw_condition_id: 2547, subtype: 4, flag_bytes: { let mut bytes = vec![0; 25]; bytes[0..4].copy_from_slice(&18_i32.to_le_bytes()); bytes }, candidate_name: None, comparator: Some("eq".to_string()), metric: Some("Limited Track Building Amount".to_string()), semantic_family: Some("world_scalar_threshold".to_string()), semantic_preview: Some( "Test Limited Track Building Amount == 18".to_string(), ), recovered_cargo_slot: None, recovered_cargo_class: None, requires_candidate_name_binding: false, notes: vec![], }, crate::SmpLoadedPackedEventConditionRowSummary { row_index: 8, raw_condition_id: 1516, subtype: 4, flag_bytes: { let mut bytes = vec![0; 25]; bytes[0..4].copy_from_slice(&750000_i32.to_le_bytes()); bytes }, candidate_name: None, comparator: Some("eq".to_string()), metric: Some("Territory Access Cost".to_string()), semantic_family: Some("world_scalar_threshold".to_string()), semantic_preview: Some( "Test Territory Access Cost == 750000".to_string(), ), recovered_cargo_slot: None, recovered_cargo_class: None, requires_candidate_name_binding: false, notes: vec![], }, ], negative_sentinel_scope: None, grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: vec![real_world_flag_row( 110, "Disable Stock Buying and Selling", true, )], decoded_conditions: vec![ RuntimeCondition::NamedLocomotiveAvailabilityThreshold { name: "Big Boy".to_string(), comparator: RuntimeConditionComparator::Eq, value: 42, }, RuntimeCondition::NamedLocomotiveCostThreshold { name: "Locomotive 1".to_string(), comparator: RuntimeConditionComparator::Eq, value: 250000, }, RuntimeCondition::CargoProductionSlotThreshold { slot: 1, label: "Cargo Production Slot 1".to_string(), comparator: RuntimeConditionComparator::Eq, value: 125, }, RuntimeCondition::CargoProductionTotalThreshold { comparator: RuntimeConditionComparator::Eq, value: 230, }, RuntimeCondition::FactoryProductionTotalThreshold { comparator: RuntimeConditionComparator::Eq, value: 125, }, RuntimeCondition::FarmMineProductionTotalThreshold { comparator: RuntimeConditionComparator::Eq, value: 75, }, RuntimeCondition::OtherCargoProductionTotalThreshold { comparator: RuntimeConditionComparator::Eq, value: 30, }, RuntimeCondition::LimitedTrackBuildingAmountThreshold { comparator: RuntimeConditionComparator::Eq, value: 18, }, RuntimeCondition::TerritoryAccessCostThreshold { comparator: RuntimeConditionComparator::Eq, value: 750000, }, ], decoded_actions: vec![RuntimeEffect::SetWorldFlag { key: "world.world_scalar_conditions_passed".to_string(), value: true, }], executable_import_ready: true, notes: vec!["world-scalar conditions gate a world-side effect".to_string()], }, ], }), notes: vec![], }; let mut import = project_save_slice_to_runtime_state_import( &save_slice, "world-scalar-condition-save-slice", None, ) .expect("save-slice import should project"); crate::execute_step_command( &mut import.state, &crate::StepCommand::ServiceTriggerKind { trigger_kind: 6 }, ) .expect("setup trigger should execute"); assert_eq!( import.state.named_locomotive_availability.get("Big Boy"), Some(&42) ); assert_eq!( import.state.named_locomotive_cost.get("Locomotive 1"), Some(&250000) ); assert_eq!(import.state.cargo_production_overrides.get(&1), Some(&125)); assert_eq!(import.state.cargo_production_overrides.get(&5), Some(&75)); assert_eq!(import.state.cargo_production_overrides.get(&9), Some(&30)); assert_eq!(import.state.cargo_catalog.len(), 3); assert_eq!( import.state.cargo_catalog[0].cargo_class, crate::RuntimeCargoClass::Factory ); assert_eq!( import.state.cargo_catalog[1].cargo_class, crate::RuntimeCargoClass::FarmMine ); assert_eq!( import.state.cargo_catalog[2].cargo_class, crate::RuntimeCargoClass::Other ); assert_eq!(import.state.event_runtime_records.len(), 2); assert_eq!( import.state.event_runtime_records[1].conditions, vec![ RuntimeCondition::NamedLocomotiveAvailabilityThreshold { name: "Big Boy".to_string(), comparator: RuntimeConditionComparator::Eq, value: 42, }, RuntimeCondition::NamedLocomotiveCostThreshold { name: "Locomotive 1".to_string(), comparator: RuntimeConditionComparator::Eq, value: 250000, }, RuntimeCondition::CargoProductionSlotThreshold { slot: 1, label: "Cargo Production Slot 1".to_string(), comparator: RuntimeConditionComparator::Eq, value: 125, }, RuntimeCondition::CargoProductionTotalThreshold { comparator: RuntimeConditionComparator::Eq, value: 230, }, RuntimeCondition::FactoryProductionTotalThreshold { comparator: RuntimeConditionComparator::Eq, value: 125, }, RuntimeCondition::FarmMineProductionTotalThreshold { comparator: RuntimeConditionComparator::Eq, value: 75, }, RuntimeCondition::OtherCargoProductionTotalThreshold { comparator: RuntimeConditionComparator::Eq, value: 30, }, RuntimeCondition::LimitedTrackBuildingAmountThreshold { comparator: RuntimeConditionComparator::Eq, value: 18, }, RuntimeCondition::TerritoryAccessCostThreshold { comparator: RuntimeConditionComparator::Eq, value: 750000, }, ] ); crate::execute_step_command( &mut import.state, &crate::StepCommand::ServiceTriggerKind { trigger_kind: 7 }, ) .expect("gated trigger should execute"); assert_eq!( import .state .world_flags .get("world.world_scalar_conditions_passed"), Some(&true) ); } #[test] fn overlays_selected_chairman_conditions_into_imported_runtime_records() { let base_state = RuntimeState { calendar: CalendarPoint { year: 1840, month_slot: 1, phase_slot: 2, tick_slot: 3, }, world_flags: BTreeMap::new(), save_profile: RuntimeSaveProfileState::default(), world_restore: RuntimeWorldRestoreState::default(), metadata: BTreeMap::new(), companies: vec![crate::RuntimeCompany { company_id: 1, controller_kind: RuntimeCompanyControllerKind::Human, current_cash: 100, debt: 0, credit_rating_score: None, prime_rate: None, track_piece_counts: RuntimeTrackPieceCounts::default(), active: true, available_track_laying_capacity: None, linked_chairman_profile_id: Some(1), book_value_per_share: 2620, investor_confidence: 37, management_attitude: 58, takeover_cooldown_year: Some(1839), merger_cooldown_year: Some(1838), }], selected_company_id: Some(1), players: Vec::new(), selected_player_id: None, chairman_profiles: vec![ crate::RuntimeChairmanProfile { profile_id: 1, name: "Chairman One".to_string(), active: true, current_cash: 500, linked_company_id: Some(1), company_holdings: BTreeMap::new(), holdings_value_total: 700, net_worth_total: 1200, purchasing_power_total: 1500, }, crate::RuntimeChairmanProfile { profile_id: 2, name: "Chairman Two".to_string(), active: true, current_cash: 200, linked_company_id: None, company_holdings: BTreeMap::new(), holdings_value_total: 400, net_worth_total: 600, purchasing_power_total: 800, }, ], selected_chairman_profile_id: Some(1), trains: Vec::new(), locomotive_catalog: Vec::new(), cargo_catalog: Vec::new(), territories: Vec::new(), company_territory_track_piece_counts: Vec::new(), company_territory_access: Vec::new(), packed_event_collection: None, event_runtime_records: Vec::new(), candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), all_cargo_price_override: None, named_cargo_price_overrides: BTreeMap::new(), all_cargo_production_override: None, factory_cargo_production_override: None, farm_mine_cargo_production_override: None, named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_runtime_variables: BTreeMap::new(), company_runtime_variables: BTreeMap::new(), player_runtime_variables: BTreeMap::new(), territory_runtime_variables: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 71, live_record_count: 1, live_entry_ids: vec![71], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 71, payload_offset: Some(0x7202), payload_len: Some(136), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(6), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(real_compact_control()), text_bands: vec![], standalone_condition_row_count: 1, standalone_condition_rows: vec![ crate::SmpLoadedPackedEventConditionRowSummary { row_index: 0, raw_condition_id: 2218, subtype: 4, flag_bytes: { let mut bytes = vec![0; 25]; bytes[0..4].copy_from_slice(&500_i32.to_le_bytes()); bytes }, candidate_name: None, comparator: Some("eq".to_string()), metric: Some("Player Cash".to_string()), semantic_family: Some("numeric_threshold".to_string()), semantic_preview: Some("Test Player Cash == 500".to_string()), recovered_cargo_slot: None, recovered_cargo_class: None, requires_candidate_name_binding: false, notes: vec![], }, ], negative_sentinel_scope: Some(selected_chairman_negative_sentinel_scope()), grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: vec![real_world_flag_row( 110, "Disable Stock Buying and Selling", true, )], decoded_conditions: vec![RuntimeCondition::ChairmanNumericThreshold { target: RuntimeChairmanTarget::SelectedChairman, metric: crate::RuntimeChairmanMetric::CurrentCash, comparator: RuntimeConditionComparator::Eq, value: 500, }], decoded_actions: vec![RuntimeEffect::SetWorldFlag { key: "world.chairman_condition_imported".to_string(), value: true, }], executable_import_ready: true, notes: vec!["chairman metric condition gates a world-side effect".to_string()], }], }), notes: vec![], }; let mut import = project_save_slice_overlay_to_runtime_state_import( &base_state, &save_slice, "chairman-condition-overlay", None, ) .expect("overlay import should project"); assert_eq!(import.state.event_runtime_records.len(), 1); assert_eq!( import.state.event_runtime_records[0].conditions, vec![RuntimeCondition::ChairmanNumericThreshold { target: RuntimeChairmanTarget::SelectedChairman, metric: crate::RuntimeChairmanMetric::CurrentCash, comparator: RuntimeConditionComparator::Eq, value: 500, }] ); crate::execute_step_command( &mut import.state, &crate::StepCommand::ServiceTriggerKind { trigger_kind: 6 }, ) .expect("chairman-gated trigger should execute"); assert_eq!( import .state .world_flags .get("world.chairman_condition_imported"), Some(&true) ); } #[test] fn overlays_book_value_conditions_into_imported_runtime_records() { let base_state = RuntimeState { calendar: CalendarPoint { year: 1840, month_slot: 1, phase_slot: 2, tick_slot: 3, }, world_flags: BTreeMap::new(), save_profile: RuntimeSaveProfileState::default(), world_restore: RuntimeWorldRestoreState::default(), metadata: BTreeMap::new(), companies: vec![crate::RuntimeCompany { company_id: 1, controller_kind: RuntimeCompanyControllerKind::Human, current_cash: 100, debt: 0, credit_rating_score: None, prime_rate: None, track_piece_counts: RuntimeTrackPieceCounts::default(), active: true, available_track_laying_capacity: None, linked_chairman_profile_id: None, book_value_per_share: 2620, investor_confidence: 37, management_attitude: 58, takeover_cooldown_year: Some(1839), merger_cooldown_year: Some(1838), }], selected_company_id: Some(1), players: Vec::new(), selected_player_id: None, chairman_profiles: Vec::new(), selected_chairman_profile_id: None, trains: Vec::new(), locomotive_catalog: Vec::new(), cargo_catalog: Vec::new(), territories: Vec::new(), company_territory_track_piece_counts: Vec::new(), company_territory_access: Vec::new(), packed_event_collection: None, event_runtime_records: Vec::new(), candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), all_cargo_price_override: None, named_cargo_price_overrides: BTreeMap::new(), all_cargo_production_override: None, factory_cargo_production_override: None, farm_mine_cargo_production_override: None, named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_runtime_variables: BTreeMap::new(), company_runtime_variables: BTreeMap::new(), player_runtime_variables: BTreeMap::new(), territory_runtime_variables: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }; let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 72, live_record_count: 1, live_entry_ids: vec![72], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 72, payload_offset: Some(0x7202), payload_len: Some(136), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(real_compact_control()), text_bands: vec![], standalone_condition_row_count: 1, standalone_condition_rows: vec![ crate::SmpLoadedPackedEventConditionRowSummary { row_index: 0, raw_condition_id: 2620, subtype: 4, flag_bytes: { let mut bytes = vec![0; 25]; bytes[0..4].copy_from_slice(&2620_i32.to_le_bytes()); bytes }, candidate_name: None, comparator: Some("eq".to_string()), metric: Some("Book Value Per Share".to_string()), semantic_family: Some("numeric_threshold".to_string()), semantic_preview: Some("Test Book Value Per Share == 2620".to_string()), recovered_cargo_slot: None, recovered_cargo_class: None, requires_candidate_name_binding: false, notes: vec![], }, ], negative_sentinel_scope: Some(company_negative_sentinel_scope( RuntimeCompanyConditionTestScope::SelectedCompanyOnly, )), grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: vec![real_world_flag_row( 110, "Disable Stock Buying and Selling", true, )], decoded_conditions: vec![RuntimeCondition::CompanyNumericThreshold { target: RuntimeCompanyTarget::ConditionTrueCompany, metric: crate::RuntimeCompanyMetric::BookValuePerShare, comparator: RuntimeConditionComparator::Eq, value: 2620, }], decoded_actions: vec![RuntimeEffect::SetWorldFlag { key: "world.book_value_condition_imported".to_string(), value: true, }], executable_import_ready: true, notes: vec![ "book value per share condition gates a world-side effect".to_string(), ], }], }), notes: vec![], }; let mut import = project_save_slice_overlay_to_runtime_state_import( &base_state, &save_slice, "company-book-value-condition-overlay", None, ) .expect("overlay import should project"); assert_eq!(import.state.event_runtime_records.len(), 1); assert_eq!( import.state.event_runtime_records[0].conditions, vec![RuntimeCondition::CompanyNumericThreshold { target: RuntimeCompanyTarget::SelectedCompany, metric: crate::RuntimeCompanyMetric::BookValuePerShare, comparator: RuntimeConditionComparator::Eq, value: 2620, }] ); crate::execute_step_command( &mut import.state, &crate::StepCommand::ServiceTriggerKind { trigger_kind: 7 }, ) .expect("company-governance trigger should execute"); assert_eq!( import .state .world_flags .get("world.book_value_condition_imported"), Some(&true) ); } #[test] fn imports_investor_confidence_condition_from_save_slice_company_context() { let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: Some(save_company_roster()), chairman_profile_table: Some(save_chairman_profile_table()), special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 73, live_record_count: 1, live_entry_ids: vec![73], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 73, payload_offset: Some(0x7202), payload_len: Some(136), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(real_compact_control()), text_bands: vec![], standalone_condition_row_count: 1, standalone_condition_rows: vec![ crate::SmpLoadedPackedEventConditionRowSummary { row_index: 0, raw_condition_id: 2366, subtype: 4, flag_bytes: { let mut bytes = vec![0; 25]; bytes[0..4].copy_from_slice(&37_i32.to_le_bytes()); bytes }, candidate_name: None, comparator: Some("eq".to_string()), metric: Some("Investor Confidence".to_string()), semantic_family: Some("numeric_threshold".to_string()), semantic_preview: Some("Test Investor Confidence == 37".to_string()), recovered_cargo_slot: None, recovered_cargo_class: None, requires_candidate_name_binding: false, notes: vec![], }, ], negative_sentinel_scope: Some(company_negative_sentinel_scope( RuntimeCompanyConditionTestScope::SelectedCompanyOnly, )), grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: vec![real_world_flag_row( 110, "Disable Stock Buying and Selling", true, )], decoded_conditions: vec![RuntimeCondition::CompanyNumericThreshold { target: RuntimeCompanyTarget::ConditionTrueCompany, metric: crate::RuntimeCompanyMetric::InvestorConfidence, comparator: RuntimeConditionComparator::Eq, value: 37, }], decoded_actions: vec![RuntimeEffect::SetWorldFlag { key: "world.investor_confidence_condition_imported".to_string(), value: true, }], executable_import_ready: true, notes: vec![ "investor confidence condition gates a world-side effect".to_string(), ], }], }), notes: vec![], }; let mut import = project_save_slice_to_runtime_state_import( &save_slice, "company-investor-confidence-condition", None, ) .expect("save-slice import should project"); assert_eq!(import.state.event_runtime_records.len(), 1); assert_eq!( import.state.event_runtime_records[0].conditions, vec![RuntimeCondition::CompanyNumericThreshold { target: RuntimeCompanyTarget::SelectedCompany, metric: crate::RuntimeCompanyMetric::InvestorConfidence, comparator: RuntimeConditionComparator::Eq, value: 37, }] ); crate::execute_step_command( &mut import.state, &crate::StepCommand::ServiceTriggerKind { trigger_kind: 7 }, ) .expect("investor-confidence trigger should execute"); assert_eq!( import .state .world_flags .get("world.investor_confidence_condition_imported"), Some(&true) ); } #[test] fn imports_management_attitude_condition_from_save_slice_company_context() { let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: Some(save_company_roster()), chairman_profile_table: Some(save_chairman_profile_table()), special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 74, live_record_count: 1, live_entry_ids: vec![74], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 74, payload_offset: Some(0x7202), payload_len: Some(136), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(real_compact_control()), text_bands: vec![], standalone_condition_row_count: 1, standalone_condition_rows: vec![ crate::SmpLoadedPackedEventConditionRowSummary { row_index: 0, raw_condition_id: 2369, subtype: 4, flag_bytes: { let mut bytes = vec![0; 25]; bytes[0..4].copy_from_slice(&58_i32.to_le_bytes()); bytes }, candidate_name: None, comparator: Some("eq".to_string()), metric: Some("Management Attitude".to_string()), semantic_family: Some("numeric_threshold".to_string()), semantic_preview: Some("Test Management Attitude == 58".to_string()), recovered_cargo_slot: None, recovered_cargo_class: None, requires_candidate_name_binding: false, notes: vec![], }, ], negative_sentinel_scope: Some(company_negative_sentinel_scope( RuntimeCompanyConditionTestScope::SelectedCompanyOnly, )), grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: vec![real_world_flag_row( 110, "Disable Stock Buying and Selling", true, )], decoded_conditions: vec![RuntimeCondition::CompanyNumericThreshold { target: RuntimeCompanyTarget::ConditionTrueCompany, metric: crate::RuntimeCompanyMetric::ManagementAttitude, comparator: RuntimeConditionComparator::Eq, value: 58, }], decoded_actions: vec![RuntimeEffect::SetWorldFlag { key: "world.management_attitude_condition_imported".to_string(), value: true, }], executable_import_ready: true, notes: vec![ "management attitude condition gates a world-side effect".to_string(), ], }], }), notes: vec![], }; let mut import = project_save_slice_to_runtime_state_import( &save_slice, "company-management-attitude-condition", None, ) .expect("save-slice import should project"); assert_eq!(import.state.event_runtime_records.len(), 1); assert_eq!( import.state.event_runtime_records[0].conditions, vec![RuntimeCondition::CompanyNumericThreshold { target: RuntimeCompanyTarget::SelectedCompany, metric: crate::RuntimeCompanyMetric::ManagementAttitude, comparator: RuntimeConditionComparator::Eq, value: 58, }] ); crate::execute_step_command( &mut import.state, &crate::StepCommand::ServiceTriggerKind { trigger_kind: 7 }, ) .expect("management-attitude trigger should execute"); assert_eq!( import .state .world_flags .get("world.management_attitude_condition_imported"), Some(&true) ); } #[test] fn overlays_recovered_world_toggle_batch_into_executable_runtime_record() { let base_state = state(); let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 25, live_record_count: 1, live_entry_ids: vec![25], decoded_record_count: 1, imported_runtime_record_count: 1, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 25, payload_offset: Some(0x7200), payload_len: Some(168), decode_status: "executable".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(real_compact_control()), text_bands: packed_text_bands(), standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![3, 0, 0, 0], grouped_effect_rows: vec![ real_world_flag_row(111, "Disable Margin Buying/Short Selling Stock", true), real_world_flag_row(120, "Disable All Track Building", true), real_world_flag_row(131, "Disable Starting Any Companies", false), ], decoded_conditions: Vec::new(), decoded_actions: vec![ RuntimeEffect::SetWorldFlag { key: "world.disable_margin_buying_short_selling_stock".to_string(), value: true, }, RuntimeEffect::SetWorldFlag { key: "world.disable_all_track_building".to_string(), value: true, }, RuntimeEffect::SetWorldFlag { key: "world.disable_starting_any_companies".to_string(), value: false, }, ], executable_import_ready: true, notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()], }], }), notes: vec![], }; let mut import = project_save_slice_overlay_to_runtime_state_import( &base_state, &save_slice, "real-world-toggle-batch-overlay", None, ) .expect("overlay import should project"); crate::execute_step_command( &mut import.state, &crate::StepCommand::ServiceTriggerKind { trigger_kind: 7 }, ) .expect("trigger service should execute"); assert_eq!( import .state .world_flags .get("world.disable_margin_buying_short_selling_stock"), Some(&true) ); assert_eq!( import .state .world_flags .get("world.disable_all_track_building"), Some(&true) ); assert_eq!( import .state .world_flags .get("world.disable_starting_any_companies"), Some(&false) ); } #[test] fn overlays_recovered_late_world_toggle_batch_into_executable_runtime_record() { let base_state = state(); let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 26, live_record_count: 1, live_entry_ids: vec![26], decoded_record_count: 1, imported_runtime_record_count: 1, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 26, payload_offset: Some(0x7200), payload_len: Some(184), decode_status: "executable".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(real_compact_control()), text_bands: packed_text_bands(), standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![5, 0, 0, 0], grouped_effect_rows: vec![ real_world_flag_row(139, "Use Bio-Accelerator Cars", true), real_world_flag_row(140, "Disable Cargo Economy", true), real_world_flag_row(142, "Disable Train Crashes", false), real_world_flag_row(143, "Disable Train Crashes AND Breakdowns", true), real_world_flag_row(144, "AI Ignore Territories At Startup", true), ], decoded_conditions: Vec::new(), decoded_actions: vec![ RuntimeEffect::SetWorldFlag { key: "world.use_bio_accelerator_cars".to_string(), value: true, }, RuntimeEffect::SetWorldFlag { key: "world.disable_cargo_economy".to_string(), value: true, }, RuntimeEffect::SetWorldFlag { key: "world.disable_train_crashes".to_string(), value: false, }, RuntimeEffect::SetWorldFlag { key: "world.disable_train_crashes_and_breakdowns".to_string(), value: true, }, RuntimeEffect::SetWorldFlag { key: "world.ai_ignore_territories_at_startup".to_string(), value: true, }, ], executable_import_ready: true, notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()], }], }), notes: vec![], }; let mut import = project_save_slice_overlay_to_runtime_state_import( &base_state, &save_slice, "real-late-world-toggle-batch-overlay", None, ) .expect("overlay import should project"); crate::execute_step_command( &mut import.state, &crate::StepCommand::ServiceTriggerKind { trigger_kind: 7 }, ) .expect("trigger service should execute"); assert_eq!( import .state .world_flags .get("world.use_bio_accelerator_cars"), Some(&true) ); assert_eq!( import.state.world_flags.get("world.disable_cargo_economy"), Some(&true) ); assert_eq!( import.state.world_flags.get("world.disable_train_crashes"), Some(&false) ); assert_eq!( import .state .world_flags .get("world.disable_train_crashes_and_breakdowns"), Some(&true) ); assert_eq!( import .state .world_flags .get("world.ai_ignore_territories_at_startup"), Some(&true) ); } #[test] fn overlays_real_confiscate_all_descriptor_into_executable_runtime_record() { let base_state = RuntimeState { companies: vec![ crate::RuntimeCompany { company_id: 42, controller_kind: RuntimeCompanyControllerKind::Human, current_cash: 500, debt: 20, credit_rating_score: None, prime_rate: None, track_piece_counts: RuntimeTrackPieceCounts::default(), active: true, available_track_laying_capacity: None, linked_chairman_profile_id: None, book_value_per_share: 0, investor_confidence: 0, management_attitude: 0, takeover_cooldown_year: None, merger_cooldown_year: None, }, crate::RuntimeCompany { company_id: 7, controller_kind: RuntimeCompanyControllerKind::Ai, current_cash: 90, debt: 10, credit_rating_score: None, prime_rate: None, track_piece_counts: RuntimeTrackPieceCounts::default(), active: true, available_track_laying_capacity: None, linked_chairman_profile_id: None, book_value_per_share: 0, investor_confidence: 0, management_attitude: 0, takeover_cooldown_year: None, merger_cooldown_year: None, }, ], selected_company_id: Some(42), trains: vec![ RuntimeTrain { train_id: 1, owner_company_id: 42, territory_id: None, locomotive_name: Some("Mikado".to_string()), active: true, retired: false, }, RuntimeTrain { train_id: 2, owner_company_id: 7, territory_id: None, locomotive_name: Some("Orca".to_string()), active: true, retired: false, }, ], ..state() }; let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 19, live_record_count: 1, live_entry_ids: vec![19], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 19, payload_offset: Some(0x7202), payload_len: Some(120), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(crate::SmpLoadedPackedEventCompactControlSummary { mode_byte_0x7ef: 7, primary_selector_0x7f0: 0x63, grouped_mode_0x7f4: 2, one_shot_header_0x7f5: 0, modifier_flag_0x7f9: 1, modifier_flag_0x7fa: 0, grouped_target_scope_ordinals_0x7fb: vec![1, 1, 1, 1], grouped_scope_checkboxes_0x7ff: vec![1, 0, 0, 0], summary_toggle_0x800: 1, grouped_territory_selectors_0x80f: vec![-1, -1, -1, -1], }), text_bands: packed_text_bands(), standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: vec![real_confiscate_all_row(true)], decoded_conditions: Vec::new(), decoded_actions: vec![RuntimeEffect::ConfiscateCompanyAssets { target: RuntimeCompanyTarget::SelectedCompany, }], executable_import_ready: true, notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()], }], }), notes: vec![], }; let mut import = project_save_slice_overlay_to_runtime_state_import( &base_state, &save_slice, "real-confiscate-all-overlay", None, ) .expect("overlay import should project"); execute_step_command( &mut import.state, &StepCommand::ServiceTriggerKind { trigger_kind: 7 }, ) .expect("real confiscate-all descriptor should execute"); assert_eq!(import.state.companies[0].current_cash, 0); assert_eq!(import.state.companies[0].debt, 0); assert!(!import.state.companies[0].active); assert_eq!(import.state.selected_company_id, None); assert!(!import.state.trains[0].active); assert!(import.state.trains[0].retired); assert!(import.state.trains[1].active); assert!(!import.state.trains[1].retired); } #[test] fn keeps_real_confiscate_all_false_row_parity_only() { let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 20, live_record_count: 1, live_entry_ids: vec![20], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 20, payload_offset: Some(0x7202), payload_len: Some(120), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(real_compact_control()), text_bands: packed_text_bands(), standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: vec![real_confiscate_all_row(false)], decoded_conditions: Vec::new(), decoded_actions: vec![], executable_import_ready: false, notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()], }], }), notes: vec![], }; let import = project_save_slice_to_runtime_state_import( &save_slice, "real-confiscate-all-false", None, ) .expect("save slice should project"); assert!(import.state.event_runtime_records.is_empty()); assert_eq!( import .state .packed_event_collection .as_ref() .and_then(|summary| summary.records[0].import_outcome.as_deref()), Some("blocked_confiscation_variant") ); } #[test] fn blocks_confiscate_all_without_train_context() { let base_state = RuntimeState { companies: vec![crate::RuntimeCompany { company_id: 42, controller_kind: RuntimeCompanyControllerKind::Human, current_cash: 500, debt: 20, credit_rating_score: None, prime_rate: None, track_piece_counts: RuntimeTrackPieceCounts::default(), active: true, available_track_laying_capacity: None, linked_chairman_profile_id: None, book_value_per_share: 0, investor_confidence: 0, management_attitude: 0, takeover_cooldown_year: None, merger_cooldown_year: None, }], selected_company_id: Some(42), ..state() }; let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 24, live_record_count: 1, live_entry_ids: vec![24], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 24, payload_offset: Some(0x7202), payload_len: Some(120), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(crate::SmpLoadedPackedEventCompactControlSummary { mode_byte_0x7ef: 7, primary_selector_0x7f0: 0x63, grouped_mode_0x7f4: 2, one_shot_header_0x7f5: 0, modifier_flag_0x7f9: 1, modifier_flag_0x7fa: 0, grouped_target_scope_ordinals_0x7fb: vec![1, 1, 1, 1], grouped_scope_checkboxes_0x7ff: vec![1, 0, 0, 0], summary_toggle_0x800: 1, grouped_territory_selectors_0x80f: vec![-1, -1, -1, -1], }), text_bands: packed_text_bands(), standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: vec![real_confiscate_all_row(true)], decoded_conditions: Vec::new(), decoded_actions: vec![RuntimeEffect::ConfiscateCompanyAssets { target: RuntimeCompanyTarget::SelectedCompany, }], executable_import_ready: true, notes: vec![], }], }), notes: vec![], }; let import = project_save_slice_overlay_to_runtime_state_import( &base_state, &save_slice, "real-confiscate-all-missing-trains", None, ) .expect("overlay import should project"); assert_eq!( import .state .packed_event_collection .as_ref() .and_then(|summary| summary.records[0].import_outcome.as_deref()), Some("blocked_missing_train_context") ); assert!(import.state.event_runtime_records.is_empty()); } #[test] fn overlays_real_retire_train_descriptor_by_company_scope() { let base_state = RuntimeState { companies: vec![ crate::RuntimeCompany { company_id: 42, controller_kind: RuntimeCompanyControllerKind::Human, current_cash: 500, debt: 20, credit_rating_score: None, prime_rate: None, track_piece_counts: RuntimeTrackPieceCounts::default(), active: true, available_track_laying_capacity: None, linked_chairman_profile_id: None, book_value_per_share: 0, investor_confidence: 0, management_attitude: 0, takeover_cooldown_year: None, merger_cooldown_year: None, }, crate::RuntimeCompany { company_id: 7, controller_kind: RuntimeCompanyControllerKind::Ai, current_cash: 90, debt: 10, credit_rating_score: None, prime_rate: None, track_piece_counts: RuntimeTrackPieceCounts::default(), active: true, available_track_laying_capacity: None, linked_chairman_profile_id: None, book_value_per_share: 0, investor_confidence: 0, management_attitude: 0, takeover_cooldown_year: None, merger_cooldown_year: None, }, ], selected_company_id: Some(42), trains: vec![ RuntimeTrain { train_id: 1, owner_company_id: 42, territory_id: Some(7), locomotive_name: Some("Mikado".to_string()), active: true, retired: false, }, RuntimeTrain { train_id: 2, owner_company_id: 42, territory_id: Some(8), locomotive_name: Some("Orca".to_string()), active: true, retired: false, }, RuntimeTrain { train_id: 3, owner_company_id: 7, territory_id: Some(7), locomotive_name: Some("Mikado".to_string()), active: true, retired: false, }, ], territories: vec![ crate::RuntimeTerritory { territory_id: 7, name: Some("Appalachia".to_string()), track_piece_counts: RuntimeTrackPieceCounts::default(), }, crate::RuntimeTerritory { territory_id: 8, name: Some("Great Plains".to_string()), track_piece_counts: RuntimeTrackPieceCounts::default(), }, ], ..state() }; let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 21, live_record_count: 1, live_entry_ids: vec![21], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 21, payload_offset: Some(0x7202), payload_len: Some(120), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(crate::SmpLoadedPackedEventCompactControlSummary { mode_byte_0x7ef: 7, primary_selector_0x7f0: 0x63, grouped_mode_0x7f4: 2, one_shot_header_0x7f5: 0, modifier_flag_0x7f9: 1, modifier_flag_0x7fa: 0, grouped_target_scope_ordinals_0x7fb: vec![1, 1, 1, 1], grouped_scope_checkboxes_0x7ff: vec![1, 0, 0, 0], summary_toggle_0x800: 1, grouped_territory_selectors_0x80f: vec![-1, -1, -1, -1], }), text_bands: packed_text_bands(), standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: vec![real_retire_train_row(true, None, vec![])], decoded_conditions: Vec::new(), decoded_actions: vec![RuntimeEffect::RetireTrains { company_target: Some(RuntimeCompanyTarget::SelectedCompany), territory_target: None, locomotive_name: None, }], executable_import_ready: true, notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()], }], }), notes: vec![], }; let mut import = project_save_slice_overlay_to_runtime_state_import( &base_state, &save_slice, "real-retire-train-company-overlay", None, ) .expect("overlay import should project"); execute_step_command( &mut import.state, &StepCommand::ServiceTriggerKind { trigger_kind: 7 }, ) .expect("real retire-train descriptor should execute"); assert!(import.state.trains[0].retired); assert!(import.state.trains[1].retired); assert!(!import.state.trains[2].retired); } #[test] fn overlays_real_retire_train_descriptor_by_territory_and_locomotive_scope() { let base_state = RuntimeState { companies: vec![crate::RuntimeCompany { company_id: 42, controller_kind: RuntimeCompanyControllerKind::Human, current_cash: 500, debt: 20, credit_rating_score: None, prime_rate: None, track_piece_counts: RuntimeTrackPieceCounts::default(), active: true, available_track_laying_capacity: None, linked_chairman_profile_id: None, book_value_per_share: 0, investor_confidence: 0, management_attitude: 0, takeover_cooldown_year: None, merger_cooldown_year: None, }], selected_company_id: Some(42), trains: vec![ RuntimeTrain { train_id: 1, owner_company_id: 42, territory_id: Some(7), locomotive_name: Some("Mikado".to_string()), active: true, retired: false, }, RuntimeTrain { train_id: 2, owner_company_id: 42, territory_id: Some(7), locomotive_name: Some("Orca".to_string()), active: true, retired: false, }, RuntimeTrain { train_id: 3, owner_company_id: 42, territory_id: Some(8), locomotive_name: Some("Mikado".to_string()), active: true, retired: false, }, ], territories: vec![ crate::RuntimeTerritory { territory_id: 7, name: Some("Appalachia".to_string()), track_piece_counts: RuntimeTrackPieceCounts::default(), }, crate::RuntimeTerritory { territory_id: 8, name: Some("Great Plains".to_string()), track_piece_counts: RuntimeTrackPieceCounts::default(), }, ], ..state() }; let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 22, live_record_count: 1, live_entry_ids: vec![22], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 22, payload_offset: Some(0x7202), payload_len: Some(120), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(crate::SmpLoadedPackedEventCompactControlSummary { mode_byte_0x7ef: 7, primary_selector_0x7f0: 0x63, grouped_mode_0x7f4: 2, one_shot_header_0x7f5: 0, modifier_flag_0x7f9: 1, modifier_flag_0x7fa: 0, grouped_target_scope_ordinals_0x7fb: vec![8, 1, 1, 1], grouped_scope_checkboxes_0x7ff: vec![1, 0, 0, 0], summary_toggle_0x800: 1, grouped_territory_selectors_0x80f: vec![7, -1, -1, -1], }), text_bands: packed_text_bands(), standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: vec![real_retire_train_row(true, Some("Mikado"), vec![])], decoded_conditions: Vec::new(), decoded_actions: vec![RuntimeEffect::RetireTrains { company_target: None, territory_target: Some(RuntimeTerritoryTarget::Ids { ids: vec![7] }), locomotive_name: Some("Mikado".to_string()), }], executable_import_ready: true, notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()], }], }), notes: vec![], }; let mut import = project_save_slice_overlay_to_runtime_state_import( &base_state, &save_slice, "real-retire-train-territory-overlay", None, ) .expect("overlay import should project"); execute_step_command( &mut import.state, &StepCommand::ServiceTriggerKind { trigger_kind: 7 }, ) .expect("territory-scoped retire-train descriptor should execute"); assert!(import.state.trains[0].retired); assert!(!import.state.trains[1].retired); assert!(!import.state.trains[2].retired); } #[test] fn keeps_real_retire_train_missing_scope_parity_only() { let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 23, live_record_count: 1, live_entry_ids: vec![23], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 23, payload_offset: Some(0x7202), payload_len: Some(120), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(crate::SmpLoadedPackedEventCompactControlSummary { mode_byte_0x7ef: 7, primary_selector_0x7f0: 0x63, grouped_mode_0x7f4: 2, one_shot_header_0x7f5: 0, modifier_flag_0x7f9: 1, modifier_flag_0x7fa: 0, grouped_target_scope_ordinals_0x7fb: vec![8, 1, 1, 1], grouped_scope_checkboxes_0x7ff: vec![1, 0, 0, 0], summary_toggle_0x800: 1, grouped_territory_selectors_0x80f: vec![-1, -1, -1, -1], }), text_bands: packed_text_bands(), standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: vec![real_retire_train_row( true, Some("Mikado"), vec!["retire train row is missing company and territory scope".to_string()], )], decoded_conditions: Vec::new(), decoded_actions: vec![], executable_import_ready: false, notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()], }], }), notes: vec![], }; let import = project_save_slice_to_runtime_state_import( &save_slice, "real-retire-train-missing-scope", None, ) .expect("save slice should project"); assert!(import.state.event_runtime_records.is_empty()); assert_eq!( import .state .packed_event_collection .as_ref() .and_then(|summary| summary.records[0].import_outcome.as_deref()), Some("blocked_retire_train_scope") ); } #[test] fn blocks_retire_train_without_train_territory_context() { let base_state = RuntimeState { companies: vec![crate::RuntimeCompany { company_id: 42, controller_kind: RuntimeCompanyControllerKind::Human, current_cash: 500, debt: 20, credit_rating_score: None, prime_rate: None, track_piece_counts: RuntimeTrackPieceCounts::default(), active: true, available_track_laying_capacity: None, linked_chairman_profile_id: None, book_value_per_share: 0, investor_confidence: 0, management_attitude: 0, takeover_cooldown_year: None, merger_cooldown_year: None, }], selected_company_id: Some(42), trains: vec![RuntimeTrain { train_id: 1, owner_company_id: 42, territory_id: None, locomotive_name: Some("Mikado".to_string()), active: true, retired: false, }], territories: vec![crate::RuntimeTerritory { territory_id: 7, name: Some("Appalachia".to_string()), track_piece_counts: RuntimeTrackPieceCounts::default(), }], ..state() }; let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 25, live_record_count: 1, live_entry_ids: vec![25], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 25, payload_offset: Some(0x7202), payload_len: Some(120), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(crate::SmpLoadedPackedEventCompactControlSummary { mode_byte_0x7ef: 7, primary_selector_0x7f0: 0x63, grouped_mode_0x7f4: 2, one_shot_header_0x7f5: 0, modifier_flag_0x7f9: 1, modifier_flag_0x7fa: 0, grouped_target_scope_ordinals_0x7fb: vec![8, 1, 1, 1], grouped_scope_checkboxes_0x7ff: vec![1, 0, 0, 0], summary_toggle_0x800: 1, grouped_territory_selectors_0x80f: vec![7, -1, -1, -1], }), text_bands: packed_text_bands(), standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: vec![real_retire_train_row(true, Some("Mikado"), vec![])], decoded_conditions: Vec::new(), decoded_actions: vec![RuntimeEffect::RetireTrains { company_target: None, territory_target: Some(RuntimeTerritoryTarget::Ids { ids: vec![7] }), locomotive_name: Some("Mikado".to_string()), }], executable_import_ready: true, notes: vec![], }], }), notes: vec![], }; let import = project_save_slice_overlay_to_runtime_state_import( &base_state, &save_slice, "real-retire-train-missing-train-territory", None, ) .expect("overlay import should project"); assert_eq!( import .state .packed_event_collection .as_ref() .and_then(|summary| summary.records[0].import_outcome.as_deref()), Some("blocked_missing_train_territory_context") ); assert!(import.state.event_runtime_records.is_empty()); } #[test] fn keeps_mixed_real_records_out_of_event_runtime_records() { let base_state = RuntimeState { companies: vec![crate::RuntimeCompany { company_id: 42, controller_kind: RuntimeCompanyControllerKind::Human, current_cash: 500, debt: 20, credit_rating_score: None, prime_rate: None, track_piece_counts: RuntimeTrackPieceCounts::default(), active: true, available_track_laying_capacity: None, linked_chairman_profile_id: None, book_value_per_share: 0, investor_confidence: 0, management_attitude: 0, takeover_cooldown_year: None, merger_cooldown_year: None, }], selected_company_id: Some(42), ..state() }; let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 17, live_record_count: 1, live_entry_ids: vec![17], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 17, payload_offset: Some(0x7202), payload_len: Some(160), decode_status: "parity_only".to_string(), payload_family: "real_packed_v1".to_string(), trigger_kind: Some(7), active: None, marks_collection_dirty: None, one_shot: Some(false), compact_control: Some(crate::SmpLoadedPackedEventCompactControlSummary { mode_byte_0x7ef: 7, primary_selector_0x7f0: 0x63, grouped_mode_0x7f4: 2, one_shot_header_0x7f5: 0, modifier_flag_0x7f9: 1, modifier_flag_0x7fa: 0, grouped_target_scope_ordinals_0x7fb: vec![1, 1, 1, 1], grouped_scope_checkboxes_0x7ff: vec![1, 1, 0, 0], summary_toggle_0x800: 1, grouped_territory_selectors_0x80f: vec![-1, -1, -1, -1], }), text_bands: packed_text_bands(), standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![1, 1, 0, 0], grouped_effect_rows: vec![ real_track_capacity_row(18), unsupported_real_grouped_row(), ], decoded_conditions: Vec::new(), decoded_actions: vec![RuntimeEffect::SetCompanyTrackLayingCapacity { target: RuntimeCompanyTarget::SelectedCompany, value: Some(18), }], executable_import_ready: false, notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()], }], }), notes: vec![], }; let import = project_save_slice_overlay_to_runtime_state_import( &base_state, &save_slice, "mixed-real-record-overlay", None, ) .expect("overlay import should project"); assert!(import.state.event_runtime_records.is_empty()); assert_eq!( import .state .packed_event_collection .as_ref() .and_then(|summary| summary.records[0].import_outcome.as_deref()), Some("blocked_confiscation_variant") ); } #[test] fn overlays_save_slice_events_onto_base_company_context() { let base_state = RuntimeState { calendar: CalendarPoint { year: 1845, month_slot: 2, phase_slot: 1, tick_slot: 3, }, world_flags: BTreeMap::from([("base.only".to_string(), true)]), save_profile: RuntimeSaveProfileState::default(), world_restore: RuntimeWorldRestoreState::default(), metadata: BTreeMap::from([("base.note".to_string(), "kept".to_string())]), companies: vec![crate::RuntimeCompany { company_id: 42, controller_kind: RuntimeCompanyControllerKind::Human, current_cash: 500, debt: 20, credit_rating_score: None, prime_rate: None, track_piece_counts: RuntimeTrackPieceCounts::default(), active: true, available_track_laying_capacity: None, linked_chairman_profile_id: None, book_value_per_share: 0, investor_confidence: 0, management_attitude: 0, takeover_cooldown_year: None, merger_cooldown_year: None, }], selected_company_id: Some(42), players: Vec::new(), selected_player_id: None, chairman_profiles: Vec::new(), selected_chairman_profile_id: None, trains: Vec::new(), locomotive_catalog: Vec::new(), cargo_catalog: Vec::new(), territories: Vec::new(), company_territory_track_piece_counts: Vec::new(), company_territory_access: Vec::new(), packed_event_collection: None, event_runtime_records: vec![RuntimeEventRecord { record_id: 1, trigger_kind: 1, active: true, service_count: 0, marks_collection_dirty: false, one_shot: false, has_fired: false, conditions: Vec::new(), effects: vec![], }], candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), all_cargo_price_override: None, named_cargo_price_overrides: BTreeMap::new(), all_cargo_production_override: None, factory_cargo_production_override: None, farm_mine_cargo_production_override: None, named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_runtime_variables: BTreeMap::new(), company_runtime_variables: BTreeMap::new(), player_runtime_variables: BTreeMap::new(), territory_runtime_variables: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState { periodic_boundary_calls: 9, annual_finance_service_calls: 0, trigger_dispatch_counts: BTreeMap::new(), total_event_record_services: 4, dirty_rerun_count: 2, world_issue_opinion_base_terms_raw_i32: Vec::new(), company_market_state: BTreeMap::new(), annual_finance_last_actions: BTreeMap::new(), annual_finance_action_counts: BTreeMap::new(), annual_dividend_adjustment_commit_count: 0, annual_bond_last_retired_principal_total: 0, annual_bond_last_issued_principal_total: 0, annual_stock_repurchase_last_share_count: 0, annual_stock_issue_last_share_count: 0, annual_finance_last_news_family_candidates: BTreeMap::new(), annual_finance_last_news_events: Vec::new(), chairman_issue_opinion_terms_raw_i32: BTreeMap::new(), chairman_personality_raw_u8: BTreeMap::new(), }, }; let save_slice = SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 42, live_record_count: 1, live_entry_ids: vec![7], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 7, payload_offset: Some(0x7202), payload_len: Some(48), decode_status: "parity_only".to_string(), payload_family: "synthetic_harness".to_string(), trigger_kind: Some(7), active: Some(true), marks_collection_dirty: Some(false), one_shot: Some(false), compact_control: None, text_bands: packed_text_bands(), standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![0, 0, 0, 0], grouped_effect_rows: vec![], decoded_conditions: Vec::new(), decoded_actions: vec![RuntimeEffect::AdjustCompanyCash { target: crate::RuntimeCompanyTarget::Ids { ids: vec![42] }, delta: 50, }], executable_import_ready: false, notes: vec!["needs company context".to_string()], }], }), notes: vec![], }; let mut import = project_save_slice_overlay_to_runtime_state_import( &base_state, &save_slice, "overlay-smoke", Some("overlay test".to_string()), ) .expect("overlay import should project"); assert_eq!(import.state.calendar, base_state.calendar); assert_eq!(import.state.companies, base_state.companies); assert_eq!(import.state.service_state, base_state.service_state); assert_eq!(import.state.event_runtime_records.len(), 1); assert_eq!( import .state .packed_event_collection .as_ref() .map(|summary| summary.imported_runtime_record_count), Some(1) ); assert_eq!( import .state .packed_event_collection .as_ref() .and_then(|summary| summary.records[0].import_outcome.as_deref()), Some("imported") ); assert_eq!( import .state .metadata .get("save_slice.import_projection") .map(String::as_str), Some("overlay-runtime-restore-v1") ); assert_eq!( import.state.metadata.get("base.note").map(String::as_str), Some("kept") ); assert_eq!(import.state.world_flags.get("base.only"), Some(&true)); execute_step_command( &mut import.state, &StepCommand::ServiceTriggerKind { trigger_kind: 7 }, ) .expect("overlay-imported company-targeted record should run"); assert_eq!(import.state.companies[0].current_cash, 550); } #[test] fn loads_overlay_import_document_with_relative_paths() { let nonce = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .expect("system time should be after epoch") .as_nanos(); let fixture_dir = std::env::temp_dir().join(format!("rrt-overlay-import-{nonce}")); std::fs::create_dir_all(&fixture_dir).expect("fixture dir should be created"); let snapshot_path = fixture_dir.join("base.json"); let save_slice_path = fixture_dir.join("slice.json"); let overlay_path = fixture_dir.join("overlay.json"); let snapshot = crate::RuntimeSnapshotDocument { format_version: crate::SNAPSHOT_FORMAT_VERSION, snapshot_id: "base".to_string(), source: crate::RuntimeSnapshotSource { source_fixture_id: None, description: Some("base snapshot".to_string()), }, state: RuntimeState { calendar: CalendarPoint { year: 1835, month_slot: 1, phase_slot: 2, tick_slot: 4, }, world_flags: BTreeMap::new(), save_profile: RuntimeSaveProfileState::default(), world_restore: RuntimeWorldRestoreState::default(), metadata: BTreeMap::new(), companies: vec![crate::RuntimeCompany { company_id: 42, controller_kind: RuntimeCompanyControllerKind::Human, current_cash: 100, debt: 0, credit_rating_score: None, prime_rate: None, track_piece_counts: RuntimeTrackPieceCounts::default(), active: true, available_track_laying_capacity: None, linked_chairman_profile_id: None, book_value_per_share: 0, investor_confidence: 0, management_attitude: 0, takeover_cooldown_year: None, merger_cooldown_year: None, }], selected_company_id: Some(42), players: Vec::new(), selected_player_id: None, chairman_profiles: Vec::new(), selected_chairman_profile_id: None, trains: Vec::new(), locomotive_catalog: Vec::new(), cargo_catalog: Vec::new(), territories: Vec::new(), company_territory_track_piece_counts: Vec::new(), company_territory_access: Vec::new(), packed_event_collection: None, event_runtime_records: Vec::new(), candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), all_cargo_price_override: None, named_cargo_price_overrides: BTreeMap::new(), all_cargo_production_override: None, factory_cargo_production_override: None, farm_mine_cargo_production_override: None, named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_runtime_variables: BTreeMap::new(), company_runtime_variables: BTreeMap::new(), player_runtime_variables: BTreeMap::new(), territory_runtime_variables: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), service_state: RuntimeServiceState::default(), }, }; crate::save_runtime_snapshot_document(&snapshot_path, &snapshot) .expect("snapshot should save"); let save_slice_document = RuntimeSaveSliceDocument { format_version: SAVE_SLICE_DOCUMENT_FORMAT_VERSION, save_slice_id: "slice".to_string(), source: RuntimeSaveSliceDocumentSource::default(), save_slice: SmpLoadedSaveSlice { file_extension_hint: Some("gms".to_string()), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), trailer_family: None, bridge_family: None, profile: None, candidate_availability_table: None, named_locomotive_availability_table: None, locomotive_catalog: None, cargo_catalog: None, world_issue_37_state: None, world_economic_tuning_state: None, world_finance_neighborhood_state: None, company_roster: None, chairman_profile_table: None, special_conditions_table: None, event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { source_kind: "packed-event-runtime-collection".to_string(), mechanism_family: "classic-save-rehydrate-v1".to_string(), mechanism_confidence: "grounded".to_string(), container_profile_family: Some("rt3-classic-save-container-v1".to_string()), metadata_tag_offset: 0x7100, records_tag_offset: 0x7200, close_tag_offset: 0x7600, packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 7, live_record_count: 1, live_entry_ids: vec![7], decoded_record_count: 1, imported_runtime_record_count: 0, records: vec![crate::SmpLoadedPackedEventRecordSummary { record_index: 0, live_entry_id: 7, payload_offset: Some(0x7202), payload_len: Some(48), decode_status: "parity_only".to_string(), payload_family: "synthetic_harness".to_string(), trigger_kind: Some(7), active: Some(true), marks_collection_dirty: Some(false), one_shot: Some(false), compact_control: None, text_bands: packed_text_bands(), standalone_condition_row_count: 0, standalone_condition_rows: vec![], negative_sentinel_scope: None, grouped_effect_row_counts: vec![0, 0, 0, 0], grouped_effect_rows: vec![], decoded_conditions: Vec::new(), decoded_actions: vec![RuntimeEffect::AdjustCompanyCash { target: crate::RuntimeCompanyTarget::Ids { ids: vec![42] }, delta: 50, }], executable_import_ready: false, notes: vec!["needs company context".to_string()], }], }), notes: vec![], }, }; save_runtime_save_slice_document(&save_slice_path, &save_slice_document) .expect("save slice should save"); let overlay = RuntimeOverlayImportDocument { format_version: OVERLAY_IMPORT_DOCUMENT_FORMAT_VERSION, import_id: "overlay-relative".to_string(), source: RuntimeOverlayImportDocumentSource { description: Some("relative overlay".to_string()), notes: vec![], }, base_snapshot_path: "base.json".to_string(), save_slice_path: "slice.json".to_string(), }; save_runtime_overlay_import_document(&overlay_path, &overlay) .expect("overlay document should save"); let import = load_runtime_state_import(&overlay_path).expect("overlay runtime import should load"); assert_eq!(import.import_id, "overlay-relative"); assert_eq!(import.state.event_runtime_records.len(), 1); assert_eq!(import.state.companies[0].company_id, 42); let _ = std::fs::remove_file(snapshot_path); let _ = std::fs::remove_file(save_slice_path); let _ = std::fs::remove_file(overlay_path); let _ = std::fs::remove_dir(fixture_dir); } }