rrt/crates/rrt-runtime/src/import.rs

14293 lines
599 KiB
Rust

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<String>,
#[serde(default)]
pub source_binary: Option<String>,
}
#[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<String>,
#[serde(default)]
pub original_save_filename: Option<String>,
#[serde(default)]
pub original_save_sha256: Option<String>,
#[serde(default)]
pub notes: Vec<String>,
}
#[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<String>,
#[serde(default)]
pub notes: Vec<String>,
}
#[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<String>,
pub state: RuntimeState,
}
#[derive(Debug)]
struct SaveSliceProjection {
world_flags: BTreeMap<String, bool>,
save_profile: RuntimeSaveProfileState,
world_restore: RuntimeWorldRestoreState,
metadata: BTreeMap<String, String>,
packed_event_collection: Option<RuntimePackedEventCollectionSummary>,
event_runtime_records: Vec<RuntimeEventRecord>,
companies: Vec<RuntimeCompany>,
has_company_projection: bool,
has_company_selection_override: bool,
selected_company_id: Option<u32>,
company_market_state: BTreeMap<u32, RuntimeCompanyMarketState>,
has_company_market_projection: bool,
world_issue_opinion_base_terms_raw_i32: Vec<i32>,
chairman_profiles: Vec<RuntimeChairmanProfile>,
has_chairman_projection: bool,
has_chairman_selection_override: bool,
selected_chairman_profile_id: Option<u32>,
chairman_issue_opinion_terms_raw_i32: BTreeMap<u32, Vec<i32>>,
chairman_personality_raw_u8: BTreeMap<u32, u8>,
candidate_availability: BTreeMap<String, u32>,
named_locomotive_availability: BTreeMap<String, u32>,
locomotive_catalog: Option<Vec<RuntimeLocomotiveCatalogEntry>>,
cargo_catalog: Option<Vec<RuntimeCargoCatalogEntry>>,
named_locomotive_cost: BTreeMap<String, u32>,
all_cargo_price_override: Option<u32>,
named_cargo_price_overrides: BTreeMap<String, u32>,
all_cargo_production_override: Option<u32>,
factory_cargo_production_override: Option<u32>,
farm_mine_cargo_production_override: Option<u32>,
named_cargo_production_overrides: BTreeMap<String, u32>,
cargo_production_overrides: BTreeMap<u32, u32>,
world_scalar_overrides: BTreeMap<String, i64>,
special_conditions: BTreeMap<String, u32>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum SaveSliceProjectionMode {
Standalone,
Overlay,
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct ImportRuntimeContext {
known_company_ids: BTreeSet<u32>,
selected_company_id: Option<u32>,
has_complete_company_controller_context: bool,
known_player_ids: BTreeSet<u32>,
selected_player_id: Option<u32>,
has_complete_player_controller_context: bool,
known_chairman_profile_ids: BTreeSet<u32>,
selected_chairman_profile_id: Option<u32>,
known_territory_ids: BTreeSet<u32>,
has_territory_context: bool,
territory_name_to_id: BTreeMap<String, u32>,
has_train_context: bool,
has_train_territory_context: bool,
locomotive_catalog_names_by_id: BTreeMap<u32, String>,
}
#[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<String>,
) -> Result<RuntimeStateImport, String> {
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_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<String>,
) -> Result<RuntimeStateImport, String> {
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_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<SaveSliceProjection, String> {
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::<Vec<_>>()
})
.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(),
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::<Vec<_>>(),
)
} 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::<Vec<_>>(),
)
} 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::<Vec<_>>(),
)
} 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::<BTreeMap<_, _>>();
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::<Vec<_>>(),
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::<Vec<_>>(),
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::<BTreeMap<_, _>>(),
table
.entries
.iter()
.filter_map(|entry| {
entry
.personality_byte_0x291
.map(|value| (entry.profile_id, value))
})
.collect::<BTreeMap<_, _>>(),
)
}
} 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<RuntimePackedEventCollectionSummary>,
Vec<RuntimeEventRecord>,
),
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::<Vec<_>>();
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<Result<RuntimeEventRecord, String>> {
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<Vec<RuntimeCondition>, 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<Vec<RuntimeEffect>, 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<Vec<RuntimeEffect>, 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<Option<RuntimeEffect>, 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<Option<RuntimeEffect>, 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<Option<RuntimeEffect>, 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<Option<RuntimeEffect>, 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<Option<RuntimeEffect>, 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<Option<RuntimeEffect>, 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<Option<RuntimeEffect>, 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<ImportBlocker> {
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<Option<RuntimeCompanyTarget>, 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<Option<RuntimePlayerTarget>, 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<RuntimeEffect, ImportBlocker> {
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::<Result<Vec<_>, _>>()?,
}),
},
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<RuntimeCondition, ImportBlocker> {
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<RuntimeCompanyTarget, ImportBlocker> {
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<RuntimePlayerTarget, ImportBlocker> {
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<RuntimeTerritoryTarget, ImportBlocker> {
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<ImportBlocker> {
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<Vec<RuntimeEffect>, 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<RuntimeEffect, String> {
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<RuntimeEventRecordTemplate, String> {
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<ImportBlocker> {
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<ImportBlocker> {
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<ImportBlocker> {
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<ImportBlocker> {
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<ImportBlocker> {
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<ImportBlocker> {
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<Option<RuntimeCompanyTarget>> {
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<RuntimeCompanyTarget> {
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<RuntimeSaveSliceDocument, Box<dyn std::error::Error>> {
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<RuntimeOverlayImportDocument, Box<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<RuntimeStateImport, Box<dyn std::error::Error>> {
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<RuntimeStateImport, Box<dyn std::error::Error>> {
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<RuntimeStateImport, Box<dyn std::error::Error>> {
if let Ok(document) = serde_json::from_str::<RuntimeStateDumpDocument>(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::<RuntimeSaveSliceDocument>(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::<RuntimeOverlayImportDocument>(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<crate::SmpLoadedPackedEventTextBandSummary> {
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<crate::SmpLoadedPackedEventConditionRowSummary> {
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<crate::SmpLoadedPackedEventGroupedEffectRowSummary> {
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<String>,
) -> 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<String>,
) -> 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");
assert_eq!(import.state.companies, base_state.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::<Vec<_>>();
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::<Vec<_>>();
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::<Vec<_>>();
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::<Vec<_>>()
}),
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,
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);
}
}