Carry trailing finance lanes into annual state

This commit is contained in:
Jan Petykiewicz 2026-04-17 22:40:06 -07:00
commit dbca9a2e78

View file

@ -135,6 +135,14 @@ pub struct RuntimeCompanyAnnualFinanceState {
#[serde(default)]
pub current_partial_year_weight_numerator: Option<i64>,
#[serde(default)]
pub trailing_full_year_year_words: Vec<u32>,
#[serde(default)]
pub trailing_full_year_net_profits: Vec<i64>,
#[serde(default)]
pub trailing_full_year_revenues: Vec<i64>,
#[serde(default)]
pub trailing_full_year_fuel_costs: Vec<i64>,
#[serde(default)]
pub current_issue_absolute_counter: Option<u32>,
#[serde(default)]
pub prior_issue_absolute_counter: Option<u32>,
@ -2054,6 +2062,29 @@ fn runtime_divide_by_rounded_stat_i64(numerator: f64, denominator: i64) -> Optio
Some(numerator / denominator as f64)
}
fn runtime_company_trailing_full_year_stat_series(
state: &RuntimeState,
company_id: u32,
slot_id: u32,
full_year_count: u32,
) -> Option<(Vec<u32>, Vec<i64>)> {
let current_year_word = u32::from(state.world_restore.packed_year_word_raw_u16?);
let mut year_words = Vec::with_capacity(full_year_count as usize);
let mut values = Vec::with_capacity(full_year_count as usize);
for year_offset in 1..=full_year_count {
let family_id = current_year_word.checked_sub(year_offset)?;
let value = runtime_company_stat_value_f64(
state,
company_id,
RuntimeCompanyStatSelector { family_id, slot_id },
)
.and_then(runtime_round_f64_to_i64)?;
year_words.push(family_id);
values.push(value);
}
Some((year_words, values))
}
pub fn runtime_world_issue_state(
state: &RuntimeState,
issue_id: u32,
@ -2196,6 +2227,15 @@ pub fn runtime_company_annual_finance_state(
}
_ => None,
};
let (trailing_full_year_year_words, trailing_full_year_net_profits) =
runtime_company_trailing_full_year_stat_series(state, company_id, 0x2b, 4)
.unwrap_or_default();
let (_, trailing_full_year_revenues) =
runtime_company_trailing_full_year_stat_series(state, company_id, 0x2c, 4)
.unwrap_or_default();
let (_, trailing_full_year_fuel_costs) =
runtime_company_trailing_full_year_stat_series(state, company_id, 0x09, 4)
.unwrap_or_default();
Some(RuntimeCompanyAnnualFinanceState {
company_id,
outstanding_shares: market_state.outstanding_shares,
@ -2216,6 +2256,10 @@ pub fn runtime_company_annual_finance_state(
years_since_last_bankruptcy,
years_since_last_dividend,
current_partial_year_weight_numerator: runtime_world_partial_year_weight_numerator(state),
trailing_full_year_year_words,
trailing_full_year_net_profits,
trailing_full_year_revenues,
trailing_full_year_fuel_costs,
current_issue_absolute_counter,
prior_issue_absolute_counter,
current_issue_age_absolute_counter_delta,
@ -4485,6 +4529,122 @@ mod tests {
);
}
#[test]
fn carries_trailing_full_year_finance_lanes_into_annual_finance_state() {
let mut year_stat_family_qword_bits =
vec![0u64; (RUNTIME_COMPANY_STAT_SLOT_COUNT * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) as usize];
let write_year_value = |bits: &mut Vec<u64>, slot_id: u32, year_delta: u32, value: f64| {
let index = (slot_id * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN + year_delta) as usize;
bits[index] = value.to_bits();
};
for (year_delta, revenue_parts, extra_profit_parts, fuel_cost) in [
(1, [60.0, 50.0, 40.0, 30.0], [10.0, 9.0, 8.0, 7.0, 6.0, 5.0, 4.0], 18.0),
(2, [50.0, 45.0, 40.0, 35.0], [9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0], 17.0),
(3, [50.0, 40.0, 35.0, 35.0], [8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0], 16.0),
(4, [45.0, 40.0, 35.0, 30.0], [7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0], 15.0),
] {
for (slot_id, value) in [0x01, 0x02, 0x03, 0x04]
.into_iter()
.zip(revenue_parts)
{
write_year_value(&mut year_stat_family_qword_bits, slot_id, year_delta, value);
}
for (slot_id, value) in [0x05, 0x06, 0x07, 0x08, 0x0a, 0x0b, 0x0c]
.into_iter()
.zip(extra_profit_parts)
{
write_year_value(&mut year_stat_family_qword_bits, slot_id, year_delta, value);
}
write_year_value(&mut year_stat_family_qword_bits, 0x09, year_delta, fuel_cost);
}
let state = RuntimeState {
calendar: CalendarPoint {
year: 1845,
month_slot: 0,
phase_slot: 0,
tick_slot: 0,
},
world_flags: BTreeMap::new(),
save_profile: RuntimeSaveProfileState::default(),
world_restore: RuntimeWorldRestoreState {
packed_year_word_raw_u16: Some(1845),
..RuntimeWorldRestoreState::default()
},
metadata: BTreeMap::new(),
companies: vec![RuntimeCompany {
company_id: 7,
current_cash: 125_000,
debt: 0,
credit_rating_score: None,
prime_rate: None,
active: true,
available_track_laying_capacity: None,
controller_kind: RuntimeCompanyControllerKind::Unknown,
linked_chairman_profile_id: None,
book_value_per_share: 2_620,
investor_confidence: 0,
management_attitude: 0,
takeover_cooldown_year: None,
merger_cooldown_year: None,
track_piece_counts: RuntimeTrackPieceCounts::default(),
}],
selected_company_id: None,
players: Vec::new(),
selected_player_id: None,
chairman_profiles: Vec::new(),
selected_chairman_profile_id: None,
trains: Vec::new(),
locomotive_catalog: Vec::new(),
cargo_catalog: Vec::new(),
territories: Vec::new(),
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
packed_event_collection: None,
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(),
all_cargo_price_override: None,
named_cargo_price_overrides: BTreeMap::new(),
all_cargo_production_override: None,
factory_cargo_production_override: None,
farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(),
world_runtime_variables: BTreeMap::new(),
company_runtime_variables: BTreeMap::new(),
player_runtime_variables: BTreeMap::new(),
territory_runtime_variables: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState {
company_market_state: BTreeMap::from([(
7,
RuntimeCompanyMarketState {
outstanding_shares: 20_000,
year_stat_family_qword_bits,
..RuntimeCompanyMarketState::default()
},
)]),
..RuntimeServiceState::default()
},
};
let finance_state =
runtime_company_annual_finance_state(&state, 7).expect("annual finance state");
assert_eq!(
finance_state.trailing_full_year_year_words,
vec![1844, 1843, 1842, 1841]
);
assert_eq!(
finance_state.trailing_full_year_net_profits,
vec![247, 229, 211, 193]
);
assert_eq!(finance_state.trailing_full_year_revenues, vec![180, 170, 160, 150]);
assert_eq!(finance_state.trailing_full_year_fuel_costs, vec![18, 17, 16, 15]);
}
#[test]
fn reads_grounded_world_issue_state_from_runtime_restore_state() {
let state = RuntimeState {
@ -4894,6 +5054,10 @@ mod tests {
years_since_last_bankruptcy: None,
years_since_last_dividend: None,
current_partial_year_weight_numerator: None,
trailing_full_year_year_words: Vec::new(),
trailing_full_year_net_profits: Vec::new(),
trailing_full_year_revenues: Vec::new(),
trailing_full_year_fuel_costs: Vec::new(),
current_issue_absolute_counter: None,
prior_issue_absolute_counter: None,
current_issue_age_absolute_counter_delta: None,