Rehost company investor confidence cache reader

This commit is contained in:
Jan Petykiewicz 2026-04-17 23:43:19 -07:00
commit 6112141bb8
3 changed files with 254 additions and 7 deletions

View file

@ -67,11 +67,12 @@ pub use runtime::{
RuntimeWorldFinanceNeighborhoodCandidate, RuntimeWorldIssueState, RuntimeWorldRestoreState,
runtime_company_annual_finance_state, runtime_company_assigned_share_pool,
runtime_company_average_live_bond_coupon, runtime_company_book_value_per_share,
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,
runtime_company_credit_rating, runtime_company_investor_confidence,
runtime_company_market_value, runtime_company_prime_rate,
runtime_company_recent_per_share_subscore, 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,

View file

@ -2047,6 +2047,91 @@ pub fn runtime_company_book_value_per_share(state: &RuntimeState, company_id: u3
.and_then(runtime_round_f64_to_i64)
}
fn runtime_decode_saved_f32_value_f64(raw_u32: u32) -> Option<f64> {
let value = f32::from_bits(raw_u32) as f64;
if !value.is_finite() {
return None;
}
Some(value)
}
pub fn runtime_company_recent_per_share_subscore(
state: &RuntimeState,
company_id: u32,
) -> Option<f64> {
let market_state = state.service_state.company_market_state.get(&company_id)?;
runtime_decode_saved_f32_value_f64(market_state.recent_per_share_subscore_raw_u32)
}
fn runtime_company_support_adjusted_share_price_scalar_f64(
state: &RuntimeState,
company_id: u32,
) -> Option<f64> {
let market_state = state.service_state.company_market_state.get(&company_id)?;
if let Some(cached_value) =
runtime_decode_saved_f32_value_f64(market_state.cached_share_price_raw_u32)
{
return Some(cached_value.max(0.0001));
}
if market_state.outstanding_shares == 0 {
return Some(0.0010000000474974513);
}
let company = state
.companies
.iter()
.find(|company| company.company_id == company_id)?;
let mut recent_per_share = runtime_company_recent_per_share_subscore(state, company_id)?;
let young_company_support =
runtime_decode_saved_f32_value_f64(market_state.young_company_support_scalar_raw_u32)?;
if recent_per_share < young_company_support {
let years_since_founding =
derive_runtime_company_elapsed_years(state.calendar.year, market_state.founding_year)
.unwrap_or(u32::MAX);
if years_since_founding <= 5 {
let elapsed_support_ticks = runtime_world_absolute_counter(state)
.unwrap_or(0)
.saturating_sub(market_state.support_progress_word);
let interpolation = (elapsed_support_ticks / 50).min(50) as f64;
recent_per_share = ((50.0 - interpolation) * young_company_support
+ (50.0 + interpolation) * recent_per_share)
/ 100.0;
}
}
let mutable_support =
runtime_decode_saved_f32_value_f64(market_state.mutable_support_scalar_raw_u32)?;
let share_count_growth_ratio = ((market_state.outstanding_shares as f64
+ 1.4
* mutable_support
* ((market_state.outstanding_shares as f64 / 20_000.0).powf(0.33)))
/ market_state.outstanding_shares as f64)
.clamp(0.3, 6.0);
let investor_multiplier = runtime_world_issue_opinion_multiplier(
state,
RUNTIME_WORLD_ISSUE_INVESTOR_CONFIDENCE,
company.linked_chairman_profile_id,
Some(company_id),
None,
)?;
Some(((recent_per_share * share_count_growth_ratio * investor_multiplier) + 1.0).max(0.0001))
}
pub fn runtime_company_investor_confidence(state: &RuntimeState, company_id: u32) -> Option<i64> {
let company = state
.companies
.iter()
.find(|company| company.company_id == company_id)?;
if company.investor_confidence != 0 {
return Some(company.investor_confidence);
}
runtime_company_support_adjusted_share_price_scalar_f64(state, company_id)
.and_then(runtime_round_f64_to_i64)
}
fn runtime_company_control_transfer_stat_value_f64(
state: &RuntimeState,
company_id: u32,
@ -2108,6 +2193,7 @@ fn runtime_company_control_transfer_stat_value_f64(
value / market_state.outstanding_shares as f64
}
}),
0x13 => runtime_company_support_adjusted_share_price_scalar_f64(state, company_id),
0x17 => runtime_company_direct_float_field_value_f64(state, company_id, 0x4b),
RUNTIME_COMPANY_STAT_SLOT_CREDIT_RATING => {
runtime_company_credit_rating(state, company_id).map(|value| value as f64)
@ -4954,6 +5040,96 @@ mod tests {
);
}
#[test]
fn reads_investor_confidence_from_rehosted_company_share_price_cache() {
let state = RuntimeState {
calendar: CalendarPoint {
year: 1830,
month_slot: 0,
phase_slot: 0,
tick_slot: 0,
},
world_flags: BTreeMap::new(),
save_profile: RuntimeSaveProfileState::default(),
world_restore: 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 {
recent_per_share_subscore_raw_u32: 12.0f32.to_bits(),
cached_share_price_raw_u32: 37.0f32.to_bits(),
..RuntimeCompanyMarketState::default()
},
)]),
..RuntimeServiceState::default()
},
};
assert_eq!(
runtime_company_recent_per_share_subscore(&state, 7),
Some(12.0)
);
assert_eq!(runtime_company_investor_confidence(&state, 7), Some(37));
assert_eq!(
runtime_company_stat_value(
&state,
7,
RuntimeCompanyStatSelector {
family_id: RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER,
slot_id: 0x13,
},
),
Some(37)
);
}
#[test]
fn reads_year_relative_company_stat_family_from_saved_market_matrix() {
let mut year_stat_family_qword_bits = vec![

View file

@ -9,7 +9,7 @@ use crate::{
RuntimeEffect, RuntimeEventRecordTemplate, RuntimePlayerTarget, RuntimeState, RuntimeSummary,
RuntimeTerritoryMetric, RuntimeTerritoryTarget, RuntimeTrackMetric, RuntimeTrackPieceCounts,
calendar::BoundaryEventKind, runtime_company_book_value_per_share,
runtime_company_credit_rating, runtime_company_prime_rate,
runtime_company_credit_rating, runtime_company_investor_confidence, runtime_company_prime_rate,
};
const PERIODIC_TRIGGER_KIND_ORDER: [u8; 6] = [1, 0, 3, 2, 5, 4];
@ -1526,7 +1526,9 @@ fn company_metric_value(
RuntimeCompanyMetric::BookValuePerShare => {
runtime_company_book_value_per_share(state, company.company_id).unwrap_or(0)
}
RuntimeCompanyMetric::InvestorConfidence => company.investor_confidence,
RuntimeCompanyMetric::InvestorConfidence => {
runtime_company_investor_confidence(state, company.company_id).unwrap_or(0)
}
RuntimeCompanyMetric::ManagementAttitude => company.management_attitude,
RuntimeCompanyMetric::TrackPiecesTotal => i64::from(company.track_piece_counts.total),
RuntimeCompanyMetric::TrackPiecesSingle => i64::from(company.track_piece_counts.single),
@ -4147,6 +4149,74 @@ mod tests {
);
}
#[test]
fn derived_investor_confidence_condition_reads_rehosted_share_price_cache() {
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: 2620,
investor_confidence: 0,
management_attitude: 58,
takeover_cooldown_year: Some(1844),
merger_cooldown_year: Some(1845),
}],
selected_company_id: Some(1),
service_state: RuntimeServiceState {
company_market_state: BTreeMap::from([(
1,
crate::RuntimeCompanyMarketState {
recent_per_share_subscore_raw_u32: 12.0f32.to_bits(),
cached_share_price_raw_u32: 37.0f32.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::InvestorConfidence,
comparator: RuntimeConditionComparator::Eq,
value: 37,
}],
effects: vec![RuntimeEffect::SetWorldFlag {
key: "world.derived_investor_confidence_gate_passed".to_string(),
value: true,
}],
}],
..state()
};
execute_step_command(
&mut state,
&StepCommand::ServiceTriggerKind { trigger_kind: 6 },
)
.expect("derived investor-confidence company condition should gate execution");
assert_eq!(
state
.world_flags
.get("world.derived_investor_confidence_gate_passed"),
Some(&true)
);
}
#[test]
fn book_value_condition_reads_rehosted_direct_company_field_band() {
let mut state = RuntimeState {