From 6572e3b8529e021d75160299227f7d500b8954a4 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sat, 18 Apr 2026 06:04:36 -0700 Subject: [PATCH] Write company credit rating through issue owner state --- crates/rrt-runtime/src/runtime.rs | 8 +- crates/rrt-runtime/src/step.rs | 191 +++++++++++++++++++++++++++--- 2 files changed, 182 insertions(+), 17 deletions(-) diff --git a/crates/rrt-runtime/src/runtime.rs b/crates/rrt-runtime/src/runtime.rs index 1451845..be2c09b 100644 --- a/crates/rrt-runtime/src/runtime.rs +++ b/crates/rrt-runtime/src/runtime.rs @@ -2281,6 +2281,7 @@ impl RuntimeState { runtime_company_direct_float_field_value_f64(self, company.company_id, 0x32f) .and_then(runtime_round_f64_to_i64); let prime_rate = runtime_company_prime_rate(self, company.company_id); + let credit_rating_score = runtime_company_credit_rating(self, company.company_id); let investor_confidence = runtime_company_investor_confidence(self, company.company_id); let management_attitude = @@ -2289,6 +2290,7 @@ impl RuntimeState { company.company_id, current_cash, book_value_per_share, + credit_rating_score, prime_rate, investor_confidence, management_attitude, @@ -2301,12 +2303,13 @@ impl RuntimeState { _, current_cash, book_value_per_share, + credit_rating_score, prime_rate, investor_confidence, management_attitude, )) = company_refresh .iter() - .find(|(company_id, _, _, _, _, _)| *company_id == company.company_id) + .find(|(company_id, _, _, _, _, _, _)| *company_id == company.company_id) { if let Some(current_cash) = current_cash { company.current_cash = *current_cash; @@ -2314,6 +2317,9 @@ impl RuntimeState { if let Some(book_value_per_share) = book_value_per_share { company.book_value_per_share = *book_value_per_share; } + if let Some(credit_rating_score) = credit_rating_score { + company.credit_rating_score = Some(*credit_rating_score); + } if let Some(prime_rate) = prime_rate { company.prime_rate = Some(*prime_rate); } diff --git a/crates/rrt-runtime/src/step.rs b/crates/rrt-runtime/src/step.rs index a02f5b0..f3d900d 100644 --- a/crates/rrt-runtime/src/step.rs +++ b/crates/rrt-runtime/src/step.rs @@ -419,6 +419,35 @@ fn service_set_company_prime_rate_target( ) } +fn service_set_company_credit_rating_target( + state: &mut RuntimeState, + company_id: u32, + value: i64, +) -> bool { + let Some(current_rating) = crate::runtime::runtime_company_credit_rating(state, company_id) + else { + return false; + }; + let current_issue_total = crate::runtime::runtime_world_issue_opinion_term_sum_raw( + state, + crate::RUNTIME_WORLD_ISSUE_CREDIT_MARKET, + state + .companies + .iter() + .find(|company| company.company_id == company_id) + .and_then(|company| company.linked_chairman_profile_id), + Some(company_id), + None, + ) + .unwrap_or(0); + service_set_company_issue_opinion_total( + state, + company_id, + crate::RUNTIME_WORLD_ISSUE_CREDIT_MARKET, + current_issue_total.saturating_add(value.saturating_sub(current_rating)), + ) +} + fn service_zero_company_current_cash(state: &mut RuntimeState, company_id: u32) -> bool { let Some(current_cash) = crate::runtime::runtime_company_stat_value( state, @@ -1401,35 +1430,31 @@ fn apply_runtime_effects( } => { let company_ids = resolve_company_target_ids(state, target, condition_context)?; for company_id in company_ids { - let mut applied_through_owner_state = false; - match metric { - RuntimeCompanyMetric::CreditRating => {} + let applied_through_owner_state = match metric { + RuntimeCompanyMetric::CreditRating => { + service_set_company_credit_rating_target(state, company_id, *value) + } RuntimeCompanyMetric::PrimeRate => { - applied_through_owner_state = - service_set_company_prime_rate_target(state, company_id, *value); + service_set_company_prime_rate_target(state, company_id, *value) } RuntimeCompanyMetric::BookValuePerShare => { - applied_through_owner_state = service_set_company_direct_float_field( + service_set_company_direct_float_field( state, company_id, 0x32f, *value as f64, - ); + ) } RuntimeCompanyMetric::InvestorConfidence => { - applied_through_owner_state = service_set_company_cached_share_price( - state, - company_id, - *value as f64, - ); + service_set_company_cached_share_price(state, company_id, *value as f64) } RuntimeCompanyMetric::ManagementAttitude => { - applied_through_owner_state = service_set_company_issue_opinion_total( + service_set_company_issue_opinion_total( state, company_id, crate::RUNTIME_WORLD_ISSUE_MANAGEMENT_ATTITUDE, i64::from(*value), - ); + ) } _ => { return Err(format!( @@ -1437,7 +1462,7 @@ fn apply_runtime_effects( metric )); } - } + }; let company = state .companies .iter_mut() @@ -1449,7 +1474,9 @@ fn apply_runtime_effects( })?; match metric { RuntimeCompanyMetric::CreditRating => { - company.credit_rating_score = Some(*value); + if !applied_through_owner_state { + company.credit_rating_score = Some(*value); + } } RuntimeCompanyMetric::PrimeRate => { if !applied_through_owner_state { @@ -4716,6 +4743,138 @@ mod tests { ); } + #[test] + fn set_company_credit_rating_governance_scalar_updates_issue38_owner_state() { + let mut year_stat_family_qword_bits = vec![ + 0u64; + (crate::RUNTIME_COMPANY_STAT_SLOT_COUNT * crate::RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) + as usize + ]; + year_stat_family_qword_bits + [(0x12 * crate::RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) as usize] = 20.0f64.to_bits(); + year_stat_family_qword_bits + [(0x01 * crate::RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN + 1) as usize] = + 100.0f64.to_bits(); + year_stat_family_qword_bits + [(0x09 * crate::RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN + 1) as usize] = 0.0f64.to_bits(); + + let mut state = RuntimeState { + companies: vec![RuntimeCompany { + company_id: 1, + controller_kind: RuntimeCompanyControllerKind::Human, + 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, + }], + world_restore: RuntimeWorldRestoreState { + issue_37_value: Some(5.0f32.to_bits()), + issue_38_value: Some(2), + packed_year_word_raw_u16: Some(1835), + ..RuntimeWorldRestoreState::default() + }, + service_state: RuntimeServiceState { + world_issue_opinion_base_terms_raw_i32: vec![0; 0x3b], + company_market_state: BTreeMap::from([( + 1, + crate::RuntimeCompanyMarketState { + outstanding_shares: 10_000, + founding_year: 1830, + last_bankruptcy_year: 1800, + year_stat_family_qword_bits, + issue_opinion_terms_raw_i32: vec![0; 0x3b], + live_bond_slots: vec![crate::RuntimeCompanyBondSlot { + slot_index: 0, + principal: 100_000, + maturity_year: 0, + coupon_rate_raw_u32: 0.05f32.to_bits(), + }], + ..crate::RuntimeCompanyMarketState::default() + }, + )]), + ..RuntimeServiceState::default() + }, + event_runtime_records: vec![RuntimeEventRecord { + record_id: 13, + trigger_kind: 7, + active: true, + service_count: 0, + marks_collection_dirty: false, + one_shot: false, + has_fired: false, + conditions: Vec::new(), + effects: vec![RuntimeEffect::SetCompanyGovernanceScalar { + target: RuntimeCompanyTarget::Ids { ids: vec![1] }, + metric: RuntimeCompanyMetric::CreditRating, + value: 7, + }], + }], + calendar: crate::CalendarPoint { + year: 1835, + month_slot: 0, + phase_slot: 0, + tick_slot: 0, + }, + world_flags: BTreeMap::new(), + save_profile: crate::RuntimeSaveProfileState::default(), + metadata: BTreeMap::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, + 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(), + }; + + execute_step_command( + &mut state, + &StepCommand::ServiceTriggerKind { trigger_kind: 7 }, + ) + .expect("credit-rating governance effect should apply through owner state"); + + assert_eq!(state.companies[0].credit_rating_score, Some(7)); + assert_eq!( + crate::runtime::runtime_company_credit_rating(&state, 1), + Some(7) + ); + assert_eq!( + state.service_state.company_market_state[&1].issue_opinion_terms_raw_i32 + [crate::RUNTIME_WORLD_ISSUE_CREDIT_MARKET as usize], + -3 + ); + } + #[test] fn adjust_company_cash_updates_owner_state_backed_current_cash() { let mut year_stat_family_qword_bits = vec![