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

4741 lines
180 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_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>,
}
#[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>,
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>,
pub current_issue_calendar_word: u32,
#[serde(default)]
pub current_issue_calendar_word_2: u32,
pub prior_issue_calendar_word: u32,
#[serde(default)]
pub prior_issue_calendar_word_2: u32,
pub city_connection_latch: bool,
pub linked_transit_latch: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct RuntimeTrackPieceCounts {
#[serde(default)]
pub total: u32,
#[serde(default)]
pub single: u32,
#[serde(default)]
pub double: u32,
#[serde(default)]
pub transition: u32,
#[serde(default)]
pub electric: u32,
#[serde(default)]
pub non_electric: u32,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RuntimeTerritory {
pub territory_id: u32,
#[serde(default)]
pub name: Option<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,
CurrentIssueCalendarWord,
CurrentIssueCalendarWord2,
PriorIssueCalendarWord,
PriorIssueCalendarWord2,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RuntimeChairmanMetric {
CurrentCash,
HoldingsValueTotal,
NetWorthTotal,
PurchasingPowerTotal,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct RuntimeCompanyStatSelector {
pub family_id: u32,
pub slot_id: u32,
}
pub const RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER: u32 = 0x2329;
pub const RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH: u32 = 0x0d;
pub const RUNTIME_COMPANY_STAT_SLOT_BOOK_VALUE_PER_SHARE: u32 = 0x1d;
pub const RUNTIME_WORLD_ISSUE_INVESTOR_CONFIDENCE: u32 = 0x37;
pub const RUNTIME_WORLD_ISSUE_CREDIT_MARKET: u32 = 0x38;
pub const RUNTIME_WORLD_ISSUE_PRIME_RATE: u32 = 0x39;
pub const RUNTIME_WORLD_ISSUE_MANAGEMENT_ATTITUDE: u32 = 0x3a;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RuntimeWorldIssueState {
pub issue_id: u32,
pub raw_value_u32: u32,
#[serde(default)]
pub multiplier_raw_u32: Option<u32>,
#[serde(default)]
pub multiplier_value_f32_text: Option<String>,
}
#[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 trigger_dispatch_counts: BTreeMap<u8, u64>,
#[serde(default)]
pub total_event_record_services: u64,
#[serde(default)]
pub dirty_rerun_count: u64,
#[serde(default)]
pub company_market_state: BTreeMap<u32, RuntimeCompanyMarketState>,
}
#[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 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 (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> {
let company = state
.companies
.iter()
.find(|company| company.company_id == company_id)?;
match (selector.family_id, selector.slot_id) {
(RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER, RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH) => {
Some(company.current_cash)
}
(
RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER,
RUNTIME_COMPANY_STAT_SLOT_BOOK_VALUE_PER_SHARE,
) => Some(company.book_value_per_share),
_ => None,
}
}
pub fn runtime_world_issue_state(
state: &RuntimeState,
issue_id: u32,
) -> Option<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_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_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);
Some(RuntimeCompanyAnnualFinanceState {
company_id,
outstanding_shares: market_state.outstanding_shares,
bond_count: market_state.bond_count,
largest_live_bond_principal: market_state.largest_live_bond_principal,
highest_coupon_live_bond_principal: market_state.highest_coupon_live_bond_principal,
assigned_share_pool,
unassigned_share_pool,
cached_share_price: rounded_cached_share_price_i64(market_state.cached_share_price_raw_u32),
chairman_salary_baseline: market_state.chairman_salary_baseline,
chairman_salary_current: market_state.chairman_salary_current,
chairman_bonus_year: market_state.chairman_bonus_year,
chairman_bonus_amount: market_state.chairman_bonus_amount,
founding_year: market_state.founding_year,
last_bankruptcy_year: market_state.last_bankruptcy_year,
last_dividend_year: market_state.last_dividend_year,
years_since_founding,
years_since_last_bankruptcy,
years_since_last_dividend,
current_partial_year_weight_numerator: runtime_world_partial_year_weight_numerator(state),
current_issue_calendar_word: market_state.current_issue_calendar_word,
current_issue_calendar_word_2: market_state.current_issue_calendar_word_2,
prior_issue_calendar_word: market_state.prior_issue_calendar_word,
prior_issue_calendar_word_2: market_state.prior_issue_calendar_word_2,
city_connection_latch: market_state.city_connection_latch,
linked_transit_latch: market_state.linked_transit_latch,
})
}
pub fn runtime_company_market_value(
state: &RuntimeState,
company_id: u32,
metric: RuntimeCompanyMarketMetric,
) -> Option<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::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 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::default(),
metadata: BTreeMap::new(),
companies: vec![
RuntimeCompany {
company_id: 1,
current_cash: 100,
debt: 0,
credit_rating_score: None,
prime_rate: None,
track_piece_counts: RuntimeTrackPieceCounts::default(),
active: true,
available_track_laying_capacity: None,
linked_chairman_profile_id: None,
book_value_per_share: 0,
investor_confidence: 0,
management_attitude: 0,
takeover_cooldown_year: None,
merger_cooldown_year: None,
controller_kind: RuntimeCompanyControllerKind::Unknown,
},
RuntimeCompany {
company_id: 1,
current_cash: 200,
debt: 0,
credit_rating_score: None,
prime_rate: None,
track_piece_counts: RuntimeTrackPieceCounts::default(),
active: true,
available_track_laying_capacity: None,
linked_chairman_profile_id: None,
book_value_per_share: 0,
investor_confidence: 0,
management_attitude: 0,
takeover_cooldown_year: None,
merger_cooldown_year: None,
controller_kind: RuntimeCompanyControllerKind::Unknown,
},
],
selected_company_id: None,
players: Vec::new(),
selected_player_id: None,
chairman_profiles: Vec::new(),
selected_chairman_profile_id: None,
trains: Vec::new(),
locomotive_catalog: Vec::new(),
cargo_catalog: Vec::new(),
territories: Vec::new(),
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
packed_event_collection: None,
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(),
all_cargo_price_override: None,
named_cargo_price_overrides: BTreeMap::new(),
all_cargo_production_override: None,
factory_cargo_production_override: None,
farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(),
world_runtime_variables: BTreeMap::new(),
company_runtime_variables: BTreeMap::new(),
player_runtime_variables: BTreeMap::new(),
territory_runtime_variables: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
};
assert!(state.validate().is_err());
}
#[test]
fn rejects_partial_world_restore_without_year_lane() {
let state = RuntimeState {
calendar: CalendarPoint {
year: 1830,
month_slot: 0,
phase_slot: 0,
tick_slot: 0,
},
world_flags: BTreeMap::new(),
save_profile: RuntimeSaveProfileState::default(),
world_restore: RuntimeWorldRestoreState {
selected_year_profile_lane: None,
campaign_scenario_enabled: Some(false),
sandbox_enabled: Some(true),
seed_tuple_written_from_raw_lane: Some(true),
absolute_counter_requires_shell_context: Some(true),
absolute_counter_reconstructible_from_save: Some(false),
packed_year_word_raw_u16: None,
partial_year_progress_raw_u8: None,
current_calendar_tuple_word_raw_u32: None,
current_calendar_tuple_word_2_raw_u32: None,
absolute_counter_raw_u32: None,
absolute_counter_mirror_raw_u32: None,
disable_cargo_economy_special_condition_slot: Some(30),
disable_cargo_economy_special_condition_reconstructible_from_save: Some(true),
disable_cargo_economy_special_condition_write_side_grounded: Some(true),
disable_cargo_economy_special_condition_enabled: Some(false),
use_bio_accelerator_cars_enabled: Some(false),
use_wartime_cargos_enabled: Some(false),
disable_train_crashes_enabled: Some(false),
disable_train_crashes_and_breakdowns_enabled: Some(false),
ai_ignore_territories_at_startup_enabled: Some(false),
limited_track_building_amount: None,
economic_status_code: None,
territory_access_cost: None,
issue_37_value: None,
issue_38_value: None,
issue_39_value: None,
issue_3a_value: None,
issue_37_multiplier_raw_u32: None,
issue_37_multiplier_value_f32_text: None,
finance_neighborhood_candidates: Vec::new(),
economic_tuning_mirror_raw_u32: None,
economic_tuning_mirror_value_f32_text: None,
economic_tuning_lane_raw_u32: Vec::new(),
economic_tuning_lane_value_f32_text: Vec::new(),
absolute_counter_restore_kind: Some(
"mode-adjusted-selected-year-lane".to_string(),
),
absolute_counter_adjustment_context: Some(
"editor-map-mode,shell-selected-year-adjust-policy-0x9d26-0x9d28,save-special-condition-disable-cargo-economy-slot-30".to_string(),
),
},
metadata: BTreeMap::new(),
companies: Vec::new(),
selected_company_id: None,
players: Vec::new(),
selected_player_id: None,
chairman_profiles: Vec::new(),
selected_chairman_profile_id: None,
trains: Vec::new(),
locomotive_catalog: Vec::new(),
cargo_catalog: Vec::new(),
territories: Vec::new(),
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
packed_event_collection: None,
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(),
all_cargo_price_override: None,
named_cargo_price_overrides: BTreeMap::new(),
all_cargo_production_override: None,
factory_cargo_production_override: None,
farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(),
world_runtime_variables: BTreeMap::new(),
company_runtime_variables: BTreeMap::new(),
player_runtime_variables: BTreeMap::new(),
territory_runtime_variables: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
};
assert!(state.validate().is_err());
}
#[test]
fn rejects_event_effect_targeting_unknown_company() {
let state = RuntimeState {
calendar: CalendarPoint {
year: 1830,
month_slot: 0,
phase_slot: 0,
tick_slot: 0,
},
world_flags: BTreeMap::new(),
save_profile: RuntimeSaveProfileState::default(),
world_restore: RuntimeWorldRestoreState::default(),
metadata: BTreeMap::new(),
companies: vec![RuntimeCompany {
company_id: 1,
current_cash: 100,
debt: 0,
credit_rating_score: None,
prime_rate: None,
track_piece_counts: RuntimeTrackPieceCounts::default(),
active: true,
available_track_laying_capacity: None,
linked_chairman_profile_id: None,
book_value_per_share: 0,
investor_confidence: 0,
management_attitude: 0,
takeover_cooldown_year: None,
merger_cooldown_year: None,
controller_kind: RuntimeCompanyControllerKind::Unknown,
}],
selected_company_id: None,
players: Vec::new(),
selected_player_id: None,
chairman_profiles: Vec::new(),
selected_chairman_profile_id: None,
trains: Vec::new(),
locomotive_catalog: Vec::new(),
cargo_catalog: Vec::new(),
territories: Vec::new(),
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
packed_event_collection: None,
event_runtime_records: vec![RuntimeEventRecord {
record_id: 7,
trigger_kind: 1,
active: true,
service_count: 0,
marks_collection_dirty: false,
one_shot: false,
has_fired: false,
conditions: Vec::new(),
effects: vec![RuntimeEffect::AdjustCompanyCash {
target: RuntimeCompanyTarget::Ids { ids: vec![2] },
delta: 50,
}],
}],
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(),
all_cargo_price_override: None,
named_cargo_price_overrides: BTreeMap::new(),
all_cargo_production_override: None,
factory_cargo_production_override: None,
farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(),
world_runtime_variables: BTreeMap::new(),
company_runtime_variables: BTreeMap::new(),
player_runtime_variables: BTreeMap::new(),
territory_runtime_variables: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
};
assert!(state.validate().is_err());
}
#[test]
fn rejects_template_effect_targeting_unknown_company() {
let state = RuntimeState {
calendar: CalendarPoint {
year: 1830,
month_slot: 0,
phase_slot: 0,
tick_slot: 0,
},
world_flags: BTreeMap::new(),
save_profile: RuntimeSaveProfileState::default(),
world_restore: RuntimeWorldRestoreState::default(),
metadata: BTreeMap::new(),
companies: vec![RuntimeCompany {
company_id: 1,
current_cash: 100,
debt: 0,
credit_rating_score: None,
prime_rate: None,
track_piece_counts: RuntimeTrackPieceCounts::default(),
active: true,
available_track_laying_capacity: None,
linked_chairman_profile_id: None,
book_value_per_share: 0,
investor_confidence: 0,
management_attitude: 0,
takeover_cooldown_year: None,
merger_cooldown_year: None,
controller_kind: RuntimeCompanyControllerKind::Unknown,
}],
selected_company_id: None,
players: Vec::new(),
selected_player_id: None,
chairman_profiles: Vec::new(),
selected_chairman_profile_id: None,
trains: Vec::new(),
locomotive_catalog: Vec::new(),
cargo_catalog: Vec::new(),
territories: Vec::new(),
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
packed_event_collection: None,
event_runtime_records: vec![RuntimeEventRecord {
record_id: 7,
trigger_kind: 1,
active: true,
service_count: 0,
marks_collection_dirty: false,
one_shot: false,
has_fired: false,
conditions: Vec::new(),
effects: vec![RuntimeEffect::AppendEventRecord {
record: Box::new(RuntimeEventRecordTemplate {
record_id: 8,
trigger_kind: 0x0a,
active: true,
marks_collection_dirty: false,
one_shot: false,
conditions: Vec::new(),
effects: vec![RuntimeEffect::AdjustCompanyCash {
target: RuntimeCompanyTarget::Ids { ids: vec![2] },
delta: 50,
}],
}),
}],
}],
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(),
all_cargo_price_override: None,
named_cargo_price_overrides: BTreeMap::new(),
all_cargo_production_override: None,
factory_cargo_production_override: None,
farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(),
world_runtime_variables: BTreeMap::new(),
company_runtime_variables: BTreeMap::new(),
player_runtime_variables: BTreeMap::new(),
territory_runtime_variables: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
};
assert!(state.validate().is_err());
}
#[test]
fn rejects_invalid_packed_event_collection_summary() {
let state = RuntimeState {
calendar: CalendarPoint {
year: 1830,
month_slot: 0,
phase_slot: 0,
tick_slot: 0,
},
world_flags: BTreeMap::new(),
save_profile: RuntimeSaveProfileState::default(),
world_restore: RuntimeWorldRestoreState::default(),
metadata: BTreeMap::new(),
companies: Vec::new(),
selected_company_id: None,
players: Vec::new(),
selected_player_id: None,
chairman_profiles: Vec::new(),
selected_chairman_profile_id: None,
trains: Vec::new(),
locomotive_catalog: Vec::new(),
cargo_catalog: Vec::new(),
territories: Vec::new(),
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
packed_event_collection: Some(RuntimePackedEventCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
mechanism_family: "classic-save-rehydrate-v1".to_string(),
mechanism_confidence: "grounded".to_string(),
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
packed_state_version: 0x3e9,
packed_state_version_hex: "0x000003e9".to_string(),
live_id_bound: 4,
live_record_count: 2,
live_entry_ids: vec![3, 3],
decoded_record_count: 0,
imported_runtime_record_count: 0,
records: vec![
RuntimePackedEventRecordSummary {
record_index: 0,
live_entry_id: 3,
payload_offset: None,
payload_len: None,
decode_status: "unsupported_framing".to_string(),
payload_family: "unsupported_framing".to_string(),
trigger_kind: None,
active: None,
marks_collection_dirty: None,
one_shot: None,
compact_control: None,
text_bands: Vec::new(),
standalone_condition_row_count: 0,
standalone_condition_rows: Vec::new(),
negative_sentinel_scope: None,
grouped_effect_row_counts: vec![0, 0, 0, 0],
grouped_effect_rows: Vec::new(),
grouped_company_targets: Vec::new(),
decoded_conditions: Vec::new(),
decoded_actions: Vec::new(),
executable_import_ready: false,
import_outcome: None,
notes: vec!["test".to_string()],
},
RuntimePackedEventRecordSummary {
record_index: 1,
live_entry_id: 3,
payload_offset: None,
payload_len: None,
decode_status: "unsupported_framing".to_string(),
payload_family: "unsupported_framing".to_string(),
trigger_kind: None,
active: None,
marks_collection_dirty: None,
one_shot: None,
compact_control: None,
text_bands: Vec::new(),
standalone_condition_row_count: 0,
standalone_condition_rows: Vec::new(),
negative_sentinel_scope: None,
grouped_effect_row_counts: vec![0, 0, 0, 0],
grouped_effect_rows: Vec::new(),
grouped_company_targets: Vec::new(),
decoded_conditions: Vec::new(),
decoded_actions: Vec::new(),
executable_import_ready: false,
import_outcome: None,
notes: vec!["test".to_string()],
},
],
}),
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(),
all_cargo_price_override: None,
named_cargo_price_overrides: BTreeMap::new(),
all_cargo_production_override: None,
factory_cargo_production_override: None,
farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(),
world_runtime_variables: BTreeMap::new(),
company_runtime_variables: BTreeMap::new(),
player_runtime_variables: BTreeMap::new(),
territory_runtime_variables: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
};
assert!(state.validate().is_err());
}
#[test]
fn rejects_selected_company_id_that_does_not_exist() {
let state = RuntimeState {
calendar: CalendarPoint {
year: 1830,
month_slot: 0,
phase_slot: 0,
tick_slot: 0,
},
world_flags: BTreeMap::new(),
save_profile: RuntimeSaveProfileState::default(),
world_restore: RuntimeWorldRestoreState::default(),
metadata: BTreeMap::new(),
companies: vec![RuntimeCompany {
company_id: 1,
current_cash: 100,
debt: 0,
credit_rating_score: None,
prime_rate: None,
track_piece_counts: RuntimeTrackPieceCounts::default(),
active: true,
available_track_laying_capacity: None,
linked_chairman_profile_id: None,
book_value_per_share: 0,
investor_confidence: 0,
management_attitude: 0,
takeover_cooldown_year: None,
merger_cooldown_year: None,
controller_kind: RuntimeCompanyControllerKind::Human,
}],
selected_company_id: Some(2),
players: Vec::new(),
selected_player_id: None,
chairman_profiles: Vec::new(),
selected_chairman_profile_id: None,
trains: Vec::new(),
locomotive_catalog: Vec::new(),
cargo_catalog: Vec::new(),
territories: Vec::new(),
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
packed_event_collection: None,
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(),
all_cargo_price_override: None,
named_cargo_price_overrides: BTreeMap::new(),
all_cargo_production_override: None,
factory_cargo_production_override: None,
farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(),
world_runtime_variables: BTreeMap::new(),
company_runtime_variables: BTreeMap::new(),
player_runtime_variables: BTreeMap::new(),
territory_runtime_variables: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
};
assert!(state.validate().is_err());
}
#[test]
fn rejects_selected_company_id_that_is_inactive() {
let state = RuntimeState {
calendar: CalendarPoint {
year: 1830,
month_slot: 0,
phase_slot: 0,
tick_slot: 0,
},
world_flags: BTreeMap::new(),
save_profile: RuntimeSaveProfileState::default(),
world_restore: RuntimeWorldRestoreState::default(),
metadata: BTreeMap::new(),
companies: vec![RuntimeCompany {
company_id: 1,
current_cash: 100,
debt: 0,
credit_rating_score: None,
prime_rate: None,
track_piece_counts: RuntimeTrackPieceCounts::default(),
active: false,
available_track_laying_capacity: None,
linked_chairman_profile_id: None,
book_value_per_share: 0,
investor_confidence: 0,
management_attitude: 0,
takeover_cooldown_year: None,
merger_cooldown_year: None,
controller_kind: RuntimeCompanyControllerKind::Human,
}],
selected_company_id: Some(1),
players: Vec::new(),
selected_player_id: None,
chairman_profiles: Vec::new(),
selected_chairman_profile_id: None,
trains: Vec::new(),
locomotive_catalog: Vec::new(),
cargo_catalog: Vec::new(),
territories: Vec::new(),
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
packed_event_collection: None,
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(),
all_cargo_price_override: None,
named_cargo_price_overrides: BTreeMap::new(),
all_cargo_production_override: None,
factory_cargo_production_override: None,
farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(),
world_runtime_variables: BTreeMap::new(),
company_runtime_variables: BTreeMap::new(),
player_runtime_variables: BTreeMap::new(),
territory_runtime_variables: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
};
assert!(state.validate().is_err());
}
#[test]
fn rejects_duplicate_train_ids() {
let state = RuntimeState {
calendar: CalendarPoint {
year: 1830,
month_slot: 0,
phase_slot: 0,
tick_slot: 0,
},
world_flags: BTreeMap::new(),
save_profile: RuntimeSaveProfileState::default(),
world_restore: RuntimeWorldRestoreState::default(),
metadata: BTreeMap::new(),
companies: vec![RuntimeCompany {
company_id: 1,
current_cash: 100,
debt: 0,
credit_rating_score: None,
prime_rate: None,
track_piece_counts: RuntimeTrackPieceCounts::default(),
active: true,
available_track_laying_capacity: None,
linked_chairman_profile_id: None,
book_value_per_share: 0,
investor_confidence: 0,
management_attitude: 0,
takeover_cooldown_year: None,
merger_cooldown_year: None,
controller_kind: RuntimeCompanyControllerKind::Human,
}],
selected_company_id: None,
players: Vec::new(),
selected_player_id: None,
chairman_profiles: Vec::new(),
selected_chairman_profile_id: None,
trains: vec![
RuntimeTrain {
train_id: 7,
owner_company_id: 1,
territory_id: None,
locomotive_name: Some("Mikado".to_string()),
active: true,
retired: false,
},
RuntimeTrain {
train_id: 7,
owner_company_id: 1,
territory_id: None,
locomotive_name: Some("Orca".to_string()),
active: true,
retired: false,
},
],
locomotive_catalog: Vec::new(),
cargo_catalog: Vec::new(),
territories: Vec::new(),
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
packed_event_collection: None,
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(),
all_cargo_price_override: None,
named_cargo_price_overrides: BTreeMap::new(),
all_cargo_production_override: None,
factory_cargo_production_override: None,
farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(),
world_runtime_variables: BTreeMap::new(),
company_runtime_variables: BTreeMap::new(),
player_runtime_variables: BTreeMap::new(),
territory_runtime_variables: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
};
assert!(state.validate().is_err());
}
#[test]
fn rejects_train_with_unknown_owner_company() {
let state = RuntimeState {
calendar: CalendarPoint {
year: 1830,
month_slot: 0,
phase_slot: 0,
tick_slot: 0,
},
world_flags: BTreeMap::new(),
save_profile: RuntimeSaveProfileState::default(),
world_restore: RuntimeWorldRestoreState::default(),
metadata: BTreeMap::new(),
companies: vec![RuntimeCompany {
company_id: 1,
current_cash: 100,
debt: 0,
credit_rating_score: None,
prime_rate: None,
track_piece_counts: RuntimeTrackPieceCounts::default(),
active: true,
available_track_laying_capacity: None,
linked_chairman_profile_id: None,
book_value_per_share: 0,
investor_confidence: 0,
management_attitude: 0,
takeover_cooldown_year: None,
merger_cooldown_year: None,
controller_kind: RuntimeCompanyControllerKind::Human,
}],
selected_company_id: None,
players: Vec::new(),
selected_player_id: None,
chairman_profiles: Vec::new(),
selected_chairman_profile_id: None,
trains: vec![RuntimeTrain {
train_id: 7,
owner_company_id: 2,
territory_id: None,
locomotive_name: Some("Mikado".to_string()),
active: true,
retired: false,
}],
locomotive_catalog: Vec::new(),
cargo_catalog: Vec::new(),
territories: Vec::new(),
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
packed_event_collection: None,
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(),
all_cargo_price_override: None,
named_cargo_price_overrides: BTreeMap::new(),
all_cargo_production_override: None,
factory_cargo_production_override: None,
farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(),
world_runtime_variables: BTreeMap::new(),
company_runtime_variables: BTreeMap::new(),
player_runtime_variables: BTreeMap::new(),
territory_runtime_variables: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
};
assert!(state.validate().is_err());
}
#[test]
fn rejects_train_with_unknown_territory() {
let state = RuntimeState {
calendar: CalendarPoint {
year: 1830,
month_slot: 0,
phase_slot: 0,
tick_slot: 0,
},
world_flags: BTreeMap::new(),
save_profile: RuntimeSaveProfileState::default(),
world_restore: RuntimeWorldRestoreState::default(),
metadata: BTreeMap::new(),
companies: vec![RuntimeCompany {
company_id: 1,
current_cash: 100,
debt: 0,
credit_rating_score: None,
prime_rate: None,
track_piece_counts: RuntimeTrackPieceCounts::default(),
active: true,
available_track_laying_capacity: None,
linked_chairman_profile_id: None,
book_value_per_share: 0,
investor_confidence: 0,
management_attitude: 0,
takeover_cooldown_year: None,
merger_cooldown_year: None,
controller_kind: RuntimeCompanyControllerKind::Human,
}],
selected_company_id: None,
players: Vec::new(),
selected_player_id: None,
chairman_profiles: Vec::new(),
selected_chairman_profile_id: None,
trains: vec![RuntimeTrain {
train_id: 7,
owner_company_id: 1,
territory_id: Some(9),
locomotive_name: Some("Mikado".to_string()),
active: true,
retired: false,
}],
locomotive_catalog: Vec::new(),
cargo_catalog: Vec::new(),
territories: vec![RuntimeTerritory {
territory_id: 1,
name: Some("Appalachia".to_string()),
track_piece_counts: RuntimeTrackPieceCounts::default(),
}],
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
packed_event_collection: None,
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(),
all_cargo_price_override: None,
named_cargo_price_overrides: BTreeMap::new(),
all_cargo_production_override: None,
factory_cargo_production_override: None,
farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(),
world_runtime_variables: BTreeMap::new(),
company_runtime_variables: BTreeMap::new(),
player_runtime_variables: BTreeMap::new(),
territory_runtime_variables: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
};
assert!(state.validate().is_err());
}
#[test]
fn rejects_train_marked_active_and_retired() {
let state = RuntimeState {
calendar: CalendarPoint {
year: 1830,
month_slot: 0,
phase_slot: 0,
tick_slot: 0,
},
world_flags: BTreeMap::new(),
save_profile: RuntimeSaveProfileState::default(),
world_restore: RuntimeWorldRestoreState::default(),
metadata: BTreeMap::new(),
companies: vec![RuntimeCompany {
company_id: 1,
current_cash: 100,
debt: 0,
credit_rating_score: None,
prime_rate: None,
track_piece_counts: RuntimeTrackPieceCounts::default(),
active: true,
available_track_laying_capacity: None,
linked_chairman_profile_id: None,
book_value_per_share: 0,
investor_confidence: 0,
management_attitude: 0,
takeover_cooldown_year: None,
merger_cooldown_year: None,
controller_kind: RuntimeCompanyControllerKind::Human,
}],
selected_company_id: None,
players: Vec::new(),
selected_player_id: None,
chairman_profiles: Vec::new(),
selected_chairman_profile_id: None,
trains: vec![RuntimeTrain {
train_id: 7,
owner_company_id: 1,
territory_id: None,
locomotive_name: Some("Mikado".to_string()),
active: true,
retired: true,
}],
locomotive_catalog: Vec::new(),
cargo_catalog: Vec::new(),
territories: Vec::new(),
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
packed_event_collection: None,
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(),
all_cargo_price_override: None,
named_cargo_price_overrides: BTreeMap::new(),
all_cargo_production_override: None,
factory_cargo_production_override: None,
farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(),
world_runtime_variables: BTreeMap::new(),
company_runtime_variables: BTreeMap::new(),
player_runtime_variables: BTreeMap::new(),
territory_runtime_variables: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
};
assert!(state.validate().is_err());
}
#[test]
fn rejects_duplicate_company_territory_access_pairs() {
let state = RuntimeState {
calendar: CalendarPoint {
year: 1830,
month_slot: 0,
phase_slot: 0,
tick_slot: 0,
},
world_flags: BTreeMap::new(),
save_profile: RuntimeSaveProfileState::default(),
world_restore: RuntimeWorldRestoreState::default(),
metadata: BTreeMap::new(),
companies: vec![RuntimeCompany {
company_id: 1,
current_cash: 100,
debt: 0,
credit_rating_score: None,
prime_rate: None,
track_piece_counts: RuntimeTrackPieceCounts::default(),
active: true,
available_track_laying_capacity: None,
linked_chairman_profile_id: None,
book_value_per_share: 0,
investor_confidence: 0,
management_attitude: 0,
takeover_cooldown_year: None,
merger_cooldown_year: None,
controller_kind: RuntimeCompanyControllerKind::Human,
}],
selected_company_id: None,
players: Vec::new(),
selected_player_id: None,
chairman_profiles: Vec::new(),
selected_chairman_profile_id: None,
trains: Vec::new(),
locomotive_catalog: Vec::new(),
cargo_catalog: Vec::new(),
territories: vec![RuntimeTerritory {
territory_id: 7,
name: Some("Appalachia".to_string()),
track_piece_counts: RuntimeTrackPieceCounts::default(),
}],
company_territory_track_piece_counts: Vec::new(),
company_territory_access: vec![
RuntimeCompanyTerritoryAccess {
company_id: 1,
territory_id: 7,
},
RuntimeCompanyTerritoryAccess {
company_id: 1,
territory_id: 7,
},
],
packed_event_collection: None,
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(),
all_cargo_price_override: None,
named_cargo_price_overrides: BTreeMap::new(),
all_cargo_production_override: None,
factory_cargo_production_override: None,
farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(),
world_runtime_variables: BTreeMap::new(),
company_runtime_variables: BTreeMap::new(),
player_runtime_variables: BTreeMap::new(),
territory_runtime_variables: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
};
assert!(state.validate().is_err());
}
#[test]
fn rejects_company_territory_access_with_unknown_company() {
let state = RuntimeState {
calendar: CalendarPoint {
year: 1830,
month_slot: 0,
phase_slot: 0,
tick_slot: 0,
},
world_flags: BTreeMap::new(),
save_profile: RuntimeSaveProfileState::default(),
world_restore: RuntimeWorldRestoreState::default(),
metadata: BTreeMap::new(),
companies: vec![RuntimeCompany {
company_id: 1,
current_cash: 100,
debt: 0,
credit_rating_score: None,
prime_rate: None,
track_piece_counts: RuntimeTrackPieceCounts::default(),
active: true,
available_track_laying_capacity: None,
linked_chairman_profile_id: None,
book_value_per_share: 0,
investor_confidence: 0,
management_attitude: 0,
takeover_cooldown_year: None,
merger_cooldown_year: None,
controller_kind: RuntimeCompanyControllerKind::Human,
}],
selected_company_id: None,
players: Vec::new(),
selected_player_id: None,
chairman_profiles: Vec::new(),
selected_chairman_profile_id: None,
trains: Vec::new(),
locomotive_catalog: Vec::new(),
cargo_catalog: Vec::new(),
territories: vec![RuntimeTerritory {
territory_id: 7,
name: Some("Appalachia".to_string()),
track_piece_counts: RuntimeTrackPieceCounts::default(),
}],
company_territory_track_piece_counts: Vec::new(),
company_territory_access: vec![RuntimeCompanyTerritoryAccess {
company_id: 2,
territory_id: 7,
}],
packed_event_collection: None,
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(),
all_cargo_price_override: None,
named_cargo_price_overrides: BTreeMap::new(),
all_cargo_production_override: None,
factory_cargo_production_override: None,
farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(),
world_runtime_variables: BTreeMap::new(),
company_runtime_variables: BTreeMap::new(),
player_runtime_variables: BTreeMap::new(),
territory_runtime_variables: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
};
assert!(state.validate().is_err());
}
#[test]
fn rejects_company_territory_access_with_unknown_territory() {
let state = RuntimeState {
calendar: CalendarPoint {
year: 1830,
month_slot: 0,
phase_slot: 0,
tick_slot: 0,
},
world_flags: BTreeMap::new(),
save_profile: RuntimeSaveProfileState::default(),
world_restore: RuntimeWorldRestoreState::default(),
metadata: BTreeMap::new(),
companies: vec![RuntimeCompany {
company_id: 1,
current_cash: 100,
debt: 0,
credit_rating_score: None,
prime_rate: None,
track_piece_counts: RuntimeTrackPieceCounts::default(),
active: true,
available_track_laying_capacity: None,
linked_chairman_profile_id: None,
book_value_per_share: 0,
investor_confidence: 0,
management_attitude: 0,
takeover_cooldown_year: None,
merger_cooldown_year: None,
controller_kind: RuntimeCompanyControllerKind::Human,
}],
selected_company_id: None,
players: Vec::new(),
selected_player_id: None,
chairman_profiles: Vec::new(),
selected_chairman_profile_id: None,
trains: Vec::new(),
locomotive_catalog: Vec::new(),
cargo_catalog: Vec::new(),
territories: vec![RuntimeTerritory {
territory_id: 7,
name: Some("Appalachia".to_string()),
track_piece_counts: RuntimeTrackPieceCounts::default(),
}],
company_territory_track_piece_counts: Vec::new(),
company_territory_access: vec![RuntimeCompanyTerritoryAccess {
company_id: 1,
territory_id: 8,
}],
packed_event_collection: None,
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(),
all_cargo_price_override: None,
named_cargo_price_overrides: BTreeMap::new(),
all_cargo_production_override: None,
factory_cargo_production_override: None,
farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(),
world_runtime_variables: BTreeMap::new(),
company_runtime_variables: BTreeMap::new(),
player_runtime_variables: BTreeMap::new(),
territory_runtime_variables: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
};
assert!(state.validate().is_err());
}
#[test]
fn rejects_company_with_unknown_linked_chairman_profile() {
let state = RuntimeState {
calendar: CalendarPoint {
year: 1830,
month_slot: 0,
phase_slot: 0,
tick_slot: 0,
},
world_flags: BTreeMap::new(),
save_profile: RuntimeSaveProfileState::default(),
world_restore: RuntimeWorldRestoreState::default(),
metadata: BTreeMap::new(),
companies: vec![RuntimeCompany {
company_id: 1,
current_cash: 100,
debt: 0,
credit_rating_score: None,
prime_rate: None,
track_piece_counts: RuntimeTrackPieceCounts::default(),
active: true,
available_track_laying_capacity: None,
linked_chairman_profile_id: Some(9),
book_value_per_share: 0,
investor_confidence: 0,
management_attitude: 0,
takeover_cooldown_year: None,
merger_cooldown_year: None,
controller_kind: RuntimeCompanyControllerKind::Human,
}],
selected_company_id: None,
players: Vec::new(),
selected_player_id: None,
chairman_profiles: Vec::new(),
selected_chairman_profile_id: None,
trains: Vec::new(),
locomotive_catalog: Vec::new(),
cargo_catalog: Vec::new(),
territories: Vec::new(),
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
packed_event_collection: None,
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(),
all_cargo_price_override: None,
named_cargo_price_overrides: BTreeMap::new(),
all_cargo_production_override: None,
factory_cargo_production_override: None,
farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(),
world_runtime_variables: BTreeMap::new(),
company_runtime_variables: BTreeMap::new(),
player_runtime_variables: BTreeMap::new(),
territory_runtime_variables: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
};
assert!(state.validate().is_err());
}
#[test]
fn rejects_mismatched_company_chairman_back_links() {
let state = RuntimeState {
calendar: CalendarPoint {
year: 1830,
month_slot: 0,
phase_slot: 0,
tick_slot: 0,
},
world_flags: BTreeMap::new(),
save_profile: RuntimeSaveProfileState::default(),
world_restore: RuntimeWorldRestoreState::default(),
metadata: BTreeMap::new(),
companies: vec![RuntimeCompany {
company_id: 1,
current_cash: 100,
debt: 0,
credit_rating_score: None,
prime_rate: None,
track_piece_counts: RuntimeTrackPieceCounts::default(),
active: true,
available_track_laying_capacity: None,
linked_chairman_profile_id: Some(1),
book_value_per_share: 0,
investor_confidence: 0,
management_attitude: 0,
takeover_cooldown_year: None,
merger_cooldown_year: None,
controller_kind: RuntimeCompanyControllerKind::Human,
}],
selected_company_id: None,
players: Vec::new(),
selected_player_id: None,
chairman_profiles: vec![RuntimeChairmanProfile {
profile_id: 1,
name: "Chairman One".to_string(),
active: true,
current_cash: 0,
linked_company_id: None,
company_holdings: BTreeMap::new(),
holdings_value_total: 0,
net_worth_total: 0,
purchasing_power_total: 0,
}],
selected_chairman_profile_id: None,
trains: Vec::new(),
locomotive_catalog: Vec::new(),
cargo_catalog: Vec::new(),
territories: Vec::new(),
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
packed_event_collection: None,
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(),
all_cargo_price_override: None,
named_cargo_price_overrides: BTreeMap::new(),
all_cargo_production_override: None,
factory_cargo_production_override: None,
farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(),
world_runtime_variables: BTreeMap::new(),
company_runtime_variables: BTreeMap::new(),
player_runtime_variables: BTreeMap::new(),
territory_runtime_variables: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
};
assert!(state.validate().is_err());
}
#[test]
fn refreshes_chairman_totals_from_company_market_state() {
let mut state = RuntimeState {
calendar: CalendarPoint {
year: 1830,
month_slot: 0,
phase_slot: 0,
tick_slot: 0,
},
world_flags: BTreeMap::new(),
save_profile: RuntimeSaveProfileState::default(),
world_restore: RuntimeWorldRestoreState::default(),
metadata: BTreeMap::new(),
companies: vec![
RuntimeCompany {
company_id: 1,
current_cash: 0,
debt: 0,
credit_rating_score: None,
prime_rate: None,
track_piece_counts: RuntimeTrackPieceCounts::default(),
active: true,
available_track_laying_capacity: None,
linked_chairman_profile_id: None,
book_value_per_share: 0,
investor_confidence: 0,
management_attitude: 0,
takeover_cooldown_year: None,
merger_cooldown_year: None,
controller_kind: RuntimeCompanyControllerKind::Human,
},
RuntimeCompany {
company_id: 2,
current_cash: 0,
debt: 0,
credit_rating_score: None,
prime_rate: None,
track_piece_counts: RuntimeTrackPieceCounts::default(),
active: true,
available_track_laying_capacity: None,
linked_chairman_profile_id: None,
book_value_per_share: 0,
investor_confidence: 0,
management_attitude: 0,
takeover_cooldown_year: None,
merger_cooldown_year: None,
controller_kind: RuntimeCompanyControllerKind::Human,
},
],
selected_company_id: None,
players: Vec::new(),
selected_player_id: None,
chairman_profiles: vec![RuntimeChairmanProfile {
profile_id: 1,
name: "Chairman One".to_string(),
active: true,
current_cash: 100,
linked_company_id: Some(1),
company_holdings: BTreeMap::from([(1, 2), (2, 3)]),
holdings_value_total: 0,
net_worth_total: 0,
purchasing_power_total: 400,
}],
selected_chairman_profile_id: None,
trains: Vec::new(),
locomotive_catalog: Vec::new(),
cargo_catalog: Vec::new(),
territories: Vec::new(),
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
packed_event_collection: None,
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(),
all_cargo_price_override: None,
named_cargo_price_overrides: BTreeMap::new(),
all_cargo_production_override: None,
factory_cargo_production_override: None,
farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(),
world_runtime_variables: BTreeMap::new(),
company_runtime_variables: BTreeMap::new(),
player_runtime_variables: BTreeMap::new(),
territory_runtime_variables: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState {
company_market_state: BTreeMap::from([
(
1,
RuntimeCompanyMarketState {
cached_share_price_raw_u32: 0x41200000,
..RuntimeCompanyMarketState::default()
},
),
(
2,
RuntimeCompanyMarketState {
cached_share_price_raw_u32: 0x41a00000,
..RuntimeCompanyMarketState::default()
},
),
]),
..RuntimeServiceState::default()
},
};
state.refresh_derived_market_state();
assert_eq!(state.chairman_profiles[0].holdings_value_total, 80);
assert_eq!(state.chairman_profiles[0].net_worth_total, 180);
assert_eq!(state.chairman_profiles[0].purchasing_power_total, 400);
}
#[test]
fn refreshes_chairman_purchasing_power_when_cash_changes() {
let mut state = RuntimeState {
calendar: CalendarPoint {
year: 1830,
month_slot: 0,
phase_slot: 0,
tick_slot: 0,
},
world_flags: BTreeMap::new(),
save_profile: RuntimeSaveProfileState::default(),
world_restore: RuntimeWorldRestoreState::default(),
metadata: BTreeMap::new(),
companies: vec![RuntimeCompany {
company_id: 1,
current_cash: 0,
debt: 0,
credit_rating_score: None,
prime_rate: None,
track_piece_counts: RuntimeTrackPieceCounts::default(),
active: true,
available_track_laying_capacity: None,
linked_chairman_profile_id: None,
book_value_per_share: 0,
investor_confidence: 0,
management_attitude: 0,
takeover_cooldown_year: None,
merger_cooldown_year: None,
controller_kind: RuntimeCompanyControllerKind::Human,
}],
selected_company_id: None,
players: Vec::new(),
selected_player_id: None,
chairman_profiles: vec![RuntimeChairmanProfile {
profile_id: 1,
name: "Chairman One".to_string(),
active: true,
current_cash: 50,
linked_company_id: Some(1),
company_holdings: BTreeMap::from([(1, 2)]),
holdings_value_total: 20,
net_worth_total: 70,
purchasing_power_total: 130,
}],
selected_chairman_profile_id: None,
trains: Vec::new(),
locomotive_catalog: Vec::new(),
cargo_catalog: Vec::new(),
territories: Vec::new(),
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
packed_event_collection: None,
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(),
all_cargo_price_override: None,
named_cargo_price_overrides: BTreeMap::new(),
all_cargo_production_override: None,
factory_cargo_production_override: None,
farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(),
world_runtime_variables: BTreeMap::new(),
company_runtime_variables: BTreeMap::new(),
player_runtime_variables: BTreeMap::new(),
territory_runtime_variables: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState {
company_market_state: BTreeMap::from([(
1,
RuntimeCompanyMarketState {
cached_share_price_raw_u32: 0x41200000,
..RuntimeCompanyMarketState::default()
},
)]),
..RuntimeServiceState::default()
},
};
state.chairman_profiles[0].current_cash = 80;
state.refresh_derived_market_state();
assert_eq!(state.chairman_profiles[0].holdings_value_total, 20);
assert_eq!(state.chairman_profiles[0].net_worth_total, 100);
assert_eq!(state.chairman_profiles[0].purchasing_power_total, 130);
}
#[test]
fn reads_grounded_company_stat_family_slots_from_runtime_state() {
let state = RuntimeState {
calendar: CalendarPoint {
year: 1830,
month_slot: 0,
phase_slot: 0,
tick_slot: 0,
},
world_flags: BTreeMap::new(),
save_profile: RuntimeSaveProfileState::default(),
world_restore: RuntimeWorldRestoreState::default(),
metadata: BTreeMap::new(),
companies: vec![RuntimeCompany {
company_id: 7,
current_cash: 125_000,
debt: 0,
credit_rating_score: None,
prime_rate: None,
active: true,
available_track_laying_capacity: None,
controller_kind: RuntimeCompanyControllerKind::Unknown,
linked_chairman_profile_id: None,
book_value_per_share: 2_620,
investor_confidence: 0,
management_attitude: 0,
takeover_cooldown_year: None,
merger_cooldown_year: None,
track_piece_counts: RuntimeTrackPieceCounts::default(),
}],
selected_company_id: None,
players: Vec::new(),
selected_player_id: None,
chairman_profiles: Vec::new(),
selected_chairman_profile_id: None,
trains: Vec::new(),
locomotive_catalog: Vec::new(),
cargo_catalog: Vec::new(),
territories: Vec::new(),
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
packed_event_collection: None,
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(),
all_cargo_price_override: None,
named_cargo_price_overrides: BTreeMap::new(),
all_cargo_production_override: None,
factory_cargo_production_override: None,
farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(),
world_runtime_variables: BTreeMap::new(),
company_runtime_variables: BTreeMap::new(),
player_runtime_variables: BTreeMap::new(),
territory_runtime_variables: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
};
assert_eq!(
runtime_company_stat_value(
&state,
7,
RuntimeCompanyStatSelector {
family_id: RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER,
slot_id: RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH,
},
),
Some(125_000)
);
assert_eq!(
runtime_company_stat_value(
&state,
7,
RuntimeCompanyStatSelector {
family_id: RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER,
slot_id: RUNTIME_COMPANY_STAT_SLOT_BOOK_VALUE_PER_SHARE,
},
),
Some(2_620)
);
assert_eq!(
runtime_company_stat_value(
&state,
7,
RuntimeCompanyStatSelector {
family_id: RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER,
slot_id: 0x2b,
},
),
None
);
assert_eq!(
runtime_company_stat_value(
&state,
99,
RuntimeCompanyStatSelector {
family_id: RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER,
slot_id: RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH,
},
),
None
);
}
#[test]
fn reads_grounded_world_issue_state_from_runtime_restore_state() {
let state = RuntimeState {
calendar: CalendarPoint {
year: 1830,
month_slot: 0,
phase_slot: 0,
tick_slot: 0,
},
world_flags: BTreeMap::new(),
save_profile: RuntimeSaveProfileState::default(),
world_restore: RuntimeWorldRestoreState {
issue_37_value: Some(3),
issue_38_value: Some(1),
issue_39_value: Some(2),
issue_3a_value: Some(4),
issue_37_multiplier_raw_u32: Some(0x3d75c28f),
issue_37_multiplier_value_f32_text: Some("0.060000".to_string()),
..RuntimeWorldRestoreState::default()
},
metadata: BTreeMap::new(),
companies: Vec::new(),
selected_company_id: None,
players: Vec::new(),
selected_player_id: None,
chairman_profiles: Vec::new(),
selected_chairman_profile_id: None,
trains: Vec::new(),
locomotive_catalog: Vec::new(),
cargo_catalog: Vec::new(),
territories: Vec::new(),
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
packed_event_collection: None,
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(),
all_cargo_price_override: None,
named_cargo_price_overrides: BTreeMap::new(),
all_cargo_production_override: None,
factory_cargo_production_override: None,
farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(),
world_runtime_variables: BTreeMap::new(),
company_runtime_variables: BTreeMap::new(),
player_runtime_variables: BTreeMap::new(),
territory_runtime_variables: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
};
let issue = runtime_world_issue_state(&state, RUNTIME_WORLD_ISSUE_INVESTOR_CONFIDENCE)
.expect("grounded issue 0x37 state");
assert_eq!(issue.issue_id, RUNTIME_WORLD_ISSUE_INVESTOR_CONFIDENCE);
assert_eq!(issue.raw_value_u32, 3);
assert_eq!(issue.multiplier_raw_u32, Some(0x3d75c28f));
assert_eq!(issue.multiplier_value_f32_text.as_deref(), Some("0.060000"));
assert_eq!(
runtime_world_issue_state(&state, RUNTIME_WORLD_ISSUE_CREDIT_MARKET)
.expect("grounded issue 0x38 state")
.raw_value_u32,
1
);
assert_eq!(
runtime_world_issue_state(&state, RUNTIME_WORLD_ISSUE_PRIME_RATE)
.expect("grounded issue 0x39 state")
.raw_value_u32,
2
);
assert_eq!(
runtime_world_issue_state(&state, RUNTIME_WORLD_ISSUE_MANAGEMENT_ATTITUDE)
.expect("grounded issue 0x3a state")
.raw_value_u32,
4
);
assert_eq!(runtime_world_issue_state(&state, 0x40), None);
assert_eq!(runtime_world_absolute_counter(&state), None);
}
#[test]
fn reads_grounded_world_absolute_counter_from_runtime_restore_state() {
let state = RuntimeState {
calendar: CalendarPoint {
year: 1830,
month_slot: 0,
phase_slot: 0,
tick_slot: 0,
},
world_flags: BTreeMap::new(),
save_profile: RuntimeSaveProfileState::default(),
world_restore: RuntimeWorldRestoreState {
absolute_counter_raw_u32: Some(5),
absolute_counter_mirror_raw_u32: Some(5),
packed_year_word_raw_u16: Some(0x0210),
partial_year_progress_raw_u8: Some(8),
current_calendar_tuple_word_raw_u32: Some(0x0108_0210),
current_calendar_tuple_word_2_raw_u32: Some(0x35e6_3160),
..RuntimeWorldRestoreState::default()
},
metadata: BTreeMap::new(),
companies: Vec::new(),
selected_company_id: None,
players: Vec::new(),
selected_player_id: None,
chairman_profiles: Vec::new(),
selected_chairman_profile_id: None,
trains: Vec::new(),
locomotive_catalog: Vec::new(),
cargo_catalog: Vec::new(),
territories: Vec::new(),
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
packed_event_collection: None,
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(),
all_cargo_price_override: None,
named_cargo_price_overrides: BTreeMap::new(),
all_cargo_production_override: None,
factory_cargo_production_override: None,
farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(),
world_runtime_variables: BTreeMap::new(),
company_runtime_variables: BTreeMap::new(),
player_runtime_variables: BTreeMap::new(),
territory_runtime_variables: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
};
assert_eq!(runtime_world_absolute_counter(&state), Some(5));
assert_eq!(runtime_world_partial_year_weight_numerator(&state), Some(35));
}
#[test]
fn derives_company_unassigned_share_pool_from_market_state_and_holdings() {
let state = RuntimeState {
calendar: CalendarPoint {
year: 1830,
month_slot: 0,
phase_slot: 0,
tick_slot: 0,
},
world_flags: BTreeMap::new(),
save_profile: RuntimeSaveProfileState::default(),
world_restore: RuntimeWorldRestoreState::default(),
metadata: BTreeMap::new(),
companies: vec![RuntimeCompany {
company_id: 4,
current_cash: 0,
debt: 0,
credit_rating_score: None,
prime_rate: None,
active: true,
available_track_laying_capacity: None,
controller_kind: RuntimeCompanyControllerKind::Unknown,
linked_chairman_profile_id: None,
book_value_per_share: 0,
investor_confidence: 0,
management_attitude: 0,
takeover_cooldown_year: None,
merger_cooldown_year: None,
track_piece_counts: RuntimeTrackPieceCounts::default(),
}],
selected_company_id: None,
players: Vec::new(),
selected_player_id: None,
chairman_profiles: vec![
RuntimeChairmanProfile {
profile_id: 1,
name: "Chairman One".to_string(),
active: true,
current_cash: 0,
linked_company_id: Some(4),
company_holdings: BTreeMap::from([(4, 8_000)]),
holdings_value_total: 0,
net_worth_total: 0,
purchasing_power_total: 0,
},
RuntimeChairmanProfile {
profile_id: 2,
name: "Chairman Two".to_string(),
active: true,
current_cash: 0,
linked_company_id: None,
company_holdings: BTreeMap::from([(4, 7_500)]),
holdings_value_total: 0,
net_worth_total: 0,
purchasing_power_total: 0,
},
],
selected_chairman_profile_id: None,
trains: Vec::new(),
locomotive_catalog: Vec::new(),
cargo_catalog: Vec::new(),
territories: Vec::new(),
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
packed_event_collection: None,
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(),
all_cargo_price_override: None,
named_cargo_price_overrides: BTreeMap::new(),
all_cargo_production_override: None,
factory_cargo_production_override: None,
farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(),
world_runtime_variables: BTreeMap::new(),
company_runtime_variables: BTreeMap::new(),
player_runtime_variables: BTreeMap::new(),
territory_runtime_variables: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState {
company_market_state: BTreeMap::from([(
4,
RuntimeCompanyMarketState {
outstanding_shares: 20_000,
..RuntimeCompanyMarketState::default()
},
)]),
..RuntimeServiceState::default()
},
};
assert_eq!(
runtime_company_unassigned_share_pool(&state, 4),
Some(4_500)
);
assert_eq!(runtime_company_unassigned_share_pool(&state, 99), None);
}
#[test]
fn derives_company_annual_finance_state_from_owned_runtime_market_state() {
let state = RuntimeState {
calendar: CalendarPoint {
year: 1830,
month_slot: 0,
phase_slot: 0,
tick_slot: 0,
},
world_flags: BTreeMap::new(),
save_profile: RuntimeSaveProfileState::default(),
world_restore: RuntimeWorldRestoreState::default(),
metadata: BTreeMap::new(),
companies: vec![RuntimeCompany {
company_id: 4,
current_cash: 0,
debt: 0,
credit_rating_score: None,
prime_rate: None,
active: true,
available_track_laying_capacity: None,
controller_kind: RuntimeCompanyControllerKind::Unknown,
linked_chairman_profile_id: None,
book_value_per_share: 0,
investor_confidence: 0,
management_attitude: 0,
takeover_cooldown_year: None,
merger_cooldown_year: None,
track_piece_counts: RuntimeTrackPieceCounts::default(),
}],
selected_company_id: None,
players: Vec::new(),
selected_player_id: None,
chairman_profiles: vec![
RuntimeChairmanProfile {
profile_id: 1,
name: "Chairman One".to_string(),
active: true,
current_cash: 0,
linked_company_id: Some(4),
company_holdings: BTreeMap::from([(4, 8_000)]),
holdings_value_total: 0,
net_worth_total: 0,
purchasing_power_total: 0,
},
RuntimeChairmanProfile {
profile_id: 2,
name: "Chairman Two".to_string(),
active: true,
current_cash: 0,
linked_company_id: None,
company_holdings: BTreeMap::from([(4, 7_500)]),
holdings_value_total: 0,
net_worth_total: 0,
purchasing_power_total: 0,
},
],
selected_chairman_profile_id: None,
trains: Vec::new(),
locomotive_catalog: Vec::new(),
cargo_catalog: Vec::new(),
territories: Vec::new(),
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
packed_event_collection: None,
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(),
all_cargo_price_override: None,
named_cargo_price_overrides: BTreeMap::new(),
all_cargo_production_override: None,
factory_cargo_production_override: None,
farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(),
world_runtime_variables: BTreeMap::new(),
company_runtime_variables: BTreeMap::new(),
player_runtime_variables: BTreeMap::new(),
territory_runtime_variables: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState {
company_market_state: BTreeMap::from([(
4,
RuntimeCompanyMarketState {
outstanding_shares: 20_000,
bond_count: 3,
largest_live_bond_principal: Some(650_000),
highest_coupon_live_bond_principal: Some(500_000),
cached_share_price_raw_u32: 0x42200000,
chairman_salary_baseline: 24,
chairman_salary_current: 30,
chairman_bonus_year: 1842,
chairman_bonus_amount: 750,
founding_year: 1831,
last_bankruptcy_year: 0,
last_dividend_year: 1841,
current_issue_calendar_word: 5,
current_issue_calendar_word_2: 6,
prior_issue_calendar_word: 4,
prior_issue_calendar_word_2: 5,
city_connection_latch: true,
linked_transit_latch: false,
..RuntimeCompanyMarketState::default()
},
)]),
..RuntimeServiceState::default()
},
};
assert_eq!(runtime_company_assigned_share_pool(&state, 4), Some(15_500));
assert_eq!(
runtime_company_annual_finance_state(&state, 4),
Some(RuntimeCompanyAnnualFinanceState {
company_id: 4,
outstanding_shares: 20_000,
bond_count: 3,
largest_live_bond_principal: Some(650_000),
highest_coupon_live_bond_principal: Some(500_000),
assigned_share_pool: 15_500,
unassigned_share_pool: 4_500,
cached_share_price: Some(40),
chairman_salary_baseline: 24,
chairman_salary_current: 30,
chairman_bonus_year: 1842,
chairman_bonus_amount: 750,
founding_year: 1831,
last_bankruptcy_year: 0,
last_dividend_year: 1841,
years_since_founding: None,
years_since_last_bankruptcy: None,
years_since_last_dividend: None,
current_partial_year_weight_numerator: None,
current_issue_calendar_word: 5,
current_issue_calendar_word_2: 6,
prior_issue_calendar_word: 4,
prior_issue_calendar_word_2: 5,
city_connection_latch: true,
linked_transit_latch: false,
})
);
assert_eq!(runtime_company_assigned_share_pool(&state, 99), None);
assert_eq!(runtime_company_annual_finance_state(&state, 99), None);
}
#[test]
fn reads_company_market_metrics_from_annual_finance_reader() {
let state = RuntimeState {
calendar: CalendarPoint {
year: 1830,
month_slot: 0,
phase_slot: 0,
tick_slot: 0,
},
world_flags: BTreeMap::new(),
save_profile: RuntimeSaveProfileState::default(),
world_restore: RuntimeWorldRestoreState::default(),
metadata: BTreeMap::new(),
companies: vec![RuntimeCompany {
company_id: 7,
current_cash: 0,
debt: 0,
credit_rating_score: None,
prime_rate: None,
active: true,
available_track_laying_capacity: None,
controller_kind: RuntimeCompanyControllerKind::Unknown,
linked_chairman_profile_id: None,
book_value_per_share: 0,
investor_confidence: 0,
management_attitude: 0,
takeover_cooldown_year: None,
merger_cooldown_year: None,
track_piece_counts: RuntimeTrackPieceCounts::default(),
}],
selected_company_id: None,
players: Vec::new(),
selected_player_id: None,
chairman_profiles: vec![RuntimeChairmanProfile {
profile_id: 1,
name: "Chairman One".to_string(),
active: true,
current_cash: 0,
linked_company_id: Some(7),
company_holdings: BTreeMap::from([(7, 12_000)]),
holdings_value_total: 0,
net_worth_total: 0,
purchasing_power_total: 0,
}],
selected_chairman_profile_id: None,
trains: Vec::new(),
locomotive_catalog: Vec::new(),
cargo_catalog: Vec::new(),
territories: Vec::new(),
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
packed_event_collection: None,
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(),
all_cargo_price_override: None,
named_cargo_price_overrides: BTreeMap::new(),
all_cargo_production_override: None,
factory_cargo_production_override: None,
farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(),
world_runtime_variables: BTreeMap::new(),
company_runtime_variables: BTreeMap::new(),
player_runtime_variables: BTreeMap::new(),
territory_runtime_variables: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState {
company_market_state: BTreeMap::from([(
7,
RuntimeCompanyMarketState {
outstanding_shares: 20_000,
bond_count: 2,
largest_live_bond_principal: Some(500_000),
highest_coupon_live_bond_principal: Some(300_000),
cached_share_price_raw_u32: 0x42200000,
chairman_salary_baseline: 18,
chairman_salary_current: 27,
chairman_bonus_year: 1843,
chairman_bonus_amount: 625,
founding_year: 1832,
last_bankruptcy_year: 0,
last_dividend_year: 1842,
current_issue_calendar_word: 9,
current_issue_calendar_word_2: 10,
prior_issue_calendar_word: 8,
prior_issue_calendar_word_2: 9,
..RuntimeCompanyMarketState::default()
},
)]),
..RuntimeServiceState::default()
},
};
assert_eq!(
runtime_company_market_value(&state, 7, RuntimeCompanyMarketMetric::OutstandingShares),
Some(20_000)
);
assert_eq!(
runtime_company_market_value(&state, 7, RuntimeCompanyMarketMetric::BondCount),
Some(2)
);
assert_eq!(
runtime_company_market_value(
&state,
7,
RuntimeCompanyMarketMetric::LargestLiveBondPrincipal
),
Some(500_000)
);
assert_eq!(
runtime_company_market_value(
&state,
7,
RuntimeCompanyMarketMetric::HighestCouponLiveBondPrincipal
),
Some(300_000)
);
assert_eq!(
runtime_company_market_value(&state, 7, RuntimeCompanyMarketMetric::AssignedSharePool),
Some(12_000)
);
assert_eq!(
runtime_company_market_value(
&state,
7,
RuntimeCompanyMarketMetric::UnassignedSharePool
),
Some(8_000)
);
assert_eq!(
runtime_company_market_value(&state, 7, RuntimeCompanyMarketMetric::CachedSharePrice),
Some(40)
);
assert_eq!(
runtime_company_market_value(
&state,
7,
RuntimeCompanyMarketMetric::CurrentIssueCalendarWord
),
Some(9)
);
assert_eq!(
runtime_company_market_value(
&state,
7,
RuntimeCompanyMarketMetric::CurrentIssueCalendarWord2
),
Some(10)
);
assert_eq!(
runtime_company_market_value(
&state,
7,
RuntimeCompanyMarketMetric::PriorIssueCalendarWord
),
Some(8)
);
assert_eq!(
runtime_company_market_value(
&state,
7,
RuntimeCompanyMarketMetric::PriorIssueCalendarWord2
),
Some(9)
);
assert_eq!(
runtime_company_market_value(
&state,
7,
RuntimeCompanyMarketMetric::ChairmanSalaryCurrent
),
Some(27)
);
assert_eq!(
runtime_company_market_value(
&state,
7,
RuntimeCompanyMarketMetric::ChairmanBonusAmount
),
Some(625)
);
assert_eq!(
runtime_company_market_value(&state, 99, RuntimeCompanyMarketMetric::OutstandingShares),
None
);
}
#[test]
fn derives_elapsed_company_finance_years_from_calendar_and_saved_market_state() {
let state = RuntimeState {
calendar: CalendarPoint {
year: 1844,
month_slot: 0,
phase_slot: 0,
tick_slot: 0,
},
world_flags: BTreeMap::new(),
save_profile: RuntimeSaveProfileState::default(),
world_restore: RuntimeWorldRestoreState::default(),
metadata: BTreeMap::new(),
companies: vec![RuntimeCompany {
company_id: 3,
current_cash: 0,
debt: 0,
credit_rating_score: None,
prime_rate: None,
active: true,
available_track_laying_capacity: None,
controller_kind: RuntimeCompanyControllerKind::Unknown,
linked_chairman_profile_id: None,
book_value_per_share: 0,
investor_confidence: 0,
management_attitude: 0,
takeover_cooldown_year: None,
merger_cooldown_year: None,
track_piece_counts: RuntimeTrackPieceCounts::default(),
}],
selected_company_id: None,
players: Vec::new(),
selected_player_id: None,
chairman_profiles: Vec::new(),
selected_chairman_profile_id: None,
trains: Vec::new(),
locomotive_catalog: Vec::new(),
cargo_catalog: Vec::new(),
territories: Vec::new(),
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
packed_event_collection: None,
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(),
all_cargo_price_override: None,
named_cargo_price_overrides: BTreeMap::new(),
all_cargo_production_override: None,
factory_cargo_production_override: None,
farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(),
world_runtime_variables: BTreeMap::new(),
company_runtime_variables: BTreeMap::new(),
player_runtime_variables: BTreeMap::new(),
territory_runtime_variables: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState {
company_market_state: BTreeMap::from([(
3,
RuntimeCompanyMarketState {
outstanding_shares: 10_000,
founding_year: 1838,
last_bankruptcy_year: 1841,
last_dividend_year: 1843,
..RuntimeCompanyMarketState::default()
},
)]),
..RuntimeServiceState::default()
},
};
let finance_state =
runtime_company_annual_finance_state(&state, 3).expect("finance state should derive");
assert_eq!(finance_state.years_since_founding, Some(6));
assert_eq!(finance_state.years_since_last_bankruptcy, Some(3));
assert_eq!(finance_state.years_since_last_dividend, Some(1));
}
}