use crate::derived::{ runtime_company_annual_finance_state, runtime_company_average_live_bond_coupon, runtime_company_control_transfer_stat_value_f64, runtime_company_derived_stat_value, runtime_round_f64_to_i64, runtime_world_issue_opinion_term_sum_raw, runtime_world_prime_rate_baseline, }; use crate::event::metrics::{ RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER, RUNTIME_WORLD_ISSUE_CREDIT_MARKET, RUNTIME_WORLD_ISSUE_PRIME_RATE, }; use crate::state::RuntimeState; pub fn runtime_company_credit_rating(state: &RuntimeState, company_id: u32) -> Option { let company = state .companies .iter() .find(|company| company.company_id == company_id)?; if let Some(credit_rating_score) = company.credit_rating_score { return Some(credit_rating_score); } let annual_finance_state = runtime_company_annual_finance_state(state, company_id)?; if annual_finance_state.outstanding_shares == 0 { return Some(-512); } let mut weighted_recent_profit_total = 0.0f64; let mut weighted_recent_profit_weight = 0.0f64; for (index, (net_profit, fuel_cost)) in annual_finance_state .trailing_full_year_net_profits .iter() .zip(annual_finance_state.trailing_full_year_fuel_costs.iter()) .take(4) .enumerate() { let weight = (4 - index) as f64; weighted_recent_profit_total += (*net_profit - *fuel_cost) as f64 * weight; weighted_recent_profit_weight += weight; } let weighted_recent_profit = if weighted_recent_profit_weight > 0.0 { weighted_recent_profit_total / weighted_recent_profit_weight } else { 0.0 }; let current_slot_12 = runtime_company_control_transfer_stat_value_f64(state, company_id, 0x12)?; let current_slot_30 = runtime_company_derived_stat_value( state, company_id, RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER, 0x30, )?; let current_slot_31 = runtime_company_derived_stat_value( state, company_id, RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER, 0x31, )?; let average_live_bond_coupon = runtime_company_average_live_bond_coupon(state, company_id)?; let mut finance_pressure = average_live_bond_coupon * current_slot_12; if company.current_cash > 0 { let prime_baseline = runtime_world_prime_rate_baseline(state)?; let raw_issue_39 = runtime_world_issue_opinion_term_sum_raw( state, RUNTIME_WORLD_ISSUE_PRIME_RATE, company.linked_chairman_profile_id, Some(company_id), None, )? as f64; finance_pressure += company.current_cash as f64 * (prime_baseline + raw_issue_39 * 0.01 + 0.03); } let profitability_ratio = if finance_pressure < 0.0 { weighted_recent_profit / (-finance_pressure) } else { 10.0 }; let mut profitability_score = runtime_credit_rating_profitability_ladder(profitability_ratio); if let Some(years_since_founding) = annual_finance_state.years_since_founding { if years_since_founding < 5 { let missing_years = (5 - years_since_founding) as f64; profitability_score += (10.0 - profitability_score) * missing_years * 0.1; } } if current_slot_31 > 1_000_000.0 { profitability_score += current_slot_31 / 100_000.0 - 10.0; } let burden_ratio = if current_slot_30 > 0.0 { (weighted_recent_profit - current_slot_12) / current_slot_30 } else { 1.0 }; let burden_score = runtime_credit_rating_burden_ladder(burden_ratio); let mut rating = (profitability_score * burden_score / 10.0 + profitability_score + burden_score) / 3.0; rating *= runtime_world_credit_market_scale(state)?; if let Some(years_since_last_bankruptcy) = annual_finance_state.years_since_last_bankruptcy { if years_since_last_bankruptcy < 15 { rating *= years_since_last_bankruptcy as f64 * 0.0666; } } let raw_issue_38 = runtime_world_issue_opinion_term_sum_raw( state, RUNTIME_WORLD_ISSUE_CREDIT_MARKET, company.linked_chairman_profile_id, Some(company_id), None, )? as f64; runtime_round_f64_to_i64((rating + raw_issue_38 + 0.5).clamp(0.0, 10.0)) } pub(super) fn runtime_credit_rating_profitability_ladder(ratio: f64) -> f64 { if !ratio.is_finite() || ratio <= 0.0 { 0.0 } else if ratio < 1.0 { 1.0 + ratio * 4.0 } else if ratio < 2.0 { 3.0 + ratio * 2.0 } else if ratio < 5.0 { 5.0 + ratio } else { 10.0 } } pub(super) fn runtime_credit_rating_burden_ladder(ratio: f64) -> f64 { if !ratio.is_finite() || ratio > 1.0 { 0.0 } else if ratio > 0.75 { 16.0 - ratio * 16.0 } else if ratio > 0.5 { 13.0 - ratio * 12.0 } else if ratio > 0.25 { 11.0 - ratio * 8.0 } else if ratio > 0.1 { 10.0 - ratio * 4.0 } else { 10.0 } } pub fn runtime_world_credit_market_scale(state: &RuntimeState) -> Option { const ISSUE_38_SCALE_TABLE: [f64; 8] = [0.8, 0.9, 1.0, 1.1, 1.2, 0.9, 0.95, 1.0]; let index = state.world_restore.issue_38_value? as usize; ISSUE_38_SCALE_TABLE.get(index).copied() }