Rehost company credit rating reader
This commit is contained in:
parent
2239f9ce72
commit
3306763bad
4 changed files with 438 additions and 19 deletions
|
|
@ -46,18 +46,18 @@ pub use pk4::{
|
|||
pub use runtime::{
|
||||
RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER, RUNTIME_COMPANY_STAT_FAMILY_SPECIAL_232A,
|
||||
RUNTIME_COMPANY_STAT_SLOT_BOOK_VALUE_PER_SHARE, RUNTIME_COMPANY_STAT_SLOT_COUNT,
|
||||
RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH, RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN,
|
||||
RUNTIME_WORLD_ISSUE_CREDIT_MARKET, RUNTIME_WORLD_ISSUE_INVESTOR_CONFIDENCE,
|
||||
RUNTIME_WORLD_ISSUE_MANAGEMENT_ATTITUDE, RUNTIME_WORLD_ISSUE_PRIME_RATE,
|
||||
RuntimeCargoCatalogEntry, RuntimeCargoClass, RuntimeCargoPriceTarget,
|
||||
RuntimeCargoProductionTarget, RuntimeChairmanMetric, RuntimeChairmanProfile,
|
||||
RuntimeChairmanTarget, RuntimeCompany, RuntimeCompanyAnnualFinanceState,
|
||||
RuntimeCompanyBondSlot, RuntimeCompanyConditionTestScope, RuntimeCompanyControllerKind,
|
||||
RuntimeCompanyMarketMetric, RuntimeCompanyMarketState, RuntimeCompanyMetric,
|
||||
RuntimeCompanyStatBandCandidate, RuntimeCompanyStatSelector, RuntimeCompanyTarget,
|
||||
RuntimeCompanyTerritoryAccess, RuntimeCompanyTerritoryTrackPieceCount, RuntimeCondition,
|
||||
RuntimeConditionComparator, RuntimeEffect, RuntimeEventRecord, RuntimeEventRecordTemplate,
|
||||
RuntimeLocomotiveCatalogEntry, RuntimePackedEventCollectionSummary,
|
||||
RUNTIME_COMPANY_STAT_SLOT_CREDIT_RATING, RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH,
|
||||
RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN, RUNTIME_WORLD_ISSUE_CREDIT_MARKET,
|
||||
RUNTIME_WORLD_ISSUE_INVESTOR_CONFIDENCE, RUNTIME_WORLD_ISSUE_MANAGEMENT_ATTITUDE,
|
||||
RUNTIME_WORLD_ISSUE_PRIME_RATE, RuntimeCargoCatalogEntry, RuntimeCargoClass,
|
||||
RuntimeCargoPriceTarget, RuntimeCargoProductionTarget, RuntimeChairmanMetric,
|
||||
RuntimeChairmanProfile, RuntimeChairmanTarget, RuntimeCompany,
|
||||
RuntimeCompanyAnnualFinanceState, RuntimeCompanyBondSlot, RuntimeCompanyConditionTestScope,
|
||||
RuntimeCompanyControllerKind, RuntimeCompanyMarketMetric, RuntimeCompanyMarketState,
|
||||
RuntimeCompanyMetric, RuntimeCompanyStatBandCandidate, RuntimeCompanyStatSelector,
|
||||
RuntimeCompanyTarget, RuntimeCompanyTerritoryAccess, RuntimeCompanyTerritoryTrackPieceCount,
|
||||
RuntimeCondition, RuntimeConditionComparator, RuntimeEffect, RuntimeEventRecord,
|
||||
RuntimeEventRecordTemplate, RuntimeLocomotiveCatalogEntry, RuntimePackedEventCollectionSummary,
|
||||
RuntimePackedEventCompactControlSummary, RuntimePackedEventConditionRowSummary,
|
||||
RuntimePackedEventGroupedEffectRowSummary, RuntimePackedEventNegativeSentinelScopeSummary,
|
||||
RuntimePackedEventRecordSummary, RuntimePackedEventTextBandSummary, RuntimePlayer,
|
||||
|
|
@ -66,11 +66,11 @@ pub use runtime::{
|
|||
RuntimeTerritoryTarget, RuntimeTrackMetric, RuntimeTrackPieceCounts, RuntimeTrain,
|
||||
RuntimeWorldFinanceNeighborhoodCandidate, RuntimeWorldIssueState, RuntimeWorldRestoreState,
|
||||
runtime_company_annual_finance_state, runtime_company_assigned_share_pool,
|
||||
runtime_company_average_live_bond_coupon, runtime_company_market_value,
|
||||
runtime_company_prime_rate, runtime_company_stat_value, runtime_company_stat_value_f64,
|
||||
runtime_company_unassigned_share_pool, runtime_world_issue_opinion_multiplier,
|
||||
runtime_world_issue_opinion_term_sum_raw, runtime_world_issue_state,
|
||||
runtime_world_prime_rate_baseline,
|
||||
runtime_company_average_live_bond_coupon, runtime_company_credit_rating,
|
||||
runtime_company_market_value, runtime_company_prime_rate, runtime_company_stat_value,
|
||||
runtime_company_stat_value_f64, runtime_company_unassigned_share_pool,
|
||||
runtime_world_issue_opinion_multiplier, runtime_world_issue_opinion_term_sum_raw,
|
||||
runtime_world_issue_state, runtime_world_prime_rate_baseline,
|
||||
};
|
||||
pub use smp::{
|
||||
SMP_FOUR_SIDECAR_BYTE_PLANES_MIN_BUNDLE_VERSION, SmpAlignedRuntimeRuleBandLane,
|
||||
|
|
|
|||
|
|
@ -439,6 +439,7 @@ pub const RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER: u32 = 0x2329;
|
|||
pub const RUNTIME_COMPANY_STAT_FAMILY_SPECIAL_232A: u32 = 0x232a;
|
||||
pub const RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH: u32 = 0x0d;
|
||||
pub const RUNTIME_COMPANY_STAT_SLOT_BOOK_VALUE_PER_SHARE: u32 = 0x1d;
|
||||
pub const RUNTIME_COMPANY_STAT_SLOT_CREDIT_RATING: u32 = 0x19;
|
||||
pub const RUNTIME_COMPANY_STAT_SLOT_COUNT: u32 = 0x2b;
|
||||
pub const RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN: u32 = 11;
|
||||
pub const RUNTIME_WORLD_ISSUE_INVESTOR_CONFIDENCE: u32 = 0x37;
|
||||
|
|
@ -2021,6 +2022,9 @@ fn runtime_company_control_transfer_stat_value_f64(
|
|||
runtime_company_current_stat_value_f64(state, company_id, slot_id)
|
||||
}
|
||||
}
|
||||
RUNTIME_COMPANY_STAT_SLOT_CREDIT_RATING => {
|
||||
runtime_company_credit_rating(state, company_id).map(|value| value as f64)
|
||||
}
|
||||
RUNTIME_COMPANY_STAT_SLOT_BOOK_VALUE_PER_SHARE => Some(company.book_value_per_share as f64),
|
||||
_ => None,
|
||||
}
|
||||
|
|
@ -2301,6 +2305,146 @@ pub fn runtime_company_prime_rate(state: &RuntimeState, company_id: u32) -> Opti
|
|||
runtime_round_f64_to_i64(baseline + (raw_issue_sum as f64) * 0.01)
|
||||
}
|
||||
|
||||
fn runtime_credit_rating_profitability_ladder(ratio: f64) -> f64 {
|
||||
if !ratio.is_finite() || ratio <= 0.0 {
|
||||
0.0
|
||||
} else if ratio < 1.0 {
|
||||
1.0 + ratio * 4.0
|
||||
} else if ratio < 2.0 {
|
||||
3.0 + ratio * 2.0
|
||||
} else if ratio < 5.0 {
|
||||
5.0 + ratio
|
||||
} else {
|
||||
10.0
|
||||
}
|
||||
}
|
||||
|
||||
fn runtime_credit_rating_burden_ladder(ratio: f64) -> f64 {
|
||||
if !ratio.is_finite() || ratio > 1.0 {
|
||||
0.0
|
||||
} else if ratio > 0.75 {
|
||||
16.0 - ratio * 16.0
|
||||
} else if ratio > 0.5 {
|
||||
13.0 - ratio * 12.0
|
||||
} else if ratio > 0.25 {
|
||||
11.0 - ratio * 8.0
|
||||
} else if ratio > 0.1 {
|
||||
10.0 - ratio * 4.0
|
||||
} else {
|
||||
10.0
|
||||
}
|
||||
}
|
||||
|
||||
fn runtime_world_credit_market_scale(state: &RuntimeState) -> Option<f64> {
|
||||
const ISSUE_38_SCALE_TABLE: [f64; 8] = [0.8, 0.9, 1.0, 1.1, 1.2, 0.9, 0.95, 1.0];
|
||||
let index = state.world_restore.issue_38_value? as usize;
|
||||
ISSUE_38_SCALE_TABLE.get(index).copied()
|
||||
}
|
||||
|
||||
pub fn runtime_company_credit_rating(state: &RuntimeState, company_id: u32) -> Option<i64> {
|
||||
let company = state
|
||||
.companies
|
||||
.iter()
|
||||
.find(|company| company.company_id == company_id)?;
|
||||
if let Some(credit_rating_score) = company.credit_rating_score {
|
||||
return Some(credit_rating_score);
|
||||
}
|
||||
|
||||
let annual_finance_state = runtime_company_annual_finance_state(state, company_id)?;
|
||||
if annual_finance_state.outstanding_shares == 0 {
|
||||
return Some(-512);
|
||||
}
|
||||
|
||||
let mut weighted_recent_profit_total = 0.0f64;
|
||||
let mut weighted_recent_profit_weight = 0.0f64;
|
||||
for (index, (net_profit, fuel_cost)) in annual_finance_state
|
||||
.trailing_full_year_net_profits
|
||||
.iter()
|
||||
.zip(annual_finance_state.trailing_full_year_fuel_costs.iter())
|
||||
.take(4)
|
||||
.enumerate()
|
||||
{
|
||||
let weight = (4 - index) as f64;
|
||||
weighted_recent_profit_total += (*net_profit - *fuel_cost) as f64 * weight;
|
||||
weighted_recent_profit_weight += weight;
|
||||
}
|
||||
let weighted_recent_profit = if weighted_recent_profit_weight > 0.0 {
|
||||
weighted_recent_profit_total / weighted_recent_profit_weight
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
let current_slot_12 = runtime_company_control_transfer_stat_value_f64(state, company_id, 0x12)?;
|
||||
let current_slot_30 = runtime_company_derived_stat_value_f64(
|
||||
state,
|
||||
company_id,
|
||||
RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER,
|
||||
0x30,
|
||||
)?;
|
||||
let current_slot_31 = runtime_company_derived_stat_value_f64(
|
||||
state,
|
||||
company_id,
|
||||
RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER,
|
||||
0x31,
|
||||
)?;
|
||||
let average_live_bond_coupon = runtime_company_average_live_bond_coupon(state, company_id)?;
|
||||
|
||||
let mut finance_pressure = average_live_bond_coupon * current_slot_12;
|
||||
if company.current_cash > 0 {
|
||||
let prime_baseline = runtime_world_prime_rate_baseline(state)?;
|
||||
let raw_issue_39 = runtime_world_issue_opinion_term_sum_raw(
|
||||
state,
|
||||
RUNTIME_WORLD_ISSUE_PRIME_RATE,
|
||||
company.linked_chairman_profile_id,
|
||||
Some(company_id),
|
||||
None,
|
||||
)? as f64;
|
||||
finance_pressure +=
|
||||
company.current_cash as f64 * (prime_baseline + raw_issue_39 * 0.01 + 0.03);
|
||||
}
|
||||
|
||||
let profitability_ratio = if finance_pressure < 0.0 {
|
||||
weighted_recent_profit / (-finance_pressure)
|
||||
} else {
|
||||
10.0
|
||||
};
|
||||
let mut profitability_score = runtime_credit_rating_profitability_ladder(profitability_ratio);
|
||||
if let Some(years_since_founding) = annual_finance_state.years_since_founding {
|
||||
if years_since_founding < 5 {
|
||||
let missing_years = (5 - years_since_founding) as f64;
|
||||
profitability_score += (10.0 - profitability_score) * missing_years * 0.1;
|
||||
}
|
||||
}
|
||||
if current_slot_31 > 1_000_000.0 {
|
||||
profitability_score += current_slot_31 / 100_000.0 - 10.0;
|
||||
}
|
||||
|
||||
let burden_ratio = if current_slot_30 > 0.0 {
|
||||
(weighted_recent_profit - current_slot_12) / current_slot_30
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
let burden_score = runtime_credit_rating_burden_ladder(burden_ratio);
|
||||
|
||||
let mut rating =
|
||||
(profitability_score * burden_score / 10.0 + profitability_score + burden_score) / 3.0;
|
||||
rating *= runtime_world_credit_market_scale(state)?;
|
||||
if let Some(years_since_last_bankruptcy) = annual_finance_state.years_since_last_bankruptcy {
|
||||
if years_since_last_bankruptcy < 15 {
|
||||
rating *= years_since_last_bankruptcy as f64 * 0.0666;
|
||||
}
|
||||
}
|
||||
|
||||
let raw_issue_38 = runtime_world_issue_opinion_term_sum_raw(
|
||||
state,
|
||||
RUNTIME_WORLD_ISSUE_CREDIT_MARKET,
|
||||
company.linked_chairman_profile_id,
|
||||
Some(company_id),
|
||||
None,
|
||||
)? as f64;
|
||||
runtime_round_f64_to_i64((rating + raw_issue_38 + 0.5).clamp(0.0, 10.0))
|
||||
}
|
||||
|
||||
pub fn runtime_company_average_live_bond_coupon(
|
||||
state: &RuntimeState,
|
||||
company_id: u32,
|
||||
|
|
@ -4520,6 +4664,7 @@ mod tests {
|
|||
world_scalar_overrides: BTreeMap::new(),
|
||||
special_conditions: BTreeMap::new(),
|
||||
service_state: RuntimeServiceState {
|
||||
world_issue_opinion_base_terms_raw_i32: vec![0; 0x3b],
|
||||
company_market_state: BTreeMap::from([(
|
||||
7,
|
||||
RuntimeCompanyMarketState {
|
||||
|
|
@ -4691,6 +4836,7 @@ mod tests {
|
|||
world_scalar_overrides: BTreeMap::new(),
|
||||
special_conditions: BTreeMap::new(),
|
||||
service_state: RuntimeServiceState {
|
||||
world_issue_opinion_base_terms_raw_i32: vec![0; 0x3b],
|
||||
company_market_state: BTreeMap::from([(
|
||||
7,
|
||||
RuntimeCompanyMarketState {
|
||||
|
|
@ -4883,6 +5029,7 @@ mod tests {
|
|||
world_scalar_overrides: BTreeMap::new(),
|
||||
special_conditions: BTreeMap::new(),
|
||||
service_state: RuntimeServiceState {
|
||||
world_issue_opinion_base_terms_raw_i32: vec![0; 0x3b],
|
||||
company_market_state: BTreeMap::from([(
|
||||
7,
|
||||
RuntimeCompanyMarketState {
|
||||
|
|
@ -5406,6 +5553,171 @@ mod tests {
|
|||
assert_eq!(runtime_company_prime_rate(&state, 7), Some(7));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derives_company_credit_rating_from_rehosted_finance_state() {
|
||||
let mut year_stat_family_qword_bits = vec![
|
||||
0u64;
|
||||
(RUNTIME_COMPANY_STAT_SLOT_COUNT * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN)
|
||||
as usize
|
||||
];
|
||||
year_stat_family_qword_bits[(0x12 * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) as usize] =
|
||||
20.0f64.to_bits();
|
||||
year_stat_family_qword_bits[(0x01 * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN + 1) as usize] =
|
||||
100.0f64.to_bits();
|
||||
year_stat_family_qword_bits[(0x09 * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN + 1) as usize] =
|
||||
0.0f64.to_bits();
|
||||
|
||||
let state = RuntimeState {
|
||||
calendar: CalendarPoint {
|
||||
year: 1835,
|
||||
month_slot: 0,
|
||||
phase_slot: 0,
|
||||
tick_slot: 0,
|
||||
},
|
||||
world_flags: BTreeMap::new(),
|
||||
save_profile: RuntimeSaveProfileState::default(),
|
||||
world_restore: RuntimeWorldRestoreState {
|
||||
issue_37_value: Some(5.0f32.to_bits()),
|
||||
issue_38_value: Some(2),
|
||||
packed_year_word_raw_u16: Some(1835),
|
||||
..RuntimeWorldRestoreState::default()
|
||||
},
|
||||
metadata: BTreeMap::new(),
|
||||
companies: vec![RuntimeCompany {
|
||||
company_id: 7,
|
||||
current_cash: 100,
|
||||
debt: 0,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
}],
|
||||
selected_company_id: None,
|
||||
players: Vec::new(),
|
||||
selected_player_id: None,
|
||||
chairman_profiles: Vec::new(),
|
||||
selected_chairman_profile_id: None,
|
||||
trains: Vec::new(),
|
||||
locomotive_catalog: Vec::new(),
|
||||
cargo_catalog: Vec::new(),
|
||||
territories: Vec::new(),
|
||||
company_territory_track_piece_counts: Vec::new(),
|
||||
company_territory_access: Vec::new(),
|
||||
packed_event_collection: None,
|
||||
event_runtime_records: Vec::new(),
|
||||
candidate_availability: BTreeMap::new(),
|
||||
named_locomotive_availability: BTreeMap::new(),
|
||||
named_locomotive_cost: BTreeMap::new(),
|
||||
all_cargo_price_override: None,
|
||||
named_cargo_price_overrides: BTreeMap::new(),
|
||||
all_cargo_production_override: None,
|
||||
factory_cargo_production_override: None,
|
||||
farm_mine_cargo_production_override: None,
|
||||
named_cargo_production_overrides: BTreeMap::new(),
|
||||
cargo_production_overrides: BTreeMap::new(),
|
||||
world_runtime_variables: BTreeMap::new(),
|
||||
company_runtime_variables: BTreeMap::new(),
|
||||
player_runtime_variables: BTreeMap::new(),
|
||||
territory_runtime_variables: BTreeMap::new(),
|
||||
world_scalar_overrides: BTreeMap::new(),
|
||||
special_conditions: BTreeMap::new(),
|
||||
service_state: RuntimeServiceState {
|
||||
world_issue_opinion_base_terms_raw_i32: vec![0; 0x3b],
|
||||
company_market_state: BTreeMap::from([(
|
||||
7,
|
||||
RuntimeCompanyMarketState {
|
||||
outstanding_shares: 10_000,
|
||||
founding_year: 1830,
|
||||
last_bankruptcy_year: 1800,
|
||||
year_stat_family_qword_bits,
|
||||
live_bond_slots: vec![RuntimeCompanyBondSlot {
|
||||
slot_index: 0,
|
||||
principal: 100_000,
|
||||
coupon_rate_raw_u32: 0.05f32.to_bits(),
|
||||
}],
|
||||
..RuntimeCompanyMarketState::default()
|
||||
},
|
||||
)]),
|
||||
..RuntimeServiceState::default()
|
||||
},
|
||||
};
|
||||
|
||||
let annual_finance_state =
|
||||
runtime_company_annual_finance_state(&state, 7).expect("annual finance state");
|
||||
assert_eq!(
|
||||
annual_finance_state.trailing_full_year_net_profits,
|
||||
vec![100, 0, 0, 0]
|
||||
);
|
||||
assert_eq!(
|
||||
runtime_company_control_transfer_stat_value_f64(&state, 7, 0x12),
|
||||
Some(20.0)
|
||||
);
|
||||
assert_eq!(
|
||||
runtime_company_derived_stat_value_f64(
|
||||
&state,
|
||||
7,
|
||||
RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER,
|
||||
0x30,
|
||||
),
|
||||
Some(100.0)
|
||||
);
|
||||
assert_eq!(
|
||||
runtime_company_derived_stat_value_f64(
|
||||
&state,
|
||||
7,
|
||||
RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER,
|
||||
0x31,
|
||||
),
|
||||
Some(120.0)
|
||||
);
|
||||
let average_live_bond_coupon =
|
||||
runtime_company_average_live_bond_coupon(&state, 7).expect("average coupon");
|
||||
assert!((average_live_bond_coupon - 0.05).abs() < 1e-6);
|
||||
assert_eq!(runtime_world_prime_rate_baseline(&state), Some(5.0));
|
||||
assert_eq!(
|
||||
runtime_world_issue_opinion_term_sum_raw(
|
||||
&state,
|
||||
RUNTIME_WORLD_ISSUE_PRIME_RATE,
|
||||
None,
|
||||
Some(7),
|
||||
None,
|
||||
),
|
||||
Some(0)
|
||||
);
|
||||
assert_eq!(
|
||||
runtime_world_issue_opinion_term_sum_raw(
|
||||
&state,
|
||||
RUNTIME_WORLD_ISSUE_CREDIT_MARKET,
|
||||
None,
|
||||
Some(7),
|
||||
None,
|
||||
),
|
||||
Some(0)
|
||||
);
|
||||
assert_eq!(runtime_world_credit_market_scale(&state), Some(1.0));
|
||||
|
||||
assert_eq!(runtime_company_credit_rating(&state, 7), Some(10));
|
||||
assert_eq!(
|
||||
runtime_company_stat_value(
|
||||
&state,
|
||||
7,
|
||||
RuntimeCompanyStatSelector {
|
||||
family_id: RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER,
|
||||
slot_id: RUNTIME_COMPANY_STAT_SLOT_CREDIT_RATING,
|
||||
},
|
||||
),
|
||||
Some(10)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn computes_weighted_average_live_bond_coupon_from_owned_market_slots() {
|
||||
let state = RuntimeState {
|
||||
|
|
|
|||
|
|
@ -16439,6 +16439,14 @@ mod tests {
|
|||
u8::from(city_connection_latch);
|
||||
bytes[record_offset + SAVE_COMPANY_RECORD_LINKED_TRANSIT_LATCH_OFFSET] =
|
||||
u8::from(linked_transit_latch);
|
||||
let current_cash: f64 = if index == 0 { 125_000.0 } else { -25_000.0 };
|
||||
let current_cash_slot_offset = record_offset
|
||||
+ SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0D7F_OFFSET
|
||||
+ (crate::RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH as usize
|
||||
* crate::RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN as usize
|
||||
* 8);
|
||||
bytes[current_cash_slot_offset..current_cash_slot_offset + 8]
|
||||
.copy_from_slice(¤t_cash.to_bits().to_le_bytes());
|
||||
}
|
||||
|
||||
let header_probe = parse_save_company_collection_header_probe(
|
||||
|
|
@ -16484,6 +16492,7 @@ mod tests {
|
|||
assert_eq!(roster.selected_company_id, Some(2));
|
||||
assert_eq!(roster.entries.len(), 2);
|
||||
assert_eq!(roster.entries[0].company_id, 1);
|
||||
assert_eq!(roster.entries[0].current_cash, 125_000);
|
||||
assert_eq!(roster.entries[0].linked_chairman_profile_id, Some(1));
|
||||
assert_eq!(roster.entries[0].debt, 1_550_000);
|
||||
assert_eq!(roster.entries[0].available_track_laying_capacity, Some(603));
|
||||
|
|
@ -16546,6 +16555,7 @@ mod tests {
|
|||
"0xd77"
|
||||
);
|
||||
assert_eq!(roster.entries[1].company_id, 2);
|
||||
assert_eq!(roster.entries[1].current_cash, -25_000);
|
||||
assert_eq!(roster.entries[1].linked_chairman_profile_id, Some(2));
|
||||
assert_eq!(roster.entries[1].debt, 500_000);
|
||||
assert_eq!(roster.entries[1].available_track_laying_capacity, None);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use crate::{
|
|||
RuntimeCompanyMetric, RuntimeCompanyTarget, RuntimeCondition, RuntimeConditionComparator,
|
||||
RuntimeEffect, RuntimeEventRecordTemplate, RuntimePlayerTarget, RuntimeState, RuntimeSummary,
|
||||
RuntimeTerritoryMetric, RuntimeTerritoryTarget, RuntimeTrackMetric, RuntimeTrackPieceCounts,
|
||||
calendar::BoundaryEventKind, runtime_company_prime_rate,
|
||||
calendar::BoundaryEventKind, runtime_company_credit_rating, runtime_company_prime_rate,
|
||||
};
|
||||
|
||||
const PERIODIC_TRIGGER_KIND_ORDER: [u8; 6] = [1, 0, 3, 2, 5, 4];
|
||||
|
|
@ -1516,7 +1516,9 @@ fn company_metric_value(
|
|||
match metric {
|
||||
RuntimeCompanyMetric::CurrentCash => company.current_cash,
|
||||
RuntimeCompanyMetric::TotalDebt => company.debt as i64,
|
||||
RuntimeCompanyMetric::CreditRating => company.credit_rating_score.unwrap_or(0),
|
||||
RuntimeCompanyMetric::CreditRating => {
|
||||
runtime_company_credit_rating(state, company.company_id).unwrap_or(0)
|
||||
}
|
||||
RuntimeCompanyMetric::PrimeRate => {
|
||||
runtime_company_prime_rate(state, company.company_id).unwrap_or(0)
|
||||
}
|
||||
|
|
@ -4142,6 +4144,101 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derived_credit_rating_condition_reads_rehosted_finance_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: 37,
|
||||
management_attitude: 58,
|
||||
takeover_cooldown_year: Some(1844),
|
||||
merger_cooldown_year: Some(1845),
|
||||
}],
|
||||
selected_company_id: Some(1),
|
||||
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,
|
||||
live_bond_slots: vec![crate::RuntimeCompanyBondSlot {
|
||||
slot_index: 0,
|
||||
principal: 100_000,
|
||||
coupon_rate_raw_u32: 0.05f32.to_bits(),
|
||||
}],
|
||||
..crate::RuntimeCompanyMarketState::default()
|
||||
},
|
||||
)]),
|
||||
..RuntimeServiceState::default()
|
||||
},
|
||||
event_runtime_records: vec![RuntimeEventRecord {
|
||||
record_id: 196,
|
||||
trigger_kind: 6,
|
||||
active: true,
|
||||
service_count: 0,
|
||||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
conditions: vec![RuntimeCondition::CompanyNumericThreshold {
|
||||
target: RuntimeCompanyTarget::SelectedCompany,
|
||||
metric: crate::RuntimeCompanyMetric::CreditRating,
|
||||
comparator: RuntimeConditionComparator::Eq,
|
||||
value: 10,
|
||||
}],
|
||||
effects: vec![RuntimeEffect::SetWorldFlag {
|
||||
key: "world.derived_credit_rating_gate_passed".to_string(),
|
||||
value: true,
|
||||
}],
|
||||
}],
|
||||
..state()
|
||||
};
|
||||
|
||||
execute_step_command(
|
||||
&mut state,
|
||||
&StepCommand::ServiceTriggerKind { trigger_kind: 6 },
|
||||
)
|
||||
.expect("derived credit-rating company condition should gate execution");
|
||||
|
||||
assert_eq!(
|
||||
state
|
||||
.world_flags
|
||||
.get("world.derived_credit_rating_gate_passed"),
|
||||
Some(&true)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chairman_metric_conditions_support_all_active_target() {
|
||||
let mut state = RuntimeState {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue