9304 lines
364 KiB
Rust
9304 lines
364 KiB
Rust
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<i64>,
|
|
#[serde(default)]
|
|
pub prime_rate: Option<i64>,
|
|
#[serde(default = "runtime_company_default_active")]
|
|
pub active: bool,
|
|
#[serde(default)]
|
|
pub available_track_laying_capacity: Option<u32>,
|
|
#[serde(default)]
|
|
pub controller_kind: RuntimeCompanyControllerKind,
|
|
#[serde(default)]
|
|
pub linked_chairman_profile_id: Option<u32>,
|
|
#[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<u32>,
|
|
#[serde(default)]
|
|
pub merger_cooldown_year: Option<u32>,
|
|
#[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<u32>,
|
|
#[serde(default)]
|
|
pub highest_coupon_live_bond_principal: Option<u32>,
|
|
#[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<RuntimeCompanyStatBandCandidate>,
|
|
#[serde(default)]
|
|
pub stat_band_root_0d7f_candidates: Vec<RuntimeCompanyStatBandCandidate>,
|
|
#[serde(default)]
|
|
pub stat_band_root_1c47_candidates: Vec<RuntimeCompanyStatBandCandidate>,
|
|
#[serde(default)]
|
|
pub year_stat_family_qword_bits: Vec<u64>,
|
|
#[serde(default)]
|
|
pub special_stat_family_232a_qword_bits: Vec<u64>,
|
|
#[serde(default)]
|
|
pub issue_opinion_terms_raw_i32: Vec<i32>,
|
|
#[serde(default)]
|
|
pub live_bond_slots: Vec<RuntimeCompanyBondSlot>,
|
|
#[serde(default)]
|
|
pub direct_control_transfer_float_fields_raw_u32: BTreeMap<u32, u32>,
|
|
#[serde(default)]
|
|
pub direct_control_transfer_int_fields_raw_u32: BTreeMap<u32, u32>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct RuntimeCompanyBondSlot {
|
|
pub slot_index: u32,
|
|
pub principal: u32,
|
|
#[serde(default)]
|
|
pub maturity_year: 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<u32>,
|
|
#[serde(default)]
|
|
pub highest_coupon_live_bond_principal: Option<u32>,
|
|
#[serde(default)]
|
|
pub live_bond_coupon_burden_total: Option<i64>,
|
|
pub assigned_share_pool: u32,
|
|
pub unassigned_share_pool: u32,
|
|
#[serde(default)]
|
|
pub cached_share_price: Option<i64>,
|
|
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<u32>,
|
|
#[serde(default)]
|
|
pub years_since_last_bankruptcy: Option<u32>,
|
|
#[serde(default)]
|
|
pub years_since_last_dividend: Option<u32>,
|
|
#[serde(default)]
|
|
pub current_partial_year_weight_numerator: Option<i64>,
|
|
#[serde(default)]
|
|
pub trailing_full_year_year_words: Vec<u32>,
|
|
#[serde(default)]
|
|
pub trailing_full_year_net_profits: Vec<i64>,
|
|
#[serde(default)]
|
|
pub trailing_full_year_revenues: Vec<i64>,
|
|
#[serde(default)]
|
|
pub trailing_full_year_fuel_costs: Vec<i64>,
|
|
#[serde(default)]
|
|
pub current_issue_absolute_counter: Option<u32>,
|
|
#[serde(default)]
|
|
pub prior_issue_absolute_counter: Option<u32>,
|
|
#[serde(default)]
|
|
pub current_issue_age_absolute_counter_delta: Option<i64>,
|
|
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<bool>,
|
|
#[serde(default)]
|
|
pub bankruptcy_allowed: Option<bool>,
|
|
#[serde(default)]
|
|
pub years_since_last_bankruptcy: Option<u32>,
|
|
#[serde(default)]
|
|
pub years_since_founding: Option<u32>,
|
|
pub recent_bad_net_profit_year_count: u32,
|
|
#[serde(default)]
|
|
pub recent_peak_revenue: Option<i64>,
|
|
#[serde(default)]
|
|
pub recent_three_year_net_profit_total: Option<i64>,
|
|
#[serde(default)]
|
|
pub pressure_ladder_cash_floor: Option<i64>,
|
|
#[serde(default)]
|
|
pub current_cash_plus_slot_12_total: Option<i64>,
|
|
#[serde(default)]
|
|
pub support_adjusted_share_price_floor: Option<i64>,
|
|
#[serde(default)]
|
|
pub support_adjusted_share_price_scalar: Option<i64>,
|
|
#[serde(default)]
|
|
pub current_fuel_cost: Option<i64>,
|
|
#[serde(default)]
|
|
pub current_fuel_cost_floor: Option<i64>,
|
|
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<bool>,
|
|
#[serde(default)]
|
|
pub years_since_founding: Option<u32>,
|
|
#[serde(default)]
|
|
pub years_since_last_bankruptcy: Option<u32>,
|
|
#[serde(default)]
|
|
pub current_cash: Option<i64>,
|
|
pub recent_first_three_net_profit_years: Vec<i64>,
|
|
#[serde(default)]
|
|
pub deep_distress_cash_floor: Option<i64>,
|
|
#[serde(default)]
|
|
pub deep_distress_net_profit_floor: Option<i64>,
|
|
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<bool>,
|
|
#[serde(default)]
|
|
pub stock_issue_and_buyback_allowed: Option<bool>,
|
|
pub city_connection_latch: bool,
|
|
#[serde(default)]
|
|
pub building_density_growth_setting: Option<u32>,
|
|
#[serde(default)]
|
|
pub linked_chairman_profile_id: Option<u32>,
|
|
#[serde(default)]
|
|
pub linked_chairman_personality_raw_u8: Option<u8>,
|
|
#[serde(default)]
|
|
pub repurchase_batch_size: Option<u32>,
|
|
#[serde(default)]
|
|
pub repurchase_factor_basis_points: Option<i64>,
|
|
#[serde(default)]
|
|
pub current_cash: Option<i64>,
|
|
#[serde(default)]
|
|
pub stock_value_gate_cash_floor: Option<i64>,
|
|
#[serde(default)]
|
|
pub support_adjusted_share_price_scalar: Option<i64>,
|
|
#[serde(default)]
|
|
pub affordability_cash_floor: Option<i64>,
|
|
#[serde(default)]
|
|
pub unassigned_share_pool: Option<u32>,
|
|
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<bool>,
|
|
#[serde(default)]
|
|
pub stock_issue_and_buyback_allowed: Option<bool>,
|
|
#[serde(default)]
|
|
pub bond_issue_and_repayment_allowed: Option<bool>,
|
|
#[serde(default)]
|
|
pub years_since_founding: Option<u32>,
|
|
#[serde(default)]
|
|
pub live_bond_count: Option<u8>,
|
|
#[serde(default)]
|
|
pub initial_issue_batch_size: Option<u32>,
|
|
#[serde(default)]
|
|
pub trimmed_issue_batch_size: Option<u32>,
|
|
#[serde(default)]
|
|
pub share_pressure_basis_points: Option<i64>,
|
|
#[serde(default)]
|
|
pub pressured_support_adjusted_share_price_scalar: Option<i64>,
|
|
#[serde(default)]
|
|
pub pressured_proceeds: Option<i64>,
|
|
#[serde(default)]
|
|
pub book_value_per_share_floor_applied: Option<i64>,
|
|
#[serde(default)]
|
|
pub price_to_book_ratio_basis_points: Option<i64>,
|
|
#[serde(default)]
|
|
pub current_cash: Option<i64>,
|
|
#[serde(default)]
|
|
pub highest_coupon_live_bond_principal: Option<u32>,
|
|
#[serde(default)]
|
|
pub highest_coupon_live_bond_rate_basis_points: Option<i64>,
|
|
#[serde(default)]
|
|
pub current_issue_age_absolute_counter_delta: Option<i64>,
|
|
#[serde(default)]
|
|
pub current_issue_cooldown_floor: Option<i64>,
|
|
#[serde(default)]
|
|
pub minimum_price_to_book_ratio_basis_points: Option<i64>,
|
|
#[serde(default)]
|
|
pub passes_share_price_floor: Option<bool>,
|
|
#[serde(default)]
|
|
pub passes_proceeds_floor: Option<bool>,
|
|
#[serde(default)]
|
|
pub passes_cash_gate: Option<bool>,
|
|
#[serde(default)]
|
|
pub passes_issue_cooldown_gate: Option<bool>,
|
|
#[serde(default)]
|
|
pub passes_coupon_price_to_book_gate: Option<bool>,
|
|
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<bool>,
|
|
#[serde(default)]
|
|
pub bond_issue_and_repayment_allowed: Option<bool>,
|
|
pub linked_transit_latch: bool,
|
|
#[serde(default)]
|
|
pub live_bond_count: Option<u8>,
|
|
#[serde(default)]
|
|
pub live_bond_principal_total: Option<u32>,
|
|
#[serde(default)]
|
|
pub matured_live_bond_count: Option<u32>,
|
|
#[serde(default)]
|
|
pub matured_live_bond_principal_total: Option<u32>,
|
|
#[serde(default)]
|
|
pub next_live_bond_maturity_year: Option<u32>,
|
|
#[serde(default)]
|
|
pub live_bond_coupon_burden_total: Option<i64>,
|
|
#[serde(default)]
|
|
pub current_cash: Option<i64>,
|
|
#[serde(default)]
|
|
pub cash_after_full_repayment: Option<i64>,
|
|
#[serde(default)]
|
|
pub issue_cash_floor: Option<i64>,
|
|
#[serde(default)]
|
|
pub issue_principal_step: Option<u32>,
|
|
#[serde(default)]
|
|
pub proposed_issue_bond_count: Option<u32>,
|
|
#[serde(default)]
|
|
pub proposed_issue_total_principal: Option<u32>,
|
|
#[serde(default)]
|
|
pub proposed_issue_years_to_maturity: Option<u32>,
|
|
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<bool>,
|
|
#[serde(default)]
|
|
pub dividend_adjustment_allowed: Option<bool>,
|
|
#[serde(default)]
|
|
pub years_since_last_dividend: Option<u32>,
|
|
#[serde(default)]
|
|
pub years_since_founding: Option<u32>,
|
|
#[serde(default)]
|
|
pub outstanding_shares: Option<u32>,
|
|
#[serde(default)]
|
|
pub unassigned_share_pool: Option<u32>,
|
|
#[serde(default)]
|
|
pub weighted_recent_net_profit_total: Option<i64>,
|
|
#[serde(default)]
|
|
pub weighted_recent_net_profit_average: Option<i64>,
|
|
#[serde(default)]
|
|
pub current_cash: Option<i64>,
|
|
pub tiny_unassigned_share_cash_supplement_branch: bool,
|
|
#[serde(default)]
|
|
pub tentative_target_dividend_per_share_tenths: Option<i64>,
|
|
#[serde(default)]
|
|
pub current_dividend_per_share_tenths: Option<i64>,
|
|
#[serde(default)]
|
|
pub building_density_growth_setting: Option<u32>,
|
|
#[serde(default)]
|
|
pub growth_adjusted_current_dividend_per_share_tenths: Option<i64>,
|
|
#[serde(default)]
|
|
pub board_approved_dividend_rate_ceiling_tenths: Option<i64>,
|
|
#[serde(default)]
|
|
pub proposed_dividend_per_share_tenths: Option<i64>,
|
|
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<String>,
|
|
#[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<u32>,
|
|
#[serde(default)]
|
|
pub company_holdings: BTreeMap<u32, u32>,
|
|
#[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<u32>,
|
|
#[serde(default)]
|
|
pub locomotive_name: Option<String>,
|
|
#[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<String>,
|
|
#[serde(default)]
|
|
pub demanded_token_stem: Option<String>,
|
|
}
|
|
|
|
#[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<u32> },
|
|
}
|
|
|
|
#[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<u32> },
|
|
}
|
|
|
|
#[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<u32> },
|
|
}
|
|
|
|
#[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<u32> },
|
|
}
|
|
|
|
#[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<u32>,
|
|
#[serde(default)]
|
|
pub multiplier_value_f32_text: Option<String>,
|
|
}
|
|
|
|
#[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<u32>,
|
|
},
|
|
RetireTrains {
|
|
#[serde(default)]
|
|
company_target: Option<RuntimeCompanyTarget>,
|
|
#[serde(default)]
|
|
territory_target: Option<RuntimeTerritoryTarget>,
|
|
#[serde(default)]
|
|
locomotive_name: Option<String>,
|
|
},
|
|
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<RuntimeEventRecordTemplate>,
|
|
},
|
|
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<RuntimeCondition>,
|
|
#[serde(default)]
|
|
pub effects: Vec<RuntimeEffect>,
|
|
}
|
|
|
|
#[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<RuntimeCondition>,
|
|
#[serde(default)]
|
|
pub effects: Vec<RuntimeEffect>,
|
|
}
|
|
|
|
#[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<String>,
|
|
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<u32>,
|
|
#[serde(default)]
|
|
pub decoded_record_count: usize,
|
|
#[serde(default)]
|
|
pub imported_runtime_record_count: usize,
|
|
#[serde(default)]
|
|
pub records: Vec<RuntimePackedEventRecordSummary>,
|
|
}
|
|
|
|
#[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<usize>,
|
|
#[serde(default)]
|
|
pub payload_len: Option<usize>,
|
|
pub decode_status: String,
|
|
#[serde(default)]
|
|
pub payload_family: String,
|
|
#[serde(default)]
|
|
pub trigger_kind: Option<u8>,
|
|
#[serde(default)]
|
|
pub active: Option<bool>,
|
|
#[serde(default)]
|
|
pub marks_collection_dirty: Option<bool>,
|
|
#[serde(default)]
|
|
pub one_shot: Option<bool>,
|
|
#[serde(default)]
|
|
pub compact_control: Option<RuntimePackedEventCompactControlSummary>,
|
|
#[serde(default)]
|
|
pub text_bands: Vec<RuntimePackedEventTextBandSummary>,
|
|
#[serde(default)]
|
|
pub standalone_condition_row_count: usize,
|
|
#[serde(default)]
|
|
pub standalone_condition_rows: Vec<RuntimePackedEventConditionRowSummary>,
|
|
#[serde(default)]
|
|
pub negative_sentinel_scope: Option<RuntimePackedEventNegativeSentinelScopeSummary>,
|
|
#[serde(default)]
|
|
pub grouped_effect_row_counts: Vec<usize>,
|
|
#[serde(default)]
|
|
pub grouped_effect_rows: Vec<RuntimePackedEventGroupedEffectRowSummary>,
|
|
#[serde(default)]
|
|
pub grouped_company_targets: Vec<Option<RuntimeCompanyTarget>>,
|
|
#[serde(default)]
|
|
pub decoded_conditions: Vec<RuntimeCondition>,
|
|
#[serde(default)]
|
|
pub decoded_actions: Vec<RuntimeEffect>,
|
|
#[serde(default)]
|
|
pub executable_import_ready: bool,
|
|
#[serde(default)]
|
|
pub import_outcome: Option<String>,
|
|
#[serde(default)]
|
|
pub notes: Vec<String>,
|
|
}
|
|
|
|
#[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<usize>,
|
|
}
|
|
|
|
#[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<u8>,
|
|
#[serde(default)]
|
|
pub grouped_scope_checkboxes_0x7ff: Vec<u8>,
|
|
pub summary_toggle_0x800: u8,
|
|
#[serde(default)]
|
|
pub grouped_territory_selectors_0x80f: Vec<i32>,
|
|
}
|
|
|
|
#[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<u8>,
|
|
#[serde(default)]
|
|
pub candidate_name: Option<String>,
|
|
#[serde(default)]
|
|
pub comparator: Option<String>,
|
|
#[serde(default)]
|
|
pub metric: Option<String>,
|
|
#[serde(default)]
|
|
pub semantic_family: Option<String>,
|
|
#[serde(default)]
|
|
pub semantic_preview: Option<String>,
|
|
#[serde(default)]
|
|
pub requires_candidate_name_binding: bool,
|
|
#[serde(default)]
|
|
pub recovered_cargo_slot: Option<u32>,
|
|
#[serde(default)]
|
|
pub recovered_cargo_class: Option<String>,
|
|
#[serde(default)]
|
|
pub recovered_cargo_label: Option<String>,
|
|
#[serde(default)]
|
|
pub recovered_cargo_supplied_token_stem: Option<String>,
|
|
#[serde(default)]
|
|
pub recovered_cargo_demanded_token_stem: Option<String>,
|
|
#[serde(default)]
|
|
pub notes: Vec<String>,
|
|
}
|
|
|
|
#[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<String>,
|
|
#[serde(default)]
|
|
pub target_mask_bits: Option<u8>,
|
|
#[serde(default)]
|
|
pub parameter_family: Option<String>,
|
|
#[serde(default)]
|
|
pub grouped_target_subject: Option<String>,
|
|
#[serde(default)]
|
|
pub grouped_target_scope: Option<String>,
|
|
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<String>,
|
|
#[serde(default)]
|
|
pub semantic_preview: Option<String>,
|
|
#[serde(default)]
|
|
pub recovered_cargo_slot: Option<u32>,
|
|
#[serde(default)]
|
|
pub recovered_cargo_class: Option<String>,
|
|
#[serde(default)]
|
|
pub recovered_cargo_label: Option<String>,
|
|
#[serde(default)]
|
|
pub recovered_cargo_supplied_token_stem: Option<String>,
|
|
#[serde(default)]
|
|
pub recovered_cargo_demanded_token_stem: Option<String>,
|
|
#[serde(default)]
|
|
pub recovered_locomotive_id: Option<u32>,
|
|
#[serde(default)]
|
|
pub locomotive_name: Option<String>,
|
|
#[serde(default)]
|
|
pub notes: Vec<String>,
|
|
}
|
|
|
|
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<u8, u64>,
|
|
#[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<i32>,
|
|
#[serde(default)]
|
|
pub company_market_state: BTreeMap<u32, RuntimeCompanyMarketState>,
|
|
#[serde(default)]
|
|
pub annual_finance_last_actions: BTreeMap<u32, RuntimeCompanyAnnualFinancePolicyAction>,
|
|
#[serde(default)]
|
|
pub annual_finance_action_counts: BTreeMap<RuntimeCompanyAnnualFinancePolicyAction, u64>,
|
|
#[serde(default)]
|
|
pub annual_dividend_adjustment_commit_count: u64,
|
|
#[serde(default)]
|
|
pub annual_bond_last_retired_principal_total: u64,
|
|
#[serde(default)]
|
|
pub annual_bond_last_issued_principal_total: u64,
|
|
#[serde(default)]
|
|
pub annual_stock_repurchase_last_share_count: u64,
|
|
#[serde(default)]
|
|
pub annual_stock_issue_last_share_count: u64,
|
|
#[serde(default)]
|
|
pub chairman_issue_opinion_terms_raw_i32: BTreeMap<u32, Vec<i32>>,
|
|
#[serde(default)]
|
|
pub chairman_personality_raw_u8: BTreeMap<u32, u8>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
|
|
pub struct RuntimeSaveProfileState {
|
|
#[serde(default)]
|
|
pub profile_kind: Option<String>,
|
|
#[serde(default)]
|
|
pub profile_family: Option<String>,
|
|
#[serde(default)]
|
|
pub map_path: Option<String>,
|
|
#[serde(default)]
|
|
pub display_name: Option<String>,
|
|
#[serde(default)]
|
|
pub selected_year_profile_lane: Option<u8>,
|
|
#[serde(default)]
|
|
pub sandbox_enabled: Option<bool>,
|
|
#[serde(default)]
|
|
pub campaign_scenario_enabled: Option<bool>,
|
|
#[serde(default)]
|
|
pub staged_profile_copy_on_restore: Option<bool>,
|
|
}
|
|
|
|
#[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<u8>,
|
|
#[serde(default)]
|
|
pub campaign_scenario_enabled: Option<bool>,
|
|
#[serde(default)]
|
|
pub sandbox_enabled: Option<bool>,
|
|
#[serde(default)]
|
|
pub seed_tuple_written_from_raw_lane: Option<bool>,
|
|
#[serde(default)]
|
|
pub absolute_counter_requires_shell_context: Option<bool>,
|
|
#[serde(default)]
|
|
pub absolute_counter_reconstructible_from_save: Option<bool>,
|
|
#[serde(default)]
|
|
pub current_calendar_tuple_word_raw_u32: Option<u32>,
|
|
#[serde(default)]
|
|
pub packed_year_word_raw_u16: Option<u16>,
|
|
#[serde(default)]
|
|
pub partial_year_progress_raw_u8: Option<u8>,
|
|
#[serde(default)]
|
|
pub current_calendar_tuple_word_2_raw_u32: Option<u32>,
|
|
#[serde(default)]
|
|
pub absolute_counter_raw_u32: Option<u32>,
|
|
#[serde(default)]
|
|
pub absolute_counter_mirror_raw_u32: Option<u32>,
|
|
#[serde(default)]
|
|
pub disable_cargo_economy_special_condition_slot: Option<u8>,
|
|
#[serde(default)]
|
|
pub disable_cargo_economy_special_condition_reconstructible_from_save: Option<bool>,
|
|
#[serde(default)]
|
|
pub disable_cargo_economy_special_condition_write_side_grounded: Option<bool>,
|
|
#[serde(default)]
|
|
pub disable_cargo_economy_special_condition_enabled: Option<bool>,
|
|
#[serde(default)]
|
|
pub use_bio_accelerator_cars_enabled: Option<bool>,
|
|
#[serde(default)]
|
|
pub use_wartime_cargos_enabled: Option<bool>,
|
|
#[serde(default)]
|
|
pub disable_train_crashes_enabled: Option<bool>,
|
|
#[serde(default)]
|
|
pub disable_train_crashes_and_breakdowns_enabled: Option<bool>,
|
|
#[serde(default)]
|
|
pub ai_ignore_territories_at_startup_enabled: Option<bool>,
|
|
#[serde(default)]
|
|
pub limited_track_building_amount: Option<i32>,
|
|
#[serde(default)]
|
|
pub economic_status_code: Option<i32>,
|
|
#[serde(default)]
|
|
pub territory_access_cost: Option<u32>,
|
|
#[serde(default)]
|
|
pub issue_37_value: Option<u32>,
|
|
#[serde(default)]
|
|
pub issue_38_value: Option<u32>,
|
|
#[serde(default)]
|
|
pub issue_39_value: Option<u32>,
|
|
#[serde(default)]
|
|
pub issue_3a_value: Option<u32>,
|
|
#[serde(default)]
|
|
pub issue_37_multiplier_raw_u32: Option<u32>,
|
|
#[serde(default)]
|
|
pub issue_37_multiplier_value_f32_text: Option<String>,
|
|
#[serde(default)]
|
|
pub stock_issue_and_buyback_policy_raw_u8: Option<u8>,
|
|
#[serde(default)]
|
|
pub bond_issue_and_repayment_policy_raw_u8: Option<u8>,
|
|
#[serde(default)]
|
|
pub bankruptcy_policy_raw_u8: Option<u8>,
|
|
#[serde(default)]
|
|
pub dividend_policy_raw_u8: Option<u8>,
|
|
#[serde(default)]
|
|
pub building_density_growth_setting_raw_u32: Option<u32>,
|
|
#[serde(default)]
|
|
pub stock_issue_and_buyback_allowed: Option<bool>,
|
|
#[serde(default)]
|
|
pub bond_issue_and_repayment_allowed: Option<bool>,
|
|
#[serde(default)]
|
|
pub bankruptcy_allowed: Option<bool>,
|
|
#[serde(default)]
|
|
pub dividend_adjustment_allowed: Option<bool>,
|
|
#[serde(default)]
|
|
pub finance_neighborhood_candidates: Vec<RuntimeWorldFinanceNeighborhoodCandidate>,
|
|
#[serde(default)]
|
|
pub economic_tuning_mirror_raw_u32: Option<u32>,
|
|
#[serde(default)]
|
|
pub economic_tuning_mirror_value_f32_text: Option<String>,
|
|
#[serde(default)]
|
|
pub economic_tuning_lane_raw_u32: Vec<u32>,
|
|
#[serde(default)]
|
|
pub economic_tuning_lane_value_f32_text: Vec<String>,
|
|
#[serde(default)]
|
|
pub absolute_counter_restore_kind: Option<String>,
|
|
#[serde(default)]
|
|
pub absolute_counter_adjustment_context: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct RuntimeState {
|
|
pub calendar: CalendarPoint,
|
|
#[serde(default)]
|
|
pub world_flags: BTreeMap<String, bool>,
|
|
#[serde(default)]
|
|
pub save_profile: RuntimeSaveProfileState,
|
|
#[serde(default)]
|
|
pub world_restore: RuntimeWorldRestoreState,
|
|
#[serde(default)]
|
|
pub metadata: BTreeMap<String, String>,
|
|
#[serde(default)]
|
|
pub companies: Vec<RuntimeCompany>,
|
|
#[serde(default)]
|
|
pub selected_company_id: Option<u32>,
|
|
#[serde(default)]
|
|
pub players: Vec<RuntimePlayer>,
|
|
#[serde(default)]
|
|
pub selected_player_id: Option<u32>,
|
|
#[serde(default)]
|
|
pub chairman_profiles: Vec<RuntimeChairmanProfile>,
|
|
#[serde(default)]
|
|
pub selected_chairman_profile_id: Option<u32>,
|
|
#[serde(default)]
|
|
pub trains: Vec<RuntimeTrain>,
|
|
#[serde(default)]
|
|
pub locomotive_catalog: Vec<RuntimeLocomotiveCatalogEntry>,
|
|
#[serde(default)]
|
|
pub cargo_catalog: Vec<RuntimeCargoCatalogEntry>,
|
|
#[serde(default)]
|
|
pub territories: Vec<RuntimeTerritory>,
|
|
#[serde(default)]
|
|
pub company_territory_track_piece_counts: Vec<RuntimeCompanyTerritoryTrackPieceCount>,
|
|
#[serde(default)]
|
|
pub company_territory_access: Vec<RuntimeCompanyTerritoryAccess>,
|
|
#[serde(default)]
|
|
pub packed_event_collection: Option<RuntimePackedEventCollectionSummary>,
|
|
#[serde(default)]
|
|
pub event_runtime_records: Vec<RuntimeEventRecord>,
|
|
#[serde(default)]
|
|
pub candidate_availability: BTreeMap<String, u32>,
|
|
#[serde(default)]
|
|
pub named_locomotive_availability: BTreeMap<String, u32>,
|
|
#[serde(default)]
|
|
pub named_locomotive_cost: BTreeMap<String, u32>,
|
|
#[serde(default)]
|
|
pub all_cargo_price_override: Option<u32>,
|
|
#[serde(default)]
|
|
pub named_cargo_price_overrides: BTreeMap<String, u32>,
|
|
#[serde(default)]
|
|
pub all_cargo_production_override: Option<u32>,
|
|
#[serde(default)]
|
|
pub factory_cargo_production_override: Option<u32>,
|
|
#[serde(default)]
|
|
pub farm_mine_cargo_production_override: Option<u32>,
|
|
#[serde(default)]
|
|
pub named_cargo_production_overrides: BTreeMap<String, u32>,
|
|
#[serde(default)]
|
|
pub cargo_production_overrides: BTreeMap<u32, u32>,
|
|
#[serde(default)]
|
|
pub world_runtime_variables: BTreeMap<u32, i64>,
|
|
#[serde(default)]
|
|
pub company_runtime_variables: BTreeMap<u32, BTreeMap<u32, i64>>,
|
|
#[serde(default)]
|
|
pub player_runtime_variables: BTreeMap<u32, BTreeMap<u32, i64>>,
|
|
#[serde(default)]
|
|
pub territory_runtime_variables: BTreeMap<u32, BTreeMap<u32, i64>>,
|
|
#[serde(default)]
|
|
pub world_scalar_overrides: BTreeMap<String, i64>,
|
|
#[serde(default)]
|
|
pub special_conditions: BTreeMap<String, u32>,
|
|
#[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::<usize>()
|
|
{
|
|
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::<BTreeMap<_, _>>();
|
|
|
|
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<i64> {
|
|
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<f64> {
|
|
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<f64> {
|
|
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<f64> {
|
|
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<f64> {
|
|
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<i64> {
|
|
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<f64> {
|
|
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<f64> {
|
|
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<u32> {
|
|
let market_state = state.service_state.company_market_state.get(&company_id)?;
|
|
Some(
|
|
market_state
|
|
.live_bond_slots
|
|
.iter()
|
|
.map(|slot| slot.principal)
|
|
.sum(),
|
|
)
|
|
}
|
|
|
|
fn runtime_company_matured_live_bond_count(
|
|
state: &RuntimeState,
|
|
company_id: u32,
|
|
current_year_word: u32,
|
|
) -> Option<u32> {
|
|
let market_state = state.service_state.company_market_state.get(&company_id)?;
|
|
Some(
|
|
market_state
|
|
.live_bond_slots
|
|
.iter()
|
|
.filter(|slot| slot.maturity_year != 0 && slot.maturity_year <= current_year_word)
|
|
.count() as u32,
|
|
)
|
|
}
|
|
|
|
fn runtime_company_matured_live_bond_principal_total(
|
|
state: &RuntimeState,
|
|
company_id: u32,
|
|
current_year_word: u32,
|
|
) -> Option<u32> {
|
|
let market_state = state.service_state.company_market_state.get(&company_id)?;
|
|
Some(
|
|
market_state
|
|
.live_bond_slots
|
|
.iter()
|
|
.filter(|slot| slot.maturity_year != 0 && slot.maturity_year <= current_year_word)
|
|
.map(|slot| slot.principal)
|
|
.sum(),
|
|
)
|
|
}
|
|
|
|
fn runtime_company_next_live_bond_maturity_year(
|
|
state: &RuntimeState,
|
|
company_id: u32,
|
|
) -> Option<u32> {
|
|
let market_state = state.service_state.company_market_state.get(&company_id)?;
|
|
market_state
|
|
.live_bond_slots
|
|
.iter()
|
|
.filter_map(|slot| (slot.maturity_year != 0).then_some(slot.maturity_year))
|
|
.min()
|
|
}
|
|
|
|
pub(crate) fn runtime_company_support_adjusted_share_price_scalar_with_pressure_f64(
|
|
state: &RuntimeState,
|
|
company_id: u32,
|
|
share_pressure_shares: i64,
|
|
) -> Option<f64> {
|
|
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<f64> {
|
|
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<f64> {
|
|
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<i64> {
|
|
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<i64> {
|
|
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<f64> {
|
|
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<f64> {
|
|
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<f64> {
|
|
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<f64> {
|
|
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<f64> {
|
|
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<u32>, Vec<i64>)> {
|
|
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<f64> {
|
|
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<u32>,
|
|
company_id: Option<u32>,
|
|
territory_id: Option<u32>,
|
|
) -> Option<i64> {
|
|
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<u32>,
|
|
company_id: Option<u32>,
|
|
territory_id: Option<u32>,
|
|
) -> Option<f64> {
|
|
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<RuntimeWorldIssueState> {
|
|
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<f64> {
|
|
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<i64> {
|
|
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<f64> {
|
|
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<i64> {
|
|
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<f64> {
|
|
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_live_bond_coupon_burden_total(
|
|
state: &RuntimeState,
|
|
company_id: u32,
|
|
) -> Option<i64> {
|
|
let market_state = state.service_state.company_market_state.get(&company_id)?;
|
|
let mut total = 0i64;
|
|
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;
|
|
}
|
|
let coupon_burden =
|
|
runtime_round_f64_to_i64((slot.principal as f64) * coupon_rate).unwrap_or(0);
|
|
total = total.checked_add(coupon_burden)?;
|
|
}
|
|
Some(total)
|
|
}
|
|
|
|
pub fn runtime_company_bond_interest_rate_quote_f64(
|
|
state: &RuntimeState,
|
|
company_id: u32,
|
|
_principal: u32,
|
|
_years_to_maturity: u32,
|
|
) -> Option<f64> {
|
|
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<bool> {
|
|
Some(state.world_restore.partial_year_progress_raw_u8? == 0x0c)
|
|
}
|
|
|
|
pub fn runtime_world_bankruptcy_allowed(state: &RuntimeState) -> Option<bool> {
|
|
Some(state.world_restore.bankruptcy_policy_raw_u8? == 0)
|
|
}
|
|
|
|
pub fn runtime_world_bond_issue_and_repayment_allowed(state: &RuntimeState) -> Option<bool> {
|
|
Some(state.world_restore.bond_issue_and_repayment_policy_raw_u8? == 0)
|
|
}
|
|
|
|
pub fn runtime_world_stock_issue_and_buyback_allowed(state: &RuntimeState) -> Option<bool> {
|
|
Some(state.world_restore.stock_issue_and_buyback_policy_raw_u8? == 0)
|
|
}
|
|
|
|
pub fn runtime_world_dividend_adjustment_allowed(state: &RuntimeState) -> Option<bool> {
|
|
Some(state.world_restore.dividend_policy_raw_u8? == 0)
|
|
}
|
|
|
|
pub fn runtime_world_building_density_growth_setting(state: &RuntimeState) -> Option<u32> {
|
|
state.world_restore.building_density_growth_setting_raw_u32
|
|
}
|
|
|
|
fn runtime_chairman_stock_repurchase_factor_f64(
|
|
state: &RuntimeState,
|
|
chairman_profile_id: Option<u32>,
|
|
) -> Option<f64> {
|
|
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<RuntimeCompanyAnnualStockRepurchaseState> {
|
|
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<RuntimeCompanyAnnualBondPolicyState> {
|
|
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_year_word = state
|
|
.world_restore
|
|
.packed_year_word_raw_u16
|
|
.map(u32::from)
|
|
.unwrap_or(state.calendar.year);
|
|
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 matured_live_bond_count =
|
|
runtime_company_matured_live_bond_count(state, company_id, current_year_word);
|
|
let matured_live_bond_principal_total =
|
|
runtime_company_matured_live_bond_principal_total(state, company_id, current_year_word);
|
|
let next_live_bond_maturity_year =
|
|
runtime_company_next_live_bond_maturity_year(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)
|
|
&& (matured_live_bond_principal_total.is_some_and(|principal| principal > 0)
|
|
|| 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,
|
|
matured_live_bond_count,
|
|
matured_live_bond_principal_total,
|
|
next_live_bond_maturity_year,
|
|
live_bond_coupon_burden_total: annual_finance_state.live_bond_coupon_burden_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<f64> {
|
|
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<RuntimeCompanyAnnualDividendPolicyState> {
|
|
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<RuntimeCompanyAnnualFinancePolicyState> {
|
|
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",
|
|
}
|
|
}
|
|
|
|
pub fn runtime_annual_bond_principal_flow_relation_label(
|
|
retired_principal_total: u64,
|
|
issued_principal_total: u64,
|
|
) -> Option<&'static str> {
|
|
match retired_principal_total.cmp(&issued_principal_total) {
|
|
std::cmp::Ordering::Equal => {
|
|
if retired_principal_total == 0 {
|
|
None
|
|
} else {
|
|
Some("retired_equals_issued")
|
|
}
|
|
}
|
|
std::cmp::Ordering::Greater => {
|
|
if issued_principal_total == 0 {
|
|
Some("retired_only")
|
|
} else {
|
|
Some("retired_exceeds_issued")
|
|
}
|
|
}
|
|
std::cmp::Ordering::Less => {
|
|
if retired_principal_total == 0 {
|
|
Some("issued_only")
|
|
} else {
|
|
Some("issued_exceeds_retired")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn runtime_company_stock_issue_price_to_book_ratio_f64(
|
|
pressured_support_adjusted_share_price_scalar: f64,
|
|
book_value_per_share: f64,
|
|
) -> Option<f64> {
|
|
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<f64> {
|
|
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<RuntimeCompanyAnnualStockIssueState> {
|
|
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<RuntimeCompanyAnnualCreditorPressureState> {
|
|
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::<i64>(),
|
|
)
|
|
} 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<RuntimeCompanyAnnualDeepDistressState> {
|
|
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::<Vec<_>>();
|
|
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<u32> {
|
|
state.world_restore.absolute_counter_raw_u32
|
|
}
|
|
|
|
pub fn runtime_world_partial_year_weight_numerator(state: &RuntimeState) -> Option<i64> {
|
|
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<u32> {
|
|
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<u32> {
|
|
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<u32> {
|
|
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::<u32>(),
|
|
)
|
|
}
|
|
|
|
pub fn runtime_company_annual_finance_state(
|
|
state: &RuntimeState,
|
|
company_id: u32,
|
|
) -> Option<RuntimeCompanyAnnualFinanceState> {
|
|
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,
|
|
live_bond_coupon_burden_total: runtime_company_live_bond_coupon_burden_total(
|
|
state, company_id,
|
|
),
|
|
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<i64> {
|
|
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<i64> {
|
|
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<f64> {
|
|
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<i64> {
|
|
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<u32> {
|
|
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<u32, u32>,
|
|
company_share_prices: &BTreeMap<u32, i64>,
|
|
) -> Option<i64> {
|
|
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<u32>,
|
|
valid_player_ids: &BTreeSet<u32>,
|
|
valid_chairman_profile_ids: &BTreeSet<u32>,
|
|
valid_territory_ids: &BTreeSet<u32>,
|
|
) -> 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<u32>,
|
|
valid_player_ids: &BTreeSet<u32>,
|
|
valid_chairman_profile_ids: &BTreeSet<u32>,
|
|
valid_territory_ids: &BTreeSet<u32>,
|
|
) -> 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<u32>,
|
|
valid_player_ids: &BTreeSet<u32>,
|
|
valid_chairman_profile_ids: &BTreeSet<u32>,
|
|
valid_territory_ids: &BTreeSet<u32>,
|
|
) -> 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<u32>,
|
|
) -> 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<u32>,
|
|
) -> 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<u32>,
|
|
) -> 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<u32>,
|
|
) -> 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<u64>, 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<u64>, 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,
|
|
maturity_year: 0,
|
|
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!(
|
|
annual_finance_state.live_bond_coupon_burden_total,
|
|
Some(5_000)
|
|
);
|
|
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,
|
|
maturity_year: 0,
|
|
coupon_rate_raw_u32: 0.04f32.to_bits(),
|
|
},
|
|
RuntimeCompanyBondSlot {
|
|
slot_index: 1,
|
|
principal: 300_000,
|
|
maturity_year: 0,
|
|
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),
|
|
live_bond_coupon_burden_total: Some(0),
|
|
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<u64>, 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<u64>, 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<u64>, 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<u64>, 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<u64>, 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<u64>, 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,
|
|
maturity_year: 0,
|
|
coupon_rate_raw_u32: 0.09f32.to_bits(),
|
|
},
|
|
RuntimeCompanyBondSlot {
|
|
slot_index: 1,
|
|
principal: 150_000,
|
|
maturity_year: 0,
|
|
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.matured_live_bond_count, Some(0));
|
|
assert_eq!(bond_state.matured_live_bond_principal_total, Some(0));
|
|
assert_eq!(bond_state.next_live_bond_maturity_year, None);
|
|
assert_eq!(bond_state.live_bond_coupon_burden_total, Some(30_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 classifies_annual_bond_principal_flow_relation() {
|
|
assert_eq!(
|
|
runtime_annual_bond_principal_flow_relation_label(0, 0),
|
|
None
|
|
);
|
|
assert_eq!(
|
|
runtime_annual_bond_principal_flow_relation_label(350_000, 0),
|
|
Some("retired_only")
|
|
);
|
|
assert_eq!(
|
|
runtime_annual_bond_principal_flow_relation_label(0, 500_000),
|
|
Some("issued_only")
|
|
);
|
|
assert_eq!(
|
|
runtime_annual_bond_principal_flow_relation_label(350_000, 1_000_000),
|
|
Some("issued_exceeds_retired")
|
|
);
|
|
assert_eq!(
|
|
runtime_annual_bond_principal_flow_relation_label(500_000, 500_000),
|
|
Some("retired_equals_issued")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn annual_bond_policy_stays_eligible_for_repayment_without_new_issue() {
|
|
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<u64>, 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, 900_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: 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: 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([(
|
|
12,
|
|
RuntimeCompanyMarketState {
|
|
bond_count: 2,
|
|
live_bond_slots: vec![
|
|
RuntimeCompanyBondSlot {
|
|
slot_index: 0,
|
|
principal: 200_000,
|
|
maturity_year: 1845,
|
|
coupon_rate_raw_u32: 0.09f32.to_bits(),
|
|
},
|
|
RuntimeCompanyBondSlot {
|
|
slot_index: 1,
|
|
principal: 150_000,
|
|
maturity_year: 1847,
|
|
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, 12).expect("bond policy state");
|
|
assert_eq!(bond_state.live_bond_principal_total, Some(350_000));
|
|
assert_eq!(bond_state.cash_after_full_repayment, Some(550_000));
|
|
assert_eq!(bond_state.proposed_issue_bond_count, Some(0));
|
|
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<u64>, 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,
|
|
maturity_year: 0,
|
|
coupon_rate_raw_u32: 0.11f32.to_bits(),
|
|
},
|
|
RuntimeCompanyBondSlot {
|
|
slot_index: 1,
|
|
principal: 200_000,
|
|
maturity_year: 0,
|
|
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<u64>, 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<u64>, 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<u64>, 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<u64>, 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));
|
|
}
|
|
}
|