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_cache_absolute_counter: u32, #[serde(default)] pub recent_per_share_cached_value_bits: u64, #[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, #[serde(default)] pub year_stat_family_qword_bits: Vec, #[serde(default)] pub special_stat_family_232a_qword_bits: Vec, #[serde(default)] pub issue_opinion_terms_raw_i32: Vec, #[serde(default)] pub live_bond_slots: Vec, #[serde(default)] pub direct_control_transfer_float_fields_raw_u32: BTreeMap, #[serde(default)] pub direct_control_transfer_int_fields_raw_u32: BTreeMap, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RuntimeCompanyBondSlot { pub slot_index: u32, pub principal: u32, pub coupon_rate_raw_u32: u32, } #[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, #[serde(default)] pub trailing_full_year_year_words: Vec, #[serde(default)] pub trailing_full_year_net_profits: Vec, #[serde(default)] pub trailing_full_year_revenues: Vec, #[serde(default)] pub trailing_full_year_fuel_costs: Vec, #[serde(default)] pub current_issue_absolute_counter: Option, #[serde(default)] pub prior_issue_absolute_counter: Option, #[serde(default)] pub current_issue_age_absolute_counter_delta: 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, PartialEq, Eq, Serialize, Deserialize)] pub struct RuntimeCompanyAnnualCreditorPressureState { pub company_id: u32, #[serde(default)] pub annual_mode_active: Option, #[serde(default)] pub bankruptcy_allowed: Option, #[serde(default)] pub years_since_last_bankruptcy: Option, #[serde(default)] pub years_since_founding: Option, pub recent_bad_net_profit_year_count: u32, #[serde(default)] pub recent_peak_revenue: Option, #[serde(default)] pub recent_three_year_net_profit_total: Option, #[serde(default)] pub pressure_ladder_cash_floor: Option, #[serde(default)] pub current_cash_plus_slot_12_total: Option, #[serde(default)] pub support_adjusted_share_price_floor: Option, #[serde(default)] pub support_adjusted_share_price_scalar: Option, #[serde(default)] pub current_fuel_cost: Option, #[serde(default)] pub current_fuel_cost_floor: Option, pub eligible_for_bankruptcy_branch: bool, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RuntimeCompanyAnnualDeepDistressState { pub company_id: u32, #[serde(default)] pub bankruptcy_allowed: Option, #[serde(default)] pub years_since_founding: Option, #[serde(default)] pub years_since_last_bankruptcy: Option, #[serde(default)] pub current_cash: Option, pub recent_first_three_net_profit_years: Vec, #[serde(default)] pub deep_distress_cash_floor: Option, #[serde(default)] pub deep_distress_net_profit_floor: Option, pub eligible_for_bankruptcy_fallback: bool, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RuntimeCompanyAnnualStockRepurchaseState { pub company_id: u32, #[serde(default)] pub annual_mode_active: Option, #[serde(default)] pub stock_issue_and_buyback_allowed: Option, pub city_connection_latch: bool, #[serde(default)] pub building_density_growth_setting: Option, #[serde(default)] pub linked_chairman_profile_id: Option, #[serde(default)] pub linked_chairman_personality_raw_u8: Option, #[serde(default)] pub repurchase_batch_size: Option, #[serde(default)] pub repurchase_factor_basis_points: Option, #[serde(default)] pub current_cash: Option, #[serde(default)] pub stock_value_gate_cash_floor: Option, #[serde(default)] pub support_adjusted_share_price_scalar: Option, #[serde(default)] pub affordability_cash_floor: Option, #[serde(default)] pub unassigned_share_pool: Option, pub eligible_for_single_batch_repurchase: bool, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RuntimeCompanyAnnualStockIssueState { pub company_id: u32, #[serde(default)] pub annual_mode_active: Option, #[serde(default)] pub stock_issue_and_buyback_allowed: Option, #[serde(default)] pub bond_issue_and_repayment_allowed: Option, #[serde(default)] pub years_since_founding: Option, #[serde(default)] pub live_bond_count: Option, #[serde(default)] pub initial_issue_batch_size: Option, #[serde(default)] pub trimmed_issue_batch_size: Option, #[serde(default)] pub share_pressure_basis_points: Option, #[serde(default)] pub pressured_support_adjusted_share_price_scalar: Option, #[serde(default)] pub pressured_proceeds: Option, #[serde(default)] pub book_value_per_share_floor_applied: Option, #[serde(default)] pub price_to_book_ratio_basis_points: Option, #[serde(default)] pub current_cash: Option, #[serde(default)] pub highest_coupon_live_bond_principal: Option, #[serde(default)] pub highest_coupon_live_bond_rate_basis_points: Option, #[serde(default)] pub current_issue_age_absolute_counter_delta: Option, #[serde(default)] pub current_issue_cooldown_floor: Option, #[serde(default)] pub minimum_price_to_book_ratio_basis_points: Option, #[serde(default)] pub passes_share_price_floor: Option, #[serde(default)] pub passes_proceeds_floor: Option, #[serde(default)] pub passes_cash_gate: Option, #[serde(default)] pub passes_issue_cooldown_gate: Option, #[serde(default)] pub passes_coupon_price_to_book_gate: Option, pub eligible_for_double_tranche_issue: bool, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RuntimeCompanyAnnualBondPolicyState { pub company_id: u32, #[serde(default)] pub annual_mode_active: Option, #[serde(default)] pub bond_issue_and_repayment_allowed: Option, pub linked_transit_latch: bool, #[serde(default)] pub live_bond_count: Option, #[serde(default)] pub live_bond_principal_total: Option, #[serde(default)] pub current_cash: Option, #[serde(default)] pub cash_after_full_repayment: Option, #[serde(default)] pub issue_cash_floor: Option, #[serde(default)] pub issue_principal_step: Option, #[serde(default)] pub proposed_issue_bond_count: Option, #[serde(default)] pub proposed_issue_total_principal: Option, #[serde(default)] pub proposed_issue_years_to_maturity: Option, pub eligible_for_bond_issue_branch: bool, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RuntimeCompanyAnnualDividendPolicyState { pub company_id: u32, #[serde(default)] pub annual_mode_active: Option, #[serde(default)] pub dividend_adjustment_allowed: Option, #[serde(default)] pub years_since_last_dividend: Option, #[serde(default)] pub years_since_founding: Option, #[serde(default)] pub outstanding_shares: Option, #[serde(default)] pub unassigned_share_pool: Option, #[serde(default)] pub weighted_recent_net_profit_total: Option, #[serde(default)] pub weighted_recent_net_profit_average: Option, #[serde(default)] pub current_cash: Option, pub tiny_unassigned_share_cash_supplement_branch: bool, #[serde(default)] pub tentative_target_dividend_per_share_tenths: Option, #[serde(default)] pub current_dividend_per_share_tenths: Option, #[serde(default)] pub building_density_growth_setting: Option, #[serde(default)] pub growth_adjusted_current_dividend_per_share_tenths: Option, #[serde(default)] pub board_approved_dividend_rate_ceiling_tenths: Option, #[serde(default)] pub proposed_dividend_per_share_tenths: Option, pub eligible_for_dividend_adjustment_branch: bool, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default, PartialOrd, Ord)] #[serde(rename_all = "snake_case")] pub enum RuntimeCompanyAnnualFinancePolicyAction { #[default] None, CreditorPressureBankruptcy, DeepDistressBankruptcyFallback, BondIssue, StockRepurchase, StockIssue, DividendAdjustment, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RuntimeCompanyAnnualFinancePolicyState { pub company_id: u32, pub action: RuntimeCompanyAnnualFinancePolicyAction, pub creditor_pressure_bankruptcy_eligible: bool, pub deep_distress_bankruptcy_fallback_eligible: bool, pub bond_issue_eligible: bool, pub stock_repurchase_eligible: bool, pub stock_issue_eligible: bool, pub dividend_adjustment_eligible: 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, CurrentIssueAbsoluteCounter, PriorIssueAbsoluteCounter, CurrentIssueAgeAbsoluteCounterDelta, 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_FAMILY_SPECIAL_232A: u32 = 0x232a; 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_COMPANY_STAT_SLOT_CREDIT_RATING: u32 = 0x19; pub const RUNTIME_COMPANY_STAT_SLOT_COUNT: u32 = 0x2b; pub const RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN: u32 = 11; 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)] pub struct RuntimePackedCalendarTuple { pub year_word: u16, pub month_1_based: u8, pub week_1_based: u8, pub day_1_based: u8, pub hour_0_based: u8, pub quarter_day_1_based: u8, pub minute_0_based: u8, } #[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 annual_finance_service_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 world_issue_opinion_base_terms_raw_i32: Vec, #[serde(default)] pub company_market_state: BTreeMap, #[serde(default)] pub annual_finance_last_actions: BTreeMap, #[serde(default)] pub annual_finance_action_counts: BTreeMap, #[serde(default)] pub annual_dividend_adjustment_commit_count: u64, #[serde(default)] pub chairman_issue_opinion_terms_raw_i32: BTreeMap>, #[serde(default)] pub chairman_personality_raw_u8: 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 stock_issue_and_buyback_policy_raw_u8: Option, #[serde(default)] pub bond_issue_and_repayment_policy_raw_u8: Option, #[serde(default)] pub bankruptcy_policy_raw_u8: Option, #[serde(default)] pub dividend_policy_raw_u8: Option, #[serde(default)] pub building_density_growth_setting_raw_u32: Option, #[serde(default)] pub stock_issue_and_buyback_allowed: Option, #[serde(default)] pub bond_issue_and_repayment_allowed: Option, #[serde(default)] pub bankruptcy_allowed: Option, #[serde(default)] pub dividend_adjustment_allowed: 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 company_id in self.service_state.annual_finance_last_actions.keys() { if !seen_company_ids.contains(company_id) { return Err(format!( "service_state.annual_finance_last_actions references unknown company_id {}", company_id )); } } for chairman_profile_id in self .service_state .chairman_issue_opinion_terms_raw_i32 .keys() { if !seen_chairman_profile_ids.contains(chairman_profile_id) { return Err(format!( "service_state.chairman_issue_opinion_terms_raw_i32 references unknown chairman_profile_id {}", chairman_profile_id )); } } for chairman_profile_id in self.service_state.chairman_personality_raw_u8.keys() { if !seen_chairman_profile_ids.contains(chairman_profile_id) { return Err(format!( "service_state.chairman_personality_raw_u8 references unknown chairman_profile_id {}", chairman_profile_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 { runtime_company_stat_value_f64(state, company_id, selector).and_then(runtime_round_f64_to_i64) } pub fn runtime_company_stat_value_f64( state: &RuntimeState, company_id: u32, selector: RuntimeCompanyStatSelector, ) -> Option { if selector.slot_id >= RUNTIME_COMPANY_STAT_SLOT_COUNT { return runtime_company_derived_stat_value_f64( state, company_id, selector.family_id, selector.slot_id, ); } match selector.family_id { RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER => { runtime_company_control_transfer_stat_value_f64(state, company_id, selector.slot_id) } RUNTIME_COMPANY_STAT_FAMILY_SPECIAL_232A => { runtime_company_special_stat_family_232a_value_f64(state, company_id, selector.slot_id) } family_id => { runtime_company_year_stat_value_f64(state, company_id, family_id, selector.slot_id) } } } fn runtime_company_current_stat_value_f64( state: &RuntimeState, company_id: u32, slot_id: u32, ) -> Option { let market_state = state.service_state.company_market_state.get(&company_id)?; let index = slot_id.checked_mul(RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN)? as usize; runtime_decode_saved_f64_bits(*market_state.year_stat_family_qword_bits.get(index)?) } fn runtime_company_direct_float_field_value_f64( state: &RuntimeState, company_id: u32, field_offset: u32, ) -> Option { let market_state = state.service_state.company_market_state.get(&company_id)?; let raw_u32 = *market_state .direct_control_transfer_float_fields_raw_u32 .get(&field_offset)?; let value = f32::from_bits(raw_u32) as f64; if !value.is_finite() { return None; } Some(value) } fn runtime_company_direct_i32_field_value_f64( state: &RuntimeState, company_id: u32, field_offset: u32, ) -> Option { let market_state = state.service_state.company_market_state.get(&company_id)?; Some(i64::from( *market_state .direct_control_transfer_int_fields_raw_u32 .get(&field_offset)? as i32, ) as f64) } pub fn runtime_company_book_value_per_share(state: &RuntimeState, company_id: u32) -> Option { let company = state .companies .iter() .find(|company| company.company_id == company_id)?; if company.book_value_per_share != 0 { return Some(company.book_value_per_share); } runtime_company_direct_float_field_value_f64(state, company_id, 0x32f) .and_then(runtime_round_f64_to_i64) } fn runtime_decode_saved_f32_value_f64(raw_u32: u32) -> Option { let value = f32::from_bits(raw_u32) as f64; if !value.is_finite() { return None; } Some(value) } fn runtime_company_highest_live_bond_coupon_rate_f64( state: &RuntimeState, company_id: u32, ) -> Option { let market_state = state.service_state.company_market_state.get(&company_id)?; market_state .live_bond_slots .iter() .filter_map(|slot| { let value = f32::from_bits(slot.coupon_rate_raw_u32) as f64; value.is_finite().then_some(value) }) .max_by(|left, right| left.partial_cmp(right).unwrap_or(std::cmp::Ordering::Equal)) } fn runtime_company_total_live_bond_principal(state: &RuntimeState, company_id: u32) -> Option { let market_state = state.service_state.company_market_state.get(&company_id)?; Some( market_state .live_bond_slots .iter() .map(|slot| slot.principal) .sum(), ) } pub(crate) fn runtime_company_support_adjusted_share_price_scalar_with_pressure_f64( state: &RuntimeState, company_id: u32, share_pressure_shares: i64, ) -> Option { let market_state = state.service_state.company_market_state.get(&company_id)?; if let Some(cached_value) = runtime_decode_saved_f32_value_f64(market_state.cached_share_price_raw_u32) { if share_pressure_shares == 0 { return Some(cached_value.max(0.0001)); } } if market_state.outstanding_shares == 0 { return Some(0.0010000000474974513); } let company = state .companies .iter() .find(|company| company.company_id == company_id)?; let mut recent_per_share = runtime_company_recent_per_share_subscore(state, company_id)?; let young_company_support = runtime_decode_saved_f32_value_f64(market_state.young_company_support_scalar_raw_u32)?; if recent_per_share < young_company_support { let years_since_founding = derive_runtime_company_elapsed_years(state.calendar.year, market_state.founding_year) .unwrap_or(u32::MAX); if years_since_founding <= 5 { let elapsed_support_ticks = runtime_world_absolute_counter(state) .unwrap_or(0) .saturating_sub(market_state.support_progress_word); let interpolation = (elapsed_support_ticks / 50).min(50) as f64; recent_per_share = ((50.0 - interpolation) * young_company_support + (50.0 + interpolation) * recent_per_share) / 100.0; } } let mutable_support = runtime_decode_saved_f32_value_f64(market_state.mutable_support_scalar_raw_u32)?; let share_pressure = (share_pressure_shares as f64 / market_state.outstanding_shares as f64).clamp(-0.2, 0.2); let effective_mutable_support = mutable_support + share_pressure; let share_count_growth_ratio = ((market_state.outstanding_shares as f64 + 1.4 * effective_mutable_support * ((market_state.outstanding_shares as f64 / 20_000.0).powf(0.33))) / market_state.outstanding_shares as f64) .clamp(0.3, 6.0); let investor_multiplier = runtime_world_issue_opinion_multiplier( state, RUNTIME_WORLD_ISSUE_INVESTOR_CONFIDENCE, company.linked_chairman_profile_id, Some(company_id), None, )?; Some(((recent_per_share * share_count_growth_ratio * investor_multiplier) + 1.0).max(0.0001)) } pub fn runtime_company_recent_per_share_subscore( state: &RuntimeState, company_id: u32, ) -> Option { let market_state = state.service_state.company_market_state.get(&company_id)?; if runtime_world_absolute_counter(state) .is_some_and(|counter| counter == market_state.recent_per_share_cache_absolute_counter) { if let Some(cached_value) = runtime_decode_saved_f64_bits(market_state.recent_per_share_cached_value_bits) { return Some(cached_value); } } runtime_decode_saved_f32_value_f64(market_state.recent_per_share_subscore_raw_u32) } fn runtime_company_support_adjusted_share_price_scalar_f64( state: &RuntimeState, company_id: u32, ) -> Option { runtime_company_support_adjusted_share_price_scalar_with_pressure_f64(state, company_id, 0) } pub fn runtime_company_investor_confidence(state: &RuntimeState, company_id: u32) -> Option { let company = state .companies .iter() .find(|company| company.company_id == company_id)?; if company.investor_confidence != 0 { return Some(company.investor_confidence); } runtime_company_support_adjusted_share_price_scalar_f64(state, company_id) .and_then(runtime_round_f64_to_i64) } pub fn runtime_company_management_attitude(state: &RuntimeState, company_id: u32) -> Option { let company = state .companies .iter() .find(|company| company.company_id == company_id)?; if company.management_attitude != 0 { return Some(company.management_attitude); } runtime_world_issue_opinion_term_sum_raw( state, RUNTIME_WORLD_ISSUE_MANAGEMENT_ATTITUDE, company.linked_chairman_profile_id, Some(company_id), None, ) } fn runtime_company_control_transfer_stat_value_f64( state: &RuntimeState, company_id: u32, slot_id: u32, ) -> Option { let company = state .companies .iter() .find(|company| company.company_id == company_id)?; match slot_id { 0x00..=0x12 if slot_id != RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH => { runtime_company_current_stat_value_f64(state, company_id, slot_id) } RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH => { if company.current_cash != 0 { Some(company.current_cash as f64) } else { runtime_company_current_stat_value_f64(state, company_id, slot_id) } } 0x14 => runtime_company_control_transfer_stat_value_f64(state, company_id, 0x31) .zip(state.service_state.company_market_state.get(&company_id)) .map(|(value, market_state)| { if market_state.outstanding_shares == 0 { 0.0 } else { value / market_state.outstanding_shares as f64 } }), 0x15 => runtime_company_stat_value_f64( state, company_id, RuntimeCompanyStatSelector { family_id: RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER, slot_id: 0x2c, }, ) .zip(state.service_state.company_market_state.get(&company_id)) .map(|(value, market_state)| { if market_state.outstanding_shares == 0 { 0.0 } else { value / market_state.outstanding_shares as f64 } }), 0x16 => runtime_company_stat_value_f64( state, company_id, RuntimeCompanyStatSelector { family_id: RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER, slot_id: 0x2b, }, ) .zip(state.service_state.company_market_state.get(&company_id)) .map(|(value, market_state)| { if market_state.outstanding_shares == 0 { 0.0 } else { value / market_state.outstanding_shares as f64 } }), 0x13 => runtime_company_support_adjusted_share_price_scalar_f64(state, company_id), 0x17 => runtime_company_direct_float_field_value_f64(state, company_id, 0x4b), RUNTIME_COMPANY_STAT_SLOT_CREDIT_RATING => { runtime_company_credit_rating(state, company_id).map(|value| value as f64) } 0x1a => runtime_company_direct_float_field_value_f64(state, company_id, 0x53), 0x1b => runtime_company_direct_float_field_value_f64(state, company_id, 0x323), 0x1c => runtime_company_direct_float_field_value_f64(state, company_id, 0x327), RUNTIME_COMPANY_STAT_SLOT_BOOK_VALUE_PER_SHARE => { runtime_company_book_value_per_share(state, company_id).map(|value| value as f64) } 0x1e => runtime_company_direct_float_field_value_f64(state, company_id, 0x333), 0x1f => runtime_company_direct_float_field_value_f64(state, company_id, 0x33b), 0x20 => runtime_company_direct_float_field_value_f64(state, company_id, 0x33f), 0x21 => runtime_company_direct_float_field_value_f64(state, company_id, 0x327).and_then( |denominator| { let numerator = runtime_company_direct_float_field_value_f64(state, company_id, 0x32b)?; Some(if denominator <= 0.0 { numerator } else { numerator / denominator }) }, ), 0x22 => runtime_company_direct_float_field_value_f64(state, company_id, 0x333).and_then( |denominator| { let numerator = runtime_company_direct_float_field_value_f64(state, company_id, 0x337)?; Some(if denominator <= 0.0 { numerator } else { numerator / denominator }) }, ), 0x23 => runtime_company_direct_float_field_value_f64(state, company_id, 0x33f).and_then( |denominator| { let numerator = runtime_company_direct_float_field_value_f64(state, company_id, 0x343)?; Some(if denominator <= 0.0 { numerator } else { numerator / denominator }) }, ), 0x26 => runtime_company_direct_i32_field_value_f64(state, company_id, 0x34b), 0x27 => runtime_company_direct_i32_field_value_f64(state, company_id, 0x14f), 0x28 => runtime_company_direct_i32_field_value_f64(state, company_id, 0x0d0b), 0x29 => runtime_company_direct_i32_field_value_f64(state, company_id, 0x0d0f), 0x2a => runtime_company_direct_i32_field_value_f64(state, company_id, 0x0d13), _ => None, } } fn runtime_company_special_stat_family_232a_value_f64( state: &RuntimeState, company_id: u32, slot_id: u32, ) -> Option { let market_state = state.service_state.company_market_state.get(&company_id)?; let value = runtime_decode_saved_f64_bits( *market_state .special_stat_family_232a_qword_bits .get(slot_id as usize)?, )?; if (0x13..=0x1b).contains(&slot_id) { Some(value + runtime_company_control_transfer_stat_value_f64(state, company_id, slot_id)?) } else { Some(value) } } fn runtime_company_year_stat_value_f64( state: &RuntimeState, company_id: u32, family_id: u32, slot_id: u32, ) -> Option { let current_year_word = u32::from(state.world_restore.packed_year_word_raw_u16?); let year_delta = current_year_word.checked_sub(family_id)?; if year_delta == 0 || year_delta >= RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN { return None; } let market_state = state.service_state.company_market_state.get(&company_id)?; let index = slot_id .checked_mul(RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN)? .checked_add(year_delta)? as usize; runtime_decode_saved_f64_bits(*market_state.year_stat_family_qword_bits.get(index)?) } fn runtime_company_derived_stat_value_f64( state: &RuntimeState, company_id: u32, family_id: u32, slot_id: u32, ) -> Option { let stat = |slot_id| { runtime_company_stat_value_f64( state, company_id, RuntimeCompanyStatSelector { family_id, slot_id }, ) }; let rounded_stat = |slot_id| stat(slot_id).and_then(runtime_round_f64_to_i64); match slot_id { 0x2b => Some(stat(0x2d)? + stat(0x2c)?), 0x2c => Some(stat(0x04)? + stat(0x03)? + stat(0x02)? + stat(0x01)?), 0x2d => Some(stat(0x2f)? + stat(0x2e)?), 0x2e => Some( stat(0x0c)? + stat(0x0b)? + stat(0x0a)? + stat(0x08)? + stat(0x07)? + stat(0x06)? + stat(0x05)?, ), 0x2f => stat(0x09), 0x30 => Some(stat(0x11)? + stat(0x10)? + stat(0x0f)? + stat(0x0e)? + stat(0x0d)?), 0x31 => Some(stat(0x30)? + stat(0x12)?), 0x32 => runtime_divide_by_rounded_stat_i64(stat(0x2c)?, rounded_stat(0x24)?), 0x33 => runtime_divide_by_rounded_stat_i64(stat(0x2c)?, rounded_stat(0x16)?), 0x34 => runtime_divide_by_rounded_stat_i64(stat(0x2c)?, rounded_stat(0x17)?), 0x35 => runtime_divide_by_rounded_stat_i64(stat(0x2c)?, rounded_stat(0x18)?), 0x36 => runtime_divide_by_rounded_stat_i64(stat(0x2c)?, rounded_stat(0x19)?), 0x37 => runtime_divide_by_rounded_stat_i64(stat(0x2c)?, rounded_stat(0x1a)?), 0x38 => runtime_divide_by_rounded_stat_i64(stat(0x2c)?, rounded_stat(0x1b)?), _ => None, } } fn runtime_divide_by_rounded_stat_i64(numerator: f64, denominator: i64) -> Option { if denominator == 0 { return Some(0.0); } Some(numerator / denominator as f64) } fn runtime_company_trailing_full_year_stat_series( state: &RuntimeState, company_id: u32, slot_id: u32, full_year_count: u32, ) -> Option<(Vec, Vec)> { let current_year_word = u32::from(state.world_restore.packed_year_word_raw_u16?); let mut year_words = Vec::with_capacity(full_year_count as usize); let mut values = Vec::with_capacity(full_year_count as usize); for year_offset in 1..=full_year_count { let family_id = current_year_word.checked_sub(year_offset)?; let value = runtime_company_stat_value_f64( state, company_id, RuntimeCompanyStatSelector { family_id, slot_id }, ) .and_then(runtime_round_f64_to_i64)?; year_words.push(family_id); values.push(value); } Some((year_words, values)) } fn runtime_company_year_or_control_transfer_metric_value_f64( state: &RuntimeState, company_id: u32, year_word: u32, slot_id: u32, ) -> Option { let current_year_word = u32::from(state.world_restore.packed_year_word_raw_u16?); if year_word == current_year_word { runtime_company_stat_value_f64( state, company_id, RuntimeCompanyStatSelector { family_id: RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER, slot_id, }, ) } else { runtime_company_stat_value_f64( state, company_id, RuntimeCompanyStatSelector { family_id: year_word, slot_id, }, ) } } pub fn runtime_world_issue_opinion_term_sum_raw( state: &RuntimeState, issue_id: u32, chairman_profile_id: Option, company_id: Option, territory_id: Option, ) -> Option { let mut total = i64::from( *state .service_state .world_issue_opinion_base_terms_raw_i32 .get(issue_id as usize)?, ); let mut resolved_company_id = company_id; if let Some(profile_id) = chairman_profile_id { if let Some(profile_terms) = state .service_state .chairman_issue_opinion_terms_raw_i32 .get(&profile_id) { if let Some(value) = profile_terms.get(issue_id as usize) { total = total.checked_add(i64::from(*value))?; } } if resolved_company_id.is_none() { resolved_company_id = state .chairman_profiles .iter() .find(|profile| profile.profile_id == profile_id) .and_then(|profile| profile.linked_company_id); } } if let Some(company_id) = resolved_company_id { if let Some(company_terms) = state .service_state .company_market_state .get(&company_id) .map(|market_state| &market_state.issue_opinion_terms_raw_i32) { if let Some(value) = company_terms.get(issue_id as usize) { total = total.checked_add(i64::from(*value))?; } } } if territory_id.is_some() { return None; } Some(total) } pub fn runtime_world_issue_opinion_multiplier( state: &RuntimeState, issue_id: u32, chairman_profile_id: Option, company_id: Option, territory_id: Option, ) -> Option { let normalize = |raw: i32| (i64::from(raw.max(-99)) as f64) / 100.0 + 1.0; let base_raw = *state .service_state .world_issue_opinion_base_terms_raw_i32 .get(issue_id as usize)?; let mut multiplier = normalize(base_raw); let mut resolved_company_id = company_id; if let Some(profile_id) = chairman_profile_id { if let Some(profile_terms) = state .service_state .chairman_issue_opinion_terms_raw_i32 .get(&profile_id) { if let Some(value) = profile_terms.get(issue_id as usize) { multiplier *= normalize(*value); } } if resolved_company_id.is_none() { resolved_company_id = state .chairman_profiles .iter() .find(|profile| profile.profile_id == profile_id) .and_then(|profile| profile.linked_company_id); } } if let Some(company_id) = resolved_company_id { if let Some(company_terms) = state .service_state .company_market_state .get(&company_id) .map(|market_state| &market_state.issue_opinion_terms_raw_i32) { if let Some(value) = company_terms.get(issue_id as usize) { multiplier *= normalize(*value); } } } if territory_id.is_some() { return None; } Some(multiplier.max(0.01)) } 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_prime_rate_baseline(state: &RuntimeState) -> Option { let raw = state.world_restore.issue_37_value?; let value = f32::from_bits(raw) as f64; if !value.is_finite() { return None; } let scaled = (value + 0.001) * 100.0; if !scaled.is_finite() { return None; } Some(scaled.round() / 100.0) } pub fn runtime_company_prime_rate(state: &RuntimeState, company_id: u32) -> Option { let company = state .companies .iter() .find(|company| company.company_id == company_id)?; if let Some(prime_rate) = company.prime_rate { return Some(prime_rate); } let baseline = runtime_world_prime_rate_baseline(state)?; let raw_issue_sum = runtime_world_issue_opinion_term_sum_raw( state, RUNTIME_WORLD_ISSUE_PRIME_RATE, company.linked_chairman_profile_id, Some(company_id), None, )?; runtime_round_f64_to_i64(baseline + (raw_issue_sum as f64) * 0.01) } fn runtime_credit_rating_profitability_ladder(ratio: f64) -> f64 { if !ratio.is_finite() || ratio <= 0.0 { 0.0 } else if ratio < 1.0 { 1.0 + ratio * 4.0 } else if ratio < 2.0 { 3.0 + ratio * 2.0 } else if ratio < 5.0 { 5.0 + ratio } else { 10.0 } } fn runtime_credit_rating_burden_ladder(ratio: f64) -> f64 { if !ratio.is_finite() || ratio > 1.0 { 0.0 } else if ratio > 0.75 { 16.0 - ratio * 16.0 } else if ratio > 0.5 { 13.0 - ratio * 12.0 } else if ratio > 0.25 { 11.0 - ratio * 8.0 } else if ratio > 0.1 { 10.0 - ratio * 4.0 } else { 10.0 } } fn runtime_world_credit_market_scale(state: &RuntimeState) -> Option { const ISSUE_38_SCALE_TABLE: [f64; 8] = [0.8, 0.9, 1.0, 1.1, 1.2, 0.9, 0.95, 1.0]; let index = state.world_restore.issue_38_value? as usize; ISSUE_38_SCALE_TABLE.get(index).copied() } pub fn runtime_company_credit_rating(state: &RuntimeState, company_id: u32) -> Option { let company = state .companies .iter() .find(|company| company.company_id == company_id)?; if let Some(credit_rating_score) = company.credit_rating_score { return Some(credit_rating_score); } let annual_finance_state = runtime_company_annual_finance_state(state, company_id)?; if annual_finance_state.outstanding_shares == 0 { return Some(-512); } let mut weighted_recent_profit_total = 0.0f64; let mut weighted_recent_profit_weight = 0.0f64; for (index, (net_profit, fuel_cost)) in annual_finance_state .trailing_full_year_net_profits .iter() .zip(annual_finance_state.trailing_full_year_fuel_costs.iter()) .take(4) .enumerate() { let weight = (4 - index) as f64; weighted_recent_profit_total += (*net_profit - *fuel_cost) as f64 * weight; weighted_recent_profit_weight += weight; } let weighted_recent_profit = if weighted_recent_profit_weight > 0.0 { weighted_recent_profit_total / weighted_recent_profit_weight } else { 0.0 }; let current_slot_12 = runtime_company_control_transfer_stat_value_f64(state, company_id, 0x12)?; let current_slot_30 = runtime_company_derived_stat_value_f64( state, company_id, RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER, 0x30, )?; let current_slot_31 = runtime_company_derived_stat_value_f64( state, company_id, RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER, 0x31, )?; let average_live_bond_coupon = runtime_company_average_live_bond_coupon(state, company_id)?; let mut finance_pressure = average_live_bond_coupon * current_slot_12; if company.current_cash > 0 { let prime_baseline = runtime_world_prime_rate_baseline(state)?; let raw_issue_39 = runtime_world_issue_opinion_term_sum_raw( state, RUNTIME_WORLD_ISSUE_PRIME_RATE, company.linked_chairman_profile_id, Some(company_id), None, )? as f64; finance_pressure += company.current_cash as f64 * (prime_baseline + raw_issue_39 * 0.01 + 0.03); } let profitability_ratio = if finance_pressure < 0.0 { weighted_recent_profit / (-finance_pressure) } else { 10.0 }; let mut profitability_score = runtime_credit_rating_profitability_ladder(profitability_ratio); if let Some(years_since_founding) = annual_finance_state.years_since_founding { if years_since_founding < 5 { let missing_years = (5 - years_since_founding) as f64; profitability_score += (10.0 - profitability_score) * missing_years * 0.1; } } if current_slot_31 > 1_000_000.0 { profitability_score += current_slot_31 / 100_000.0 - 10.0; } let burden_ratio = if current_slot_30 > 0.0 { (weighted_recent_profit - current_slot_12) / current_slot_30 } else { 1.0 }; let burden_score = runtime_credit_rating_burden_ladder(burden_ratio); let mut rating = (profitability_score * burden_score / 10.0 + profitability_score + burden_score) / 3.0; rating *= runtime_world_credit_market_scale(state)?; if let Some(years_since_last_bankruptcy) = annual_finance_state.years_since_last_bankruptcy { if years_since_last_bankruptcy < 15 { rating *= years_since_last_bankruptcy as f64 * 0.0666; } } let raw_issue_38 = runtime_world_issue_opinion_term_sum_raw( state, RUNTIME_WORLD_ISSUE_CREDIT_MARKET, company.linked_chairman_profile_id, Some(company_id), None, )? as f64; runtime_round_f64_to_i64((rating + raw_issue_38 + 0.5).clamp(0.0, 10.0)) } pub fn runtime_company_average_live_bond_coupon( state: &RuntimeState, company_id: u32, ) -> Option { let market_state = state.service_state.company_market_state.get(&company_id)?; if market_state.live_bond_slots.is_empty() { return Some(0.0); } let mut weighted_coupon_sum = 0.0f64; let mut total_principal = 0u64; for slot in &market_state.live_bond_slots { let coupon_rate = f32::from_bits(slot.coupon_rate_raw_u32) as f64; if !coupon_rate.is_finite() { continue; } weighted_coupon_sum += coupon_rate * (slot.principal as f64); total_principal = total_principal.checked_add(slot.principal as u64)?; } if total_principal == 0 { return Some(0.0); } Some(weighted_coupon_sum / total_principal as f64) } pub fn runtime_company_bond_interest_rate_quote_f64( state: &RuntimeState, company_id: u32, _principal: u32, _years_to_maturity: u32, ) -> Option { let credit_rating = runtime_company_credit_rating(state, company_id)? as f64; let prime_rate_percent = runtime_company_prime_rate(state, company_id)? as f64; let quote = (prime_rate_percent + (10.0 - credit_rating)) / 100.0; quote.is_finite().then_some(quote) } pub fn runtime_world_annual_finance_mode_active(state: &RuntimeState) -> Option { Some(state.world_restore.partial_year_progress_raw_u8? == 0x0c) } pub fn runtime_world_bankruptcy_allowed(state: &RuntimeState) -> Option { Some(state.world_restore.bankruptcy_policy_raw_u8? == 0) } pub fn runtime_world_bond_issue_and_repayment_allowed(state: &RuntimeState) -> Option { Some(state.world_restore.bond_issue_and_repayment_policy_raw_u8? == 0) } pub fn runtime_world_stock_issue_and_buyback_allowed(state: &RuntimeState) -> Option { Some(state.world_restore.stock_issue_and_buyback_policy_raw_u8? == 0) } pub fn runtime_world_dividend_adjustment_allowed(state: &RuntimeState) -> Option { Some(state.world_restore.dividend_policy_raw_u8? == 0) } pub fn runtime_world_building_density_growth_setting(state: &RuntimeState) -> Option { state.world_restore.building_density_growth_setting_raw_u32 } fn runtime_chairman_stock_repurchase_factor_f64( state: &RuntimeState, chairman_profile_id: Option, ) -> Option { let personality_byte = chairman_profile_id .and_then(|profile_id| { state .service_state .chairman_personality_raw_u8 .get(&profile_id) }) .copied(); let mut factor = personality_byte .map(|byte| (f64::from(byte) * 39.0 + 300.0) / 400.0) .unwrap_or(1.0); if runtime_world_building_density_growth_setting(state) == Some(1) { factor *= 1.6; } Some(factor) } pub fn runtime_company_annual_stock_repurchase_state( state: &RuntimeState, company_id: u32, ) -> Option { let annual_finance_state = runtime_company_annual_finance_state(state, company_id)?; let company = state .companies .iter() .find(|company| company.company_id == company_id)?; let current_cash = runtime_company_control_transfer_stat_value_f64( state, company_id, RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH, ) .and_then(runtime_round_f64_to_i64); let support_adjusted_share_price_scalar = runtime_company_support_adjusted_share_price_scalar_f64(state, company_id); let repurchase_factor = runtime_chairman_stock_repurchase_factor_f64(state, company.linked_chairman_profile_id)?; let repurchase_factor_basis_points = runtime_round_f64_to_i64(repurchase_factor * 100.0); let stock_value_gate_cash_floor = runtime_round_f64_to_i64(repurchase_factor * 800_000.0); let affordability_cash_floor = support_adjusted_share_price_scalar .and_then(|value| runtime_round_f64_to_i64(value * repurchase_factor * 1_000.0 * 1.2)); let support_adjusted_share_price_scalar = support_adjusted_share_price_scalar.and_then(runtime_round_f64_to_i64); let unassigned_share_pool = runtime_company_unassigned_share_pool(state, company_id); let eligible_for_single_batch_repurchase = runtime_world_annual_finance_mode_active(state) == Some(true) && runtime_world_stock_issue_and_buyback_allowed(state) == Some(true) && annual_finance_state.city_connection_latch && current_cash .zip(stock_value_gate_cash_floor) .is_some_and(|(value, floor)| value >= floor) && current_cash .zip(affordability_cash_floor) .is_some_and(|(value, floor)| value >= floor) && unassigned_share_pool.is_some_and(|value| value >= 1_000); Some(RuntimeCompanyAnnualStockRepurchaseState { company_id, annual_mode_active: runtime_world_annual_finance_mode_active(state), stock_issue_and_buyback_allowed: runtime_world_stock_issue_and_buyback_allowed(state), city_connection_latch: annual_finance_state.city_connection_latch, building_density_growth_setting: runtime_world_building_density_growth_setting(state), linked_chairman_profile_id: company.linked_chairman_profile_id, linked_chairman_personality_raw_u8: company .linked_chairman_profile_id .and_then(|profile_id| { state .service_state .chairman_personality_raw_u8 .get(&profile_id) }) .copied(), repurchase_batch_size: Some(1_000), repurchase_factor_basis_points, current_cash, stock_value_gate_cash_floor, support_adjusted_share_price_scalar, affordability_cash_floor, unassigned_share_pool, eligible_for_single_batch_repurchase, }) } pub fn runtime_company_annual_bond_policy_state( state: &RuntimeState, company_id: u32, ) -> Option { const STANDARD_CASH_FLOOR: i64 = -250_000; const LINKED_TRANSIT_CASH_FLOOR: i64 = -30_000; const ISSUE_PRINCIPAL_STEP: u32 = 500_000; const ISSUE_YEARS_TO_MATURITY: u32 = 30; let annual_finance_state = runtime_company_annual_finance_state(state, company_id)?; let current_cash = runtime_company_control_transfer_stat_value_f64( state, company_id, RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH, ) .and_then(runtime_round_f64_to_i64); let live_bond_principal_total = runtime_company_total_live_bond_principal(state, company_id); let cash_after_full_repayment = current_cash .zip(live_bond_principal_total) .map(|(cash, principal)| cash - i64::from(principal)); let issue_cash_floor = Some(if annual_finance_state.linked_transit_latch { LINKED_TRANSIT_CASH_FLOOR } else { STANDARD_CASH_FLOOR }); let proposed_issue_bond_count = cash_after_full_repayment.zip(issue_cash_floor).map( |(cash_after_repayment, cash_floor)| { if cash_after_repayment >= cash_floor { 0 } else { let deficit = (cash_floor - cash_after_repayment) as u64; deficit.div_ceil(u64::from(ISSUE_PRINCIPAL_STEP)) as u32 } }, ); let proposed_issue_total_principal = proposed_issue_bond_count.and_then(|count| count.checked_mul(ISSUE_PRINCIPAL_STEP)); let eligible_for_bond_issue_branch = runtime_world_annual_finance_mode_active(state) == Some(true) && runtime_world_bond_issue_and_repayment_allowed(state) == Some(true) && proposed_issue_bond_count.is_some_and(|count| count > 0); Some(RuntimeCompanyAnnualBondPolicyState { company_id, annual_mode_active: runtime_world_annual_finance_mode_active(state), bond_issue_and_repayment_allowed: runtime_world_bond_issue_and_repayment_allowed(state), linked_transit_latch: annual_finance_state.linked_transit_latch, live_bond_count: Some(annual_finance_state.bond_count), live_bond_principal_total, current_cash, cash_after_full_repayment, issue_cash_floor, issue_principal_step: Some(ISSUE_PRINCIPAL_STEP), proposed_issue_bond_count, proposed_issue_total_principal, proposed_issue_years_to_maturity: Some(ISSUE_YEARS_TO_MATURITY), eligible_for_bond_issue_branch, }) } fn runtime_company_board_approved_dividend_rate_ceiling_f64( state: &RuntimeState, company_id: u32, ) -> Option { const REVENUE_GUARD_DIVISOR: f64 = 2.0; const EARLY_SUPPORT_MULTIPLIER: f64 = 0.05; const HISTORICAL_GUARD_SCALE: f64 = 1.25; const ANCHOR_SCALE: f64 = 0.35; let market_state = state.service_state.company_market_state.get(&company_id)?; let current_cash = runtime_company_control_transfer_stat_value_f64( state, company_id, RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH, )?; let shares_plus_one = market_state.outstanding_shares.checked_add(1)?; let shares_plus_one_f64 = shares_plus_one as f64; let current_cash_per_share_ceiling = current_cash / shares_plus_one_f64; let current_year_word = u32::from(state.world_restore.packed_year_word_raw_u16?); let years_since_founding = current_year_word .checked_sub(market_state.founding_year) .unwrap_or(0) .min(3); let start_year_offset = if state.world_restore.partial_year_progress_raw_u8 == Some(0x0c) { 0 } else { 1 }; let mut strongest_net_profit_guard = 0.0f64; let mut strongest_revenue_guard = 0.0f64; if start_year_offset <= years_since_founding { for year_offset in start_year_offset..=years_since_founding { let year_word = current_year_word.checked_sub(year_offset)?; let net_profit = runtime_company_year_or_control_transfer_metric_value_f64( state, company_id, year_word, 0x2b, )?; strongest_net_profit_guard = strongest_net_profit_guard.max(net_profit); let revenue = runtime_company_year_or_control_transfer_metric_value_f64( state, company_id, year_word, 0x2c, )?; strongest_revenue_guard = strongest_revenue_guard.max(revenue); } } let mut historical_guard_total = strongest_net_profit_guard.min(strongest_revenue_guard / REVENUE_GUARD_DIVISOR); if years_since_founding <= 1 { let early_support_guard = market_state.outstanding_shares as f64 * runtime_decode_saved_f32_value_f64( market_state.young_company_support_scalar_raw_u32, )? * EARLY_SUPPORT_MULTIPLIER; historical_guard_total = historical_guard_total.max(early_support_guard); } let historical_guard_per_share_ceiling = historical_guard_total / shares_plus_one_f64 * HISTORICAL_GUARD_SCALE; let mut ceiling = current_cash_per_share_ceiling.min(historical_guard_per_share_ceiling); let anchor_value = if years_since_founding == 0 { runtime_decode_saved_f32_value_f64(market_state.young_company_support_scalar_raw_u32)? } else { runtime_company_year_or_control_transfer_metric_value_f64( state, company_id, current_year_word.checked_sub(1)?, 0x1c, )? }; ceiling = ceiling.min(anchor_value * ANCHOR_SCALE); Some(ceiling.max(0.0)) } pub fn runtime_company_annual_dividend_policy_state( state: &RuntimeState, company_id: u32, ) -> Option { const WEIGHTED_NET_PROFIT_DIVISOR: f64 = 6.0; const CASH_SUPPLEMENT_DIVISOR: f64 = 3.0; const STANDARD_TARGET_DIVISOR: f64 = 6.0; const DIVIDEND_DELTA_COLLAPSE_THRESHOLD: f64 = 0.1; const GROWTH_SETTING_ONE_DIVIDEND_SCALE: f64 = 0.66; let annual_finance_state = runtime_company_annual_finance_state(state, company_id)?; let current_cash = runtime_company_control_transfer_stat_value_f64( state, company_id, RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH, ) .and_then(runtime_round_f64_to_i64); let current_year_word = u32::from(state.world_restore.packed_year_word_raw_u16?); let current_dividend_per_share = runtime_company_control_transfer_stat_value_f64(state, company_id, 0x20)?; let building_density_growth_setting = runtime_world_building_density_growth_setting(state); let weighted_recent_net_profit_total = Some( runtime_company_year_or_control_transfer_metric_value_f64( state, company_id, current_year_word, 0x2b, ) .and_then(runtime_round_f64_to_i64)? .checked_mul(3)? .checked_add( runtime_company_year_or_control_transfer_metric_value_f64( state, company_id, current_year_word.checked_sub(1)?, 0x2b, ) .and_then(runtime_round_f64_to_i64)? .checked_mul(2)?, )? .checked_add( runtime_company_year_or_control_transfer_metric_value_f64( state, company_id, current_year_word.checked_sub(2)?, 0x2b, ) .and_then(runtime_round_f64_to_i64)?, )?, ); let weighted_recent_net_profit_average = weighted_recent_net_profit_total .and_then(|value| runtime_round_f64_to_i64(value as f64 / WEIGHTED_NET_PROFIT_DIVISOR)); let tiny_unassigned_share_cash_supplement_branch = annual_finance_state.unassigned_share_pool <= 1_000; let tentative_target_dividend_per_share = weighted_recent_net_profit_average.and_then(|value| { if annual_finance_state.outstanding_shares == 0 { return None; } let shares = annual_finance_state.outstanding_shares as f64; if tiny_unassigned_share_cash_supplement_branch { let cash_component = current_cash.unwrap_or(0).max(0) as f64; Some( ((value as f64 / CASH_SUPPLEMENT_DIVISOR) + cash_component / CASH_SUPPLEMENT_DIVISOR) / shares, ) } else { Some((value as f64 / STANDARD_TARGET_DIVISOR) / shares) } }); let growth_adjusted_current_dividend_per_share = Some(match building_density_growth_setting { Some(1) => current_dividend_per_share * GROWTH_SETTING_ONE_DIVIDEND_SCALE, Some(2) => 0.0, _ => current_dividend_per_share, }); let proposed_dividend_per_share = if tentative_target_dividend_per_share .is_some_and(|value| value <= DIVIDEND_DELTA_COLLAPSE_THRESHOLD) { Some(0.0) } else { growth_adjusted_current_dividend_per_share .zip(tentative_target_dividend_per_share) .map(|(current_dividend, target)| { ((current_dividend + target + DIVIDEND_DELTA_COLLAPSE_THRESHOLD) / 2.0 * 10.0) .round() / 10.0 }) }; let board_approved_dividend_rate_ceiling = runtime_company_board_approved_dividend_rate_ceiling_f64(state, company_id); let proposed_dividend_per_share = proposed_dividend_per_share .zip(board_approved_dividend_rate_ceiling) .map(|(proposed, ceiling)| proposed.min(ceiling)); let current_dividend_per_share_tenths = runtime_round_f64_to_i64(current_dividend_per_share * 10.0); let eligible_for_dividend_adjustment_branch = runtime_world_annual_finance_mode_active(state) == Some(true) && runtime_world_dividend_adjustment_allowed(state) == Some(true) && annual_finance_state .years_since_last_dividend .is_some_and(|years| years >= 1) && annual_finance_state .years_since_founding .is_some_and(|years| years >= 2) && !runtime_company_annual_creditor_pressure_state(state, company_id)? .eligible_for_bankruptcy_branch && !runtime_company_annual_deep_distress_state(state, company_id)? .eligible_for_bankruptcy_fallback && !runtime_company_annual_bond_policy_state(state, company_id)? .eligible_for_bond_issue_branch && !runtime_company_annual_stock_repurchase_state(state, company_id)? .eligible_for_single_batch_repurchase && !runtime_company_annual_stock_issue_state(state, company_id)? .eligible_for_double_tranche_issue && proposed_dividend_per_share.and_then(|value| runtime_round_f64_to_i64(value * 10.0)) != current_dividend_per_share_tenths; Some(RuntimeCompanyAnnualDividendPolicyState { company_id, annual_mode_active: runtime_world_annual_finance_mode_active(state), dividend_adjustment_allowed: runtime_world_dividend_adjustment_allowed(state), years_since_last_dividend: annual_finance_state.years_since_last_dividend, years_since_founding: annual_finance_state.years_since_founding, outstanding_shares: Some(annual_finance_state.outstanding_shares), unassigned_share_pool: Some(annual_finance_state.unassigned_share_pool), weighted_recent_net_profit_total, weighted_recent_net_profit_average, current_cash, tiny_unassigned_share_cash_supplement_branch, tentative_target_dividend_per_share_tenths: tentative_target_dividend_per_share .and_then(|value| runtime_round_f64_to_i64(value * 10.0)), current_dividend_per_share_tenths, building_density_growth_setting, growth_adjusted_current_dividend_per_share_tenths: growth_adjusted_current_dividend_per_share .and_then(|value| runtime_round_f64_to_i64(value * 10.0)), board_approved_dividend_rate_ceiling_tenths: board_approved_dividend_rate_ceiling .and_then(|value| runtime_round_f64_to_i64(value * 10.0)), proposed_dividend_per_share_tenths: proposed_dividend_per_share .and_then(|value| runtime_round_f64_to_i64(value * 10.0)), eligible_for_dividend_adjustment_branch, }) } pub fn runtime_company_annual_finance_policy_state( state: &RuntimeState, company_id: u32, ) -> Option { runtime_company_annual_finance_state(state, company_id)?; let creditor_pressure_bankruptcy_eligible = runtime_company_annual_creditor_pressure_state(state, company_id) .map(|state| state.eligible_for_bankruptcy_branch) .unwrap_or(false); let deep_distress_bankruptcy_fallback_eligible = runtime_company_annual_deep_distress_state(state, company_id) .map(|state| state.eligible_for_bankruptcy_fallback) .unwrap_or(false); let bond_issue_eligible = runtime_company_annual_bond_policy_state(state, company_id) .map(|state| state.eligible_for_bond_issue_branch) .unwrap_or(false); let stock_repurchase_eligible = runtime_company_annual_stock_repurchase_state(state, company_id) .map(|state| state.eligible_for_single_batch_repurchase) .unwrap_or(false); let stock_issue_eligible = runtime_company_annual_stock_issue_state(state, company_id) .map(|state| state.eligible_for_double_tranche_issue) .unwrap_or(false); let dividend_adjustment_eligible = runtime_company_annual_dividend_policy_state(state, company_id) .map(|state| state.eligible_for_dividend_adjustment_branch) .unwrap_or(false); let action = if creditor_pressure_bankruptcy_eligible { RuntimeCompanyAnnualFinancePolicyAction::CreditorPressureBankruptcy } else if deep_distress_bankruptcy_fallback_eligible { RuntimeCompanyAnnualFinancePolicyAction::DeepDistressBankruptcyFallback } else if bond_issue_eligible { RuntimeCompanyAnnualFinancePolicyAction::BondIssue } else if stock_repurchase_eligible { RuntimeCompanyAnnualFinancePolicyAction::StockRepurchase } else if stock_issue_eligible { RuntimeCompanyAnnualFinancePolicyAction::StockIssue } else if dividend_adjustment_eligible { RuntimeCompanyAnnualFinancePolicyAction::DividendAdjustment } else { RuntimeCompanyAnnualFinancePolicyAction::None }; Some(RuntimeCompanyAnnualFinancePolicyState { company_id, action, creditor_pressure_bankruptcy_eligible, deep_distress_bankruptcy_fallback_eligible, bond_issue_eligible, stock_repurchase_eligible, stock_issue_eligible, dividend_adjustment_eligible, }) } pub fn runtime_company_annual_finance_policy_action_label( action: RuntimeCompanyAnnualFinancePolicyAction, ) -> &'static str { match action { RuntimeCompanyAnnualFinancePolicyAction::None => "none", RuntimeCompanyAnnualFinancePolicyAction::CreditorPressureBankruptcy => { "creditor_pressure_bankruptcy" } RuntimeCompanyAnnualFinancePolicyAction::DeepDistressBankruptcyFallback => { "deep_distress_bankruptcy_fallback" } RuntimeCompanyAnnualFinancePolicyAction::BondIssue => "bond_issue", RuntimeCompanyAnnualFinancePolicyAction::StockRepurchase => "stock_repurchase", RuntimeCompanyAnnualFinancePolicyAction::StockIssue => "stock_issue", RuntimeCompanyAnnualFinancePolicyAction::DividendAdjustment => "dividend_adjustment", } } fn runtime_company_stock_issue_price_to_book_ratio_f64( pressured_support_adjusted_share_price_scalar: f64, book_value_per_share: f64, ) -> Option { let denominator = book_value_per_share.max(1.0); if !pressured_support_adjusted_share_price_scalar.is_finite() || !denominator.is_finite() { return None; } Some(pressured_support_adjusted_share_price_scalar / denominator) } fn runtime_company_stock_issue_minimum_price_to_book_ratio_f64( highest_coupon_rate: f64, ) -> Option { if !highest_coupon_rate.is_finite() || highest_coupon_rate <= 0.0 { return None; } Some(if highest_coupon_rate <= 0.07 { 1.30 } else if highest_coupon_rate <= 0.08 { 1.20 } else if highest_coupon_rate <= 0.09 { 1.10 } else if highest_coupon_rate <= 0.10 { 0.95 } else if highest_coupon_rate <= 0.11 { 0.80 } else if highest_coupon_rate <= 0.12 { 0.62 } else if highest_coupon_rate <= 0.13 { 0.50 } else if highest_coupon_rate <= 0.14 { 0.35 } else { return None; }) } pub fn runtime_company_annual_stock_issue_state( state: &RuntimeState, company_id: u32, ) -> Option { const ISSUE_PROCEEDS_CAP: i64 = 55_000; const SHARE_PRICE_FLOOR: i64 = 22; const ONE_YEAR_ABSOLUTE_COUNTER_SPAN: i64 = 12 * 28 * 24 * 60; let annual_finance_state = runtime_company_annual_finance_state(state, company_id)?; let current_cash = runtime_company_control_transfer_stat_value_f64( state, company_id, RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH, ) .and_then(runtime_round_f64_to_i64); let highest_coupon_live_bond_principal = annual_finance_state.highest_coupon_live_bond_principal; let highest_coupon_live_bond_rate = runtime_company_highest_live_bond_coupon_rate_f64(state, company_id); let highest_coupon_live_bond_rate_basis_points = highest_coupon_live_bond_rate.and_then(|value| runtime_round_f64_to_i64(value * 10_000.0)); let mut initial_issue_batch_size = (annual_finance_state.outstanding_shares / 10 / 1_000) * 1_000; if initial_issue_batch_size < 2_000 { initial_issue_batch_size = 2_000; } let initial_issue_batch_size = Some(initial_issue_batch_size); let mut trimmed_issue_batch_size = initial_issue_batch_size?; let mut pressured_support_adjusted_share_price_scalar = runtime_company_support_adjusted_share_price_scalar_with_pressure_f64( state, company_id, -(trimmed_issue_batch_size as i64), ); let mut pressured_proceeds = pressured_support_adjusted_share_price_scalar .and_then(|value| runtime_round_f64_to_i64(value * trimmed_issue_batch_size as f64)); while trimmed_issue_batch_size > 2_000 && pressured_proceeds.is_some_and(|value| value > ISSUE_PROCEEDS_CAP) { trimmed_issue_batch_size = trimmed_issue_batch_size.saturating_sub(1_000); pressured_support_adjusted_share_price_scalar = runtime_company_support_adjusted_share_price_scalar_with_pressure_f64( state, company_id, -(trimmed_issue_batch_size as i64), ); pressured_proceeds = pressured_support_adjusted_share_price_scalar .and_then(|value| runtime_round_f64_to_i64(value * trimmed_issue_batch_size as f64)); } let pressured_support_adjusted_share_price_scalar_i64 = pressured_support_adjusted_share_price_scalar.and_then(runtime_round_f64_to_i64); let book_value_per_share_floor_applied = runtime_company_book_value_per_share(state, company_id).map(|value| value.max(1)); let price_to_book_ratio = pressured_support_adjusted_share_price_scalar .zip(book_value_per_share_floor_applied) .and_then(|(share_price, book_value)| { runtime_company_stock_issue_price_to_book_ratio_f64(share_price, book_value as f64) }); let price_to_book_ratio_basis_points = price_to_book_ratio.and_then(|value| runtime_round_f64_to_i64(value * 10_000.0)); let minimum_price_to_book_ratio = highest_coupon_live_bond_rate .and_then(runtime_company_stock_issue_minimum_price_to_book_ratio_f64); let minimum_price_to_book_ratio_basis_points = minimum_price_to_book_ratio.and_then(|value| runtime_round_f64_to_i64(value * 10_000.0)); let passes_share_price_floor = pressured_support_adjusted_share_price_scalar_i64.map(|value| value >= SHARE_PRICE_FLOOR); let passes_proceeds_floor = pressured_proceeds.map(|value| value >= ISSUE_PROCEEDS_CAP); let passes_cash_gate = current_cash .zip(highest_coupon_live_bond_principal) .map(|(cash, principal)| cash <= i64::from(principal) + 5_000); let passes_issue_cooldown_gate = Some( annual_finance_state .current_issue_age_absolute_counter_delta .is_none_or(|delta| delta >= ONE_YEAR_ABSOLUTE_COUNTER_SPAN), ); let passes_coupon_price_to_book_gate = price_to_book_ratio_basis_points .zip(minimum_price_to_book_ratio_basis_points) .map(|(actual, minimum)| actual >= minimum); let eligible_for_double_tranche_issue = runtime_world_annual_finance_mode_active(state) == Some(true) && runtime_world_stock_issue_and_buyback_allowed(state) == Some(true) && runtime_world_bond_issue_and_repayment_allowed(state) == Some(true) && annual_finance_state.bond_count >= 2 && annual_finance_state .years_since_founding .is_some_and(|years| years >= 1) && !runtime_company_annual_creditor_pressure_state(state, company_id)? .eligible_for_bankruptcy_branch && !runtime_company_annual_deep_distress_state(state, company_id)? .eligible_for_bankruptcy_fallback && !runtime_company_annual_bond_policy_state(state, company_id)? .eligible_for_bond_issue_branch && !runtime_company_annual_stock_repurchase_state(state, company_id)? .eligible_for_single_batch_repurchase && passes_share_price_floor == Some(true) && passes_proceeds_floor == Some(true) && passes_cash_gate == Some(true) && passes_issue_cooldown_gate == Some(true) && passes_coupon_price_to_book_gate == Some(true); Some(RuntimeCompanyAnnualStockIssueState { company_id, annual_mode_active: runtime_world_annual_finance_mode_active(state), stock_issue_and_buyback_allowed: runtime_world_stock_issue_and_buyback_allowed(state), bond_issue_and_repayment_allowed: runtime_world_bond_issue_and_repayment_allowed(state), years_since_founding: annual_finance_state.years_since_founding, live_bond_count: Some(annual_finance_state.bond_count), initial_issue_batch_size, trimmed_issue_batch_size: Some(trimmed_issue_batch_size), share_pressure_basis_points: runtime_round_f64_to_i64( -(trimmed_issue_batch_size as f64) / annual_finance_state.outstanding_shares as f64 * 10_000.0, ), pressured_support_adjusted_share_price_scalar: pressured_support_adjusted_share_price_scalar_i64, pressured_proceeds, book_value_per_share_floor_applied, price_to_book_ratio_basis_points, current_cash, highest_coupon_live_bond_principal, highest_coupon_live_bond_rate_basis_points, current_issue_age_absolute_counter_delta: annual_finance_state .current_issue_age_absolute_counter_delta, current_issue_cooldown_floor: Some(ONE_YEAR_ABSOLUTE_COUNTER_SPAN), minimum_price_to_book_ratio_basis_points, passes_share_price_floor, passes_proceeds_floor, passes_cash_gate, passes_issue_cooldown_gate, passes_coupon_price_to_book_gate, eligible_for_double_tranche_issue, }) } pub fn runtime_company_annual_creditor_pressure_state( state: &RuntimeState, company_id: u32, ) -> Option { let annual_finance_state = runtime_company_annual_finance_state(state, company_id)?; let current_cash_plus_slot_12_total = runtime_company_control_transfer_stat_value_f64(state, company_id, 0x12) .and_then(runtime_round_f64_to_i64) .and_then(|slot_12| { runtime_company_control_transfer_stat_value_f64( state, company_id, RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH, ) .and_then(runtime_round_f64_to_i64) .map(|current_cash| current_cash + slot_12) }); let support_adjusted_share_price_scalar = runtime_company_support_adjusted_share_price_scalar_f64(state, company_id) .and_then(runtime_round_f64_to_i64); let current_fuel_cost = runtime_company_stat_value_f64( state, company_id, RuntimeCompanyStatSelector { family_id: RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER, slot_id: 0x09, }, ) .and_then(runtime_round_f64_to_i64); let recent_bad_net_profit_year_count = annual_finance_state .trailing_full_year_net_profits .iter() .take(3) .filter(|value| **value < -10_000) .count() as u32; let recent_peak_revenue = annual_finance_state .trailing_full_year_revenues .iter() .take(3) .copied() .max(); let recent_three_year_net_profit_total = if annual_finance_state.trailing_full_year_net_profits.len() >= 3 { Some( annual_finance_state .trailing_full_year_net_profits .iter() .take(3) .sum::(), ) } else { None }; let pressure_ladder_cash_floor = recent_peak_revenue.map(|revenue| { if revenue < 120_000 { -600_000 } else if revenue < 230_000 { -1_100_000 } else if revenue < 340_000 { -1_600_000 } else { -2_000_000 } }); let support_adjusted_share_price_floor = Some(if recent_bad_net_profit_year_count == 3 { 20 } else { 15 }); let current_fuel_cost_floor = pressure_ladder_cash_floor.map(|floor| floor * 8 / 100); let eligible_for_bankruptcy_branch = runtime_world_annual_finance_mode_active(state) == Some(true) && runtime_world_bankruptcy_allowed(state) == Some(true) && annual_finance_state .years_since_last_bankruptcy .is_some_and(|years| years >= 13) && annual_finance_state .years_since_founding .is_some_and(|years| years >= 4) && recent_bad_net_profit_year_count >= 2 && current_cash_plus_slot_12_total .zip(pressure_ladder_cash_floor) .is_some_and(|(value, floor)| value <= floor) && support_adjusted_share_price_scalar .zip(support_adjusted_share_price_floor) .is_some_and(|(value, floor)| value >= floor) && current_fuel_cost .zip(current_fuel_cost_floor) .is_some_and(|(value, floor)| value <= floor) && recent_three_year_net_profit_total.is_some_and(|value| value <= -60_000); Some(RuntimeCompanyAnnualCreditorPressureState { company_id, annual_mode_active: runtime_world_annual_finance_mode_active(state), bankruptcy_allowed: runtime_world_bankruptcy_allowed(state), years_since_last_bankruptcy: annual_finance_state.years_since_last_bankruptcy, years_since_founding: annual_finance_state.years_since_founding, recent_bad_net_profit_year_count, recent_peak_revenue, recent_three_year_net_profit_total, pressure_ladder_cash_floor, current_cash_plus_slot_12_total, support_adjusted_share_price_floor, support_adjusted_share_price_scalar, current_fuel_cost, current_fuel_cost_floor, eligible_for_bankruptcy_branch, }) } pub fn runtime_company_annual_deep_distress_state( state: &RuntimeState, company_id: u32, ) -> Option { let annual_finance_state = runtime_company_annual_finance_state(state, company_id)?; let current_cash = runtime_company_control_transfer_stat_value_f64( state, company_id, RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH, ) .and_then(runtime_round_f64_to_i64); let recent_first_three_net_profit_years = annual_finance_state .trailing_full_year_net_profits .iter() .take(3) .copied() .collect::>(); let deep_distress_cash_floor = Some(-300_000); let deep_distress_net_profit_floor = Some(-20_000); let eligible_for_bankruptcy_fallback = runtime_world_bankruptcy_allowed(state) == Some(true) && current_cash .zip(deep_distress_cash_floor) .is_some_and(|(value, floor)| value <= floor) && annual_finance_state .years_since_founding .is_some_and(|years| years >= 3) && recent_first_three_net_profit_years.len() == 3 && recent_first_three_net_profit_years .iter() .all(|value| *value <= deep_distress_net_profit_floor.unwrap()) && annual_finance_state .years_since_last_bankruptcy .is_some_and(|years| years >= 5); Some(RuntimeCompanyAnnualDeepDistressState { company_id, bankruptcy_allowed: runtime_world_bankruptcy_allowed(state), years_since_founding: annual_finance_state.years_since_founding, years_since_last_bankruptcy: annual_finance_state.years_since_last_bankruptcy, current_cash, recent_first_three_net_profit_years, deep_distress_cash_floor, deep_distress_net_profit_floor, eligible_for_bankruptcy_fallback, }) } 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_decode_packed_calendar_tuple( word_0: u32, word_1: u32, ) -> RuntimePackedCalendarTuple { let bytes_0 = word_0.to_le_bytes(); let bytes_1 = word_1.to_le_bytes(); RuntimePackedCalendarTuple { year_word: u16::from_le_bytes([bytes_0[0], bytes_0[1]]), month_1_based: bytes_0[2], week_1_based: bytes_0[3], day_1_based: bytes_1[0], hour_0_based: bytes_1[1], quarter_day_1_based: bytes_1[2], minute_0_based: bytes_1[3], } } pub fn runtime_pack_packed_calendar_tuple_to_absolute_counter( tuple: RuntimePackedCalendarTuple, ) -> Option { if !(1..=12).contains(&tuple.month_1_based) { return None; } if !(1..=28).contains(&tuple.day_1_based) { return None; } if tuple.hour_0_based >= 24 { return None; } if tuple.minute_0_based >= 60 { return None; } let year = u64::from(tuple.year_word); let month = u64::from(tuple.month_1_based); let day = u64::from(tuple.day_1_based); let hour = u64::from(tuple.hour_0_based); let minute = u64::from(tuple.minute_0_based); let absolute_counter = ((((year * 12 + month) * 28 + day).checked_sub(29)? * 24 + hour) * 60) + minute; u32::try_from(absolute_counter).ok() } 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); let current_issue_absolute_counter = runtime_pack_packed_calendar_tuple_to_absolute_counter( runtime_decode_packed_calendar_tuple( market_state.current_issue_calendar_word, market_state.current_issue_calendar_word_2, ), ); let prior_issue_absolute_counter = runtime_pack_packed_calendar_tuple_to_absolute_counter( runtime_decode_packed_calendar_tuple( market_state.prior_issue_calendar_word, market_state.prior_issue_calendar_word_2, ), ); let current_issue_age_absolute_counter_delta = match ( runtime_world_absolute_counter(state), current_issue_absolute_counter, ) { (Some(world_counter), Some(issue_counter)) if world_counter >= issue_counter => { Some(i64::from(world_counter - issue_counter)) } _ => None, }; let (trailing_full_year_year_words, trailing_full_year_net_profits) = runtime_company_trailing_full_year_stat_series(state, company_id, 0x2b, 4) .unwrap_or_default(); let (_, trailing_full_year_revenues) = runtime_company_trailing_full_year_stat_series(state, company_id, 0x2c, 4) .unwrap_or_default(); let (_, trailing_full_year_fuel_costs) = runtime_company_trailing_full_year_stat_series(state, company_id, 0x09, 4) .unwrap_or_default(); 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), trailing_full_year_year_words, trailing_full_year_net_profits, trailing_full_year_revenues, trailing_full_year_fuel_costs, current_issue_absolute_counter, prior_issue_absolute_counter, current_issue_age_absolute_counter_delta, 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::CurrentIssueAbsoluteCounter => annual_finance_state .current_issue_absolute_counter .map(i64::from), RuntimeCompanyMarketMetric::PriorIssueAbsoluteCounter => annual_finance_state .prior_issue_absolute_counter .map(i64::from), RuntimeCompanyMarketMetric::CurrentIssueAgeAbsoluteCounterDelta => { annual_finance_state.current_issue_age_absolute_counter_delta } 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 runtime_decode_saved_f64_bits(bits: u64) -> Option { let value = f64::from_bits(bits); if !value.is_finite() { return None; } Some(value) } pub(crate) fn runtime_round_f64_to_i64(value: f64) -> Option { if !value.is_finite() { return None; } if value < i64::MIN as f64 || value > i64::MAX as f64 { 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 { absolute_counter_raw_u32: Some(5), ..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, stock_issue_and_buyback_policy_raw_u8: None, bond_issue_and_repayment_policy_raw_u8: None, bankruptcy_policy_raw_u8: None, dividend_policy_raw_u8: None, building_density_growth_setting_raw_u32: None, stock_issue_and_buyback_allowed: None, bond_issue_and_repayment_allowed: None, bankruptcy_allowed: None, dividend_adjustment_allowed: 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 { absolute_counter_raw_u32: Some(5), ..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 { absolute_counter_raw_u32: Some(5), ..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 mut year_stat_family_qword_bits = vec![ 0u64; ((RUNTIME_COMPANY_STAT_SLOT_COUNT + 2) * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) as usize ]; year_stat_family_qword_bits[(0x12 * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) as usize] = 75.0f64.to_bits(); 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 { world_issue_opinion_base_terms_raw_i32: vec![0; 0x3b], company_market_state: BTreeMap::from([( 7, RuntimeCompanyMarketState { year_stat_family_qword_bits, ..RuntimeCompanyMarketState::default() }, )]), ..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: 0x12, }, ), Some(75) ); assert_eq!( runtime_company_stat_value( &state, 7, RuntimeCompanyStatSelector { family_id: RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER, slot_id: 0x2b, }, ), Some(0) ); 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_book_value_per_share_from_rehosted_direct_company_field_band() { 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::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([( 7, RuntimeCompanyMarketState { direct_control_transfer_float_fields_raw_u32: BTreeMap::from([( 0x32f, 2620.0f32.to_bits(), )]), ..RuntimeCompanyMarketState::default() }, )]), ..RuntimeServiceState::default() }, }; assert_eq!(runtime_company_book_value_per_share(&state, 7), Some(2620)); 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(2620) ); } #[test] fn reads_investor_confidence_from_rehosted_company_share_price_cache() { 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), ..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::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([( 7, RuntimeCompanyMarketState { recent_per_share_cache_absolute_counter: 5, recent_per_share_cached_value_bits: 14.5f64.to_bits(), recent_per_share_subscore_raw_u32: 12.0f32.to_bits(), cached_share_price_raw_u32: 37.0f32.to_bits(), ..RuntimeCompanyMarketState::default() }, )]), ..RuntimeServiceState::default() }, }; assert_eq!( runtime_company_recent_per_share_subscore(&state, 7), Some(14.5) ); assert_eq!(runtime_company_investor_confidence(&state, 7), Some(37)); assert_eq!( runtime_company_stat_value( &state, 7, RuntimeCompanyStatSelector { family_id: RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER, slot_id: 0x13, }, ), Some(37) ); } #[test] fn derives_company_management_attitude_from_issue3a_owner_state() { let mut company_terms = vec![0; 0x3b]; company_terms[RUNTIME_WORLD_ISSUE_MANAGEMENT_ATTITUDE as usize] = 12; let mut chairman_terms = vec![0; 0x3b]; chairman_terms[RUNTIME_WORLD_ISSUE_MANAGEMENT_ATTITUDE as usize] = 6; let mut world_terms = vec![0; 0x3b]; world_terms[RUNTIME_WORLD_ISSUE_MANAGEMENT_ATTITUDE as usize] = 40; 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: Some(3), 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: 3, name: "Chairman".to_string(), active: true, current_cash: 0, linked_company_id: Some(7), 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 { world_issue_opinion_base_terms_raw_i32: world_terms, company_market_state: BTreeMap::from([( 7, RuntimeCompanyMarketState { issue_opinion_terms_raw_i32: company_terms, ..RuntimeCompanyMarketState::default() }, )]), chairman_issue_opinion_terms_raw_i32: BTreeMap::from([(3, chairman_terms)]), ..RuntimeServiceState::default() }, }; assert_eq!(runtime_company_management_attitude(&state, 7), Some(58)); } #[test] fn reads_year_relative_company_stat_family_from_saved_market_matrix() { let mut year_stat_family_qword_bits = vec![ 0u64; ((RUNTIME_COMPANY_STAT_SLOT_COUNT + 2) * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) as usize ]; let write_year_value = |bits: &mut Vec, slot_id: u32, year_delta: u32, value: f64| { let index = (slot_id * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN + year_delta) as usize; bits[index] = value.to_bits(); }; write_year_value(&mut year_stat_family_qword_bits, 0x01, 1, 10.0); write_year_value(&mut year_stat_family_qword_bits, 0x02, 1, 20.0); write_year_value(&mut year_stat_family_qword_bits, 0x03, 1, 30.0); write_year_value(&mut year_stat_family_qword_bits, 0x04, 1, 40.0); write_year_value(&mut year_stat_family_qword_bits, 0x05, 1, 5.0); write_year_value(&mut year_stat_family_qword_bits, 0x06, 1, 6.0); write_year_value(&mut year_stat_family_qword_bits, 0x07, 1, 7.0); write_year_value(&mut year_stat_family_qword_bits, 0x08, 1, 8.0); write_year_value(&mut year_stat_family_qword_bits, 0x09, 1, 9.0); write_year_value(&mut year_stat_family_qword_bits, 0x0a, 1, 10.0); write_year_value(&mut year_stat_family_qword_bits, 0x0b, 1, 11.0); write_year_value(&mut year_stat_family_qword_bits, 0x0c, 1, 12.0); write_year_value(&mut year_stat_family_qword_bits, 0x0d, 1, 13.0); write_year_value(&mut year_stat_family_qword_bits, 0x0e, 1, 14.0); write_year_value(&mut year_stat_family_qword_bits, 0x0f, 1, 15.0); write_year_value(&mut year_stat_family_qword_bits, 0x10, 1, 16.0); write_year_value(&mut year_stat_family_qword_bits, 0x11, 1, 17.0); write_year_value(&mut year_stat_family_qword_bits, 0x12, 1, 18.0); write_year_value(&mut year_stat_family_qword_bits, 0x16, 1, 4.0); write_year_value(&mut year_stat_family_qword_bits, 0x17, 1, 10.0); write_year_value(&mut year_stat_family_qword_bits, 0x18, 1, 20.0); write_year_value(&mut year_stat_family_qword_bits, 0x19, 1, 25.0); write_year_value(&mut year_stat_family_qword_bits, 0x1a, 1, 50.0); write_year_value(&mut year_stat_family_qword_bits, 0x1b, 1, 100.0); write_year_value(&mut year_stat_family_qword_bits, 0x24, 1, 5.0); let mut special_stat_family_232a_qword_bits = vec![0u64; RUNTIME_COMPANY_STAT_SLOT_COUNT as usize]; special_stat_family_232a_qword_bits[RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH as usize] = 111.0f64.to_bits(); let state = RuntimeState { calendar: CalendarPoint { year: 1845, month_slot: 0, phase_slot: 0, tick_slot: 0, }, world_flags: BTreeMap::new(), save_profile: RuntimeSaveProfileState::default(), world_restore: RuntimeWorldRestoreState { packed_year_word_raw_u16: Some(1845), ..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 { world_issue_opinion_base_terms_raw_i32: vec![0; 0x3b], company_market_state: BTreeMap::from([( 7, RuntimeCompanyMarketState { year_stat_family_qword_bits, special_stat_family_232a_qword_bits, ..RuntimeCompanyMarketState::default() }, )]), ..RuntimeServiceState::default() }, }; let prior_year = RuntimeCompanyStatSelector { family_id: 1844, slot_id: 0x09, }; assert_eq!( runtime_company_stat_value_f64(&state, 7, prior_year), Some(9.0) ); assert_eq!( runtime_company_stat_value_f64( &state, 7, RuntimeCompanyStatSelector { family_id: 1844, slot_id: 0x2c, }, ), Some(100.0) ); assert_eq!( runtime_company_stat_value_f64( &state, 7, RuntimeCompanyStatSelector { family_id: 1844, slot_id: 0x2b, }, ), Some(168.0) ); assert_eq!( runtime_company_stat_value_f64( &state, 7, RuntimeCompanyStatSelector { family_id: 1844, slot_id: 0x32, }, ), Some(20.0) ); assert_eq!( runtime_company_stat_value_f64( &state, 7, RuntimeCompanyStatSelector { family_id: 1844, slot_id: 0x38, }, ), Some(1.0) ); assert_eq!( runtime_company_stat_value_f64( &state, 7, RuntimeCompanyStatSelector { family_id: RUNTIME_COMPANY_STAT_FAMILY_SPECIAL_232A, slot_id: RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH, }, ), Some(111.0) ); } #[test] fn carries_trailing_full_year_finance_lanes_into_annual_finance_state() { let mut year_stat_family_qword_bits = vec![ 0u64; (RUNTIME_COMPANY_STAT_SLOT_COUNT * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) as usize ]; let write_year_value = |bits: &mut Vec, slot_id: u32, year_delta: u32, value: f64| { let index = (slot_id * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN + year_delta) as usize; bits[index] = value.to_bits(); }; for (year_delta, revenue_parts, extra_profit_parts, fuel_cost) in [ ( 1, [60.0, 50.0, 40.0, 30.0], [10.0, 9.0, 8.0, 7.0, 6.0, 5.0, 4.0], 18.0, ), ( 2, [50.0, 45.0, 40.0, 35.0], [9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0], 17.0, ), ( 3, [50.0, 40.0, 35.0, 35.0], [8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0], 16.0, ), ( 4, [45.0, 40.0, 35.0, 30.0], [7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0], 15.0, ), ] { for (slot_id, value) in [0x01, 0x02, 0x03, 0x04].into_iter().zip(revenue_parts) { write_year_value(&mut year_stat_family_qword_bits, slot_id, year_delta, value); } for (slot_id, value) in [0x05, 0x06, 0x07, 0x08, 0x0a, 0x0b, 0x0c] .into_iter() .zip(extra_profit_parts) { write_year_value(&mut year_stat_family_qword_bits, slot_id, year_delta, value); } write_year_value( &mut year_stat_family_qword_bits, 0x09, year_delta, fuel_cost, ); } let state = RuntimeState { calendar: CalendarPoint { year: 1845, month_slot: 0, phase_slot: 0, tick_slot: 0, }, world_flags: BTreeMap::new(), save_profile: RuntimeSaveProfileState::default(), world_restore: RuntimeWorldRestoreState { packed_year_word_raw_u16: Some(1845), ..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 { world_issue_opinion_base_terms_raw_i32: vec![0; 0x3b], company_market_state: BTreeMap::from([( 7, RuntimeCompanyMarketState { outstanding_shares: 20_000, year_stat_family_qword_bits, ..RuntimeCompanyMarketState::default() }, )]), ..RuntimeServiceState::default() }, }; let finance_state = runtime_company_annual_finance_state(&state, 7).expect("annual finance state"); assert_eq!( finance_state.trailing_full_year_year_words, vec![1844, 1843, 1842, 1841] ); assert_eq!( finance_state.trailing_full_year_net_profits, vec![247, 229, 211, 193] ); assert_eq!( finance_state.trailing_full_year_revenues, vec![180, 170, 160, 150] ); assert_eq!( finance_state.trailing_full_year_fuel_costs, vec![18, 17, 16, 15] ); } #[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 sums_save_native_issue_opinion_terms_with_linked_company_fallback() { 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: Some(3), 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: 3, name: "Chairman".to_string(), active: true, current_cash: 0, linked_company_id: Some(7), 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 { world_issue_opinion_base_terms_raw_i32: (0..0x3b) .map(|value| value as i32) .collect(), company_market_state: BTreeMap::from([( 7, RuntimeCompanyMarketState { issue_opinion_terms_raw_i32: (0..0x3b) .map(|value| (value as i32) * 2) .collect(), ..RuntimeCompanyMarketState::default() }, )]), chairman_issue_opinion_terms_raw_i32: BTreeMap::from([( 3, (0..0x3b).map(|value| (value as i32) * 3).collect(), )]), ..RuntimeServiceState::default() }, }; assert_eq!( runtime_world_issue_opinion_term_sum_raw(&state, 0x39, Some(3), None, None), Some(57 + 114 + 171) ); assert_eq!( runtime_world_issue_opinion_term_sum_raw(&state, 0x39, None, Some(7), None), Some(57 + 114) ); } #[test] fn computes_save_native_issue_opinion_multiplier_with_floor() { 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: 5, 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: 9, name: "Chairman".to_string(), active: true, current_cash: 0, linked_company_id: Some(5), 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 { world_issue_opinion_base_terms_raw_i32: { let mut values = vec![0; 0x3b]; values[0x38] = -150; values }, company_market_state: BTreeMap::from([( 5, RuntimeCompanyMarketState { issue_opinion_terms_raw_i32: { let mut values = vec![0; 0x3b]; values[0x38] = -60; values }, ..RuntimeCompanyMarketState::default() }, )]), chairman_issue_opinion_terms_raw_i32: BTreeMap::from([(9, { let mut values = vec![0; 0x3b]; values[0x38] = 50; values })]), ..RuntimeServiceState::default() }, }; let multiplier = runtime_world_issue_opinion_multiplier(&state, 0x38, Some(9), None, None) .expect("issue multiplier"); assert!((multiplier - 0.01).abs() < f64::EPSILON); } #[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_prime_rate_baseline_from_saved_world_raw_word() { 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(5.0f32.to_bits()), ..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_prime_rate_baseline(&state), Some(5.0)); } #[test] fn derives_company_prime_rate_from_issue39_owner_state() { let mut company_terms = vec![0; 0x3b]; company_terms[RUNTIME_WORLD_ISSUE_PRIME_RATE as usize] = 50; let mut chairman_terms = vec![0; 0x3b]; chairman_terms[RUNTIME_WORLD_ISSUE_PRIME_RATE as usize] = 25; let mut world_terms = vec![0; 0x3b]; world_terms[RUNTIME_WORLD_ISSUE_PRIME_RATE as usize] = 100; 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(5.0f32.to_bits()), ..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: Some(3), 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: 3, name: "Chairman".to_string(), active: true, current_cash: 0, linked_company_id: Some(7), 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 { world_issue_opinion_base_terms_raw_i32: world_terms, company_market_state: BTreeMap::from([( 7, RuntimeCompanyMarketState { issue_opinion_terms_raw_i32: company_terms, ..RuntimeCompanyMarketState::default() }, )]), chairman_issue_opinion_terms_raw_i32: BTreeMap::from([(3, chairman_terms)]), ..RuntimeServiceState::default() }, }; assert_eq!(runtime_company_prime_rate(&state, 7), Some(7)); } #[test] fn derives_company_credit_rating_from_rehosted_finance_state() { let mut year_stat_family_qword_bits = vec![ 0u64; (RUNTIME_COMPANY_STAT_SLOT_COUNT * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) as usize ]; year_stat_family_qword_bits[(0x12 * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) as usize] = 20.0f64.to_bits(); year_stat_family_qword_bits[(0x01 * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN + 1) as usize] = 100.0f64.to_bits(); year_stat_family_qword_bits[(0x09 * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN + 1) as usize] = 0.0f64.to_bits(); let state = RuntimeState { calendar: CalendarPoint { year: 1835, month_slot: 0, phase_slot: 0, tick_slot: 0, }, world_flags: BTreeMap::new(), save_profile: RuntimeSaveProfileState::default(), world_restore: RuntimeWorldRestoreState { issue_37_value: Some(5.0f32.to_bits()), issue_38_value: Some(2), packed_year_word_raw_u16: Some(1835), ..RuntimeWorldRestoreState::default() }, metadata: BTreeMap::new(), companies: vec![RuntimeCompany { company_id: 7, current_cash: 100, 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 { world_issue_opinion_base_terms_raw_i32: vec![0; 0x3b], company_market_state: BTreeMap::from([( 7, RuntimeCompanyMarketState { outstanding_shares: 10_000, founding_year: 1830, last_bankruptcy_year: 1800, year_stat_family_qword_bits, live_bond_slots: vec![RuntimeCompanyBondSlot { slot_index: 0, principal: 100_000, coupon_rate_raw_u32: 0.05f32.to_bits(), }], ..RuntimeCompanyMarketState::default() }, )]), ..RuntimeServiceState::default() }, }; let annual_finance_state = runtime_company_annual_finance_state(&state, 7).expect("annual finance state"); assert_eq!( annual_finance_state.trailing_full_year_net_profits, vec![100, 0, 0, 0] ); assert_eq!( runtime_company_control_transfer_stat_value_f64(&state, 7, 0x12), Some(20.0) ); assert_eq!( runtime_company_derived_stat_value_f64( &state, 7, RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER, 0x30, ), Some(100.0) ); assert_eq!( runtime_company_derived_stat_value_f64( &state, 7, RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER, 0x31, ), Some(120.0) ); let average_live_bond_coupon = runtime_company_average_live_bond_coupon(&state, 7).expect("average coupon"); assert!((average_live_bond_coupon - 0.05).abs() < 1e-6); assert_eq!(runtime_world_prime_rate_baseline(&state), Some(5.0)); assert_eq!( runtime_world_issue_opinion_term_sum_raw( &state, RUNTIME_WORLD_ISSUE_PRIME_RATE, None, Some(7), None, ), Some(0) ); assert_eq!( runtime_world_issue_opinion_term_sum_raw( &state, RUNTIME_WORLD_ISSUE_CREDIT_MARKET, None, Some(7), None, ), Some(0) ); assert_eq!(runtime_world_credit_market_scale(&state), Some(1.0)); assert_eq!(runtime_company_credit_rating(&state, 7), Some(10)); assert_eq!( runtime_company_stat_value( &state, 7, RuntimeCompanyStatSelector { family_id: RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER, slot_id: RUNTIME_COMPANY_STAT_SLOT_CREDIT_RATING, }, ), Some(10) ); } #[test] fn computes_weighted_average_live_bond_coupon_from_owned_market_slots() { 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::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([( 7, RuntimeCompanyMarketState { live_bond_slots: vec![ RuntimeCompanyBondSlot { slot_index: 0, principal: 100_000, coupon_rate_raw_u32: 0.04f32.to_bits(), }, RuntimeCompanyBondSlot { slot_index: 1, principal: 300_000, coupon_rate_raw_u32: 0.08f32.to_bits(), }, ], ..RuntimeCompanyMarketState::default() }, )]), ..RuntimeServiceState::default() }, }; let average = runtime_company_average_live_bond_coupon(&state, 7) .expect("weighted average live bond coupon"); assert!((average - 0.07).abs() < 1e-6); } #[test] fn decodes_and_packs_company_issue_calendar_tuple() { let tuple = runtime_decode_packed_calendar_tuple(0x0101_0726, 0x0001_0001); assert_eq!( tuple, RuntimePackedCalendarTuple { year_word: 0x0726, month_1_based: 1, week_1_based: 1, day_1_based: 1, hour_0_based: 0, quarter_day_1_based: 1, minute_0_based: 0, } ); assert_eq!( runtime_pack_packed_calendar_tuple_to_absolute_counter(tuple), Some(885_427_200) ); assert_eq!( runtime_pack_packed_calendar_tuple_to_absolute_counter(RuntimePackedCalendarTuple { year_word: 1830, month_1_based: 13, week_1_based: 1, day_1_based: 1, hour_0_based: 0, quarter_day_1_based: 1, minute_0_based: 0, }), None ); } #[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, trailing_full_year_year_words: Vec::new(), trailing_full_year_net_profits: Vec::new(), trailing_full_year_revenues: Vec::new(), trailing_full_year_fuel_costs: Vec::new(), current_issue_absolute_counter: None, prior_issue_absolute_counter: None, current_issue_age_absolute_counter_delta: 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 derives_annual_creditor_pressure_from_rehosted_finance_owner_state() { let mut year_stat_family_qword_bits = vec![ 0u64; (RUNTIME_COMPANY_STAT_SLOT_COUNT * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) as usize ]; let write_current_value = |bits: &mut Vec, slot_id: u32, value: f64| { let index = (slot_id * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) as usize; bits[index] = value.to_bits(); }; let write_prior_year_value = |bits: &mut Vec, slot_id: u32, year_delta: u32, value: f64| { let index = (slot_id * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN + year_delta) as usize; bits[index] = value.to_bits(); }; write_current_value(&mut year_stat_family_qword_bits, 0x09, -50_000.0); write_current_value(&mut year_stat_family_qword_bits, 0x0d, -700_000.0); write_current_value(&mut year_stat_family_qword_bits, 0x12, 0.0); write_prior_year_value(&mut year_stat_family_qword_bits, 0x01, 1, 100_000.0); write_prior_year_value(&mut year_stat_family_qword_bits, 0x01, 2, 90_000.0); write_prior_year_value(&mut year_stat_family_qword_bits, 0x01, 3, 80_000.0); write_prior_year_value(&mut year_stat_family_qword_bits, 0x09, 1, -115_000.0); write_prior_year_value(&mut year_stat_family_qword_bits, 0x09, 2, -110_000.0); write_prior_year_value(&mut year_stat_family_qword_bits, 0x09, 3, -110_000.0); let state = RuntimeState { calendar: CalendarPoint { year: 1845, month_slot: 0, phase_slot: 0, tick_slot: 0, }, world_flags: BTreeMap::new(), save_profile: RuntimeSaveProfileState::default(), world_restore: RuntimeWorldRestoreState { packed_year_word_raw_u16: Some(1845), partial_year_progress_raw_u8: Some(0x0c), bankruptcy_policy_raw_u8: Some(0), bankruptcy_allowed: Some(true), ..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::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([( 7, RuntimeCompanyMarketState { founding_year: 1841, last_bankruptcy_year: 1832, cached_share_price_raw_u32: 25.0f32.to_bits(), year_stat_family_qword_bits, ..RuntimeCompanyMarketState::default() }, )]), ..RuntimeServiceState::default() }, }; assert_eq!(runtime_world_annual_finance_mode_active(&state), Some(true)); assert_eq!(runtime_world_bankruptcy_allowed(&state), Some(true)); let pressure_state = runtime_company_annual_creditor_pressure_state(&state, 7) .expect("creditor pressure state"); assert_eq!(pressure_state.recent_bad_net_profit_year_count, 3); assert_eq!(pressure_state.recent_peak_revenue, Some(100_000)); assert_eq!( pressure_state.recent_three_year_net_profit_total, Some(-65_000) ); assert_eq!(pressure_state.pressure_ladder_cash_floor, Some(-600_000)); assert_eq!( pressure_state.current_cash_plus_slot_12_total, Some(-700_000) ); assert_eq!(pressure_state.support_adjusted_share_price_floor, Some(20)); assert_eq!(pressure_state.support_adjusted_share_price_scalar, Some(25)); assert_eq!(pressure_state.current_fuel_cost, Some(-50_000)); assert_eq!(pressure_state.current_fuel_cost_floor, Some(-48_000)); assert!(pressure_state.eligible_for_bankruptcy_branch); } #[test] fn derives_annual_deep_distress_bankruptcy_fallback_from_rehosted_finance_owner_state() { let mut year_stat_family_qword_bits = vec![ 0u64; ((RUNTIME_COMPANY_STAT_SLOT_COUNT + 2) * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) as usize ]; let write_current_value = |bits: &mut Vec, slot_id: u32, value: f64| { let index = (slot_id * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) as usize; bits[index] = value.to_bits(); }; let write_prior_year_value = |bits: &mut Vec, slot_id: u32, year_delta: u32, value: f64| { let index = (slot_id * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN + year_delta) as usize; bits[index] = value.to_bits(); }; write_current_value(&mut year_stat_family_qword_bits, 0x0d, -350_000.0); write_prior_year_value(&mut year_stat_family_qword_bits, 0x01, 1, 10_000.0); write_prior_year_value(&mut year_stat_family_qword_bits, 0x01, 2, 15_000.0); write_prior_year_value(&mut year_stat_family_qword_bits, 0x01, 3, 12_000.0); write_prior_year_value(&mut year_stat_family_qword_bits, 0x09, 1, -35_000.0); write_prior_year_value(&mut year_stat_family_qword_bits, 0x09, 2, -38_000.0); write_prior_year_value(&mut year_stat_family_qword_bits, 0x09, 3, -33_000.0); let state = RuntimeState { calendar: CalendarPoint { year: 1845, month_slot: 0, phase_slot: 0, tick_slot: 0, }, world_flags: BTreeMap::new(), save_profile: RuntimeSaveProfileState::default(), world_restore: RuntimeWorldRestoreState { packed_year_word_raw_u16: Some(1845), bankruptcy_policy_raw_u8: Some(0), bankruptcy_allowed: Some(true), ..RuntimeWorldRestoreState::default() }, metadata: BTreeMap::new(), companies: vec![RuntimeCompany { company_id: 9, 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([( 9, RuntimeCompanyMarketState { founding_year: 1841, last_bankruptcy_year: 1840, year_stat_family_qword_bits, ..RuntimeCompanyMarketState::default() }, )]), ..RuntimeServiceState::default() }, }; let pressure_state = runtime_company_annual_deep_distress_state(&state, 9).expect("deep distress state"); assert_eq!(pressure_state.current_cash, Some(-350_000)); assert_eq!( pressure_state.recent_first_three_net_profit_years, vec![-25_000, -23_000, -21_000] ); assert_eq!(pressure_state.deep_distress_cash_floor, Some(-300_000)); assert_eq!(pressure_state.deep_distress_net_profit_floor, Some(-20_000)); assert!(pressure_state.eligible_for_bankruptcy_fallback); } #[test] fn derives_annual_stock_repurchase_state_from_rehosted_owner_state() { let mut year_stat_family_qword_bits = vec![ 0u64; ((RUNTIME_COMPANY_STAT_SLOT_COUNT + 2) * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) as usize ]; let write_current_value = |bits: &mut Vec, slot_id: u32, value: f64| { let index = (slot_id * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) as usize; bits[index] = value.to_bits(); }; write_current_value(&mut year_stat_family_qword_bits, 0x0d, 1_600_000.0); let state = RuntimeState { calendar: CalendarPoint { year: 1845, month_slot: 0, phase_slot: 0, tick_slot: 0, }, world_flags: BTreeMap::new(), save_profile: RuntimeSaveProfileState::default(), world_restore: RuntimeWorldRestoreState { partial_year_progress_raw_u8: Some(0x0c), stock_issue_and_buyback_policy_raw_u8: Some(0), stock_issue_and_buyback_allowed: Some(true), building_density_growth_setting_raw_u32: Some(1), ..RuntimeWorldRestoreState::default() }, metadata: BTreeMap::new(), companies: vec![RuntimeCompany { company_id: 12, 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: Some(3), 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: 3, name: "Jay".to_string(), active: true, current_cash: 200, linked_company_id: Some(12), company_holdings: BTreeMap::from([(12, 14_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([( 12, RuntimeCompanyMarketState { outstanding_shares: 20_000, cached_share_price_raw_u32: 20.0f32.to_bits(), founding_year: 1835, city_connection_latch: true, year_stat_family_qword_bits, ..RuntimeCompanyMarketState::default() }, )]), chairman_personality_raw_u8: BTreeMap::from([(3, 20)]), ..RuntimeServiceState::default() }, }; let repurchase_state = runtime_company_annual_stock_repurchase_state(&state, 12) .expect("stock repurchase state"); assert_eq!(repurchase_state.building_density_growth_setting, Some(1)); assert_eq!( repurchase_state.linked_chairman_personality_raw_u8, Some(20) ); assert_eq!(repurchase_state.repurchase_batch_size, Some(1_000)); assert_eq!(repurchase_state.repurchase_factor_basis_points, Some(432)); assert_eq!(repurchase_state.current_cash, Some(1_600_000)); assert_eq!( repurchase_state.stock_value_gate_cash_floor, Some(3_456_000) ); assert_eq!( repurchase_state.support_adjusted_share_price_scalar, Some(20) ); assert_eq!(repurchase_state.affordability_cash_floor, Some(103_680)); assert_eq!(repurchase_state.unassigned_share_pool, Some(5_500)); assert!(!repurchase_state.eligible_for_single_batch_repurchase); } #[test] fn derives_annual_bond_policy_state_from_rehosted_owner_state() { let mut year_stat_family_qword_bits = vec![ 0u64; ((RUNTIME_COMPANY_STAT_SLOT_COUNT + 2) * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) as usize ]; let write_current_value = |bits: &mut Vec, slot_id: u32, value: f64| { let index = (slot_id * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) as usize; bits[index] = value.to_bits(); }; write_current_value(&mut year_stat_family_qword_bits, 0x0d, -400_000.0); let state = RuntimeState { calendar: CalendarPoint { year: 1845, month_slot: 0, phase_slot: 0, tick_slot: 0, }, world_flags: BTreeMap::new(), save_profile: RuntimeSaveProfileState::default(), world_restore: RuntimeWorldRestoreState { partial_year_progress_raw_u8: Some(0x0c), bond_issue_and_repayment_policy_raw_u8: Some(0), bond_issue_and_repayment_allowed: Some(true), ..RuntimeWorldRestoreState::default() }, metadata: BTreeMap::new(), companies: vec![RuntimeCompany { company_id: 11, 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([( 11, RuntimeCompanyMarketState { bond_count: 2, linked_transit_latch: true, live_bond_slots: vec![ RuntimeCompanyBondSlot { slot_index: 0, principal: 200_000, coupon_rate_raw_u32: 0.09f32.to_bits(), }, RuntimeCompanyBondSlot { slot_index: 1, principal: 150_000, coupon_rate_raw_u32: 0.08f32.to_bits(), }, ], year_stat_family_qword_bits, ..RuntimeCompanyMarketState::default() }, )]), ..RuntimeServiceState::default() }, }; let bond_state = runtime_company_annual_bond_policy_state(&state, 11).expect("bond policy state"); assert_eq!(bond_state.live_bond_count, Some(2)); assert_eq!(bond_state.live_bond_principal_total, Some(350_000)); assert_eq!(bond_state.current_cash, Some(-400_000)); assert_eq!(bond_state.cash_after_full_repayment, Some(-750_000)); assert_eq!(bond_state.issue_cash_floor, Some(-30_000)); assert_eq!(bond_state.issue_principal_step, Some(500_000)); assert_eq!(bond_state.proposed_issue_bond_count, Some(2)); assert_eq!(bond_state.proposed_issue_total_principal, Some(1_000_000)); assert_eq!(bond_state.proposed_issue_years_to_maturity, Some(30)); assert!(bond_state.eligible_for_bond_issue_branch); } #[test] fn derives_annual_stock_issue_state_from_rehosted_owner_state() { let mut year_stat_family_qword_bits = vec![ 0u64; ((RUNTIME_COMPANY_STAT_SLOT_COUNT + 2) * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) as usize ]; let write_current_value = |bits: &mut Vec, slot_id: u32, value: f64| { let index = (slot_id * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) as usize; bits[index] = value.to_bits(); }; write_current_value(&mut year_stat_family_qword_bits, 0x0d, 250_000.0); let state = RuntimeState { calendar: CalendarPoint { year: 1845, month_slot: 0, phase_slot: 0, tick_slot: 0, }, world_flags: BTreeMap::new(), save_profile: RuntimeSaveProfileState::default(), world_restore: RuntimeWorldRestoreState { partial_year_progress_raw_u8: Some(0x0c), stock_issue_and_buyback_policy_raw_u8: Some(0), stock_issue_and_buyback_allowed: Some(true), bond_issue_and_repayment_policy_raw_u8: Some(0), bond_issue_and_repayment_allowed: Some(true), issue_37_value: Some(2), issue_37_multiplier_raw_u32: Some(1.0f32.to_bits()), issue_37_multiplier_value_f32_text: Some("1.000000".to_string()), absolute_counter_raw_u32: Some(885_911_040), ..RuntimeWorldRestoreState::default() }, metadata: BTreeMap::new(), companies: vec![RuntimeCompany { company_id: 14, 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: Some(8), 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: 8, name: "Taylor".to_string(), active: true, current_cash: 200, linked_company_id: Some(14), company_holdings: BTreeMap::from([(14, 14_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 { world_issue_opinion_base_terms_raw_i32: vec![0; 0x3b], chairman_personality_raw_u8: BTreeMap::from([(8, 20)]), company_market_state: BTreeMap::from([( 14, RuntimeCompanyMarketState { outstanding_shares: 20_000, bond_count: 2, highest_coupon_live_bond_principal: Some(300_000), current_issue_calendar_word: 0x0101_0725, current_issue_calendar_word_2: 0x0001_0001, founding_year: 1840, cached_share_price_raw_u32: 35.0f32.to_bits(), recent_per_share_cache_absolute_counter: 885_911_040, recent_per_share_cached_value_bits: 34.0f64.to_bits(), city_connection_latch: false, live_bond_slots: vec![ RuntimeCompanyBondSlot { slot_index: 0, principal: 300_000, coupon_rate_raw_u32: 0.11f32.to_bits(), }, RuntimeCompanyBondSlot { slot_index: 1, principal: 200_000, coupon_rate_raw_u32: 0.07f32.to_bits(), }, ], direct_control_transfer_float_fields_raw_u32: BTreeMap::from([( 0x32f, 30.0f32.to_bits(), )]), year_stat_family_qword_bits, ..RuntimeCompanyMarketState::default() }, )]), ..RuntimeServiceState::default() }, }; let stock_issue_state = runtime_company_annual_stock_issue_state(&state, 14).expect("stock issue state"); assert_eq!(stock_issue_state.live_bond_count, Some(2)); assert_eq!(stock_issue_state.initial_issue_batch_size, Some(2_000)); assert_eq!(stock_issue_state.trimmed_issue_batch_size, Some(2_000)); assert_eq!(stock_issue_state.share_pressure_basis_points, Some(-1_000)); assert_eq!( stock_issue_state.pressured_support_adjusted_share_price_scalar, Some(35) ); assert_eq!(stock_issue_state.pressured_proceeds, Some(70_000)); assert_eq!( stock_issue_state.book_value_per_share_floor_applied, Some(30) ); assert_eq!( stock_issue_state.price_to_book_ratio_basis_points, Some(11_667) ); assert_eq!( stock_issue_state.highest_coupon_live_bond_rate_basis_points, Some(1_100) ); assert_eq!( stock_issue_state.minimum_price_to_book_ratio_basis_points, Some(8_000) ); assert_eq!(stock_issue_state.current_cash, Some(250_000)); assert_eq!( stock_issue_state.highest_coupon_live_bond_principal, Some(300_000) ); assert_eq!( stock_issue_state.current_issue_age_absolute_counter_delta, Some(967_680) ); assert_eq!( stock_issue_state.current_issue_cooldown_floor, Some(483_840) ); assert_eq!(stock_issue_state.passes_share_price_floor, Some(true)); assert_eq!(stock_issue_state.passes_proceeds_floor, Some(true)); assert_eq!(stock_issue_state.passes_cash_gate, Some(true)); assert_eq!(stock_issue_state.passes_issue_cooldown_gate, Some(true)); assert_eq!( stock_issue_state.passes_coupon_price_to_book_gate, Some(true) ); assert!(stock_issue_state.eligible_for_double_tranche_issue); } #[test] fn derives_annual_dividend_policy_state_from_rehosted_owner_state() { let mut year_stat_family_qword_bits = vec![ 0u64; ((RUNTIME_COMPANY_STAT_SLOT_COUNT + 2) * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) as usize ]; let write_current_value = |bits: &mut Vec, slot_id: u32, value: f64| { let index = (slot_id * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) as usize; bits[index] = value.to_bits(); }; let write_prior_year_value = |bits: &mut Vec, slot_id: u32, year_delta: u32, value: f64| { let index = (slot_id * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN + year_delta) as usize; bits[index] = value.to_bits(); }; write_current_value(&mut year_stat_family_qword_bits, 0x0d, 300_000.0); write_current_value(&mut year_stat_family_qword_bits, 0x01, 300_000.0); write_current_value(&mut year_stat_family_qword_bits, 0x09, -180_000.0); write_prior_year_value(&mut year_stat_family_qword_bits, 0x01, 1, 280_000.0); write_prior_year_value(&mut year_stat_family_qword_bits, 0x09, 1, -190_000.0); write_prior_year_value(&mut year_stat_family_qword_bits, 0x01, 2, 260_000.0); write_prior_year_value(&mut year_stat_family_qword_bits, 0x09, 2, -200_000.0); write_prior_year_value(&mut year_stat_family_qword_bits, 0x1c, 1, 5.0); let state = RuntimeState { calendar: CalendarPoint { year: 1845, month_slot: 0, phase_slot: 0, tick_slot: 0, }, world_flags: BTreeMap::new(), save_profile: RuntimeSaveProfileState::default(), world_restore: RuntimeWorldRestoreState { packed_year_word_raw_u16: Some(1845), partial_year_progress_raw_u8: Some(0x0c), dividend_policy_raw_u8: Some(0), dividend_adjustment_allowed: Some(true), stock_issue_and_buyback_policy_raw_u8: Some(0), stock_issue_and_buyback_allowed: Some(true), bond_issue_and_repayment_policy_raw_u8: Some(0), bond_issue_and_repayment_allowed: Some(true), bankruptcy_policy_raw_u8: Some(0), bankruptcy_allowed: Some(true), building_density_growth_setting_raw_u32: Some(1), ..RuntimeWorldRestoreState::default() }, metadata: BTreeMap::new(), companies: vec![RuntimeCompany { company_id: 15, 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: 3, name: "Chairman Three".to_string(), active: true, current_cash: 0, linked_company_id: Some(15), company_holdings: BTreeMap::from([(15, 9_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([( 15, RuntimeCompanyMarketState { outstanding_shares: 10_000, founding_year: 1840, last_dividend_year: 1844, year_stat_family_qword_bits, direct_control_transfer_float_fields_raw_u32: BTreeMap::from([( 0x33f, 0.4f32.to_bits(), )]), ..RuntimeCompanyMarketState::default() }, )]), ..RuntimeServiceState::default() }, }; let dividend_state = runtime_company_annual_dividend_policy_state(&state, 15) .expect("annual dividend policy state"); assert_eq!(dividend_state.years_since_last_dividend, Some(1)); assert_eq!(dividend_state.years_since_founding, Some(5)); assert_eq!(dividend_state.outstanding_shares, Some(10_000)); assert_eq!(dividend_state.unassigned_share_pool, Some(500)); assert_eq!( dividend_state.weighted_recent_net_profit_total, Some(600_000) ); assert_eq!( dividend_state.weighted_recent_net_profit_average, Some(100_000) ); assert_eq!(dividend_state.current_cash, Some(300_000)); assert!(dividend_state.tiny_unassigned_share_cash_supplement_branch); assert_eq!( dividend_state.tentative_target_dividend_per_share_tenths, Some(133) ); assert_eq!(dividend_state.current_dividend_per_share_tenths, Some(4)); assert_eq!( dividend_state.growth_adjusted_current_dividend_per_share_tenths, Some(3) ); assert_eq!( dividend_state.board_approved_dividend_rate_ceiling_tenths, Some(18) ); assert_eq!(dividend_state.proposed_dividend_per_share_tenths, Some(18)); assert!(dividend_state.eligible_for_dividend_adjustment_branch); } #[test] fn derives_annual_finance_policy_action_from_branch_priority_order() { let mut year_stat_family_qword_bits = vec![ 0u64; ((RUNTIME_COMPANY_STAT_SLOT_COUNT + 2) * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) as usize ]; let write_current_value = |bits: &mut Vec, slot_id: u32, value: f64| { let index = (slot_id * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) as usize; bits[index] = value.to_bits(); }; let write_prior_year_value = |bits: &mut Vec, slot_id: u32, year_delta: u32, value: f64| { let index = (slot_id * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN + year_delta) as usize; bits[index] = value.to_bits(); }; write_current_value(&mut year_stat_family_qword_bits, 0x0d, 300_000.0); write_current_value(&mut year_stat_family_qword_bits, 0x01, 300_000.0); write_current_value(&mut year_stat_family_qword_bits, 0x09, -180_000.0); write_prior_year_value(&mut year_stat_family_qword_bits, 0x01, 1, 280_000.0); write_prior_year_value(&mut year_stat_family_qword_bits, 0x09, 1, -190_000.0); write_prior_year_value(&mut year_stat_family_qword_bits, 0x01, 2, 260_000.0); write_prior_year_value(&mut year_stat_family_qword_bits, 0x09, 2, -200_000.0); write_prior_year_value(&mut year_stat_family_qword_bits, 0x1c, 1, 5.0); let state = RuntimeState { calendar: CalendarPoint { year: 1845, month_slot: 0, phase_slot: 0, tick_slot: 0, }, world_flags: BTreeMap::new(), save_profile: RuntimeSaveProfileState::default(), world_restore: RuntimeWorldRestoreState { packed_year_word_raw_u16: Some(1845), partial_year_progress_raw_u8: Some(0x0c), dividend_policy_raw_u8: Some(0), dividend_adjustment_allowed: Some(true), stock_issue_and_buyback_policy_raw_u8: Some(0), stock_issue_and_buyback_allowed: Some(true), bond_issue_and_repayment_policy_raw_u8: Some(0), bond_issue_and_repayment_allowed: Some(true), bankruptcy_policy_raw_u8: Some(0), bankruptcy_allowed: Some(true), building_density_growth_setting_raw_u32: Some(1), ..RuntimeWorldRestoreState::default() }, metadata: BTreeMap::new(), companies: vec![RuntimeCompany { company_id: 16, 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: Some(4), book_value_per_share: 0, investor_confidence: 0, management_attitude: 0, takeover_cooldown_year: None, merger_cooldown_year: None, track_piece_counts: RuntimeTrackPieceCounts::default(), }], selected_company_id: Some(16), players: Vec::new(), selected_player_id: None, chairman_profiles: vec![RuntimeChairmanProfile { profile_id: 4, name: "Chairman Four".to_string(), active: true, current_cash: 0, linked_company_id: Some(16), company_holdings: BTreeMap::from([(16, 9_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 { chairman_personality_raw_u8: BTreeMap::from([(4, 20)]), company_market_state: BTreeMap::from([( 16, RuntimeCompanyMarketState { outstanding_shares: 10_000, founding_year: 1840, last_dividend_year: 1844, city_connection_latch: true, year_stat_family_qword_bits, direct_control_transfer_float_fields_raw_u32: BTreeMap::from([( 0x33f, 0.4f32.to_bits(), )]), ..RuntimeCompanyMarketState::default() }, )]), ..RuntimeServiceState::default() }, }; let policy_state = runtime_company_annual_finance_policy_state(&state, 16) .expect("annual finance policy state"); assert_eq!( policy_state.action, RuntimeCompanyAnnualFinancePolicyAction::DividendAdjustment ); assert!(!policy_state.stock_repurchase_eligible); assert!(!policy_state.stock_issue_eligible); assert!(policy_state.dividend_adjustment_eligible); } #[test] fn reads_company_market_metrics_from_annual_finance_reader() { let current_issue_calendar_word = 0x0101_0726; let current_issue_calendar_word_2 = 0x0001_0001; let prior_issue_calendar_word = 0x0101_0725; let prior_issue_calendar_word_2 = 0x0001_0001; 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(885_427_260), ..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, current_issue_calendar_word_2, prior_issue_calendar_word, prior_issue_calendar_word_2, ..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::CurrentIssueAbsoluteCounter ), Some(885_427_200) ); assert_eq!( runtime_company_market_value( &state, 7, RuntimeCompanyMarketMetric::PriorIssueAbsoluteCounter ), Some(884_943_360) ); assert_eq!( runtime_company_market_value( &state, 7, RuntimeCompanyMarketMetric::CurrentIssueAgeAbsoluteCounterDelta ), Some(60) ); assert_eq!( runtime_company_market_value( &state, 7, RuntimeCompanyMarketMetric::CurrentIssueCalendarWord ), Some(i64::from(current_issue_calendar_word)) ); assert_eq!( runtime_company_market_value( &state, 7, RuntimeCompanyMarketMetric::CurrentIssueCalendarWord2 ), Some(i64::from(current_issue_calendar_word_2)) ); assert_eq!( runtime_company_market_value( &state, 7, RuntimeCompanyMarketMetric::PriorIssueCalendarWord ), Some(i64::from(prior_issue_calendar_word)) ); assert_eq!( runtime_company_market_value( &state, 7, RuntimeCompanyMarketMetric::PriorIssueCalendarWord2 ), Some(i64::from(prior_issue_calendar_word_2)) ); 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)); } }