Rehost annual creditor pressure policy branch

This commit is contained in:
Jan Petykiewicz 2026-04-18 00:09:09 -07:00
commit 64fc4a8488
8 changed files with 739 additions and 17 deletions

View file

@ -89,7 +89,10 @@ bond table now also contributes both the largest live bond principal and the cho
highest-coupon live bond principal into owned company market and annual-finance state, so the highest-coupon live bond principal into owned company market and annual-finance state, so the
stock-capital approval ladder can extend one rehosted owner-state surface instead of hunting stock-capital approval ladder can extend one rehosted owner-state surface instead of hunting
another isolated finance leaf. A checked-in another isolated finance leaf. A checked-in
The working rule on the remaining frontier is explicit now too: when a lane is still ambiguous, we fixed-world finance-policy seam now also carries the raw stock, bond, bankruptcy, and dividend
policy bytes from the `0x32c8` save block, and the first annual creditor-pressure branch now runs
headlessly as a pure runtime reader over owned annual-finance state, support-adjusted share price,
and current world finance policy rather than as a notes-only atlas fragment. The working rule on the remaining frontier is explicit now too: when a lane is still ambiguous, we
should prefer rehosting the owning source state or the real reader/setter family rather than should prefer rehosting the owning source state or the real reader/setter family rather than
guessing one more derived leaf field from nearby offsets. A checked-in guessing one more derived leaf field from nearby offsets. A checked-in
`EventEffects` export now exists too in `EventEffects` export now exists too in

View file

