use std::collections::{BTreeMap, BTreeSet}; use serde::{Deserialize, Serialize}; use crate::CalendarPoint; #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] #[serde(rename_all = "snake_case")] pub enum RuntimeCompanyControllerKind { #[default] Unknown, Human, Ai, } fn runtime_company_default_active() -> bool { true } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RuntimeCompany { pub company_id: u32, pub current_cash: i64, pub debt: u64, #[serde(default)] pub credit_rating_score: Option, #[serde(default)] pub prime_rate: Option, #[serde(default = "runtime_company_default_active")] pub active: bool, #[serde(default)] pub available_track_laying_capacity: Option, #[serde(default)] pub controller_kind: RuntimeCompanyControllerKind, #[serde(default)] pub linked_chairman_profile_id: Option, #[serde(default)] pub book_value_per_share: i64, #[serde(default)] pub investor_confidence: i64, #[serde(default)] pub management_attitude: i64, #[serde(default)] pub takeover_cooldown_year: Option, #[serde(default)] pub merger_cooldown_year: Option, #[serde(default)] pub track_piece_counts: RuntimeTrackPieceCounts, } #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] pub struct RuntimeCompanyMarketState { #[serde(default)] pub outstanding_shares: u32, #[serde(default)] pub bond_count: u8, #[serde(default)] pub largest_live_bond_principal: Option, #[serde(default)] pub highest_coupon_live_bond_principal: Option, #[serde(default)] pub mutable_support_scalar_raw_u32: u32, #[serde(default)] pub young_company_support_scalar_raw_u32: u32, #[serde(default)] pub support_progress_word: u32, #[serde(default)] pub recent_per_share_subscore_raw_u32: u32, #[serde(default)] pub cached_share_price_raw_u32: u32, #[serde(default)] pub chairman_salary_baseline: u32, #[serde(default)] pub chairman_salary_current: u32, #[serde(default)] pub chairman_bonus_year: u32, #[serde(default)] pub chairman_bonus_amount: i32, #[serde(default)] pub founding_year: u32, #[serde(default)] pub last_bankruptcy_year: u32, #[serde(default)] pub last_dividend_year: u32, #[serde(default)] pub current_issue_calendar_word: u32, #[serde(default)] pub current_issue_calendar_word_2: u32, #[serde(default)] pub prior_issue_calendar_word: u32, #[serde(default)] pub prior_issue_calendar_word_2: u32, #[serde(default)] pub city_connection_latch: bool, #[serde(default)] pub linked_transit_latch: bool, #[serde(default)] pub stat_band_root_0cfb_candidates: Vec, #[serde(default)] pub stat_band_root_0d7f_candidates: Vec, #[serde(default)] pub stat_band_root_1c47_candidates: Vec, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RuntimeCompanyAnnualFinanceState { pub company_id: u32, pub outstanding_shares: u32, pub bond_count: u8, #[serde(default)] pub largest_live_bond_principal: Option, #[serde(default)] pub highest_coupon_live_bond_principal: Option, pub assigned_share_pool: u32, pub unassigned_share_pool: u32, #[serde(default)] pub cached_share_price: Option, pub chairman_salary_baseline: u32, pub chairman_salary_current: u32, pub chairman_bonus_year: u32, pub chairman_bonus_amount: i32, pub founding_year: u32, pub last_bankruptcy_year: u32, pub last_dividend_year: u32, #[serde(default)] pub years_since_founding: Option, #[serde(default)] pub years_since_last_bankruptcy: Option, #[serde(default)] pub years_since_last_dividend: Option, #[serde(default)] pub current_partial_year_weight_numerator: Option, pub current_issue_calendar_word: u32, #[serde(default)] pub current_issue_calendar_word_2: u32, pub prior_issue_calendar_word: u32, #[serde(default)] pub prior_issue_calendar_word_2: u32, pub city_connection_latch: bool, pub linked_transit_latch: bool, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] pub struct RuntimeTrackPieceCounts { #[serde(default)] pub total: u32, #[serde(default)] pub single: u32, #[serde(default)] pub double: u32, #[serde(default)] pub transition: u32, #[serde(default)] pub electric: u32, #[serde(default)] pub non_electric: u32, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RuntimeTerritory { pub territory_id: u32, #[serde(default)] pub name: Option, #[serde(default)] pub track_piece_counts: RuntimeTrackPieceCounts, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RuntimeCompanyTerritoryTrackPieceCount { pub company_id: u32, pub territory_id: u32, #[serde(default)] pub track_piece_counts: RuntimeTrackPieceCounts, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RuntimeCompanyTerritoryAccess { pub company_id: u32, pub territory_id: u32, } fn runtime_player_default_active() -> bool { true } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RuntimePlayer { pub player_id: u32, pub current_cash: i64, #[serde(default = "runtime_player_default_active")] pub active: bool, #[serde(default)] pub controller_kind: RuntimeCompanyControllerKind, } fn runtime_chairman_profile_default_active() -> bool { true } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RuntimeChairmanProfile { pub profile_id: u32, pub name: String, #[serde(default = "runtime_chairman_profile_default_active")] pub active: bool, #[serde(default)] pub current_cash: i64, #[serde(default)] pub linked_company_id: Option, #[serde(default)] pub company_holdings: BTreeMap, #[serde(default)] pub holdings_value_total: i64, #[serde(default)] pub net_worth_total: i64, #[serde(default)] pub purchasing_power_total: i64, } fn runtime_train_default_active() -> bool { true } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RuntimeTrain { pub train_id: u32, pub owner_company_id: u32, #[serde(default)] pub territory_id: Option, #[serde(default)] pub locomotive_name: Option, #[serde(default = "runtime_train_default_active")] pub active: bool, #[serde(default)] pub retired: bool, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RuntimeLocomotiveCatalogEntry { pub locomotive_id: u32, pub name: String, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RuntimeCargoCatalogEntry { pub slot_id: u32, pub label: String, #[serde(default)] pub cargo_class: RuntimeCargoClass, #[serde(default)] pub supplied_token_stem: Option, #[serde(default)] pub demanded_token_stem: Option, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] #[serde(rename_all = "snake_case")] pub enum RuntimeCargoClass { #[default] Other, Factory, FarmMine, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(tag = "kind", rename_all = "snake_case")] pub enum RuntimeCompanyTarget { AllActive, HumanCompanies, AiCompanies, SelectedCompany, ConditionTrueCompany, Ids { ids: Vec }, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(tag = "kind", rename_all = "snake_case")] pub enum RuntimePlayerTarget { AllActive, HumanPlayers, AiPlayers, SelectedPlayer, ConditionTruePlayer, Ids { ids: Vec }, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(tag = "kind", rename_all = "snake_case")] pub enum RuntimeChairmanTarget { AllActive, HumanChairmen, AiChairmen, SelectedChairman, ConditionTrueChairman, Ids { ids: Vec }, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(tag = "kind", rename_all = "snake_case")] pub enum RuntimeCargoPriceTarget { All, Named { name: String }, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(tag = "kind", rename_all = "snake_case")] pub enum RuntimeCargoProductionTarget { All, Factory, FarmMine, Named { name: String }, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(tag = "kind", rename_all = "snake_case")] pub enum RuntimeTerritoryTarget { AllTerritories, Ids { ids: Vec }, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] #[serde(rename_all = "snake_case")] pub enum RuntimeCompanyConditionTestScope { #[default] Disabled, AllCompanies, SelectedCompanyOnly, AiCompaniesOnly, HumanCompaniesOnly, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] #[serde(rename_all = "snake_case")] pub enum RuntimePlayerConditionTestScope { #[default] Disabled, AllPlayers, SelectedPlayerOnly, AiPlayersOnly, HumanPlayersOnly, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum RuntimeConditionComparator { Ge, Le, Gt, Lt, Eq, Ne, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum RuntimeCompanyMetric { CurrentCash, TotalDebt, CreditRating, PrimeRate, BookValuePerShare, InvestorConfidence, ManagementAttitude, TrackPiecesTotal, TrackPiecesSingle, TrackPiecesDouble, TrackPiecesTransition, TrackPiecesElectric, TrackPiecesNonElectric, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum RuntimeCompanyMarketMetric { OutstandingShares, BondCount, LargestLiveBondPrincipal, HighestCouponLiveBondPrincipal, AssignedSharePool, UnassignedSharePool, CachedSharePrice, ChairmanSalaryBaseline, ChairmanSalaryCurrent, ChairmanBonusAmount, CurrentIssueCalendarWord, CurrentIssueCalendarWord2, PriorIssueCalendarWord, PriorIssueCalendarWord2, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum RuntimeChairmanMetric { CurrentCash, HoldingsValueTotal, NetWorthTotal, PurchasingPowerTotal, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub struct RuntimeCompanyStatSelector { pub family_id: u32, pub slot_id: u32, } pub const RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER: u32 = 0x2329; pub const RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH: u32 = 0x0d; pub const RUNTIME_COMPANY_STAT_SLOT_BOOK_VALUE_PER_SHARE: u32 = 0x1d; pub const RUNTIME_WORLD_ISSUE_INVESTOR_CONFIDENCE: u32 = 0x37; pub const RUNTIME_WORLD_ISSUE_CREDIT_MARKET: u32 = 0x38; pub const RUNTIME_WORLD_ISSUE_PRIME_RATE: u32 = 0x39; pub const RUNTIME_WORLD_ISSUE_MANAGEMENT_ATTITUDE: u32 = 0x3a; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RuntimeWorldIssueState { pub issue_id: u32, pub raw_value_u32: u32, #[serde(default)] pub multiplier_raw_u32: Option, #[serde(default)] pub multiplier_value_f32_text: Option, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum RuntimeTerritoryMetric { TrackPiecesTotal, TrackPiecesSingle, TrackPiecesDouble, TrackPiecesTransition, TrackPiecesElectric, TrackPiecesNonElectric, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum RuntimeTrackMetric { Total, Single, Double, Transition, Electric, NonElectric, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(tag = "kind", rename_all = "snake_case")] pub enum RuntimeCondition { WorldVariableThreshold { index: u32, comparator: RuntimeConditionComparator, value: i64, }, CompanyNumericThreshold { target: RuntimeCompanyTarget, metric: RuntimeCompanyMetric, comparator: RuntimeConditionComparator, value: i64, }, CompanyVariableThreshold { target: RuntimeCompanyTarget, index: u32, comparator: RuntimeConditionComparator, value: i64, }, ChairmanNumericThreshold { target: RuntimeChairmanTarget, metric: RuntimeChairmanMetric, comparator: RuntimeConditionComparator, value: i64, }, PlayerVariableThreshold { target: RuntimePlayerTarget, index: u32, comparator: RuntimeConditionComparator, value: i64, }, TerritoryNumericThreshold { target: RuntimeTerritoryTarget, metric: RuntimeTerritoryMetric, comparator: RuntimeConditionComparator, value: i64, }, TerritoryVariableThreshold { target: RuntimeTerritoryTarget, index: u32, comparator: RuntimeConditionComparator, value: i64, }, CompanyTerritoryNumericThreshold { target: RuntimeCompanyTarget, territory: RuntimeTerritoryTarget, metric: RuntimeTrackMetric, comparator: RuntimeConditionComparator, value: i64, }, SpecialConditionThreshold { label: String, comparator: RuntimeConditionComparator, value: i64, }, CandidateAvailabilityThreshold { name: String, comparator: RuntimeConditionComparator, value: i64, }, NamedLocomotiveAvailabilityThreshold { name: String, comparator: RuntimeConditionComparator, value: i64, }, NamedLocomotiveCostThreshold { name: String, comparator: RuntimeConditionComparator, value: i64, }, CargoProductionSlotThreshold { slot: u32, label: String, comparator: RuntimeConditionComparator, value: i64, }, CargoProductionTotalThreshold { comparator: RuntimeConditionComparator, value: i64, }, FactoryProductionTotalThreshold { comparator: RuntimeConditionComparator, value: i64, }, FarmMineProductionTotalThreshold { comparator: RuntimeConditionComparator, value: i64, }, OtherCargoProductionTotalThreshold { comparator: RuntimeConditionComparator, value: i64, }, LimitedTrackBuildingAmountThreshold { comparator: RuntimeConditionComparator, value: i64, }, TerritoryAccessCostThreshold { comparator: RuntimeConditionComparator, value: i64, }, EconomicStatusCodeThreshold { comparator: RuntimeConditionComparator, value: i64, }, WorldFlagEquals { key: String, value: bool, }, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(tag = "kind", rename_all = "snake_case")] pub enum RuntimeEffect { SetWorldFlag { key: String, value: bool, }, SetLimitedTrackBuildingAmount { value: i32, }, SetEconomicStatusCode { value: i32, }, SetCompanyCash { target: RuntimeCompanyTarget, value: i64, }, SetPlayerCash { target: RuntimePlayerTarget, value: i64, }, SetChairmanCash { target: RuntimeChairmanTarget, value: i64, }, SetCompanyGovernanceScalar { target: RuntimeCompanyTarget, metric: RuntimeCompanyMetric, value: i64, }, DeactivatePlayer { target: RuntimePlayerTarget, }, DeactivateChairman { target: RuntimeChairmanTarget, }, SetCompanyTerritoryAccess { target: RuntimeCompanyTarget, territory: RuntimeTerritoryTarget, value: bool, }, ConfiscateCompanyAssets { target: RuntimeCompanyTarget, }, DeactivateCompany { target: RuntimeCompanyTarget, }, SetCompanyTrackLayingCapacity { target: RuntimeCompanyTarget, value: Option, }, RetireTrains { #[serde(default)] company_target: Option, #[serde(default)] territory_target: Option, #[serde(default)] locomotive_name: Option, }, AdjustCompanyCash { target: RuntimeCompanyTarget, delta: i64, }, AdjustCompanyDebt { target: RuntimeCompanyTarget, delta: i64, }, SetCandidateAvailability { name: String, value: u32, }, SetNamedLocomotiveAvailability { name: String, value: bool, }, SetNamedLocomotiveAvailabilityValue { name: String, value: u32, }, SetNamedLocomotiveCost { name: String, value: u32, }, SetCargoPriceOverride { target: RuntimeCargoPriceTarget, value: u32, }, SetCargoProductionOverride { target: RuntimeCargoProductionTarget, value: u32, }, SetCargoProductionSlot { slot: u32, value: u32, }, SetWorldVariable { index: u32, value: i64, }, SetCompanyVariable { target: RuntimeCompanyTarget, index: u32, value: i64, }, SetPlayerVariable { target: RuntimePlayerTarget, index: u32, value: i64, }, SetTerritoryVariable { target: RuntimeTerritoryTarget, index: u32, value: i64, }, SetWorldScalarOverride { key: String, value: i64, }, SetTerritoryAccessCost { value: u32, }, SetSpecialCondition { label: String, value: u32, }, AppendEventRecord { record: Box, }, ActivateEventRecord { record_id: u32, }, DeactivateEventRecord { record_id: u32, }, RemoveEventRecord { record_id: u32, }, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RuntimeEventRecordTemplate { pub record_id: u32, pub trigger_kind: u8, pub active: bool, #[serde(default)] pub marks_collection_dirty: bool, #[serde(default)] pub one_shot: bool, #[serde(default)] pub conditions: Vec, #[serde(default)] pub effects: Vec, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RuntimeEventRecord { pub record_id: u32, pub trigger_kind: u8, pub active: bool, #[serde(default)] pub service_count: u32, #[serde(default)] pub marks_collection_dirty: bool, #[serde(default)] pub one_shot: bool, #[serde(default)] pub has_fired: bool, #[serde(default)] pub conditions: Vec, #[serde(default)] pub effects: Vec, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RuntimePackedEventCollectionSummary { pub source_kind: String, pub mechanism_family: String, pub mechanism_confidence: String, #[serde(default)] pub container_profile_family: Option, pub packed_state_version: u32, pub packed_state_version_hex: String, pub live_id_bound: u32, pub live_record_count: usize, pub live_entry_ids: Vec, #[serde(default)] pub decoded_record_count: usize, #[serde(default)] pub imported_runtime_record_count: usize, #[serde(default)] pub records: Vec, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RuntimePackedEventRecordSummary { pub record_index: usize, pub live_entry_id: u32, #[serde(default)] pub payload_offset: Option, #[serde(default)] pub payload_len: Option, pub decode_status: String, #[serde(default)] pub payload_family: String, #[serde(default)] pub trigger_kind: Option, #[serde(default)] pub active: Option, #[serde(default)] pub marks_collection_dirty: Option, #[serde(default)] pub one_shot: Option, #[serde(default)] pub compact_control: Option, #[serde(default)] pub text_bands: Vec, #[serde(default)] pub standalone_condition_row_count: usize, #[serde(default)] pub standalone_condition_rows: Vec, #[serde(default)] pub negative_sentinel_scope: Option, #[serde(default)] pub grouped_effect_row_counts: Vec, #[serde(default)] pub grouped_effect_rows: Vec, #[serde(default)] pub grouped_company_targets: Vec>, #[serde(default)] pub decoded_conditions: Vec, #[serde(default)] pub decoded_actions: Vec, #[serde(default)] pub executable_import_ready: bool, #[serde(default)] pub import_outcome: Option, #[serde(default)] pub notes: Vec, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RuntimePackedEventNegativeSentinelScopeSummary { pub company_test_scope: RuntimeCompanyConditionTestScope, pub player_test_scope: RuntimePlayerConditionTestScope, pub territory_scope_selector_is_0x63: bool, #[serde(default)] pub source_row_indexes: Vec, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RuntimePackedEventCompactControlSummary { pub mode_byte_0x7ef: u8, pub primary_selector_0x7f0: u32, pub grouped_mode_0x7f4: u8, pub one_shot_header_0x7f5: u32, pub modifier_flag_0x7f9: u8, pub modifier_flag_0x7fa: u8, #[serde(default)] pub grouped_target_scope_ordinals_0x7fb: Vec, #[serde(default)] pub grouped_scope_checkboxes_0x7ff: Vec, pub summary_toggle_0x800: u8, #[serde(default)] pub grouped_territory_selectors_0x80f: Vec, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RuntimePackedEventTextBandSummary { pub label: String, pub packed_len: usize, pub present: bool, pub preview: String, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RuntimePackedEventConditionRowSummary { pub row_index: usize, pub raw_condition_id: i32, pub subtype: u8, #[serde(default)] pub flag_bytes: Vec, #[serde(default)] pub candidate_name: Option, #[serde(default)] pub comparator: Option, #[serde(default)] pub metric: Option, #[serde(default)] pub semantic_family: Option, #[serde(default)] pub semantic_preview: Option, #[serde(default)] pub requires_candidate_name_binding: bool, #[serde(default)] pub recovered_cargo_slot: Option, #[serde(default)] pub recovered_cargo_class: Option, #[serde(default)] pub recovered_cargo_label: Option, #[serde(default)] pub recovered_cargo_supplied_token_stem: Option, #[serde(default)] pub recovered_cargo_demanded_token_stem: Option, #[serde(default)] pub notes: Vec, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RuntimePackedEventGroupedEffectRowSummary { pub group_index: usize, pub row_index: usize, pub descriptor_id: u32, #[serde(default)] pub descriptor_label: Option, #[serde(default)] pub target_mask_bits: Option, #[serde(default)] pub parameter_family: Option, #[serde(default)] pub grouped_target_subject: Option, #[serde(default)] pub grouped_target_scope: Option, pub opcode: u8, pub raw_scalar_value: i32, pub value_byte_0x09: u8, pub value_dword_0x0d: u32, pub value_byte_0x11: u8, pub value_byte_0x12: u8, pub value_word_0x14: u16, pub value_word_0x16: u16, pub row_shape: String, #[serde(default)] pub semantic_family: Option, #[serde(default)] pub semantic_preview: Option, #[serde(default)] pub recovered_cargo_slot: Option, #[serde(default)] pub recovered_cargo_class: Option, #[serde(default)] pub recovered_cargo_label: Option, #[serde(default)] pub recovered_cargo_supplied_token_stem: Option, #[serde(default)] pub recovered_cargo_demanded_token_stem: Option, #[serde(default)] pub recovered_locomotive_id: Option, #[serde(default)] pub locomotive_name: Option, #[serde(default)] pub notes: Vec, } impl RuntimeEventRecordTemplate { pub fn into_runtime_record(self) -> RuntimeEventRecord { RuntimeEventRecord { record_id: self.record_id, trigger_kind: self.trigger_kind, active: self.active, service_count: 0, marks_collection_dirty: self.marks_collection_dirty, one_shot: self.one_shot, has_fired: false, conditions: self.conditions, effects: self.effects, } } } #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] pub struct RuntimeServiceState { #[serde(default)] pub periodic_boundary_calls: u64, #[serde(default)] pub trigger_dispatch_counts: BTreeMap, #[serde(default)] pub total_event_record_services: u64, #[serde(default)] pub dirty_rerun_count: u64, #[serde(default)] pub company_market_state: BTreeMap, } #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] pub struct RuntimeSaveProfileState { #[serde(default)] pub profile_kind: Option, #[serde(default)] pub profile_family: Option, #[serde(default)] pub map_path: Option, #[serde(default)] pub display_name: Option, #[serde(default)] pub selected_year_profile_lane: Option, #[serde(default)] pub sandbox_enabled: Option, #[serde(default)] pub campaign_scenario_enabled: Option, #[serde(default)] pub staged_profile_copy_on_restore: Option, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RuntimeWorldFinanceNeighborhoodCandidate { pub label: String, pub relative_offset: usize, pub relative_offset_hex: String, pub raw_u32: u32, pub raw_u32_hex: String, pub value_i32: i32, pub value_f32_text: String, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RuntimeCompanyStatBandCandidate { pub label: String, pub relative_offset: usize, pub relative_offset_hex: String, pub raw_u32: u32, pub raw_u32_hex: String, pub value_i32: i32, pub value_f32_text: String, } #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] pub struct RuntimeWorldRestoreState { #[serde(default)] pub selected_year_profile_lane: Option, #[serde(default)] pub campaign_scenario_enabled: Option, #[serde(default)] pub sandbox_enabled: Option, #[serde(default)] pub seed_tuple_written_from_raw_lane: Option, #[serde(default)] pub absolute_counter_requires_shell_context: Option, #[serde(default)] pub absolute_counter_reconstructible_from_save: Option, #[serde(default)] pub current_calendar_tuple_word_raw_u32: Option, #[serde(default)] pub packed_year_word_raw_u16: Option, #[serde(default)] pub partial_year_progress_raw_u8: Option, #[serde(default)] pub current_calendar_tuple_word_2_raw_u32: Option, #[serde(default)] pub absolute_counter_raw_u32: Option, #[serde(default)] pub absolute_counter_mirror_raw_u32: Option, #[serde(default)] pub disable_cargo_economy_special_condition_slot: Option, #[serde(default)] pub disable_cargo_economy_special_condition_reconstructible_from_save: Option, #[serde(default)] pub disable_cargo_economy_special_condition_write_side_grounded: Option, #[serde(default)] pub disable_cargo_economy_special_condition_enabled: Option, #[serde(default)] pub use_bio_accelerator_cars_enabled: Option, #[serde(default)] pub use_wartime_cargos_enabled: Option, #[serde(default)] pub disable_train_crashes_enabled: Option, #[serde(default)] pub disable_train_crashes_and_breakdowns_enabled: Option, #[serde(default)] pub ai_ignore_territories_at_startup_enabled: Option, #[serde(default)] pub limited_track_building_amount: Option, #[serde(default)] pub economic_status_code: Option, #[serde(default)] pub territory_access_cost: Option, #[serde(default)] pub issue_37_value: Option, #[serde(default)] pub issue_38_value: Option, #[serde(default)] pub issue_39_value: Option, #[serde(default)] pub issue_3a_value: Option, #[serde(default)] pub issue_37_multiplier_raw_u32: Option, #[serde(default)] pub issue_37_multiplier_value_f32_text: Option, #[serde(default)] pub finance_neighborhood_candidates: Vec, #[serde(default)] pub economic_tuning_mirror_raw_u32: Option, #[serde(default)] pub economic_tuning_mirror_value_f32_text: Option, #[serde(default)] pub economic_tuning_lane_raw_u32: Vec, #[serde(default)] pub economic_tuning_lane_value_f32_text: Vec, #[serde(default)] pub absolute_counter_restore_kind: Option, #[serde(default)] pub absolute_counter_adjustment_context: Option, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RuntimeState { pub calendar: CalendarPoint, #[serde(default)] pub world_flags: BTreeMap, #[serde(default)] pub save_profile: RuntimeSaveProfileState, #[serde(default)] pub world_restore: RuntimeWorldRestoreState, #[serde(default)] pub metadata: BTreeMap, #[serde(default)] pub companies: Vec, #[serde(default)] pub selected_company_id: Option, #[serde(default)] pub players: Vec, #[serde(default)] pub selected_player_id: Option, #[serde(default)] pub chairman_profiles: Vec, #[serde(default)] pub selected_chairman_profile_id: Option, #[serde(default)] pub trains: Vec, #[serde(default)] pub locomotive_catalog: Vec, #[serde(default)] pub cargo_catalog: Vec, #[serde(default)] pub territories: Vec, #[serde(default)] pub company_territory_track_piece_counts: Vec, #[serde(default)] pub company_territory_access: Vec, #[serde(default)] pub packed_event_collection: Option, #[serde(default)] pub event_runtime_records: Vec, #[serde(default)] pub candidate_availability: BTreeMap, #[serde(default)] pub named_locomotive_availability: BTreeMap, #[serde(default)] pub named_locomotive_cost: BTreeMap, #[serde(default)] pub all_cargo_price_override: Option, #[serde(default)] pub named_cargo_price_overrides: BTreeMap, #[serde(default)] pub all_cargo_production_override: Option, #[serde(default)] pub factory_cargo_production_override: Option, #[serde(default)] pub farm_mine_cargo_production_override: Option, #[serde(default)] pub named_cargo_production_overrides: BTreeMap, #[serde(default)] pub cargo_production_overrides: BTreeMap, #[serde(default)] pub world_runtime_variables: BTreeMap, #[serde(default)] pub company_runtime_variables: BTreeMap>, #[serde(default)] pub player_runtime_variables: BTreeMap>, #[serde(default)] pub territory_runtime_variables: BTreeMap>, #[serde(default)] pub world_scalar_overrides: BTreeMap, #[serde(default)] pub special_conditions: BTreeMap, #[serde(default)] pub service_state: RuntimeServiceState, } impl RuntimeState { pub fn validate(&self) -> Result<(), String> { self.calendar.validate()?; let mut seen_company_ids = BTreeSet::new(); let mut active_company_ids = BTreeSet::new(); for company in &self.companies { if !seen_company_ids.insert(company.company_id) { return Err(format!("duplicate company_id {}", company.company_id)); } if company.active { active_company_ids.insert(company.company_id); } } if let Some(selected_company_id) = self.selected_company_id { if !seen_company_ids.contains(&selected_company_id) { return Err(format!( "selected_company_id {} does not reference a live company", selected_company_id )); } if !active_company_ids.contains(&selected_company_id) { return Err(format!( "selected_company_id {} must reference an active company", selected_company_id )); } } let mut seen_player_ids = BTreeSet::new(); let mut active_player_ids = BTreeSet::new(); for player in &self.players { if !seen_player_ids.insert(player.player_id) { return Err(format!("duplicate player_id {}", player.player_id)); } if player.active { active_player_ids.insert(player.player_id); } } let mut seen_chairman_profile_ids = BTreeSet::new(); let mut seen_chairman_names = BTreeSet::new(); let mut active_chairman_profile_ids = BTreeSet::new(); for chairman in &self.chairman_profiles { if !seen_chairman_profile_ids.insert(chairman.profile_id) { return Err(format!( "duplicate chairman_profile.profile_id {}", chairman.profile_id )); } if chairman.name.trim().is_empty() { return Err(format!( "chairman_profile {} has an empty name", chairman.profile_id )); } if !seen_chairman_names.insert(chairman.name.clone()) { return Err(format!( "duplicate chairman_profile.name {:?}", chairman.name )); } if chairman.active { active_chairman_profile_ids.insert(chairman.profile_id); } if let Some(linked_company_id) = chairman.linked_company_id { if !seen_company_ids.contains(&linked_company_id) { return Err(format!( "chairman_profile {} references unknown linked_company_id {}", chairman.profile_id, linked_company_id )); } } for company_id in chairman.company_holdings.keys() { if !seen_company_ids.contains(company_id) { return Err(format!( "chairman_profile {} references unknown holdings company_id {}", chairman.profile_id, company_id )); } } } if let Some(selected_chairman_profile_id) = self.selected_chairman_profile_id { if !seen_chairman_profile_ids.contains(&selected_chairman_profile_id) { return Err(format!( "selected_chairman_profile_id {} does not reference a live chairman profile", selected_chairman_profile_id )); } if !active_chairman_profile_ids.contains(&selected_chairman_profile_id) { return Err(format!( "selected_chairman_profile_id {} must reference an active chairman profile", selected_chairman_profile_id )); } } for company in &self.companies { if let Some(linked_chairman_profile_id) = company.linked_chairman_profile_id { let linked_profile = self .chairman_profiles .iter() .find(|profile| profile.profile_id == linked_chairman_profile_id) .ok_or_else(|| { format!( "company {} references unknown linked_chairman_profile_id {}", company.company_id, linked_chairman_profile_id ) })?; if linked_profile.linked_company_id != Some(company.company_id) { return Err(format!( "company {} linked_chairman_profile_id {} must point back through linked_company_id", company.company_id, linked_chairman_profile_id )); } } } for chairman in &self.chairman_profiles { if let Some(linked_company_id) = chairman.linked_company_id { let linked_company = self .companies .iter() .find(|company| company.company_id == linked_company_id) .ok_or_else(|| { format!( "chairman_profile {} references unknown linked_company_id {}", chairman.profile_id, linked_company_id ) })?; if linked_company.linked_chairman_profile_id != Some(chairman.profile_id) { return Err(format!( "chairman_profile {} linked_company_id {} must point back through linked_chairman_profile_id", chairman.profile_id, linked_company_id )); } } } if let Some(selected_player_id) = self.selected_player_id { if !seen_player_ids.contains(&selected_player_id) { return Err(format!( "selected_player_id {} does not reference a live player", selected_player_id )); } if !active_player_ids.contains(&selected_player_id) { return Err(format!( "selected_player_id {} must reference an active player", selected_player_id )); } } let mut seen_territory_ids = BTreeSet::new(); let mut seen_territory_names = BTreeSet::new(); for territory in &self.territories { if !seen_territory_ids.insert(territory.territory_id) { return Err(format!("duplicate territory_id {}", territory.territory_id)); } if let Some(name) = territory.name.as_deref() { if name.trim().is_empty() { return Err(format!( "territory_id {} has an empty name", territory.territory_id )); } if !seen_territory_names.insert(name.to_string()) { return Err(format!("duplicate territory name {name:?}")); } } } let mut seen_train_ids = BTreeSet::new(); for train in &self.trains { if !seen_train_ids.insert(train.train_id) { return Err(format!("duplicate train_id {}", train.train_id)); } if !seen_company_ids.contains(&train.owner_company_id) { return Err(format!( "train_id {} references unknown owner_company_id {}", train.train_id, train.owner_company_id )); } if let Some(territory_id) = train.territory_id { if !seen_territory_ids.contains(&territory_id) { return Err(format!( "train_id {} references unknown territory_id {}", train.train_id, territory_id )); } } if train.retired && train.active { return Err(format!( "train_id {} cannot be active and retired at the same time", train.train_id )); } if train .locomotive_name .as_deref() .is_some_and(|value| value.trim().is_empty()) { return Err(format!( "train_id {} has an empty locomotive_name", train.train_id )); } } let mut seen_locomotive_ids = BTreeSet::new(); let mut seen_locomotive_names = BTreeSet::new(); for entry in &self.locomotive_catalog { if !seen_locomotive_ids.insert(entry.locomotive_id) { return Err(format!( "duplicate locomotive_catalog.locomotive_id {}", entry.locomotive_id )); } if entry.name.trim().is_empty() { return Err(format!( "locomotive_catalog entry {} has an empty name", entry.locomotive_id )); } if !seen_locomotive_names.insert(entry.name.clone()) { return Err(format!( "duplicate locomotive_catalog.name {:?}", entry.name )); } } let mut seen_cargo_slots = BTreeSet::new(); let mut seen_cargo_labels = BTreeSet::new(); for entry in &self.cargo_catalog { if !(1..=11).contains(&entry.slot_id) { return Err(format!( "cargo_catalog entry has out-of-range slot_id {}", entry.slot_id )); } if !seen_cargo_slots.insert(entry.slot_id) { return Err(format!("duplicate cargo_catalog.slot_id {}", entry.slot_id)); } if entry.label.trim().is_empty() { return Err(format!( "cargo_catalog entry {} has an empty label", entry.slot_id )); } if !seen_cargo_labels.insert(entry.label.clone()) { return Err(format!("duplicate cargo_catalog.label {:?}", entry.label)); } } for entry in &self.company_territory_track_piece_counts { if !seen_company_ids.contains(&entry.company_id) { return Err(format!( "company_territory_track_piece_counts references unknown company_id {}", entry.company_id )); } if !seen_territory_ids.contains(&entry.territory_id) { return Err(format!( "company_territory_track_piece_counts references unknown territory_id {}", entry.territory_id )); } } let mut seen_company_territory_access = BTreeSet::new(); for entry in &self.company_territory_access { if !seen_company_ids.contains(&entry.company_id) { return Err(format!( "company_territory_access references unknown company_id {}", entry.company_id )); } if !seen_territory_ids.contains(&entry.territory_id) { return Err(format!( "company_territory_access references unknown territory_id {}", entry.territory_id )); } if !seen_company_territory_access.insert((entry.company_id, entry.territory_id)) { return Err(format!( "duplicate company_territory_access pair ({}, {})", entry.company_id, entry.territory_id )); } } let mut seen_record_ids = BTreeSet::new(); for record in &self.event_runtime_records { if !seen_record_ids.insert(record.record_id) { return Err(format!("duplicate record_id {}", record.record_id)); } for (condition_index, condition) in record.conditions.iter().enumerate() { validate_runtime_condition( condition, &seen_company_ids, &seen_player_ids, &seen_chairman_profile_ids, &seen_territory_ids, ) .map_err(|err| { format!( "event_runtime_records[record_id={}].conditions[{condition_index}] {err}", record.record_id ) })?; } for (effect_index, effect) in record.effects.iter().enumerate() { validate_runtime_effect( effect, &seen_company_ids, &seen_player_ids, &seen_chairman_profile_ids, &seen_territory_ids, ) .map_err(|err| { format!( "event_runtime_records[record_id={}].effects[{effect_index}] {err}", record.record_id ) })?; } } if let Some(summary) = &self.packed_event_collection { if summary.source_kind.trim().is_empty() { return Err("packed_event_collection.source_kind must not be empty".to_string()); } if summary.mechanism_family.trim().is_empty() { return Err( "packed_event_collection.mechanism_family must not be empty".to_string() ); } if summary.mechanism_confidence.trim().is_empty() { return Err( "packed_event_collection.mechanism_confidence must not be empty".to_string(), ); } if summary .container_profile_family .as_deref() .is_some_and(|value| value.trim().is_empty()) { return Err( "packed_event_collection.container_profile_family must not be empty" .to_string(), ); } if summary.packed_state_version_hex.trim().is_empty() { return Err( "packed_event_collection.packed_state_version_hex must not be empty" .to_string(), ); } if summary.live_record_count != summary.live_entry_ids.len() { return Err( "packed_event_collection.live_record_count must match live_entry_ids length" .to_string(), ); } if summary.live_record_count != summary.records.len() { return Err( "packed_event_collection.live_record_count must match records length" .to_string(), ); } let decoded_record_count = summary .records .iter() .filter(|record| record.decode_status != "unsupported_framing") .count(); if summary.decoded_record_count != decoded_record_count { return Err( "packed_event_collection.decoded_record_count must match decoded records" .to_string(), ); } let importable_or_imported_count = summary .records .iter() .filter(|record| { record.executable_import_ready || record.import_outcome.as_deref() == Some("imported") }) .count(); if summary.imported_runtime_record_count > importable_or_imported_count { return Err( "packed_event_collection.imported_runtime_record_count must not exceed importable or imported records" .to_string(), ); } let mut previous_id = None; for (record_index, entry_id) in summary.live_entry_ids.iter().enumerate() { if *entry_id == 0 { return Err( "packed_event_collection.live_entry_ids must not contain id 0".to_string(), ); } if *entry_id > summary.live_id_bound { return Err(format!( "packed_event_collection.live_entry_id {} exceeds live_id_bound {}", entry_id, summary.live_id_bound )); } if previous_id.is_some_and(|prior| prior >= *entry_id) { return Err( "packed_event_collection.live_entry_ids must be strictly ascending" .to_string(), ); } previous_id = Some(*entry_id); let record = &summary.records[record_index]; if record.live_entry_id != *entry_id { return Err(format!( "packed_event_collection.records[{record_index}].live_entry_id must match live_entry_ids" )); } if record.record_index != record_index { return Err(format!( "packed_event_collection.records[{record_index}].record_index must match position" )); } if record.decode_status.trim().is_empty() { return Err(format!( "packed_event_collection.records[{record_index}].decode_status must not be empty" )); } if record.payload_family.trim().is_empty() { return Err(format!( "packed_event_collection.records[{record_index}].payload_family must not be empty" )); } if record .import_outcome .as_deref() .is_some_and(|value| value.trim().is_empty()) { return Err(format!( "packed_event_collection.records[{record_index}].import_outcome must not be empty" )); } if record.grouped_effect_row_counts.len() != 4 { return Err(format!( "packed_event_collection.records[{record_index}].grouped_effect_row_counts must contain exactly 4 entries" )); } if record.payload_family == "real_packed_v1" && record.standalone_condition_rows.len() != record.standalone_condition_row_count { return Err(format!( "packed_event_collection.records[{record_index}].standalone_condition_rows must match standalone_condition_row_count" )); } if record.payload_family == "real_packed_v1" && record.grouped_effect_rows.len() != record.grouped_effect_row_counts.iter().sum::() { return Err(format!( "packed_event_collection.records[{record_index}].grouped_effect_rows must match grouped_effect_row_counts" )); } for band in &record.text_bands { if band.label.trim().is_empty() { return Err(format!( "packed_event_collection.records[{record_index}].text_bands contains an empty label" )); } } if let Some(control) = &record.compact_control { if control.grouped_target_scope_ordinals_0x7fb.len() != 4 { return Err(format!( "packed_event_collection.records[{record_index}].compact_control.grouped_target_scope_ordinals_0x7fb must contain exactly 4 entries" )); } if control.grouped_scope_checkboxes_0x7ff.len() != 4 { return Err(format!( "packed_event_collection.records[{record_index}].compact_control.grouped_scope_checkboxes_0x7ff must contain exactly 4 entries" )); } if control.grouped_territory_selectors_0x80f.len() != 4 { return Err(format!( "packed_event_collection.records[{record_index}].compact_control.grouped_territory_selectors_0x80f must contain exactly 4 entries" )); } } for row in &record.standalone_condition_rows { if row .candidate_name .as_deref() .is_some_and(|value| value.trim().is_empty()) { return Err(format!( "packed_event_collection.records[{record_index}].standalone_condition_rows contains an empty candidate_name" )); } if row .comparator .as_deref() .is_some_and(|value| value.trim().is_empty()) { return Err(format!( "packed_event_collection.records[{record_index}].standalone_condition_rows contains an empty comparator" )); } if row .metric .as_deref() .is_some_and(|value| value.trim().is_empty()) { return Err(format!( "packed_event_collection.records[{record_index}].standalone_condition_rows contains an empty metric" )); } if row .semantic_family .as_deref() .is_some_and(|value| value.trim().is_empty()) { return Err(format!( "packed_event_collection.records[{record_index}].standalone_condition_rows contains an empty semantic_family" )); } if row .semantic_preview .as_deref() .is_some_and(|value| value.trim().is_empty()) { return Err(format!( "packed_event_collection.records[{record_index}].standalone_condition_rows contains an empty semantic_preview" )); } } for row in &record.grouped_effect_rows { if row.row_shape.trim().is_empty() { return Err(format!( "packed_event_collection.records[{record_index}].grouped_effect_rows contains an empty row_shape" )); } if row .locomotive_name .as_deref() .is_some_and(|value| value.trim().is_empty()) { return Err(format!( "packed_event_collection.records[{record_index}].grouped_effect_rows contains an empty locomotive_name" )); } } } } for key in self.world_flags.keys() { if key.trim().is_empty() { return Err("world_flags contains an empty key".to_string()); } } for (label, value) in [ ( "save_profile.profile_kind", self.save_profile.profile_kind.as_deref(), ), ( "save_profile.profile_family", self.save_profile.profile_family.as_deref(), ), ( "save_profile.map_path", self.save_profile.map_path.as_deref(), ), ( "save_profile.display_name", self.save_profile.display_name.as_deref(), ), ] { if value.is_some_and(|text| text.trim().is_empty()) { return Err(format!("{label} must not be empty")); } } if self.world_restore.selected_year_profile_lane.is_none() && (self.world_restore.campaign_scenario_enabled.is_some() || self.world_restore.sandbox_enabled.is_some()) { return Err( "world_restore.selected_year_profile_lane must be present when world restore flags are populated" .to_string(), ); } if self .world_restore .absolute_counter_restore_kind .as_deref() .is_some_and(|text| text.trim().is_empty()) { return Err( "world_restore.absolute_counter_restore_kind must not be empty".to_string(), ); } if self .world_restore .absolute_counter_adjustment_context .as_deref() .is_some_and(|text| text.trim().is_empty()) { return Err( "world_restore.absolute_counter_adjustment_context must not be empty".to_string(), ); } for (key, value) in &self.metadata { if key.trim().is_empty() { return Err("metadata contains an empty key".to_string()); } if value.trim().is_empty() { return Err(format!("metadata[{key}] must not be empty")); } } for key in self.candidate_availability.keys() { if key.trim().is_empty() { return Err("candidate_availability contains an empty key".to_string()); } } for key in self.named_locomotive_availability.keys() { if key.trim().is_empty() { return Err("named_locomotive_availability contains an empty key".to_string()); } } for key in self.named_locomotive_cost.keys() { if key.trim().is_empty() { return Err("named_locomotive_cost contains an empty key".to_string()); } } for key in self.named_cargo_price_overrides.keys() { if key.trim().is_empty() { return Err("named_cargo_price_overrides contains an empty key".to_string()); } } for key in self.named_cargo_production_overrides.keys() { if key.trim().is_empty() { return Err("named_cargo_production_overrides contains an empty key".to_string()); } } for slot in self.cargo_production_overrides.keys() { if !(1..=11).contains(slot) { return Err(format!( "cargo_production_overrides contains out-of-range slot {}", slot )); } } for index in self.world_runtime_variables.keys() { if !(1..=4).contains(index) { return Err(format!( "world_runtime_variables contains out-of-range index {}", index )); } } for (company_id, vars) in &self.company_runtime_variables { if !seen_company_ids.contains(company_id) { return Err(format!( "company_runtime_variables references unknown company_id {}", company_id )); } for index in vars.keys() { if !(1..=4).contains(index) { return Err(format!( "company_runtime_variables[{company_id}] contains out-of-range index {}", index )); } } } for company_id in self.service_state.company_market_state.keys() { if !seen_company_ids.contains(company_id) { return Err(format!( "service_state.company_market_state references unknown company_id {}", company_id )); } } for (player_id, vars) in &self.player_runtime_variables { if !seen_player_ids.contains(player_id) { return Err(format!( "player_runtime_variables references unknown player_id {}", player_id )); } for index in vars.keys() { if !(1..=4).contains(index) { return Err(format!( "player_runtime_variables[{player_id}] contains out-of-range index {}", index )); } } } for (territory_id, vars) in &self.territory_runtime_variables { if !seen_territory_ids.contains(territory_id) { return Err(format!( "territory_runtime_variables references unknown territory_id {}", territory_id )); } for index in vars.keys() { if !(1..=4).contains(index) { return Err(format!( "territory_runtime_variables[{territory_id}] contains out-of-range index {}", index )); } } } for key in self.world_scalar_overrides.keys() { if key.trim().is_empty() { return Err("world_scalar_overrides contains an empty key".to_string()); } } for key in self.special_conditions.keys() { if key.trim().is_empty() { return Err("special_conditions contains an empty key".to_string()); } } Ok(()) } pub fn refresh_derived_market_state(&mut self) { let company_share_prices = self .service_state .company_market_state .iter() .filter_map(|(company_id, market_state)| { rounded_cached_share_price_i64(market_state.cached_share_price_raw_u32) .map(|share_price| (*company_id, share_price)) }) .collect::>(); for profile in &mut self.chairman_profiles { let preserved_threshold_adjusted_holdings_component = profile .purchasing_power_total .saturating_sub(profile.current_cash) .max(0); if let Some(holdings_value_total) = derive_runtime_chairman_holdings_share_price_total( &profile.company_holdings, &company_share_prices, ) { profile.holdings_value_total = holdings_value_total; } profile.net_worth_total = profile .current_cash .saturating_add(profile.holdings_value_total); profile.purchasing_power_total = profile .current_cash .saturating_add(preserved_threshold_adjusted_holdings_component) .max(profile.net_worth_total); } } } pub fn runtime_company_stat_value( state: &RuntimeState, company_id: u32, selector: RuntimeCompanyStatSelector, ) -> Option { let company = state .companies .iter() .find(|company| company.company_id == company_id)?; match (selector.family_id, selector.slot_id) { (RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER, RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH) => { Some(company.current_cash) } ( RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER, RUNTIME_COMPANY_STAT_SLOT_BOOK_VALUE_PER_SHARE, ) => Some(company.book_value_per_share), _ => None, } } pub fn runtime_world_issue_state( state: &RuntimeState, issue_id: u32, ) -> Option { match issue_id { RUNTIME_WORLD_ISSUE_INVESTOR_CONFIDENCE => Some(RuntimeWorldIssueState { issue_id, raw_value_u32: state.world_restore.issue_37_value?, multiplier_raw_u32: state.world_restore.issue_37_multiplier_raw_u32, multiplier_value_f32_text: state .world_restore .issue_37_multiplier_value_f32_text .clone(), }), RUNTIME_WORLD_ISSUE_CREDIT_MARKET => Some(RuntimeWorldIssueState { issue_id, raw_value_u32: state.world_restore.issue_38_value?, multiplier_raw_u32: None, multiplier_value_f32_text: None, }), RUNTIME_WORLD_ISSUE_PRIME_RATE => Some(RuntimeWorldIssueState { issue_id, raw_value_u32: state.world_restore.issue_39_value?, multiplier_raw_u32: None, multiplier_value_f32_text: None, }), RUNTIME_WORLD_ISSUE_MANAGEMENT_ATTITUDE => Some(RuntimeWorldIssueState { issue_id, raw_value_u32: state.world_restore.issue_3a_value?, multiplier_raw_u32: None, multiplier_value_f32_text: None, }), _ => None, } } pub fn runtime_world_absolute_counter(state: &RuntimeState) -> Option { state.world_restore.absolute_counter_raw_u32 } pub fn runtime_world_partial_year_weight_numerator(state: &RuntimeState) -> Option { Some(i64::from(state.world_restore.partial_year_progress_raw_u8?) * 5 - 5) } pub fn runtime_company_unassigned_share_pool(state: &RuntimeState, company_id: u32) -> Option { let outstanding_shares = state .service_state .company_market_state .get(&company_id)? .outstanding_shares; let assigned_shares = runtime_company_assigned_share_pool(state, company_id)?; Some(outstanding_shares.saturating_sub(assigned_shares)) } pub fn runtime_company_assigned_share_pool(state: &RuntimeState, company_id: u32) -> Option { state.service_state.company_market_state.get(&company_id)?; Some( state .chairman_profiles .iter() .filter_map(|profile| profile.company_holdings.get(&company_id).copied()) .sum::(), ) } pub fn runtime_company_annual_finance_state( state: &RuntimeState, company_id: u32, ) -> Option { let market_state = state.service_state.company_market_state.get(&company_id)?; let assigned_share_pool = runtime_company_assigned_share_pool(state, company_id)?; let unassigned_share_pool = runtime_company_unassigned_share_pool(state, company_id)?; let years_since_founding = derive_runtime_company_elapsed_years(state.calendar.year, market_state.founding_year); let years_since_last_bankruptcy = derive_runtime_company_elapsed_years( state.calendar.year, market_state.last_bankruptcy_year, ); let years_since_last_dividend = derive_runtime_company_elapsed_years(state.calendar.year, market_state.last_dividend_year); Some(RuntimeCompanyAnnualFinanceState { company_id, outstanding_shares: market_state.outstanding_shares, bond_count: market_state.bond_count, largest_live_bond_principal: market_state.largest_live_bond_principal, highest_coupon_live_bond_principal: market_state.highest_coupon_live_bond_principal, assigned_share_pool, unassigned_share_pool, cached_share_price: rounded_cached_share_price_i64(market_state.cached_share_price_raw_u32), chairman_salary_baseline: market_state.chairman_salary_baseline, chairman_salary_current: market_state.chairman_salary_current, chairman_bonus_year: market_state.chairman_bonus_year, chairman_bonus_amount: market_state.chairman_bonus_amount, founding_year: market_state.founding_year, last_bankruptcy_year: market_state.last_bankruptcy_year, last_dividend_year: market_state.last_dividend_year, years_since_founding, years_since_last_bankruptcy, years_since_last_dividend, current_partial_year_weight_numerator: runtime_world_partial_year_weight_numerator(state), current_issue_calendar_word: market_state.current_issue_calendar_word, current_issue_calendar_word_2: market_state.current_issue_calendar_word_2, prior_issue_calendar_word: market_state.prior_issue_calendar_word, prior_issue_calendar_word_2: market_state.prior_issue_calendar_word_2, city_connection_latch: market_state.city_connection_latch, linked_transit_latch: market_state.linked_transit_latch, }) } pub fn runtime_company_market_value( state: &RuntimeState, company_id: u32, metric: RuntimeCompanyMarketMetric, ) -> Option { let annual_finance_state = runtime_company_annual_finance_state(state, company_id)?; match metric { RuntimeCompanyMarketMetric::OutstandingShares => { Some(annual_finance_state.outstanding_shares as i64) } RuntimeCompanyMarketMetric::BondCount => Some(annual_finance_state.bond_count as i64), RuntimeCompanyMarketMetric::LargestLiveBondPrincipal => annual_finance_state .largest_live_bond_principal .map(|value| value as i64), RuntimeCompanyMarketMetric::HighestCouponLiveBondPrincipal => annual_finance_state .highest_coupon_live_bond_principal .map(|value| value as i64), RuntimeCompanyMarketMetric::AssignedSharePool => { Some(annual_finance_state.assigned_share_pool as i64) } RuntimeCompanyMarketMetric::UnassignedSharePool => { Some(annual_finance_state.unassigned_share_pool as i64) } RuntimeCompanyMarketMetric::CachedSharePrice => annual_finance_state.cached_share_price, RuntimeCompanyMarketMetric::ChairmanSalaryBaseline => { Some(annual_finance_state.chairman_salary_baseline as i64) } RuntimeCompanyMarketMetric::ChairmanSalaryCurrent => { Some(annual_finance_state.chairman_salary_current as i64) } RuntimeCompanyMarketMetric::ChairmanBonusAmount => { Some(annual_finance_state.chairman_bonus_amount as i64) } RuntimeCompanyMarketMetric::CurrentIssueCalendarWord => { Some(annual_finance_state.current_issue_calendar_word as i64) } RuntimeCompanyMarketMetric::CurrentIssueCalendarWord2 => { Some(annual_finance_state.current_issue_calendar_word_2 as i64) } RuntimeCompanyMarketMetric::PriorIssueCalendarWord => { Some(annual_finance_state.prior_issue_calendar_word as i64) } RuntimeCompanyMarketMetric::PriorIssueCalendarWord2 => { Some(annual_finance_state.prior_issue_calendar_word_2 as i64) } } } fn rounded_cached_share_price_i64(raw_u32: u32) -> Option { let value = f32::from_bits(raw_u32); if !value.is_finite() { return None; } if value < i64::MIN as f32 || value > i64::MAX as f32 { return None; } Some(value.round() as i64) } fn derive_runtime_company_elapsed_years(current_year: u32, prior_year: u32) -> Option { if prior_year == 0 || prior_year > current_year { return None; } Some(current_year - prior_year) } fn derive_runtime_chairman_holdings_share_price_total( holdings_by_company: &BTreeMap, company_share_prices: &BTreeMap, ) -> Option { let mut total = 0i64; for (company_id, units) in holdings_by_company { let share_price = *company_share_prices.get(company_id)?; total = total.checked_add((*units as i64).checked_mul(share_price)?)?; } Some(total) } fn validate_runtime_effect( effect: &RuntimeEffect, valid_company_ids: &BTreeSet, valid_player_ids: &BTreeSet, valid_chairman_profile_ids: &BTreeSet, valid_territory_ids: &BTreeSet, ) -> Result<(), String> { match effect { RuntimeEffect::SetWorldFlag { key, .. } => { if key.trim().is_empty() { return Err("key must not be empty".to_string()); } } RuntimeEffect::SetWorldVariable { index, .. } => { if !(1..=4).contains(index) { return Err(format!( "world runtime variable index {index} must be in 1..=4" )); } } RuntimeEffect::SetWorldScalarOverride { key, .. } => { if key.trim().is_empty() { return Err("key must not be empty".to_string()); } } RuntimeEffect::SetLimitedTrackBuildingAmount { .. } | RuntimeEffect::SetEconomicStatusCode { .. } => {} RuntimeEffect::SetCompanyVariable { target, index, .. } => { validate_company_target(target, valid_company_ids)?; if !(1..=4).contains(index) { return Err(format!("runtime variable index {index} must be in 1..=4")); } } RuntimeEffect::SetCompanyCash { target, .. } | RuntimeEffect::ConfiscateCompanyAssets { target } | RuntimeEffect::DeactivateCompany { target } | RuntimeEffect::SetCompanyTrackLayingCapacity { target, .. } | RuntimeEffect::AdjustCompanyCash { target, .. } | RuntimeEffect::AdjustCompanyDebt { target, .. } => { validate_company_target(target, valid_company_ids)?; } RuntimeEffect::SetCompanyGovernanceScalar { target, metric, .. } => { validate_company_target(target, valid_company_ids)?; validate_company_governance_scalar_metric(*metric)?; } RuntimeEffect::SetCompanyTerritoryAccess { target, territory, .. } => { validate_company_target(target, valid_company_ids)?; validate_territory_target(territory, valid_territory_ids)?; } RuntimeEffect::SetPlayerVariable { target, index, .. } => { validate_player_target(target, valid_player_ids)?; if !(1..=4).contains(index) { return Err(format!("runtime variable index {index} must be in 1..=4")); } } RuntimeEffect::SetPlayerCash { target, .. } | RuntimeEffect::DeactivatePlayer { target } => { validate_player_target(target, valid_player_ids)?; } RuntimeEffect::SetChairmanCash { target, .. } | RuntimeEffect::DeactivateChairman { target } => { validate_chairman_target(target, valid_chairman_profile_ids)?; } RuntimeEffect::RetireTrains { company_target, territory_target, locomotive_name, } => { if let Some(company_target) = company_target { validate_company_target(company_target, valid_company_ids)?; } if let Some(territory_target) = territory_target { validate_territory_target(territory_target, valid_territory_ids)?; } if company_target.is_none() && territory_target.is_none() && locomotive_name.is_none() { return Err( "retire_trains requires at least one company_target, territory_target, or locomotive_name filter" .to_string(), ); } if locomotive_name .as_deref() .is_some_and(|value| value.trim().is_empty()) { return Err("locomotive_name must not be empty".to_string()); } } RuntimeEffect::SetCandidateAvailability { name, .. } => { if name.trim().is_empty() { return Err("name must not be empty".to_string()); } } RuntimeEffect::SetNamedLocomotiveAvailability { name, .. } => { if name.trim().is_empty() { return Err("name must not be empty".to_string()); } } RuntimeEffect::SetNamedLocomotiveAvailabilityValue { name, .. } => { if name.trim().is_empty() { return Err("name must not be empty".to_string()); } } RuntimeEffect::SetNamedLocomotiveCost { name, .. } => { if name.trim().is_empty() { return Err("name must not be empty".to_string()); } } RuntimeEffect::SetCargoPriceOverride { target, .. } => match target { RuntimeCargoPriceTarget::All => {} RuntimeCargoPriceTarget::Named { name } => { if name.trim().is_empty() { return Err("name must not be empty".to_string()); } } }, RuntimeEffect::SetCargoProductionOverride { target, .. } => match target { RuntimeCargoProductionTarget::All | RuntimeCargoProductionTarget::Factory | RuntimeCargoProductionTarget::FarmMine => {} RuntimeCargoProductionTarget::Named { name } => { if name.trim().is_empty() { return Err("name must not be empty".to_string()); } } }, RuntimeEffect::SetCargoProductionSlot { slot, .. } => { if !(1..=11).contains(slot) { return Err("slot must be in 1..=11".to_string()); } } RuntimeEffect::SetTerritoryVariable { target, index, .. } => { validate_territory_target(target, valid_territory_ids)?; if !(1..=4).contains(index) { return Err(format!("runtime variable index {index} must be in 1..=4")); } } RuntimeEffect::SetTerritoryAccessCost { .. } => {} RuntimeEffect::SetSpecialCondition { label, .. } => { if label.trim().is_empty() { return Err("label must not be empty".to_string()); } } RuntimeEffect::AppendEventRecord { record } => { validate_event_record_template( record, valid_company_ids, valid_player_ids, valid_chairman_profile_ids, valid_territory_ids, )?; } RuntimeEffect::ActivateEventRecord { .. } | RuntimeEffect::DeactivateEventRecord { .. } | RuntimeEffect::RemoveEventRecord { .. } => {} } Ok(()) } fn validate_event_record_template( record: &RuntimeEventRecordTemplate, valid_company_ids: &BTreeSet, valid_player_ids: &BTreeSet, valid_chairman_profile_ids: &BTreeSet, valid_territory_ids: &BTreeSet, ) -> Result<(), String> { for (condition_index, condition) in record.conditions.iter().enumerate() { validate_runtime_condition( condition, valid_company_ids, valid_player_ids, valid_chairman_profile_ids, valid_territory_ids, ) .map_err(|err| { format!( "template record_id={}.conditions[{condition_index}] {err}", record.record_id ) })?; } for (effect_index, effect) in record.effects.iter().enumerate() { validate_runtime_effect( effect, valid_company_ids, valid_player_ids, valid_chairman_profile_ids, valid_territory_ids, ) .map_err(|err| { format!( "template record_id={}.effects[{effect_index}] {err}", record.record_id ) })?; } Ok(()) } fn validate_runtime_condition( condition: &RuntimeCondition, valid_company_ids: &BTreeSet, valid_player_ids: &BTreeSet, valid_chairman_profile_ids: &BTreeSet, valid_territory_ids: &BTreeSet, ) -> Result<(), String> { match condition { RuntimeCondition::WorldVariableThreshold { index, .. } => { if !(1..=4).contains(index) { Err("index must be in 1..=4".to_string()) } else { Ok(()) } } RuntimeCondition::CompanyNumericThreshold { target, .. } => { validate_company_target(target, valid_company_ids) } RuntimeCondition::CompanyVariableThreshold { target, index, .. } => { validate_company_target(target, valid_company_ids)?; if !(1..=4).contains(index) { Err("index must be in 1..=4".to_string()) } else { Ok(()) } } RuntimeCondition::ChairmanNumericThreshold { target, .. } => { validate_chairman_target(target, valid_chairman_profile_ids) } RuntimeCondition::PlayerVariableThreshold { target, index, .. } => { validate_player_target(target, valid_player_ids)?; if !(1..=4).contains(index) { Err("index must be in 1..=4".to_string()) } else { Ok(()) } } RuntimeCondition::TerritoryNumericThreshold { target, .. } => { validate_territory_target(target, valid_territory_ids) } RuntimeCondition::TerritoryVariableThreshold { target, index, .. } => { validate_territory_target(target, valid_territory_ids)?; if !(1..=4).contains(index) { Err("index must be in 1..=4".to_string()) } else { Ok(()) } } RuntimeCondition::CompanyTerritoryNumericThreshold { target, territory, .. } => { validate_company_target(target, valid_company_ids)?; validate_territory_target(territory, valid_territory_ids) } RuntimeCondition::SpecialConditionThreshold { label, .. } => { if label.trim().is_empty() { Err("label must not be empty".to_string()) } else { Ok(()) } } RuntimeCondition::CandidateAvailabilityThreshold { name, .. } => { if name.trim().is_empty() { Err("name must not be empty".to_string()) } else { Ok(()) } } RuntimeCondition::NamedLocomotiveAvailabilityThreshold { name, .. } | RuntimeCondition::NamedLocomotiveCostThreshold { name, .. } => { if name.trim().is_empty() { Err("name must not be empty".to_string()) } else { Ok(()) } } RuntimeCondition::CargoProductionSlotThreshold { slot, label, .. } => { if !(1..=11).contains(slot) { Err("slot must be in 1..=11".to_string()) } else if label.trim().is_empty() { Err("label must not be empty".to_string()) } else { Ok(()) } } RuntimeCondition::CargoProductionTotalThreshold { .. } | RuntimeCondition::FactoryProductionTotalThreshold { .. } | RuntimeCondition::FarmMineProductionTotalThreshold { .. } | RuntimeCondition::OtherCargoProductionTotalThreshold { .. } | RuntimeCondition::LimitedTrackBuildingAmountThreshold { .. } | RuntimeCondition::TerritoryAccessCostThreshold { .. } => Ok(()), RuntimeCondition::EconomicStatusCodeThreshold { .. } => Ok(()), RuntimeCondition::WorldFlagEquals { key, .. } => { if key.trim().is_empty() { Err("key must not be empty".to_string()) } else { Ok(()) } } } } fn validate_company_target( target: &RuntimeCompanyTarget, valid_company_ids: &BTreeSet, ) -> Result<(), String> { match target { RuntimeCompanyTarget::AllActive | RuntimeCompanyTarget::HumanCompanies | RuntimeCompanyTarget::AiCompanies | RuntimeCompanyTarget::SelectedCompany | RuntimeCompanyTarget::ConditionTrueCompany => Ok(()), RuntimeCompanyTarget::Ids { ids } => { if ids.is_empty() { return Err("target ids must not be empty".to_string()); } for company_id in ids { if !valid_company_ids.contains(company_id) { return Err(format!("target references unknown company_id {company_id}")); } } Ok(()) } } } fn validate_player_target( target: &RuntimePlayerTarget, valid_player_ids: &BTreeSet, ) -> Result<(), String> { match target { RuntimePlayerTarget::AllActive | RuntimePlayerTarget::HumanPlayers | RuntimePlayerTarget::AiPlayers | RuntimePlayerTarget::SelectedPlayer | RuntimePlayerTarget::ConditionTruePlayer => Ok(()), RuntimePlayerTarget::Ids { ids } => { if ids.is_empty() { return Err("target ids must not be empty".to_string()); } for player_id in ids { if !valid_player_ids.contains(player_id) { return Err(format!("target references unknown player_id {player_id}")); } } Ok(()) } } } fn validate_chairman_target( target: &RuntimeChairmanTarget, valid_chairman_profile_ids: &BTreeSet, ) -> Result<(), String> { match target { RuntimeChairmanTarget::AllActive | RuntimeChairmanTarget::HumanChairmen | RuntimeChairmanTarget::AiChairmen | RuntimeChairmanTarget::SelectedChairman | RuntimeChairmanTarget::ConditionTrueChairman => Ok(()), RuntimeChairmanTarget::Ids { ids } => { if ids.is_empty() { return Err("target ids must not be empty".to_string()); } for profile_id in ids { if !valid_chairman_profile_ids.contains(profile_id) { return Err(format!( "target references unknown chairman profile_id {profile_id}" )); } } Ok(()) } } } fn validate_company_governance_scalar_metric(metric: RuntimeCompanyMetric) -> Result<(), String> { match metric { RuntimeCompanyMetric::CreditRating | RuntimeCompanyMetric::PrimeRate | RuntimeCompanyMetric::BookValuePerShare | RuntimeCompanyMetric::InvestorConfidence | RuntimeCompanyMetric::ManagementAttitude => Ok(()), _ => Err( "governance scalar effect requires a writable company governance metric".to_string(), ), } } fn validate_territory_target( target: &RuntimeTerritoryTarget, valid_territory_ids: &BTreeSet, ) -> Result<(), String> { match target { RuntimeTerritoryTarget::AllTerritories => Ok(()), RuntimeTerritoryTarget::Ids { ids } => { if ids.is_empty() { return Err("territory target ids must not be empty".to_string()); } for territory_id in ids { if !valid_territory_ids.contains(territory_id) { return Err(format!( "territory target references unknown territory_id {territory_id}" )); } } Ok(()) } } } #[cfg(test)] mod tests { use super::*; #[test] fn rejects_duplicate_company_ids() { let state = 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![ RuntimeCompany { company_id: 1, 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, controller_kind: RuntimeCompanyControllerKind::Unknown, }, RuntimeCompany { company_id: 1, current_cash: 200, 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, controller_kind: RuntimeCompanyControllerKind::Unknown, }, ], 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(), }; assert!(state.validate().is_err()); } #[test] fn rejects_partial_world_restore_without_year_lane() { let state = 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 { selected_year_profile_lane: None, campaign_scenario_enabled: Some(false), sandbox_enabled: Some(true), seed_tuple_written_from_raw_lane: Some(true), absolute_counter_requires_shell_context: Some(true), absolute_counter_reconstructible_from_save: Some(false), packed_year_word_raw_u16: None, partial_year_progress_raw_u8: None, current_calendar_tuple_word_raw_u32: None, current_calendar_tuple_word_2_raw_u32: None, absolute_counter_raw_u32: None, absolute_counter_mirror_raw_u32: None, 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: Some(false), use_bio_accelerator_cars_enabled: Some(false), use_wartime_cargos_enabled: Some(false), disable_train_crashes_enabled: Some(false), disable_train_crashes_and_breakdowns_enabled: Some(false), ai_ignore_territories_at_startup_enabled: Some(false), limited_track_building_amount: None, economic_status_code: None, territory_access_cost: None, issue_37_value: None, issue_38_value: None, issue_39_value: None, issue_3a_value: None, issue_37_multiplier_raw_u32: None, issue_37_multiplier_value_f32_text: None, finance_neighborhood_candidates: Vec::new(), economic_tuning_mirror_raw_u32: None, economic_tuning_mirror_value_f32_text: None, economic_tuning_lane_raw_u32: Vec::new(), economic_tuning_lane_value_f32_text: Vec::new(), absolute_counter_restore_kind: Some( "mode-adjusted-selected-year-lane".to_string(), ), absolute_counter_adjustment_context: Some( "editor-map-mode,shell-selected-year-adjust-policy-0x9d26-0x9d28,save-special-condition-disable-cargo-economy-slot-30".to_string(), ), }, 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(), }; assert!(state.validate().is_err()); } #[test] fn rejects_event_effect_targeting_unknown_company() { let state = 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![RuntimeCompany { company_id: 1, 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, controller_kind: RuntimeCompanyControllerKind::Unknown, }], 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![RuntimeEventRecord { record_id: 7, trigger_kind: 1, active: true, service_count: 0, marks_collection_dirty: false, one_shot: false, has_fired: false, conditions: Vec::new(), effects: vec![RuntimeEffect::AdjustCompanyCash { target: RuntimeCompanyTarget::Ids { ids: vec![2] }, delta: 50, }], }], 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(), }; assert!(state.validate().is_err()); } #[test] fn rejects_template_effect_targeting_unknown_company() { let state = 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![RuntimeCompany { company_id: 1, 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, controller_kind: RuntimeCompanyControllerKind::Unknown, }], 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![RuntimeEventRecord { record_id: 7, trigger_kind: 1, active: true, service_count: 0, marks_collection_dirty: false, one_shot: false, has_fired: false, conditions: Vec::new(), effects: vec![RuntimeEffect::AppendEventRecord { record: Box::new(RuntimeEventRecordTemplate { record_id: 8, trigger_kind: 0x0a, active: true, marks_collection_dirty: false, one_shot: false, conditions: Vec::new(), effects: vec![RuntimeEffect::AdjustCompanyCash { target: RuntimeCompanyTarget::Ids { ids: vec![2] }, delta: 50, }], }), }], }], 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(), }; assert!(state.validate().is_err()); } #[test] fn rejects_invalid_packed_event_collection_summary() { let state = 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: Some(RuntimePackedEventCollectionSummary { 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()), packed_state_version: 0x3e9, packed_state_version_hex: "0x000003e9".to_string(), live_id_bound: 4, live_record_count: 2, live_entry_ids: vec![3, 3], decoded_record_count: 0, imported_runtime_record_count: 0, records: vec![ RuntimePackedEventRecordSummary { record_index: 0, 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(), grouped_company_targets: Vec::new(), decoded_conditions: Vec::new(), decoded_actions: Vec::new(), executable_import_ready: false, import_outcome: None, notes: vec!["test".to_string()], }, RuntimePackedEventRecordSummary { 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(), grouped_company_targets: Vec::new(), decoded_conditions: Vec::new(), decoded_actions: Vec::new(), executable_import_ready: false, import_outcome: None, notes: vec!["test".to_string()], }, ], }), 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(), }; assert!(state.validate().is_err()); } #[test] fn rejects_selected_company_id_that_does_not_exist() { let state = 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![RuntimeCompany { company_id: 1, 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, controller_kind: RuntimeCompanyControllerKind::Human, }], selected_company_id: Some(2), 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(), }; assert!(state.validate().is_err()); } #[test] fn rejects_selected_company_id_that_is_inactive() { let state = 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![RuntimeCompany { company_id: 1, current_cash: 100, debt: 0, credit_rating_score: None, prime_rate: None, track_piece_counts: RuntimeTrackPieceCounts::default(), active: false, 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, controller_kind: RuntimeCompanyControllerKind::Human, }], 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(), }; assert!(state.validate().is_err()); } #[test] fn rejects_duplicate_train_ids() { let state = 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![RuntimeCompany { company_id: 1, 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, controller_kind: RuntimeCompanyControllerKind::Human, }], selected_company_id: None, players: Vec::new(), selected_player_id: None, chairman_profiles: Vec::new(), selected_chairman_profile_id: None, trains: vec![ RuntimeTrain { train_id: 7, owner_company_id: 1, territory_id: None, locomotive_name: Some("Mikado".to_string()), active: true, retired: false, }, RuntimeTrain { train_id: 7, owner_company_id: 1, territory_id: None, locomotive_name: Some("Orca".to_string()), active: true, retired: false, }, ], 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(), }; assert!(state.validate().is_err()); } #[test] fn rejects_train_with_unknown_owner_company() { let state = 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![RuntimeCompany { company_id: 1, 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, controller_kind: RuntimeCompanyControllerKind::Human, }], selected_company_id: None, players: Vec::new(), selected_player_id: None, chairman_profiles: Vec::new(), selected_chairman_profile_id: None, trains: vec![RuntimeTrain { train_id: 7, owner_company_id: 2, territory_id: None, locomotive_name: Some("Mikado".to_string()), active: true, retired: false, }], 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(), }; assert!(state.validate().is_err()); } #[test] fn rejects_train_with_unknown_territory() { let state = 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![RuntimeCompany { company_id: 1, 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, controller_kind: RuntimeCompanyControllerKind::Human, }], selected_company_id: None, players: Vec::new(), selected_player_id: None, chairman_profiles: Vec::new(), selected_chairman_profile_id: None, trains: vec![RuntimeTrain { train_id: 7, owner_company_id: 1, territory_id: Some(9), locomotive_name: Some("Mikado".to_string()), active: true, retired: false, }], locomotive_catalog: Vec::new(), cargo_catalog: Vec::new(), territories: vec![RuntimeTerritory { territory_id: 1, name: Some("Appalachia".to_string()), track_piece_counts: RuntimeTrackPieceCounts::default(), }], 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(), }; assert!(state.validate().is_err()); } #[test] fn rejects_train_marked_active_and_retired() { let state = 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![RuntimeCompany { company_id: 1, 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, controller_kind: RuntimeCompanyControllerKind::Human, }], selected_company_id: None, players: Vec::new(), selected_player_id: None, chairman_profiles: Vec::new(), selected_chairman_profile_id: None, trains: vec![RuntimeTrain { train_id: 7, owner_company_id: 1, territory_id: None, locomotive_name: Some("Mikado".to_string()), active: true, retired: true, }], 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(), }; assert!(state.validate().is_err()); } #[test] fn rejects_duplicate_company_territory_access_pairs() { let state = 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![RuntimeCompany { company_id: 1, 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, controller_kind: RuntimeCompanyControllerKind::Human, }], 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![RuntimeTerritory { territory_id: 7, name: Some("Appalachia".to_string()), track_piece_counts: RuntimeTrackPieceCounts::default(), }], company_territory_track_piece_counts: Vec::new(), company_territory_access: vec![ RuntimeCompanyTerritoryAccess { company_id: 1, territory_id: 7, }, RuntimeCompanyTerritoryAccess { company_id: 1, territory_id: 7, }, ], 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(), }; assert!(state.validate().is_err()); } #[test] fn rejects_company_territory_access_with_unknown_company() { let state = 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![RuntimeCompany { company_id: 1, 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, controller_kind: RuntimeCompanyControllerKind::Human, }], 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![RuntimeTerritory { territory_id: 7, name: Some("Appalachia".to_string()), track_piece_counts: RuntimeTrackPieceCounts::default(), }], company_territory_track_piece_counts: Vec::new(), company_territory_access: vec![RuntimeCompanyTerritoryAccess { company_id: 2, territory_id: 7, }], 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(), }; assert!(state.validate().is_err()); } #[test] fn rejects_company_territory_access_with_unknown_territory() { let state = 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![RuntimeCompany { company_id: 1, 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, controller_kind: RuntimeCompanyControllerKind::Human, }], 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![RuntimeTerritory { territory_id: 7, name: Some("Appalachia".to_string()), track_piece_counts: RuntimeTrackPieceCounts::default(), }], company_territory_track_piece_counts: Vec::new(), company_territory_access: vec![RuntimeCompanyTerritoryAccess { company_id: 1, territory_id: 8, }], 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(), }; assert!(state.validate().is_err()); } #[test] fn rejects_company_with_unknown_linked_chairman_profile() { let state = 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![RuntimeCompany { company_id: 1, 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(9), book_value_per_share: 0, investor_confidence: 0, management_attitude: 0, takeover_cooldown_year: None, merger_cooldown_year: None, controller_kind: RuntimeCompanyControllerKind::Human, }], 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(), }; assert!(state.validate().is_err()); } #[test] fn rejects_mismatched_company_chairman_back_links() { let state = 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![RuntimeCompany { company_id: 1, 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: 0, investor_confidence: 0, management_attitude: 0, takeover_cooldown_year: None, merger_cooldown_year: None, controller_kind: RuntimeCompanyControllerKind::Human, }], selected_company_id: None, players: Vec::new(), selected_player_id: None, chairman_profiles: vec![RuntimeChairmanProfile { profile_id: 1, name: "Chairman One".to_string(), active: true, current_cash: 0, linked_company_id: None, company_holdings: BTreeMap::new(), holdings_value_total: 0, net_worth_total: 0, purchasing_power_total: 0, }], 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(), }; assert!(state.validate().is_err()); } #[test] fn refreshes_chairman_totals_from_company_market_state() { let mut state = 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![ RuntimeCompany { company_id: 1, current_cash: 0, 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, controller_kind: RuntimeCompanyControllerKind::Human, }, RuntimeCompany { company_id: 2, current_cash: 0, 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, controller_kind: RuntimeCompanyControllerKind::Human, }, ], selected_company_id: None, players: Vec::new(), selected_player_id: None, chairman_profiles: vec![RuntimeChairmanProfile { profile_id: 1, name: "Chairman One".to_string(), active: true, current_cash: 100, linked_company_id: Some(1), company_holdings: BTreeMap::from([(1, 2), (2, 3)]), holdings_value_total: 0, net_worth_total: 0, purchasing_power_total: 400, }], 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 { company_market_state: BTreeMap::from([ ( 1, RuntimeCompanyMarketState { cached_share_price_raw_u32: 0x41200000, ..RuntimeCompanyMarketState::default() }, ), ( 2, RuntimeCompanyMarketState { cached_share_price_raw_u32: 0x41a00000, ..RuntimeCompanyMarketState::default() }, ), ]), ..RuntimeServiceState::default() }, }; state.refresh_derived_market_state(); assert_eq!(state.chairman_profiles[0].holdings_value_total, 80); assert_eq!(state.chairman_profiles[0].net_worth_total, 180); assert_eq!(state.chairman_profiles[0].purchasing_power_total, 400); } #[test] fn refreshes_chairman_purchasing_power_when_cash_changes() { let mut state = 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![RuntimeCompany { company_id: 1, current_cash: 0, 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, controller_kind: RuntimeCompanyControllerKind::Human, }], selected_company_id: None, players: Vec::new(), selected_player_id: None, chairman_profiles: vec![RuntimeChairmanProfile { profile_id: 1, name: "Chairman One".to_string(), active: true, current_cash: 50, linked_company_id: Some(1), company_holdings: BTreeMap::from([(1, 2)]), holdings_value_total: 20, net_worth_total: 70, purchasing_power_total: 130, }], 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 { company_market_state: BTreeMap::from([( 1, RuntimeCompanyMarketState { cached_share_price_raw_u32: 0x41200000, ..RuntimeCompanyMarketState::default() }, )]), ..RuntimeServiceState::default() }, }; state.chairman_profiles[0].current_cash = 80; state.refresh_derived_market_state(); assert_eq!(state.chairman_profiles[0].holdings_value_total, 20); assert_eq!(state.chairman_profiles[0].net_worth_total, 100); assert_eq!(state.chairman_profiles[0].purchasing_power_total, 130); } #[test] fn reads_grounded_company_stat_family_slots_from_runtime_state() { let state = 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![RuntimeCompany { company_id: 7, current_cash: 125_000, debt: 0, credit_rating_score: None, prime_rate: None, active: true, available_track_laying_capacity: None, controller_kind: RuntimeCompanyControllerKind::Unknown, linked_chairman_profile_id: None, book_value_per_share: 2_620, investor_confidence: 0, management_attitude: 0, takeover_cooldown_year: None, merger_cooldown_year: None, track_piece_counts: RuntimeTrackPieceCounts::default(), }], 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(), }; assert_eq!( runtime_company_stat_value( &state, 7, RuntimeCompanyStatSelector { family_id: RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER, slot_id: RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH, }, ), Some(125_000) ); assert_eq!( runtime_company_stat_value( &state, 7, RuntimeCompanyStatSelector { family_id: RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER, slot_id: RUNTIME_COMPANY_STAT_SLOT_BOOK_VALUE_PER_SHARE, }, ), Some(2_620) ); assert_eq!( runtime_company_stat_value( &state, 7, RuntimeCompanyStatSelector { family_id: RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER, slot_id: 0x2b, }, ), None ); assert_eq!( runtime_company_stat_value( &state, 99, RuntimeCompanyStatSelector { family_id: RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER, slot_id: RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH, }, ), None ); } #[test] fn reads_grounded_world_issue_state_from_runtime_restore_state() { let state = 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 { issue_37_value: Some(3), issue_38_value: Some(1), issue_39_value: Some(2), issue_3a_value: Some(4), issue_37_multiplier_raw_u32: Some(0x3d75c28f), issue_37_multiplier_value_f32_text: Some("0.060000".to_string()), ..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(), }; let issue = runtime_world_issue_state(&state, RUNTIME_WORLD_ISSUE_INVESTOR_CONFIDENCE) .expect("grounded issue 0x37 state"); assert_eq!(issue.issue_id, RUNTIME_WORLD_ISSUE_INVESTOR_CONFIDENCE); assert_eq!(issue.raw_value_u32, 3); assert_eq!(issue.multiplier_raw_u32, Some(0x3d75c28f)); assert_eq!(issue.multiplier_value_f32_text.as_deref(), Some("0.060000")); assert_eq!( runtime_world_issue_state(&state, RUNTIME_WORLD_ISSUE_CREDIT_MARKET) .expect("grounded issue 0x38 state") .raw_value_u32, 1 ); assert_eq!( runtime_world_issue_state(&state, RUNTIME_WORLD_ISSUE_PRIME_RATE) .expect("grounded issue 0x39 state") .raw_value_u32, 2 ); assert_eq!( runtime_world_issue_state(&state, RUNTIME_WORLD_ISSUE_MANAGEMENT_ATTITUDE) .expect("grounded issue 0x3a state") .raw_value_u32, 4 ); assert_eq!(runtime_world_issue_state(&state, 0x40), None); assert_eq!(runtime_world_absolute_counter(&state), None); } #[test] fn reads_grounded_world_absolute_counter_from_runtime_restore_state() { let state = 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 { absolute_counter_raw_u32: Some(5), absolute_counter_mirror_raw_u32: Some(5), packed_year_word_raw_u16: Some(0x0210), partial_year_progress_raw_u8: Some(8), current_calendar_tuple_word_raw_u32: Some(0x0108_0210), current_calendar_tuple_word_2_raw_u32: Some(0x35e6_3160), ..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(), }; assert_eq!(runtime_world_absolute_counter(&state), Some(5)); assert_eq!(runtime_world_partial_year_weight_numerator(&state), Some(35)); } #[test] fn derives_company_unassigned_share_pool_from_market_state_and_holdings() { let state = 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![RuntimeCompany { company_id: 4, current_cash: 0, debt: 0, credit_rating_score: None, prime_rate: None, active: true, available_track_laying_capacity: None, controller_kind: RuntimeCompanyControllerKind::Unknown, linked_chairman_profile_id: None, 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: None, players: Vec::new(), selected_player_id: None, chairman_profiles: vec![ RuntimeChairmanProfile { profile_id: 1, name: "Chairman One".to_string(), active: true, current_cash: 0, linked_company_id: Some(4), company_holdings: BTreeMap::from([(4, 8_000)]), holdings_value_total: 0, net_worth_total: 0, purchasing_power_total: 0, }, RuntimeChairmanProfile { profile_id: 2, name: "Chairman Two".to_string(), active: true, current_cash: 0, linked_company_id: None, company_holdings: BTreeMap::from([(4, 7_500)]), holdings_value_total: 0, net_worth_total: 0, purchasing_power_total: 0, }, ], 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 { company_market_state: BTreeMap::from([( 4, RuntimeCompanyMarketState { outstanding_shares: 20_000, ..RuntimeCompanyMarketState::default() }, )]), ..RuntimeServiceState::default() }, }; assert_eq!( runtime_company_unassigned_share_pool(&state, 4), Some(4_500) ); assert_eq!(runtime_company_unassigned_share_pool(&state, 99), None); } #[test] fn derives_company_annual_finance_state_from_owned_runtime_market_state() { let state = 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![RuntimeCompany { company_id: 4, current_cash: 0, debt: 0, credit_rating_score: None, prime_rate: None, active: true, available_track_laying_capacity: None, controller_kind: RuntimeCompanyControllerKind::Unknown, linked_chairman_profile_id: None, 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: None, players: Vec::new(), selected_player_id: None, chairman_profiles: vec![ RuntimeChairmanProfile { profile_id: 1, name: "Chairman One".to_string(), active: true, current_cash: 0, linked_company_id: Some(4), company_holdings: BTreeMap::from([(4, 8_000)]), holdings_value_total: 0, net_worth_total: 0, purchasing_power_total: 0, }, RuntimeChairmanProfile { profile_id: 2, name: "Chairman Two".to_string(), active: true, current_cash: 0, linked_company_id: None, company_holdings: BTreeMap::from([(4, 7_500)]), holdings_value_total: 0, net_worth_total: 0, purchasing_power_total: 0, }, ], 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 { company_market_state: BTreeMap::from([( 4, RuntimeCompanyMarketState { outstanding_shares: 20_000, bond_count: 3, largest_live_bond_principal: Some(650_000), highest_coupon_live_bond_principal: Some(500_000), cached_share_price_raw_u32: 0x42200000, chairman_salary_baseline: 24, chairman_salary_current: 30, chairman_bonus_year: 1842, chairman_bonus_amount: 750, founding_year: 1831, last_bankruptcy_year: 0, last_dividend_year: 1841, 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, ..RuntimeCompanyMarketState::default() }, )]), ..RuntimeServiceState::default() }, }; assert_eq!(runtime_company_assigned_share_pool(&state, 4), Some(15_500)); assert_eq!( runtime_company_annual_finance_state(&state, 4), Some(RuntimeCompanyAnnualFinanceState { company_id: 4, outstanding_shares: 20_000, bond_count: 3, largest_live_bond_principal: Some(650_000), highest_coupon_live_bond_principal: Some(500_000), assigned_share_pool: 15_500, unassigned_share_pool: 4_500, cached_share_price: Some(40), chairman_salary_baseline: 24, chairman_salary_current: 30, chairman_bonus_year: 1842, chairman_bonus_amount: 750, founding_year: 1831, last_bankruptcy_year: 0, last_dividend_year: 1841, years_since_founding: None, years_since_last_bankruptcy: None, years_since_last_dividend: None, current_partial_year_weight_numerator: None, 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, }) ); assert_eq!(runtime_company_assigned_share_pool(&state, 99), None); assert_eq!(runtime_company_annual_finance_state(&state, 99), None); } #[test] fn reads_company_market_metrics_from_annual_finance_reader() { let state = 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![RuntimeCompany { company_id: 7, current_cash: 0, debt: 0, credit_rating_score: None, prime_rate: None, active: true, available_track_laying_capacity: None, controller_kind: RuntimeCompanyControllerKind::Unknown, linked_chairman_profile_id: None, 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: None, players: Vec::new(), selected_player_id: None, chairman_profiles: vec![RuntimeChairmanProfile { profile_id: 1, name: "Chairman One".to_string(), active: true, current_cash: 0, linked_company_id: Some(7), company_holdings: BTreeMap::from([(7, 12_000)]), holdings_value_total: 0, net_worth_total: 0, purchasing_power_total: 0, }], 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 { company_market_state: BTreeMap::from([( 7, RuntimeCompanyMarketState { outstanding_shares: 20_000, bond_count: 2, largest_live_bond_principal: Some(500_000), highest_coupon_live_bond_principal: Some(300_000), cached_share_price_raw_u32: 0x42200000, chairman_salary_baseline: 18, chairman_salary_current: 27, chairman_bonus_year: 1843, chairman_bonus_amount: 625, founding_year: 1832, last_bankruptcy_year: 0, last_dividend_year: 1842, current_issue_calendar_word: 9, current_issue_calendar_word_2: 10, prior_issue_calendar_word: 8, prior_issue_calendar_word_2: 9, ..RuntimeCompanyMarketState::default() }, )]), ..RuntimeServiceState::default() }, }; assert_eq!( runtime_company_market_value(&state, 7, RuntimeCompanyMarketMetric::OutstandingShares), Some(20_000) ); assert_eq!( runtime_company_market_value(&state, 7, RuntimeCompanyMarketMetric::BondCount), Some(2) ); assert_eq!( runtime_company_market_value( &state, 7, RuntimeCompanyMarketMetric::LargestLiveBondPrincipal ), Some(500_000) ); assert_eq!( runtime_company_market_value( &state, 7, RuntimeCompanyMarketMetric::HighestCouponLiveBondPrincipal ), Some(300_000) ); assert_eq!( runtime_company_market_value(&state, 7, RuntimeCompanyMarketMetric::AssignedSharePool), Some(12_000) ); assert_eq!( runtime_company_market_value( &state, 7, RuntimeCompanyMarketMetric::UnassignedSharePool ), Some(8_000) ); assert_eq!( runtime_company_market_value(&state, 7, RuntimeCompanyMarketMetric::CachedSharePrice), Some(40) ); assert_eq!( runtime_company_market_value( &state, 7, RuntimeCompanyMarketMetric::CurrentIssueCalendarWord ), Some(9) ); assert_eq!( runtime_company_market_value( &state, 7, RuntimeCompanyMarketMetric::CurrentIssueCalendarWord2 ), Some(10) ); assert_eq!( runtime_company_market_value( &state, 7, RuntimeCompanyMarketMetric::PriorIssueCalendarWord ), Some(8) ); assert_eq!( runtime_company_market_value( &state, 7, RuntimeCompanyMarketMetric::PriorIssueCalendarWord2 ), Some(9) ); assert_eq!( runtime_company_market_value( &state, 7, RuntimeCompanyMarketMetric::ChairmanSalaryCurrent ), Some(27) ); assert_eq!( runtime_company_market_value( &state, 7, RuntimeCompanyMarketMetric::ChairmanBonusAmount ), Some(625) ); assert_eq!( runtime_company_market_value(&state, 99, RuntimeCompanyMarketMetric::OutstandingShares), None ); } #[test] fn derives_elapsed_company_finance_years_from_calendar_and_saved_market_state() { let state = RuntimeState { calendar: CalendarPoint { year: 1844, 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![RuntimeCompany { company_id: 3, current_cash: 0, debt: 0, credit_rating_score: None, prime_rate: None, active: true, available_track_laying_capacity: None, controller_kind: RuntimeCompanyControllerKind::Unknown, linked_chairman_profile_id: None, 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: 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 { company_market_state: BTreeMap::from([( 3, RuntimeCompanyMarketState { outstanding_shares: 10_000, founding_year: 1838, last_bankruptcy_year: 1841, last_dividend_year: 1843, ..RuntimeCompanyMarketState::default() }, )]), ..RuntimeServiceState::default() }, }; let finance_state = runtime_company_annual_finance_state(&state, 3).expect("finance state should derive"); assert_eq!(finance_state.years_since_founding, Some(6)); assert_eq!(finance_state.years_since_last_bankruptcy, Some(3)); assert_eq!(finance_state.years_since_last_dividend, Some(1)); } }