@ -851,6 +851,38 @@ fn project_save_slice_components(
.world_issue_37_state .world_issue_37_state
.as_ref() .as_ref()
.map(|state| state.multiplier_value_f32_text.clone()), .map(|state| state.multiplier_value_f32_text.clone()),
stock_issue_and_buyback_policy_raw_u8: save_slice
.world_finance_neighborhood_state
.as_ref()
.map(|state| state.stock_policy_raw_u8),
bond_issue_and_repayment_policy_raw_u8: save_slice
.world_finance_neighborhood_state
.as_ref()
.map(|state| state.bond_policy_raw_u8),
bankruptcy_policy_raw_u8: save_slice
.world_finance_neighborhood_state
.as_ref()
.map(|state| state.bankruptcy_policy_raw_u8),
dividend_policy_raw_u8: save_slice
.world_finance_neighborhood_state
.as_ref()
.map(|state| state.dividend_policy_raw_u8),
stock_issue_and_buyback_allowed: save_slice
.world_finance_neighborhood_state
.as_ref()
.map(|state| state.stock_policy_raw_u8 == 0),
bond_issue_and_repayment_allowed: save_slice
.world_finance_neighborhood_state
.as_ref()
.map(|state| state.bond_policy_raw_u8 == 0),
bankruptcy_allowed: save_slice
.world_finance_neighborhood_state
.as_ref()
.map(|state| state.bankruptcy_policy_raw_u8 == 0),
dividend_adjustment_allowed: save_slice
.world_finance_neighborhood_state
.as_ref()
.map(|state| state.dividend_policy_raw_u8 == 0),
finance_neighborhood_candidates: save_slice finance_neighborhood_candidates: save_slice
.world_finance_neighborhood_state .world_finance_neighborhood_state
.as_ref() .as_ref()
@ -5979,6 +6011,14 @@ mod tests {
absolute_counter_raw_hex: "0x00000003".to_string(), absolute_counter_raw_hex: "0x00000003".to_string(),
absolute_counter_mirror_raw_u32: 4, absolute_counter_mirror_raw_u32: 4,
absolute_counter_mirror_raw_hex: "0x00000004".to_string(), absolute_counter_mirror_raw_hex: "0x00000004".to_string(),
stock_policy_raw_u8: 0,
stock_policy_raw_hex: "0x00".to_string(),
bond_policy_raw_u8: 1,
bond_policy_raw_hex: "0x01".to_string(),
bankruptcy_policy_raw_u8: 0,
bankruptcy_policy_raw_hex: "0x00".to_string(),
dividend_policy_raw_u8: 1,
dividend_policy_raw_hex: "0x01".to_string(),
labels: vec![ labels: vec![
"current_calendar_tuple_word".to_string(), "current_calendar_tuple_word".to_string(),
"current_calendar_tuple_word_2".to_string(), "current_calendar_tuple_word_2".to_string(),
@ -6263,6 +6303,35 @@ mod tests {
import.state.world_restore.issue_37_multiplier_raw_u32, import.state.world_restore.issue_37_multiplier_raw_u32,
Some(0x3d75c28f) Some(0x3d75c28f)
); );
assert_eq!(
import
.state
.world_restore
.stock_issue_and_buyback_policy_raw_u8,
Some(0)
);
assert_eq!(
import
.state
.world_restore
.bond_issue_and_repayment_policy_raw_u8,
Some(1)
);
assert_eq!(import.state.world_restore.bankruptcy_policy_raw_u8, Some(0));
assert_eq!(import.state.world_restore.dividend_policy_raw_u8, Some(1));
assert_eq!(
import.state.world_restore.stock_issue_and_buyback_allowed,
Some(true)
);
assert_eq!(
import.state.world_restore.bond_issue_and_repayment_allowed,
Some(false)
);
assert_eq!(import.state.world_restore.bankruptcy_allowed, Some(true));
assert_eq!(
import.state.world_restore.dividend_adjustment_allowed,
Some(false)
);
assert_eq!( assert_eq!(
import import
.state .state

View file

@ -52,12 +52,13 @@ pub use runtime::{
RUNTIME_WORLD_ISSUE_PRIME_RATE, RuntimeCargoCatalogEntry, RuntimeCargoClass, RUNTIME_WORLD_ISSUE_PRIME_RATE, RuntimeCargoCatalogEntry, RuntimeCargoClass,
RuntimeCargoPriceTarget, RuntimeCargoProductionTarget, RuntimeChairmanMetric, RuntimeCargoPriceTarget, RuntimeCargoProductionTarget, RuntimeChairmanMetric,
RuntimeChairmanProfile, RuntimeChairmanTarget, RuntimeCompany, RuntimeChairmanProfile, RuntimeChairmanTarget, RuntimeCompany,
RuntimeCompanyAnnualFinanceState, RuntimeCompanyBondSlot, RuntimeCompanyConditionTestScope, RuntimeCompanyAnnualCreditorPressureState, RuntimeCompanyAnnualFinanceState,
RuntimeCompanyControllerKind, RuntimeCompanyMarketMetric, RuntimeCompanyMarketState, RuntimeCompanyBondSlot, RuntimeCompanyConditionTestScope, RuntimeCompanyControllerKind,
RuntimeCompanyMetric, RuntimeCompanyStatBandCandidate, RuntimeCompanyStatSelector, RuntimeCompanyMarketMetric, RuntimeCompanyMarketState, RuntimeCompanyMetric,
RuntimeCompanyTarget, RuntimeCompanyTerritoryAccess, RuntimeCompanyTerritoryTrackPieceCount, RuntimeCompanyStatBandCandidate, RuntimeCompanyStatSelector, RuntimeCompanyTarget,
RuntimeCondition, RuntimeConditionComparator, RuntimeEffect, RuntimeEventRecord, RuntimeCompanyTerritoryAccess, RuntimeCompanyTerritoryTrackPieceCount, RuntimeCondition,
RuntimeEventRecordTemplate, RuntimeLocomotiveCatalogEntry, RuntimePackedEventCollectionSummary, RuntimeConditionComparator, RuntimeEffect, RuntimeEventRecord, RuntimeEventRecordTemplate,
RuntimeLocomotiveCatalogEntry, RuntimePackedEventCollectionSummary,
RuntimePackedEventCompactControlSummary, RuntimePackedEventConditionRowSummary, RuntimePackedEventCompactControlSummary, RuntimePackedEventConditionRowSummary,
RuntimePackedEventGroupedEffectRowSummary, RuntimePackedEventNegativeSentinelScopeSummary, RuntimePackedEventGroupedEffectRowSummary, RuntimePackedEventNegativeSentinelScopeSummary,
RuntimePackedEventRecordSummary, RuntimePackedEventTextBandSummary, RuntimePlayer, RuntimePackedEventRecordSummary, RuntimePackedEventTextBandSummary, RuntimePlayer,
@ -65,14 +66,18 @@ pub use runtime::{
RuntimeServiceState, RuntimeState, RuntimeTerritory, RuntimeTerritoryMetric, RuntimeServiceState, RuntimeState, RuntimeTerritory, RuntimeTerritoryMetric,
RuntimeTerritoryTarget, RuntimeTrackMetric, RuntimeTrackPieceCounts, RuntimeTrain, RuntimeTerritoryTarget, RuntimeTrackMetric, RuntimeTrackPieceCounts, RuntimeTrain,
RuntimeWorldFinanceNeighborhoodCandidate, RuntimeWorldIssueState, RuntimeWorldRestoreState, RuntimeWorldFinanceNeighborhoodCandidate, RuntimeWorldIssueState, RuntimeWorldRestoreState,
runtime_company_annual_finance_state, runtime_company_assigned_share_pool, runtime_company_annual_creditor_pressure_state, runtime_company_annual_finance_state,
runtime_company_average_live_bond_coupon, runtime_company_book_value_per_share, runtime_company_assigned_share_pool, runtime_company_average_live_bond_coupon,
runtime_company_credit_rating, runtime_company_investor_confidence, runtime_company_book_value_per_share, runtime_company_credit_rating,
runtime_company_management_attitude, runtime_company_market_value, runtime_company_prime_rate, runtime_company_investor_confidence, runtime_company_management_attitude,
runtime_company_market_value, runtime_company_prime_rate,
runtime_company_recent_per_share_subscore, runtime_company_stat_value, runtime_company_recent_per_share_subscore, runtime_company_stat_value,
runtime_company_stat_value_f64, runtime_company_unassigned_share_pool, runtime_company_stat_value_f64, runtime_company_unassigned_share_pool,
runtime_world_annual_finance_mode_active, runtime_world_bankruptcy_allowed,
runtime_world_bond_issue_and_repayment_allowed, runtime_world_dividend_adjustment_allowed,
runtime_world_issue_opinion_multiplier, runtime_world_issue_opinion_term_sum_raw, runtime_world_issue_opinion_multiplier, runtime_world_issue_opinion_term_sum_raw,
runtime_world_issue_state, runtime_world_prime_rate_baseline, runtime_world_issue_state, runtime_world_prime_rate_baseline,
runtime_world_stock_issue_and_buyback_allowed,
}; };
pub use smp::{ pub use smp::{
SMP_FOUR_SIDECAR_BYTE_PLANES_MIN_BUNDLE_VERSION, SmpAlignedRuntimeRuleBandLane, SMP_FOUR_SIDECAR_BYTE_PLANES_MIN_BUNDLE_VERSION, SmpAlignedRuntimeRuleBandLane,

View file

@ -177,6 +177,37 @@ pub struct RuntimeCompanyAnnualFinanceState {
pub linked_transit_latch: bool, pub linked_transit_latch: bool,
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RuntimeCompanyAnnualCreditorPressureState {
pub company_id: u32,
#[serde(default)]
pub annual_mode_active: Option<bool>,
#[serde(default)]
pub bankruptcy_allowed: Option<bool>,
#[serde(default)]
pub years_since_last_bankruptcy: Option<u32>,
#[serde(default)]
pub years_since_founding: Option<u32>,
pub recent_bad_net_profit_year_count: u32,
#[serde(default)]
pub recent_peak_revenue: Option<i64>,
#[serde(default)]
pub recent_three_year_net_profit_total: Option<i64>,
#[serde(default)]
pub pressure_ladder_cash_floor: Option<i64>,
#[serde(default)]
pub current_cash_plus_slot_12_total: Option<i64>,
#[serde(default)]
pub support_adjusted_share_price_floor: Option<i64>,
#[serde(default)]
pub support_adjusted_share_price_scalar: Option<i64>,
#[serde(default)]
pub current_fuel_cost: Option<i64>,
#[serde(default)]
pub current_fuel_cost_floor: Option<i64>,
pub eligible_for_bankruptcy_branch: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct RuntimeTrackPieceCounts { pub struct RuntimeTrackPieceCounts {
#[serde(default)] #[serde(default)]
@ -1101,6 +1132,22 @@ pub struct RuntimeWorldRestoreState {
#[serde(default)] #[serde(default)]
pub issue_37_multiplier_value_f32_text: Option<String>, pub issue_37_multiplier_value_f32_text: Option<String>,
#[serde(default)] #[serde(default)]
pub stock_issue_and_buyback_policy_raw_u8: Option<u8>,
#[serde(default)]
pub bond_issue_and_repayment_policy_raw_u8: Option<u8>,
#[serde(default)]
pub bankruptcy_policy_raw_u8: Option<u8>,
#[serde(default)]
pub dividend_policy_raw_u8: Option<u8>,
#[serde(default)]
pub stock_issue_and_buyback_allowed: Option<bool>,
#[serde(default)]
pub bond_issue_and_repayment_allowed: Option<bool>,
#[serde(default)]
pub bankruptcy_allowed: Option<bool>,
#[serde(default)]
pub dividend_adjustment_allowed: Option<bool>,
#[serde(default)]
pub finance_neighborhood_candidates: Vec<RuntimeWorldFinanceNeighborhoodCandidate>, pub finance_neighborhood_candidates: Vec<RuntimeWorldFinanceNeighborhoodCandidate>,
#[serde(default)] #[serde(default)]
pub economic_tuning_mirror_raw_u32: Option<u32>, pub economic_tuning_mirror_raw_u32: Option<u32>,
@ -2718,6 +2765,135 @@ pub fn runtime_company_average_live_bond_coupon(
Some(weighted_coupon_sum / total_principal as f64) Some(weighted_coupon_sum / total_principal as f64)
} }
pub fn runtime_world_annual_finance_mode_active(state: &RuntimeState) -> Option<bool> {
Some(state.world_restore.partial_year_progress_raw_u8? == 0x0c)
}
pub fn runtime_world_bankruptcy_allowed(state: &RuntimeState) -> Option<bool> {
Some(state.world_restore.bankruptcy_policy_raw_u8? == 0)
}
pub fn runtime_world_bond_issue_and_repayment_allowed(state: &RuntimeState) -> Option<bool> {
Some(state.world_restore.bond_issue_and_repayment_policy_raw_u8? == 0)
}
pub fn runtime_world_stock_issue_and_buyback_allowed(state: &RuntimeState) -> Option<bool> {
Some(state.world_restore.stock_issue_and_buyback_policy_raw_u8? == 0)
}
pub fn runtime_world_dividend_adjustment_allowed(state: &RuntimeState) -> Option<bool> {
Some(state.world_restore.dividend_policy_raw_u8? == 0)
}
pub fn runtime_company_annual_creditor_pressure_state(
state: &RuntimeState,
company_id: u32,
) -> Option<RuntimeCompanyAnnualCreditorPressureState> {
let annual_finance_state = runtime_company_annual_finance_state(state, company_id)?;
let current_cash_plus_slot_12_total =
runtime_company_control_transfer_stat_value_f64(state, company_id, 0x12)
.and_then(runtime_round_f64_to_i64)
.and_then(|slot_12| {
runtime_company_control_transfer_stat_value_f64(
state,
company_id,
RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH,
)
.and_then(runtime_round_f64_to_i64)
.map(|current_cash| current_cash + slot_12)
});
let support_adjusted_share_price_scalar =
runtime_company_support_adjusted_share_price_scalar_f64(state, company_id)
.and_then(runtime_round_f64_to_i64);
let current_fuel_cost = runtime_company_stat_value_f64(
state,
company_id,
RuntimeCompanyStatSelector {
family_id: RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER,
slot_id: 0x09,
},
)
.and_then(runtime_round_f64_to_i64);
let recent_bad_net_profit_year_count = annual_finance_state
.trailing_full_year_net_profits
.iter()
.take(3)
.filter(|value| **value < -10_000)
.count() as u32;
let recent_peak_revenue = annual_finance_state
.trailing_full_year_revenues
.iter()
.take(3)
.copied()
.max();
let recent_three_year_net_profit_total =
if annual_finance_state.trailing_full_year_net_profits.len() >= 3 {
Some(
annual_finance_state
.trailing_full_year_net_profits
.iter()
.take(3)
.sum::<i64>(),
)
} else {
None
};
let pressure_ladder_cash_floor = recent_peak_revenue.map(|revenue| {
if revenue < 120_000 {
-600_000
} else if revenue < 230_000 {
-1_100_000
} else if revenue < 340_000 {
-1_600_000
} else {
-2_000_000
}
});
let support_adjusted_share_price_floor = Some(if recent_bad_net_profit_year_count == 3 {
20
} else {
15
});
let current_fuel_cost_floor = pressure_ladder_cash_floor.map(|floor| floor * 8 / 100);
let eligible_for_bankruptcy_branch = runtime_world_annual_finance_mode_active(state)
== Some(true)
&& runtime_world_bankruptcy_allowed(state) == Some(true)
&& annual_finance_state
.years_since_last_bankruptcy
.is_some_and(|years| years >= 13)
&& annual_finance_state
.years_since_founding
.is_some_and(|years| years >= 4)
&& recent_bad_net_profit_year_count >= 2
&& current_cash_plus_slot_12_total
.zip(pressure_ladder_cash_floor)
.is_some_and(|(value, floor)| value <= floor)
&& support_adjusted_share_price_scalar
.zip(support_adjusted_share_price_floor)
.is_some_and(|(value, floor)| value >= floor)
&& current_fuel_cost
.zip(current_fuel_cost_floor)
.is_some_and(|(value, floor)| value <= floor)
&& recent_three_year_net_profit_total.is_some_and(|value| value <= -60_000);
Some(RuntimeCompanyAnnualCreditorPressureState {
company_id,
annual_mode_active: runtime_world_annual_finance_mode_active(state),
bankruptcy_allowed: runtime_world_bankruptcy_allowed(state),
years_since_last_bankruptcy: annual_finance_state.years_since_last_bankruptcy,
years_since_founding: annual_finance_state.years_since_founding,
recent_bad_net_profit_year_count,
recent_peak_revenue,
recent_three_year_net_profit_total,
pressure_ladder_cash_floor,
current_cash_plus_slot_12_total,
support_adjusted_share_price_floor,
support_adjusted_share_price_scalar,
current_fuel_cost,
current_fuel_cost_floor,
eligible_for_bankruptcy_branch,
})
}
pub fn runtime_world_absolute_counter(state: &RuntimeState) -> Option<u32> { pub fn runtime_world_absolute_counter(state: &RuntimeState) -> Option<u32> {
state.world_restore.absolute_counter_raw_u32 state.world_restore.absolute_counter_raw_u32
} }
@ -3522,6 +3698,14 @@ mod tests {
issue_3a_value: None, issue_3a_value: None,
issue_37_multiplier_raw_u32: None, issue_37_multiplier_raw_u32: None,
issue_37_multiplier_value_f32_text: None, issue_37_multiplier_value_f32_text: None,
stock_issue_and_buyback_policy_raw_u8: None,
bond_issue_and_repayment_policy_raw_u8: None,
bankruptcy_policy_raw_u8: None,
dividend_policy_raw_u8: None,
stock_issue_and_buyback_allowed: None,
bond_issue_and_repayment_allowed: None,
bankruptcy_allowed: None,
dividend_adjustment_allowed: None,
finance_neighborhood_candidates: Vec::new(), finance_neighborhood_candidates: Vec::new(),
economic_tuning_mirror_raw_u32: None, economic_tuning_mirror_raw_u32: None,
economic_tuning_mirror_value_f32_text: None, economic_tuning_mirror_value_f32_text: None,
@ -4859,7 +5043,7 @@ mod tests {
fn reads_grounded_company_stat_family_slots_from_runtime_state() { fn reads_grounded_company_stat_family_slots_from_runtime_state() {
let mut year_stat_family_qword_bits = vec![ let mut year_stat_family_qword_bits = vec![
0u64; 0u64;
(RUNTIME_COMPANY_STAT_SLOT_COUNT * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) ((RUNTIME_COMPANY_STAT_SLOT_COUNT + 2) * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN)
as usize as usize
]; ];
year_stat_family_qword_bits[(0x12 * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) as usize] = year_stat_family_qword_bits[(0x12 * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) as usize] =
@ -5271,7 +5455,7 @@ mod tests {
fn reads_year_relative_company_stat_family_from_saved_market_matrix() { fn reads_year_relative_company_stat_family_from_saved_market_matrix() {
let mut year_stat_family_qword_bits = vec![ let mut year_stat_family_qword_bits = vec![
0u64; 0u64;
(RUNTIME_COMPANY_STAT_SLOT_COUNT * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) ((RUNTIME_COMPANY_STAT_SLOT_COUNT + 2) * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN)
as usize as usize
]; ];
let write_year_value = |bits: &mut Vec<u64>, slot_id: u32, year_delta: u32, value: f64| { let write_year_value = |bits: &mut Vec<u64>, slot_id: u32, year_delta: u32, value: f64| {
@ -6626,6 +6810,132 @@ mod tests {
assert_eq!(runtime_company_annual_finance_state(&state, 99), None); assert_eq!(runtime_company_annual_finance_state(&state, 99), None);
} }
#[test]
fn derives_annual_creditor_pressure_from_rehosted_finance_owner_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_current_value = |bits: &mut Vec<u64>, slot_id: u32, value: f64| {
let index = (slot_id * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) as usize;
bits[index] = value.to_bits();
};
let write_prior_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();
};
write_current_value(&mut year_stat_family_qword_bits, 0x09, -50_000.0);
write_current_value(&mut year_stat_family_qword_bits, 0x0d, -700_000.0);
write_current_value(&mut year_stat_family_qword_bits, 0x12, 0.0);
write_prior_year_value(&mut year_stat_family_qword_bits, 0x01, 1, 100_000.0);
write_prior_year_value(&mut year_stat_family_qword_bits, 0x01, 2, 90_000.0);
write_prior_year_value(&mut year_stat_family_qword_bits, 0x01, 3, 80_000.0);
write_prior_year_value(&mut year_stat_family_qword_bits, 0x09, 1, -115_000.0);
write_prior_year_value(&mut year_stat_family_qword_bits, 0x09, 2, -110_000.0);
write_prior_year_value(&mut year_stat_family_qword_bits, 0x09, 3, -110_000.0);
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),
partial_year_progress_raw_u8: Some(0x0c),
bankruptcy_policy_raw_u8: Some(0),
bankruptcy_allowed: Some(true),
..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::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 {
founding_year: 1841,
last_bankruptcy_year: 1832,
cached_share_price_raw_u32: 25.0f32.to_bits(),
year_stat_family_qword_bits,
..RuntimeCompanyMarketState::default()
},
)]),
..RuntimeServiceState::default()
},
};
assert_eq!(runtime_world_annual_finance_mode_active(&state), Some(true));
assert_eq!(runtime_world_bankruptcy_allowed(&state), Some(true));
let pressure_state = runtime_company_annual_creditor_pressure_state(&state, 7)
.expect("creditor pressure state");
assert_eq!(pressure_state.recent_bad_net_profit_year_count, 3);
assert_eq!(pressure_state.recent_peak_revenue, Some(100_000));
assert_eq!(
pressure_state.recent_three_year_net_profit_total,
Some(-65_000)
);
assert_eq!(pressure_state.pressure_ladder_cash_floor, Some(-600_000));
assert_eq!(
pressure_state.current_cash_plus_slot_12_total,
Some(-700_000)
);
assert_eq!(pressure_state.support_adjusted_share_price_floor, Some(20));
assert_eq!(pressure_state.support_adjusted_share_price_scalar, Some(25));
assert_eq!(pressure_state.current_fuel_cost, Some(-50_000));
assert_eq!(pressure_state.current_fuel_cost_floor, Some(-48_000));
assert!(pressure_state.eligible_for_bankruptcy_branch);
}
#[test] #[test]
fn reads_company_market_metrics_from_annual_finance_reader() { fn reads_company_market_metrics_from_annual_finance_reader() {
let current_issue_calendar_word = 0x0101_0726; let current_issue_calendar_word = 0x0101_0726;

View file

@ -133,6 +133,10 @@ const RT3_SAVE_WORLD_BLOCK_ISSUE_OPINION_TERM_COUNT: usize = 0x3b;
const RT3_SAVE_WORLD_BLOCK_CHAIRMAN_SLOT_SELECTOR_RELATIVE_OFFSET: usize = 0x83; const RT3_SAVE_WORLD_BLOCK_CHAIRMAN_SLOT_SELECTOR_RELATIVE_OFFSET: usize = 0x83;
const RT3_SAVE_WORLD_BLOCK_CAMPAIGN_OVERRIDE_FLAG_RELATIVE_OFFSET: usize = 0xc1; const RT3_SAVE_WORLD_BLOCK_CAMPAIGN_OVERRIDE_FLAG_RELATIVE_OFFSET: usize = 0xc1;
const RT3_SAVE_WORLD_BLOCK_CHAIRMAN_ROLE_GATE_RELATIVE_OFFSET: usize = 0x0bbf; const RT3_SAVE_WORLD_BLOCK_CHAIRMAN_ROLE_GATE_RELATIVE_OFFSET: usize = 0x0bbf;
const RT3_SAVE_WORLD_BLOCK_STOCK_POLICY_RELATIVE_OFFSET: usize = 0x4a83;
const RT3_SAVE_WORLD_BLOCK_BOND_POLICY_RELATIVE_OFFSET: usize = 0x4a87;
const RT3_SAVE_WORLD_BLOCK_BANKRUPTCY_POLICY_RELATIVE_OFFSET: usize = 0x4a8b;
const RT3_SAVE_WORLD_BLOCK_DIVIDEND_POLICY_RELATIVE_OFFSET: usize = 0x4a8f;
const RT3_SAVE_WORLD_BLOCK_ECONOMIC_TUNING_MIRROR_RELATIVE_OFFSET: usize = 0x0bda; const RT3_SAVE_WORLD_BLOCK_ECONOMIC_TUNING_MIRROR_RELATIVE_OFFSET: usize = 0x0bda;
const RT3_SAVE_WORLD_BLOCK_ECONOMIC_TUNING_PRIMARY_RELATIVE_OFFSETS: [usize; 6] = const RT3_SAVE_WORLD_BLOCK_ECONOMIC_TUNING_PRIMARY_RELATIVE_OFFSETS: [usize; 6] =
[0x0bde, 0x0be2, 0x0be6, 0x0bea, 0x0bee, 0x0bf2]; [0x0bde, 0x0be2, 0x0be6, 0x0bea, 0x0bee, 0x0bf2];
@ -1572,6 +1576,14 @@ pub struct SmpSaveWorldFinanceNeighborhoodProbe {
pub current_calendar_tuple_word_2_lane: SmpSaveDwordCandidate, pub current_calendar_tuple_word_2_lane: SmpSaveDwordCandidate,
pub absolute_counter_lane: SmpSaveDwordCandidate, pub absolute_counter_lane: SmpSaveDwordCandidate,
pub absolute_counter_mirror_lane: SmpSaveDwordCandidate, pub absolute_counter_mirror_lane: SmpSaveDwordCandidate,
pub stock_policy_raw_u8: u8,
pub stock_policy_raw_hex: String,
pub bond_policy_raw_u8: u8,
pub bond_policy_raw_hex: String,
pub bankruptcy_policy_raw_u8: u8,
pub bankruptcy_policy_raw_hex: String,
pub dividend_policy_raw_u8: u8,
pub dividend_policy_raw_hex: String,
pub dword_candidates: Vec<SmpSaveDwordCandidate>, pub dword_candidates: Vec<SmpSaveDwordCandidate>,
pub evidence: Vec<String>, pub evidence: Vec<String>,
} }
@ -2256,6 +2268,14 @@ pub struct SmpLoadedWorldFinanceNeighborhoodState {
pub absolute_counter_raw_hex: String, pub absolute_counter_raw_hex: String,
pub absolute_counter_mirror_raw_u32: u32, pub absolute_counter_mirror_raw_u32: u32,
pub absolute_counter_mirror_raw_hex: String, pub absolute_counter_mirror_raw_hex: String,
pub stock_policy_raw_u8: u8,
pub stock_policy_raw_hex: String,
pub bond_policy_raw_u8: u8,
pub bond_policy_raw_hex: String,
pub bankruptcy_policy_raw_u8: u8,
pub bankruptcy_policy_raw_hex: String,
pub dividend_policy_raw_u8: u8,
pub dividend_policy_raw_hex: String,
pub labels: Vec<String>, pub labels: Vec<String>,
pub relative_offsets: Vec<usize>, pub relative_offsets: Vec<usize>,
pub relative_offset_hex: Vec<String>, pub relative_offset_hex: Vec<String>,
@ -3500,6 +3520,14 @@ fn derive_loaded_world_finance_neighborhood_state_from_probe(
absolute_counter_raw_hex: probe.absolute_counter_lane.raw_u32_hex.clone(), absolute_counter_raw_hex: probe.absolute_counter_lane.raw_u32_hex.clone(),
absolute_counter_mirror_raw_u32: probe.absolute_counter_mirror_lane.raw_u32, absolute_counter_mirror_raw_u32: probe.absolute_counter_mirror_lane.raw_u32,
absolute_counter_mirror_raw_hex: probe.absolute_counter_mirror_lane.raw_u32_hex.clone(), absolute_counter_mirror_raw_hex: probe.absolute_counter_mirror_lane.raw_u32_hex.clone(),
stock_policy_raw_u8: probe.stock_policy_raw_u8,
stock_policy_raw_hex: probe.stock_policy_raw_hex.clone(),
bond_policy_raw_u8: probe.bond_policy_raw_u8,
bond_policy_raw_hex: probe.bond_policy_raw_hex.clone(),
bankruptcy_policy_raw_u8: probe.bankruptcy_policy_raw_u8,
bankruptcy_policy_raw_hex: probe.bankruptcy_policy_raw_hex.clone(),
dividend_policy_raw_u8: probe.dividend_policy_raw_u8,
dividend_policy_raw_hex: probe.dividend_policy_raw_hex.clone(),
labels: probe labels: probe
.dword_candidates .dword_candidates
.iter() .iter()
@ -9100,6 +9128,22 @@ fn parse_save_world_finance_neighborhood_probe(
"absolute_calendar_counter_mirror", "absolute_calendar_counter_mirror",
RT3_SAVE_WORLD_BLOCK_ABSOLUTE_COUNTER_MIRROR_RELATIVE_OFFSET, RT3_SAVE_WORLD_BLOCK_ABSOLUTE_COUNTER_MIRROR_RELATIVE_OFFSET,
)?; )?;
let stock_policy_raw_u8 = read_u8_at(
bytes,
payload_offset + RT3_SAVE_WORLD_BLOCK_STOCK_POLICY_RELATIVE_OFFSET,
)?;
let bond_policy_raw_u8 = read_u8_at(
bytes,
payload_offset + RT3_SAVE_WORLD_BLOCK_BOND_POLICY_RELATIVE_OFFSET,
)?;
let bankruptcy_policy_raw_u8 = read_u8_at(
bytes,
payload_offset + RT3_SAVE_WORLD_BLOCK_BANKRUPTCY_POLICY_RELATIVE_OFFSET,
)?;
let dividend_policy_raw_u8 = read_u8_at(
bytes,
payload_offset + RT3_SAVE_WORLD_BLOCK_DIVIDEND_POLICY_RELATIVE_OFFSET,
)?;
let dword_candidates = let dword_candidates =
build_save_world_finance_neighborhood_candidates(bytes, payload_offset)?; build_save_world_finance_neighborhood_candidates(bytes, payload_offset)?;
@ -9119,6 +9163,14 @@ fn parse_save_world_finance_neighborhood_probe(
current_calendar_tuple_word_2_lane, current_calendar_tuple_word_2_lane,
absolute_counter_lane, absolute_counter_lane,
absolute_counter_mirror_lane, absolute_counter_mirror_lane,
stock_policy_raw_u8,
stock_policy_raw_hex: format!("0x{stock_policy_raw_u8:02x}"),
bond_policy_raw_u8,
bond_policy_raw_hex: format!("0x{bond_policy_raw_u8:02x}"),
bankruptcy_policy_raw_u8,
bankruptcy_policy_raw_hex: format!("0x{bankruptcy_policy_raw_u8:02x}"),
dividend_policy_raw_u8,
dividend_policy_raw_hex: format!("0x{dividend_policy_raw_u8:02x}"),
dword_candidates, dword_candidates,
evidence: vec![ evidence: vec![
format!( format!(
@ -9133,6 +9185,13 @@ fn parse_save_world_finance_neighborhood_probe(
RT3_SAVE_WORLD_BLOCK_CURRENT_CALENDAR_TUPLE_WORD_2_RELATIVE_OFFSET, RT3_SAVE_WORLD_BLOCK_CURRENT_CALENDAR_TUPLE_WORD_2_RELATIVE_OFFSET,
RT3_SAVE_WORLD_BLOCK_ABSOLUTE_COUNTER_RELATIVE_OFFSET RT3_SAVE_WORLD_BLOCK_ABSOLUTE_COUNTER_RELATIVE_OFFSET
), ),
format!(
"payload +0x{:x}/+0x{:x}/+0x{:x}/+0x{:x} carry the stock, bond, bankruptcy, and dividend finance-policy bytes mirrored from scenario offsets 0x4a87/0x4a8b/0x4a8f/0x4a93",
RT3_SAVE_WORLD_BLOCK_STOCK_POLICY_RELATIVE_OFFSET,
RT3_SAVE_WORLD_BLOCK_BOND_POLICY_RELATIVE_OFFSET,
RT3_SAVE_WORLD_BLOCK_BANKRUPTCY_POLICY_RELATIVE_OFFSET,
RT3_SAVE_WORLD_BLOCK_DIVIDEND_POLICY_RELATIVE_OFFSET
),
"finance-neighborhood candidates cover the fixed dword strip around the grounded world calendar tuple, absolute-counter, selection-context, and issue-0x37 lanes so broader finance reader closure can build on one rehosted owner surface.".to_string(), "finance-neighborhood candidates cover the fixed dword strip around the grounded world calendar tuple, absolute-counter, selection-context, and issue-0x37 lanes so broader finance reader closure can build on one rehosted owner surface.".to_string(),
], ],
}); });
@ -15868,6 +15927,10 @@ mod tests {
bytes[payload_offset + relative_offset..payload_offset + relative_offset + 4] bytes[payload_offset + relative_offset..payload_offset + relative_offset + 4]
.copy_from_slice(&((index as u32) + 1).to_le_bytes()); .copy_from_slice(&((index as u32) + 1).to_le_bytes());
} }
bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_STOCK_POLICY_RELATIVE_OFFSET] = 1;
bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_BOND_POLICY_RELATIVE_OFFSET] = 2;
bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_BANKRUPTCY_POLICY_RELATIVE_OFFSET] = 3;
bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_DIVIDEND_POLICY_RELATIVE_OFFSET] = 4;
let next_chunk_offset = payload_offset + RT3_SAVE_WORLD_BLOCK_LEN; let next_chunk_offset = payload_offset + RT3_SAVE_WORLD_BLOCK_LEN;
bytes[next_chunk_offset..next_chunk_offset + 4] bytes[next_chunk_offset..next_chunk_offset + 4]
.copy_from_slice(&RT3_SAVE_WORLD_BLOCK_NEXT_CHUNK_TAG.to_le_bytes()); .copy_from_slice(&RT3_SAVE_WORLD_BLOCK_NEXT_CHUNK_TAG.to_le_bytes());
@ -15897,6 +15960,14 @@ mod tests {
assert_eq!(probe.packed_year_word_raw_hex, "0x0001"); assert_eq!(probe.packed_year_word_raw_hex, "0x0001");
assert_eq!(probe.partial_year_progress_raw_u8, 0); assert_eq!(probe.partial_year_progress_raw_u8, 0);
assert_eq!(probe.partial_year_progress_raw_hex, "0x00"); assert_eq!(probe.partial_year_progress_raw_hex, "0x00");
assert_eq!(probe.stock_policy_raw_u8, 1);
assert_eq!(probe.stock_policy_raw_hex, "0x01");
assert_eq!(probe.bond_policy_raw_u8, 2);
assert_eq!(probe.bond_policy_raw_hex, "0x02");
assert_eq!(probe.bankruptcy_policy_raw_u8, 3);
assert_eq!(probe.bankruptcy_policy_raw_hex, "0x03");
assert_eq!(probe.dividend_policy_raw_u8, 4);
assert_eq!(probe.dividend_policy_raw_hex, "0x04");
assert_eq!(probe.current_calendar_tuple_word_lane.value_i32, 1); assert_eq!(probe.current_calendar_tuple_word_lane.value_i32, 1);
assert_eq!( assert_eq!(
probe.current_calendar_tuple_word_2_lane.relative_offset_hex, probe.current_calendar_tuple_word_2_lane.relative_offset_hex,

View file

@ -1,8 +1,8 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{
CalendarPoint, RuntimeState, runtime_company_annual_finance_state, CalendarPoint, RuntimeState, runtime_company_annual_creditor_pressure_state,
runtime_company_unassigned_share_pool, runtime_company_annual_finance_state, runtime_company_unassigned_share_pool,
}; };
fn raw_u32_to_f32_text(raw: u32) -> String { fn raw_u32_to_f32_text(raw: u32) -> String {
@ -50,6 +50,14 @@ pub struct RuntimeSummary {
pub world_restore_issue_3a_value: Option<u32>, pub world_restore_issue_3a_value: Option<u32>,
pub world_restore_issue_37_multiplier_raw_u32: Option<u32>, pub world_restore_issue_37_multiplier_raw_u32: Option<u32>,
pub world_restore_issue_37_multiplier_value_f32_text: Option<String>, pub world_restore_issue_37_multiplier_value_f32_text: Option<String>,
pub world_restore_stock_issue_and_buyback_policy_raw_u8: Option<u8>,
pub world_restore_bond_issue_and_repayment_policy_raw_u8: Option<u8>,
pub world_restore_bankruptcy_policy_raw_u8: Option<u8>,
pub world_restore_dividend_policy_raw_u8: Option<u8>,
pub world_restore_stock_issue_and_buyback_allowed: Option<bool>,
pub world_restore_bond_issue_and_repayment_allowed: Option<bool>,
pub world_restore_bankruptcy_allowed: Option<bool>,
pub world_restore_dividend_adjustment_allowed: Option<bool>,
pub world_restore_finance_neighborhood_count: usize, pub world_restore_finance_neighborhood_count: usize,
pub world_restore_finance_neighborhood_labels: Vec<String>, pub world_restore_finance_neighborhood_labels: Vec<String>,
pub world_restore_economic_tuning_mirror_raw_u32: Option<u32>, pub world_restore_economic_tuning_mirror_raw_u32: Option<u32>,
@ -87,6 +95,16 @@ pub struct RuntimeSummary {
pub selected_company_current_issue_age_absolute_counter_delta: Option<i64>, pub selected_company_current_issue_age_absolute_counter_delta: Option<i64>,
pub selected_company_chairman_bonus_year: Option<u32>, pub selected_company_chairman_bonus_year: Option<u32>,
pub selected_company_chairman_bonus_amount: Option<i32>, pub selected_company_chairman_bonus_amount: Option<i32>,
pub selected_company_creditor_pressure_recent_bad_net_profit_year_count: Option<u32>,
pub selected_company_creditor_pressure_recent_peak_revenue: Option<i64>,
pub selected_company_creditor_pressure_recent_three_year_net_profit_total: Option<i64>,
pub selected_company_creditor_pressure_cash_floor: Option<i64>,
pub selected_company_creditor_pressure_cash_plus_slot_12_total: Option<i64>,
pub selected_company_creditor_pressure_share_price_floor: Option<i64>,
pub selected_company_creditor_pressure_share_price_scalar: Option<i64>,
pub selected_company_creditor_pressure_current_fuel_cost: Option<i64>,
pub selected_company_creditor_pressure_current_fuel_cost_floor: Option<i64>,
pub selected_company_creditor_pressure_eligible_for_bankruptcy_branch: Option<bool>,
pub player_count: usize, pub player_count: usize,
pub chairman_profile_count: usize, pub chairman_profile_count: usize,
pub active_chairman_profile_count: usize, pub active_chairman_profile_count: usize,
@ -176,6 +194,10 @@ impl RuntimeSummary {
let selected_company_annual_finance_state = state let selected_company_annual_finance_state = state
.selected_company_id .selected_company_id
.and_then(|company_id| runtime_company_annual_finance_state(state, company_id)); .and_then(|company_id| runtime_company_annual_finance_state(state, company_id));
let selected_company_creditor_pressure_state =
state.selected_company_id.and_then(|company_id| {
runtime_company_annual_creditor_pressure_state(state, company_id)
});
Self { Self {
calendar: state.calendar, calendar: state.calendar,
calendar_projection_source: state.metadata.get("save_slice.calendar_source").cloned(), calendar_projection_source: state.metadata.get("save_slice.calendar_source").cloned(),
@ -255,6 +277,24 @@ impl RuntimeSummary {
.world_restore .world_restore
.issue_37_multiplier_value_f32_text .issue_37_multiplier_value_f32_text
.clone(), .clone(),
world_restore_stock_issue_and_buyback_policy_raw_u8: state
.world_restore
.stock_issue_and_buyback_policy_raw_u8,
world_restore_bond_issue_and_repayment_policy_raw_u8: state
.world_restore
.bond_issue_and_repayment_policy_raw_u8,
world_restore_bankruptcy_policy_raw_u8: state.world_restore.bankruptcy_policy_raw_u8,
world_restore_dividend_policy_raw_u8: state.world_restore.dividend_policy_raw_u8,
world_restore_stock_issue_and_buyback_allowed: state
.world_restore
.stock_issue_and_buyback_allowed,
world_restore_bond_issue_and_repayment_allowed: state
.world_restore
.bond_issue_and_repayment_allowed,
world_restore_bankruptcy_allowed: state.world_restore.bankruptcy_allowed,
world_restore_dividend_adjustment_allowed: state
.world_restore
.dividend_adjustment_allowed,
world_restore_finance_neighborhood_count: state world_restore_finance_neighborhood_count: state
.world_restore .world_restore
.finance_neighborhood_candidates .finance_neighborhood_candidates
@ -372,6 +412,45 @@ impl RuntimeSummary {
selected_company_chairman_bonus_amount: selected_company_market_state selected_company_chairman_bonus_amount: selected_company_market_state
.map(|market_state| market_state.chairman_bonus_amount) .map(|market_state| market_state.chairman_bonus_amount)
.filter(|amount| *amount != 0), .filter(|amount| *amount != 0),
selected_company_creditor_pressure_recent_bad_net_profit_year_count:
selected_company_creditor_pressure_state
.as_ref()
.map(|pressure_state| pressure_state.recent_bad_net_profit_year_count),
selected_company_creditor_pressure_recent_peak_revenue:
selected_company_creditor_pressure_state
.as_ref()
.and_then(|pressure_state| pressure_state.recent_peak_revenue),
selected_company_creditor_pressure_recent_three_year_net_profit_total:
selected_company_creditor_pressure_state
.as_ref()
.and_then(|pressure_state| pressure_state.recent_three_year_net_profit_total),
selected_company_creditor_pressure_cash_floor: selected_company_creditor_pressure_state
.as_ref()
.and_then(|pressure_state| pressure_state.pressure_ladder_cash_floor),
selected_company_creditor_pressure_cash_plus_slot_12_total:
selected_company_creditor_pressure_state
.as_ref()
.and_then(|pressure_state| pressure_state.current_cash_plus_slot_12_total),
selected_company_creditor_pressure_share_price_floor:
selected_company_creditor_pressure_state
.as_ref()
.and_then(|pressure_state| pressure_state.support_adjusted_share_price_floor),
selected_company_creditor_pressure_share_price_scalar:
selected_company_creditor_pressure_state
.as_ref()
.and_then(|pressure_state| pressure_state.support_adjusted_share_price_scalar),
selected_company_creditor_pressure_current_fuel_cost:
selected_company_creditor_pressure_state
.as_ref()
.and_then(|pressure_state| pressure_state.current_fuel_cost),
selected_company_creditor_pressure_current_fuel_cost_floor:
selected_company_creditor_pressure_state
.as_ref()
.and_then(|pressure_state| pressure_state.current_fuel_cost_floor),
selected_company_creditor_pressure_eligible_for_bankruptcy_branch:
selected_company_creditor_pressure_state
.as_ref()
.map(|pressure_state| pressure_state.eligible_for_bankruptcy_branch),
player_count: state.players.len(), player_count: state.players.len(),
chairman_profile_count: state.chairman_profiles.len(), chairman_profile_count: state.chairman_profiles.len(),
active_chairman_profile_count: state active_chairman_profile_count: state
@ -1217,6 +1296,14 @@ mod tests {
issue_3a_value: Some(4), issue_3a_value: Some(4),
issue_37_multiplier_raw_u32: Some(0x3d75c28f), issue_37_multiplier_raw_u32: Some(0x3d75c28f),
issue_37_multiplier_value_f32_text: Some("0.06".to_string()), issue_37_multiplier_value_f32_text: Some("0.06".to_string()),
stock_issue_and_buyback_policy_raw_u8: Some(0),
bond_issue_and_repayment_policy_raw_u8: Some(1),
bankruptcy_policy_raw_u8: Some(0),
dividend_policy_raw_u8: Some(1),
stock_issue_and_buyback_allowed: Some(true),
bond_issue_and_repayment_allowed: Some(false),
bankruptcy_allowed: Some(true),
dividend_adjustment_allowed: Some(false),
economic_tuning_mirror_raw_u32: Some(0x3f46dff5), economic_tuning_mirror_raw_u32: Some(0x3f46dff5),
economic_tuning_mirror_value_f32_text: Some("0.7766201".to_string()), economic_tuning_mirror_value_f32_text: Some("0.7766201".to_string()),
economic_tuning_lane_raw_u32: vec![ economic_tuning_lane_raw_u32: vec![
@ -1301,6 +1388,29 @@ mod tests {
summary.world_restore_issue_37_multiplier_raw_u32, summary.world_restore_issue_37_multiplier_raw_u32,
Some(0x3d75c28f) Some(0x3d75c28f)
); );
assert_eq!(
summary.world_restore_stock_issue_and_buyback_policy_raw_u8,
Some(0)
);
assert_eq!(
summary.world_restore_bond_issue_and_repayment_policy_raw_u8,
Some(1)
);
assert_eq!(summary.world_restore_bankruptcy_policy_raw_u8, Some(0));
assert_eq!(summary.world_restore_dividend_policy_raw_u8, Some(1));
assert_eq!(
summary.world_restore_stock_issue_and_buyback_allowed,
Some(true)
);
assert_eq!(
summary.world_restore_bond_issue_and_repayment_allowed,
Some(false)
);
assert_eq!(summary.world_restore_bankruptcy_allowed, Some(true));
assert_eq!(
summary.world_restore_dividend_adjustment_allowed,
Some(false)
);
assert_eq!( assert_eq!(
summary summary
.world_restore_issue_37_multiplier_value_f32_text .world_restore_issue_37_multiplier_value_f32_text
@ -2177,4 +2287,153 @@ mod tests {
assert_eq!(summary.selected_company_chairman_bonus_year, Some(1842)); assert_eq!(summary.selected_company_chairman_bonus_year, Some(1842));
assert_eq!(summary.selected_company_chairman_bonus_amount, Some(750)); assert_eq!(summary.selected_company_chairman_bonus_amount, Some(750));
} }
#[test]
fn summarizes_selected_company_creditor_pressure_branch_state() {
let mut year_stat_family_qword_bits = vec![
0u64;
((crate::RUNTIME_COMPANY_STAT_SLOT_COUNT + 2)
* crate::RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN)
as usize
];
let write_current_value = |bits: &mut Vec<u64>, slot_id: u32, value: f64| {
let index = (slot_id * crate::RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) as usize;
bits[index] = value.to_bits();
};
let write_prior_year_value =
|bits: &mut Vec<u64>, slot_id: u32, year_delta: u32, value: f64| {
let index =
(slot_id * crate::RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN + year_delta) as usize;
bits[index] = value.to_bits();
};
write_current_value(&mut year_stat_family_qword_bits, 0x09, -50_000.0);
write_current_value(&mut year_stat_family_qword_bits, 0x0d, -700_000.0);
write_current_value(&mut year_stat_family_qword_bits, 0x12, 0.0);
write_prior_year_value(&mut year_stat_family_qword_bits, 0x01, 1, 100_000.0);
write_prior_year_value(&mut year_stat_family_qword_bits, 0x01, 2, 90_000.0);
write_prior_year_value(&mut year_stat_family_qword_bits, 0x01, 3, 80_000.0);
write_prior_year_value(&mut year_stat_family_qword_bits, 0x09, 1, -115_000.0);
write_prior_year_value(&mut year_stat_family_qword_bits, 0x09, 2, -110_000.0);
write_prior_year_value(&mut year_stat_family_qword_bits, 0x09, 3, -110_000.0);
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),
partial_year_progress_raw_u8: Some(0x0c),
bankruptcy_policy_raw_u8: Some(0),
bankruptcy_allowed: Some(true),
..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: crate::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: Some(7),
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,
crate::RuntimeCompanyMarketState {
founding_year: 1841,
last_bankruptcy_year: 1832,
cached_share_price_raw_u32: 25.0f32.to_bits(),
year_stat_family_qword_bits,
..crate::RuntimeCompanyMarketState::default()
},
)]),
..RuntimeServiceState::default()
},
};
let summary = RuntimeSummary::from_state(&state);
assert_eq!(
summary.selected_company_creditor_pressure_recent_bad_net_profit_year_count,
Some(3)
);
assert_eq!(
summary.selected_company_creditor_pressure_recent_peak_revenue,
Some(100_000)
);
assert_eq!(
summary.selected_company_creditor_pressure_recent_three_year_net_profit_total,
Some(-65_000)
);
assert_eq!(
summary.selected_company_creditor_pressure_cash_floor,
Some(-600_000)
);
assert_eq!(
summary.selected_company_creditor_pressure_cash_plus_slot_12_total,
Some(-700_000)
);
assert_eq!(
summary.selected_company_creditor_pressure_share_price_floor,
Some(20)
);
assert_eq!(
summary.selected_company_creditor_pressure_share_price_scalar,
Some(25)
);
assert_eq!(
summary.selected_company_creditor_pressure_current_fuel_cost,
Some(-50_000)
);
assert_eq!(
summary.selected_company_creditor_pressure_current_fuel_cost_floor,
Some(-48_000)
);
assert_eq!(
summary.selected_company_creditor_pressure_eligible_for_bankruptcy_branch,
Some(true)
);
}
} }

View file

@ -134,7 +134,9 @@ The highest-value next passes are now:
and last bankruptcy for later annual finance-policy rehosting; live bond-slot count now travels through that same owned annual-finance and last bankruptcy for later annual finance-policy rehosting; live bond-slot count now travels through that same owned annual-finance
state for the stock-capital branch gate, and the grounded bond table now also contributes both state for the stock-capital branch gate, and the grounded bond table now also contributes both
the largest live bond principal and the chosen highest-coupon live bond principal into that same the largest live bond principal and the chosen highest-coupon live bond principal into that same
owner-state surface owner-state surface; the same fixed-world save block now also carries the raw stock, bond,
bankruptcy, and dividend finance-policy bytes, and the first annual creditor-pressure branch now
executes as a pure runtime reader over that owner state instead of remaining atlas-only
- the project rule on the remaining closure work is now explicit too: when one runtime-facing field - the project rule on the remaining closure work is now explicit too: when one runtime-facing field
is still ambiguous, prefer rehosting the owning source state or real reader/setter family first is still ambiguous, prefer rehosting the owning source state or real reader/setter family first
instead of guessing another derived leaf field from neighboring raw offsets instead of guessing another derived leaf field from neighboring raw offsets

View file

@ -225,7 +225,10 @@ owned company market and annual-finance state, matching the stock-capital branch
at least two live bonds. The same grounded bond table now also contributes both the largest live at least two live bonds. The same grounded bond table now also contributes both the largest live
bond principal and the chosen highest-coupon live bond principal into owned company market and bond principal and the chosen highest-coupon live bond principal into owned company market and
annual-finance state, so later stock-capital gates can extend a rehosted owner-state seam instead annual-finance state, so later stock-capital gates can extend a rehosted owner-state seam instead
of guessing another finance leaf. of guessing another finance leaf. The same fixed-world save block now also carries the raw stock,
bond, bankruptcy, and dividend finance-policy bytes, and the earliest annual creditor-pressure
bankruptcy branch now runs as a pure runtime reader over owned annual-finance state, support-
adjusted share price, and those policy bytes rather than staying in atlas prose only.
## Why This Boundary ## Why This Boundary