Rehost annual stock-capital issue branch

This commit is contained in:
Jan Petykiewicz 2026-04-18 00:41:35 -07:00
commit d303ee994b
6 changed files with 789 additions and 39 deletions

View file

@ -88,7 +88,9 @@ matches the stock-capital branch gate that requires at least two live bonds. The
bond table now also contributes both the largest live bond principal and the chosen
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
another isolated finance leaf. A checked-in
another isolated finance leaf. The same bond-slot owner state now also exposes the highest live
coupon rate, which is enough to run the stock-capital price-to-book approval ladder as another
save-native runtime reader instead of a notes-only threshold table. A checked-in
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,

View file

@ -53,26 +53,27 @@ pub use runtime::{
RuntimeCargoPriceTarget, RuntimeCargoProductionTarget, RuntimeChairmanMetric,
RuntimeChairmanProfile, RuntimeChairmanTarget, RuntimeCompany,
RuntimeCompanyAnnualCreditorPressureState, RuntimeCompanyAnnualDeepDistressState,
RuntimeCompanyAnnualFinanceState, RuntimeCompanyAnnualStockRepurchaseState,
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,
RuntimePlayerConditionTestScope, RuntimePlayerTarget, RuntimeSaveProfileState,
RuntimeServiceState, RuntimeState, RuntimeTerritory, RuntimeTerritoryMetric,
RuntimeTerritoryTarget, RuntimeTrackMetric, RuntimeTrackPieceCounts, RuntimeTrain,
RuntimeWorldFinanceNeighborhoodCandidate, RuntimeWorldIssueState, RuntimeWorldRestoreState,
RuntimeCompanyAnnualFinanceState, RuntimeCompanyAnnualStockIssueState,
RuntimeCompanyAnnualStockRepurchaseState, 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, RuntimePlayerConditionTestScope,
RuntimePlayerTarget, RuntimeSaveProfileState, RuntimeServiceState, RuntimeState,
RuntimeTerritory, RuntimeTerritoryMetric, RuntimeTerritoryTarget, RuntimeTrackMetric,
RuntimeTrackPieceCounts, RuntimeTrain, RuntimeWorldFinanceNeighborhoodCandidate,
RuntimeWorldIssueState, RuntimeWorldRestoreState,
runtime_company_annual_creditor_pressure_state, runtime_company_annual_deep_distress_state,
runtime_company_annual_finance_state, runtime_company_annual_stock_repurchase_state,
runtime_company_assigned_share_pool, runtime_company_average_live_bond_coupon,
runtime_company_book_value_per_share, runtime_company_credit_rating,
runtime_company_investor_confidence, runtime_company_management_attitude,
runtime_company_market_value, runtime_company_prime_rate,
runtime_company_annual_finance_state, runtime_company_annual_stock_issue_state,
runtime_company_annual_stock_repurchase_state, runtime_company_assigned_share_pool,
runtime_company_average_live_bond_coupon, runtime_company_book_value_per_share,
runtime_company_credit_rating, 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_stat_value_f64, runtime_company_unassigned_share_pool,
runtime_world_annual_finance_mode_active, runtime_world_bankruptcy_allowed,

View file

@ -258,6 +258,58 @@ pub struct RuntimeCompanyAnnualStockRepurchaseState {
pub eligible_for_single_batch_repurchase: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RuntimeCompanyAnnualStockIssueState {
pub company_id: u32,
#[serde(default)]
pub annual_mode_active: Option<bool>,
#[serde(default)]
pub stock_issue_and_buyback_allowed: Option<bool>,
#[serde(default)]
pub bond_issue_and_repayment_allowed: Option<bool>,
#[serde(default)]
pub years_since_founding: Option<u32>,
#[serde(default)]
pub live_bond_count: Option<u8>,
#[serde(default)]
pub initial_issue_batch_size: Option<u32>,
#[serde(default)]
pub trimmed_issue_batch_size: Option<u32>,
#[serde(default)]
pub share_pressure_basis_points: Option<i64>,
#[serde(default)]
pub pressured_support_adjusted_share_price_scalar: Option<i64>,
#[serde(default)]
pub pressured_proceeds: Option<i64>,
#[serde(default)]
pub book_value_per_share_floor_applied: Option<i64>,
#[serde(default)]
pub price_to_book_ratio_basis_points: Option<i64>,
#[serde(default)]
pub current_cash: Option<i64>,
#[serde(default)]
pub highest_coupon_live_bond_principal: Option<u32>,
#[serde(default)]
pub highest_coupon_live_bond_rate_basis_points: Option<i64>,
#[serde(default)]
pub current_issue_age_absolute_counter_delta: Option<i64>,
#[serde(default)]
pub current_issue_cooldown_floor: Option<i64>,
#[serde(default)]
pub minimum_price_to_book_ratio_basis_points: Option<i64>,
#[serde(default)]
pub passes_share_price_floor: Option<bool>,
#[serde(default)]
pub passes_proceeds_floor: Option<bool>,
#[serde(default)]
pub passes_cash_gate: Option<bool>,
#[serde(default)]
pub passes_issue_cooldown_gate: Option<bool>,
#[serde(default)]
pub passes_coupon_price_to_book_gate: Option<bool>,
pub eligible_for_double_tranche_issue: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct RuntimeTrackPieceCounts {
#[serde(default)]
@ -2168,32 +2220,33 @@ fn runtime_decode_saved_f32_value_f64(raw_u32: u32) -> Option<f64> {
Some(value)
}
pub fn runtime_company_recent_per_share_subscore(
fn runtime_company_highest_live_bond_coupon_rate_f64(
state: &RuntimeState,
company_id: u32,
) -> Option<f64> {
let market_state = state.service_state.company_market_state.get(&company_id)?;
if runtime_world_absolute_counter(state)
.is_some_and(|counter| counter == market_state.recent_per_share_cache_absolute_counter)
{
if let Some(cached_value) =
runtime_decode_saved_f64_bits(market_state.recent_per_share_cached_value_bits)
{
return Some(cached_value);
}
}
runtime_decode_saved_f32_value_f64(market_state.recent_per_share_subscore_raw_u32)
market_state
.live_bond_slots
.iter()
.filter_map(|slot| {
let value = f32::from_bits(slot.coupon_rate_raw_u32) as f64;
value.is_finite().then_some(value)
})
.max_by(|left, right| left.partial_cmp(right).unwrap_or(std::cmp::Ordering::Equal))
}
fn runtime_company_support_adjusted_share_price_scalar_f64(
fn runtime_company_support_adjusted_share_price_scalar_with_pressure_f64(
state: &RuntimeState,
company_id: u32,
share_pressure_shares: i64,
) -> 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 share_pressure_shares == 0 {
return Some(cached_value.max(0.0001));
}
}
if market_state.outstanding_shares == 0 {
@ -2224,9 +2277,12 @@ fn runtime_company_support_adjusted_share_price_scalar_f64(
let mutable_support =
runtime_decode_saved_f32_value_f64(market_state.mutable_support_scalar_raw_u32)?;
let share_pressure =
(share_pressure_shares as f64 / market_state.outstanding_shares as f64).clamp(-0.2, 0.2);
let effective_mutable_support = mutable_support + share_pressure;
let share_count_growth_ratio = ((market_state.outstanding_shares as f64
+ 1.4
* mutable_support
* effective_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);
@ -2242,6 +2298,30 @@ fn runtime_company_support_adjusted_share_price_scalar_f64(
Some(((recent_per_share * share_count_growth_ratio * investor_multiplier) + 1.0).max(0.0001))
}
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)?;
if runtime_world_absolute_counter(state)
.is_some_and(|counter| counter == market_state.recent_per_share_cache_absolute_counter)
{
if let Some(cached_value) =
runtime_decode_saved_f64_bits(market_state.recent_per_share_cached_value_bits)
{
return Some(cached_value);
}
}
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> {
runtime_company_support_adjusted_share_price_scalar_with_pressure_f64(state, company_id, 0)
}
pub fn runtime_company_investor_confidence(state: &RuntimeState, company_id: u32) -> Option<i64> {
let company = state
.companies
@ -2936,6 +3016,175 @@ pub fn runtime_company_annual_stock_repurchase_state(
})
}
fn runtime_company_stock_issue_price_to_book_ratio_f64(
pressured_support_adjusted_share_price_scalar: f64,
book_value_per_share: f64,
) -> Option<f64> {
let denominator = book_value_per_share.max(1.0);
if !pressured_support_adjusted_share_price_scalar.is_finite() || !denominator.is_finite() {
return None;
}
Some(pressured_support_adjusted_share_price_scalar / denominator)
}
fn runtime_company_stock_issue_minimum_price_to_book_ratio_f64(
highest_coupon_rate: f64,
) -> Option<f64> {
if !highest_coupon_rate.is_finite() || highest_coupon_rate <= 0.0 {
return None;
}
Some(if highest_coupon_rate <= 0.07 {
1.30
} else if highest_coupon_rate <= 0.08 {
1.20
} else if highest_coupon_rate <= 0.09 {
1.10
} else if highest_coupon_rate <= 0.10 {
0.95
} else if highest_coupon_rate <= 0.11 {
0.80
} else if highest_coupon_rate <= 0.12 {
0.62
} else if highest_coupon_rate <= 0.13 {
0.50
} else if highest_coupon_rate <= 0.14 {
0.35
} else {
return None;
})
}
pub fn runtime_company_annual_stock_issue_state(
state: &RuntimeState,
company_id: u32,
) -> Option<RuntimeCompanyAnnualStockIssueState> {
const ISSUE_PROCEEDS_CAP: i64 = 55_000;
const SHARE_PRICE_FLOOR: i64 = 22;
const ONE_YEAR_ABSOLUTE_COUNTER_SPAN: i64 = 12 * 28 * 24 * 60;
let annual_finance_state = runtime_company_annual_finance_state(state, company_id)?;
let current_cash = runtime_company_control_transfer_stat_value_f64(
state,
company_id,
RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH,
)
.and_then(runtime_round_f64_to_i64);
let highest_coupon_live_bond_principal =
annual_finance_state.highest_coupon_live_bond_principal;
let highest_coupon_live_bond_rate =
runtime_company_highest_live_bond_coupon_rate_f64(state, company_id);
let highest_coupon_live_bond_rate_basis_points =
highest_coupon_live_bond_rate.and_then(|value| runtime_round_f64_to_i64(value * 10_000.0));
let mut initial_issue_batch_size =
(annual_finance_state.outstanding_shares / 10 / 1_000) * 1_000;
if initial_issue_batch_size < 2_000 {
initial_issue_batch_size = 2_000;
}
let initial_issue_batch_size = Some(initial_issue_batch_size);
let mut trimmed_issue_batch_size = initial_issue_batch_size?;
let mut pressured_support_adjusted_share_price_scalar =
runtime_company_support_adjusted_share_price_scalar_with_pressure_f64(
state,
company_id,
-(trimmed_issue_batch_size as i64),
);
let mut pressured_proceeds = pressured_support_adjusted_share_price_scalar
.and_then(|value| runtime_round_f64_to_i64(value * trimmed_issue_batch_size as f64));
while trimmed_issue_batch_size > 2_000
&& pressured_proceeds.is_some_and(|value| value > ISSUE_PROCEEDS_CAP)
{
trimmed_issue_batch_size = trimmed_issue_batch_size.saturating_sub(1_000);
pressured_support_adjusted_share_price_scalar =
runtime_company_support_adjusted_share_price_scalar_with_pressure_f64(
state,
company_id,
-(trimmed_issue_batch_size as i64),
);
pressured_proceeds = pressured_support_adjusted_share_price_scalar
.and_then(|value| runtime_round_f64_to_i64(value * trimmed_issue_batch_size as f64));
}
let pressured_support_adjusted_share_price_scalar_i64 =
pressured_support_adjusted_share_price_scalar.and_then(runtime_round_f64_to_i64);
let book_value_per_share_floor_applied =
runtime_company_book_value_per_share(state, company_id).map(|value| value.max(1));
let price_to_book_ratio = pressured_support_adjusted_share_price_scalar
.zip(book_value_per_share_floor_applied)
.and_then(|(share_price, book_value)| {
runtime_company_stock_issue_price_to_book_ratio_f64(share_price, book_value as f64)
});
let price_to_book_ratio_basis_points =
price_to_book_ratio.and_then(|value| runtime_round_f64_to_i64(value * 10_000.0));
let minimum_price_to_book_ratio = highest_coupon_live_bond_rate
.and_then(runtime_company_stock_issue_minimum_price_to_book_ratio_f64);
let minimum_price_to_book_ratio_basis_points =
minimum_price_to_book_ratio.and_then(|value| runtime_round_f64_to_i64(value * 10_000.0));
let passes_share_price_floor =
pressured_support_adjusted_share_price_scalar_i64.map(|value| value >= SHARE_PRICE_FLOOR);
let passes_proceeds_floor = pressured_proceeds.map(|value| value >= ISSUE_PROCEEDS_CAP);
let passes_cash_gate = current_cash
.zip(highest_coupon_live_bond_principal)
.map(|(cash, principal)| cash <= i64::from(principal) + 5_000);
let passes_issue_cooldown_gate = Some(
annual_finance_state
.current_issue_age_absolute_counter_delta
.is_none_or(|delta| delta >= ONE_YEAR_ABSOLUTE_COUNTER_SPAN),
);
let passes_coupon_price_to_book_gate = price_to_book_ratio_basis_points
.zip(minimum_price_to_book_ratio_basis_points)
.map(|(actual, minimum)| actual >= minimum);
let eligible_for_double_tranche_issue = runtime_world_annual_finance_mode_active(state)
== Some(true)
&& runtime_world_stock_issue_and_buyback_allowed(state) == Some(true)
&& runtime_world_bond_issue_and_repayment_allowed(state) == Some(true)
&& annual_finance_state.bond_count >= 2
&& annual_finance_state
.years_since_founding
.is_some_and(|years| years >= 1)
&& !runtime_company_annual_creditor_pressure_state(state, company_id)?
.eligible_for_bankruptcy_branch
&& !runtime_company_annual_deep_distress_state(state, company_id)?
.eligible_for_bankruptcy_fallback
&& !runtime_company_annual_stock_repurchase_state(state, company_id)?
.eligible_for_single_batch_repurchase
&& passes_share_price_floor == Some(true)
&& passes_proceeds_floor == Some(true)
&& passes_cash_gate == Some(true)
&& passes_issue_cooldown_gate == Some(true)
&& passes_coupon_price_to_book_gate == Some(true);
Some(RuntimeCompanyAnnualStockIssueState {
company_id,
annual_mode_active: runtime_world_annual_finance_mode_active(state),
stock_issue_and_buyback_allowed: runtime_world_stock_issue_and_buyback_allowed(state),
bond_issue_and_repayment_allowed: runtime_world_bond_issue_and_repayment_allowed(state),
years_since_founding: annual_finance_state.years_since_founding,
live_bond_count: Some(annual_finance_state.bond_count),
initial_issue_batch_size,
trimmed_issue_batch_size: Some(trimmed_issue_batch_size),
share_pressure_basis_points: runtime_round_f64_to_i64(
-(trimmed_issue_batch_size as f64) / annual_finance_state.outstanding_shares as f64
* 10_000.0,
),
pressured_support_adjusted_share_price_scalar:
pressured_support_adjusted_share_price_scalar_i64,
pressured_proceeds,
book_value_per_share_floor_applied,
price_to_book_ratio_basis_points,
current_cash,
highest_coupon_live_bond_principal,
highest_coupon_live_bond_rate_basis_points,
current_issue_age_absolute_counter_delta: annual_finance_state
.current_issue_age_absolute_counter_delta,
current_issue_cooldown_floor: Some(ONE_YEAR_ABSOLUTE_COUNTER_SPAN),
minimum_price_to_book_ratio_basis_points,
passes_share_price_floor,
passes_proceeds_floor,
passes_cash_gate,
passes_issue_cooldown_gate,
passes_coupon_price_to_book_gate,
eligible_for_double_tranche_issue,
})
}
pub fn runtime_company_annual_creditor_pressure_state(
state: &RuntimeState,
company_id: u32,
@ -7372,6 +7621,188 @@ mod tests {
assert!(!repurchase_state.eligible_for_single_batch_repurchase);
}
#[test]
fn derives_annual_stock_issue_state_from_rehosted_owner_state() {
let mut year_stat_family_qword_bits = vec![
0u64;
((RUNTIME_COMPANY_STAT_SLOT_COUNT + 2) * 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();
};
write_current_value(&mut year_stat_family_qword_bits, 0x0d, 250_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 {
partial_year_progress_raw_u8: Some(0x0c),
stock_issue_and_buyback_policy_raw_u8: Some(0),
stock_issue_and_buyback_allowed: Some(true),
bond_issue_and_repayment_policy_raw_u8: Some(0),
bond_issue_and_repayment_allowed: Some(true),
issue_37_value: Some(2),
issue_37_multiplier_raw_u32: Some(1.0f32.to_bits()),
issue_37_multiplier_value_f32_text: Some("1.000000".to_string()),
absolute_counter_raw_u32: Some(885_911_040),
..RuntimeWorldRestoreState::default()
},
metadata: BTreeMap::new(),
companies: vec![RuntimeCompany {
company_id: 14,
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: Some(8),
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![RuntimeChairmanProfile {
profile_id: 8,
name: "Taylor".to_string(),
active: true,
current_cash: 200,
linked_company_id: Some(14),
company_holdings: BTreeMap::from([(14, 14_000)]),
holdings_value_total: 0,
net_worth_total: 0,
purchasing_power_total: 0,
}],
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],
chairman_personality_raw_u8: BTreeMap::from([(8, 20)]),
company_market_state: BTreeMap::from([(
14,
RuntimeCompanyMarketState {
outstanding_shares: 20_000,
bond_count: 2,
highest_coupon_live_bond_principal: Some(300_000),
current_issue_calendar_word: 0x0101_0725,
current_issue_calendar_word_2: 0x0001_0001,
founding_year: 1840,
cached_share_price_raw_u32: 35.0f32.to_bits(),
recent_per_share_cache_absolute_counter: 885_911_040,
recent_per_share_cached_value_bits: 34.0f64.to_bits(),
city_connection_latch: false,
live_bond_slots: vec![
RuntimeCompanyBondSlot {
slot_index: 0,
principal: 300_000,
coupon_rate_raw_u32: 0.11f32.to_bits(),
},
RuntimeCompanyBondSlot {
slot_index: 1,
principal: 200_000,
coupon_rate_raw_u32: 0.07f32.to_bits(),
},
],
direct_control_transfer_float_fields_raw_u32: BTreeMap::from([(
0x32f,
30.0f32.to_bits(),
)]),
year_stat_family_qword_bits,
..RuntimeCompanyMarketState::default()
},
)]),
..RuntimeServiceState::default()
},
};
let stock_issue_state =
runtime_company_annual_stock_issue_state(&state, 14).expect("stock issue state");
assert_eq!(stock_issue_state.live_bond_count, Some(2));
assert_eq!(stock_issue_state.initial_issue_batch_size, Some(2_000));
assert_eq!(stock_issue_state.trimmed_issue_batch_size, Some(2_000));
assert_eq!(stock_issue_state.share_pressure_basis_points, Some(-1_000));
assert_eq!(
stock_issue_state.pressured_support_adjusted_share_price_scalar,
Some(35)
);
assert_eq!(stock_issue_state.pressured_proceeds, Some(70_000));
assert_eq!(
stock_issue_state.book_value_per_share_floor_applied,
Some(30)
);
assert_eq!(
stock_issue_state.price_to_book_ratio_basis_points,
Some(11_667)
);
assert_eq!(
stock_issue_state.highest_coupon_live_bond_rate_basis_points,
Some(1_100)
);
assert_eq!(
stock_issue_state.minimum_price_to_book_ratio_basis_points,
Some(8_000)
);
assert_eq!(stock_issue_state.current_cash, Some(250_000));
assert_eq!(
stock_issue_state.highest_coupon_live_bond_principal,
Some(300_000)
);
assert_eq!(
stock_issue_state.current_issue_age_absolute_counter_delta,
Some(967_680)
);
assert_eq!(
stock_issue_state.current_issue_cooldown_floor,
Some(483_840)
);
assert_eq!(stock_issue_state.passes_share_price_floor, Some(true));
assert_eq!(stock_issue_state.passes_proceeds_floor, Some(true));
assert_eq!(stock_issue_state.passes_cash_gate, Some(true));
assert_eq!(stock_issue_state.passes_issue_cooldown_gate, Some(true));
assert_eq!(
stock_issue_state.passes_coupon_price_to_book_gate,
Some(true)
);
assert!(stock_issue_state.eligible_for_double_tranche_issue);
}
#[test]
fn reads_company_market_metrics_from_annual_finance_reader() {
let current_issue_calendar_word = 0x0101_0726;

View file

@ -3,7 +3,8 @@ use serde::{Deserialize, Serialize};
use crate::{
CalendarPoint, RuntimeState, runtime_company_annual_creditor_pressure_state,
runtime_company_annual_deep_distress_state, runtime_company_annual_finance_state,
runtime_company_annual_stock_repurchase_state, runtime_company_unassigned_share_pool,
runtime_company_annual_stock_issue_state, runtime_company_annual_stock_repurchase_state,
runtime_company_unassigned_share_pool,
};
fn raw_u32_to_f32_text(raw: u32) -> String {
@ -124,6 +125,26 @@ pub struct RuntimeSummary {
pub selected_company_stock_repurchase_affordability_cash_floor: Option<i64>,
pub selected_company_stock_repurchase_unassigned_share_pool: Option<u32>,
pub selected_company_stock_repurchase_eligible_for_single_batch: Option<bool>,
pub selected_company_stock_issue_live_bond_count: Option<u8>,
pub selected_company_stock_issue_initial_batch_size: Option<u32>,
pub selected_company_stock_issue_trimmed_batch_size: Option<u32>,
pub selected_company_stock_issue_share_pressure_basis_points: Option<i64>,
pub selected_company_stock_issue_pressured_share_price_scalar: Option<i64>,
pub selected_company_stock_issue_pressured_proceeds: Option<i64>,
pub selected_company_stock_issue_book_value_per_share_floor_applied: Option<i64>,
pub selected_company_stock_issue_price_to_book_ratio_basis_points: Option<i64>,
pub selected_company_stock_issue_current_cash: Option<i64>,
pub selected_company_stock_issue_highest_coupon_live_bond_principal: Option<u32>,
pub selected_company_stock_issue_highest_coupon_live_bond_rate_basis_points: Option<i64>,
pub selected_company_stock_issue_current_issue_age_absolute_counter_delta: Option<i64>,
pub selected_company_stock_issue_current_issue_cooldown_floor: Option<i64>,
pub selected_company_stock_issue_minimum_price_to_book_ratio_basis_points: Option<i64>,
pub selected_company_stock_issue_passes_share_price_floor: Option<bool>,
pub selected_company_stock_issue_passes_proceeds_floor: Option<bool>,
pub selected_company_stock_issue_passes_cash_gate: Option<bool>,
pub selected_company_stock_issue_passes_issue_cooldown_gate: Option<bool>,
pub selected_company_stock_issue_passes_coupon_price_to_book_gate: Option<bool>,
pub selected_company_stock_issue_eligible_for_double_tranche: Option<bool>,
pub player_count: usize,
pub chairman_profile_count: usize,
pub active_chairman_profile_count: usize,
@ -224,6 +245,9 @@ impl RuntimeSummary {
state.selected_company_id.and_then(|company_id| {
runtime_company_annual_stock_repurchase_state(state, company_id)
});
let selected_company_stock_issue_state = state
.selected_company_id
.and_then(|company_id| runtime_company_annual_stock_issue_state(state, company_id));
Self {
calendar: state.calendar,
calendar_projection_source: state.metadata.get("save_slice.calendar_source").cloned(),
@ -550,6 +574,81 @@ impl RuntimeSummary {
selected_company_stock_repurchase_state
.as_ref()
.map(|repurchase_state| repurchase_state.eligible_for_single_batch_repurchase),
selected_company_stock_issue_live_bond_count: selected_company_stock_issue_state
.as_ref()
.and_then(|issue_state| issue_state.live_bond_count),
selected_company_stock_issue_initial_batch_size: selected_company_stock_issue_state
.as_ref()
.and_then(|issue_state| issue_state.initial_issue_batch_size),
selected_company_stock_issue_trimmed_batch_size: selected_company_stock_issue_state
.as_ref()
.and_then(|issue_state| issue_state.trimmed_issue_batch_size),
selected_company_stock_issue_share_pressure_basis_points:
selected_company_stock_issue_state
.as_ref()
.and_then(|issue_state| issue_state.share_pressure_basis_points),
selected_company_stock_issue_pressured_share_price_scalar:
selected_company_stock_issue_state
.as_ref()
.and_then(|issue_state| {
issue_state.pressured_support_adjusted_share_price_scalar
}),
selected_company_stock_issue_pressured_proceeds: selected_company_stock_issue_state
.as_ref()
.and_then(|issue_state| issue_state.pressured_proceeds),
selected_company_stock_issue_book_value_per_share_floor_applied:
selected_company_stock_issue_state
.as_ref()
.and_then(|issue_state| issue_state.book_value_per_share_floor_applied),
selected_company_stock_issue_price_to_book_ratio_basis_points:
selected_company_stock_issue_state
.as_ref()
.and_then(|issue_state| issue_state.price_to_book_ratio_basis_points),
selected_company_stock_issue_current_cash: selected_company_stock_issue_state
.as_ref()
.and_then(|issue_state| issue_state.current_cash),
selected_company_stock_issue_highest_coupon_live_bond_principal:
selected_company_stock_issue_state
.as_ref()
.and_then(|issue_state| issue_state.highest_coupon_live_bond_principal),
selected_company_stock_issue_highest_coupon_live_bond_rate_basis_points:
selected_company_stock_issue_state
.as_ref()
.and_then(|issue_state| issue_state.highest_coupon_live_bond_rate_basis_points),
selected_company_stock_issue_current_issue_age_absolute_counter_delta:
selected_company_stock_issue_state
.as_ref()
.and_then(|issue_state| issue_state.current_issue_age_absolute_counter_delta),
selected_company_stock_issue_current_issue_cooldown_floor:
selected_company_stock_issue_state
.as_ref()
.and_then(|issue_state| issue_state.current_issue_cooldown_floor),
selected_company_stock_issue_minimum_price_to_book_ratio_basis_points:
selected_company_stock_issue_state
.as_ref()
.and_then(|issue_state| issue_state.minimum_price_to_book_ratio_basis_points),
selected_company_stock_issue_passes_share_price_floor:
selected_company_stock_issue_state
.as_ref()
.and_then(|issue_state| issue_state.passes_share_price_floor),
selected_company_stock_issue_passes_proceeds_floor: selected_company_stock_issue_state
.as_ref()
.and_then(|issue_state| issue_state.passes_proceeds_floor),
selected_company_stock_issue_passes_cash_gate: selected_company_stock_issue_state
.as_ref()
.and_then(|issue_state| issue_state.passes_cash_gate),
selected_company_stock_issue_passes_issue_cooldown_gate:
selected_company_stock_issue_state
.as_ref()
.and_then(|issue_state| issue_state.passes_issue_cooldown_gate),
selected_company_stock_issue_passes_coupon_price_to_book_gate:
selected_company_stock_issue_state
.as_ref()
.and_then(|issue_state| issue_state.passes_coupon_price_to_book_gate),
selected_company_stock_issue_eligible_for_double_tranche:
selected_company_stock_issue_state
.as_ref()
.map(|issue_state| issue_state.eligible_for_double_tranche_issue),
player_count: state.players.len(),
chairman_profile_count: state.chairman_profiles.len(),
active_chairman_profile_count: state
@ -2811,4 +2910,216 @@ mod tests {
Some(false)
);
}
#[test]
fn summarizes_selected_company_stock_issue_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
];
year_stat_family_qword_bits[(crate::RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH
* crate::RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN)
as usize] = 250_000.0f64.to_bits();
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 {
partial_year_progress_raw_u8: Some(0x0c),
stock_issue_and_buyback_policy_raw_u8: Some(0),
stock_issue_and_buyback_allowed: Some(true),
bond_issue_and_repayment_policy_raw_u8: Some(0),
bond_issue_and_repayment_allowed: Some(true),
issue_37_value: Some(2),
issue_37_multiplier_raw_u32: Some(1.0f32.to_bits()),
issue_37_multiplier_value_f32_text: Some("1.000000".to_string()),
absolute_counter_raw_u32: Some(885_911_040),
..RuntimeWorldRestoreState::default()
},
metadata: BTreeMap::new(),
companies: vec![RuntimeCompany {
company_id: 14,
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: Some(8),
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(14),
players: Vec::new(),
selected_player_id: None,
chairman_profiles: vec![crate::RuntimeChairmanProfile {
profile_id: 8,
name: "Taylor".to_string(),
active: true,
current_cash: 200,
linked_company_id: Some(14),
company_holdings: BTreeMap::from([(14, 14_000)]),
holdings_value_total: 0,
net_worth_total: 0,
purchasing_power_total: 0,
}],
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: Vec::new().into_iter().collect(),
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],
chairman_personality_raw_u8: BTreeMap::from([(8, 20)]),
company_market_state: BTreeMap::from([(
14,
crate::RuntimeCompanyMarketState {
outstanding_shares: 20_000,
bond_count: 2,
highest_coupon_live_bond_principal: Some(300_000),
current_issue_calendar_word: 0x0101_0725,
current_issue_calendar_word_2: 0x0001_0001,
founding_year: 1840,
cached_share_price_raw_u32: 35.0f32.to_bits(),
recent_per_share_cache_absolute_counter: 885_911_040,
recent_per_share_cached_value_bits: 34.0f64.to_bits(),
live_bond_slots: vec![
crate::RuntimeCompanyBondSlot {
slot_index: 0,
principal: 300_000,
coupon_rate_raw_u32: 0.11f32.to_bits(),
},
crate::RuntimeCompanyBondSlot {
slot_index: 1,
principal: 200_000,
coupon_rate_raw_u32: 0.07f32.to_bits(),
},
],
direct_control_transfer_float_fields_raw_u32: BTreeMap::from([(
0x32f,
30.0f32.to_bits(),
)]),
year_stat_family_qword_bits,
..crate::RuntimeCompanyMarketState::default()
},
)]),
..RuntimeServiceState::default()
},
};
let summary = RuntimeSummary::from_state(&state);
assert_eq!(
summary.selected_company_stock_issue_live_bond_count,
Some(2)
);
assert_eq!(
summary.selected_company_stock_issue_initial_batch_size,
Some(2_000)
);
assert_eq!(
summary.selected_company_stock_issue_trimmed_batch_size,
Some(2_000)
);
assert_eq!(
summary.selected_company_stock_issue_share_pressure_basis_points,
Some(-1_000)
);
assert_eq!(
summary.selected_company_stock_issue_pressured_share_price_scalar,
Some(35)
);
assert_eq!(
summary.selected_company_stock_issue_pressured_proceeds,
Some(70_000)
);
assert_eq!(
summary.selected_company_stock_issue_book_value_per_share_floor_applied,
Some(30)
);
assert_eq!(
summary.selected_company_stock_issue_price_to_book_ratio_basis_points,
Some(11_667)
);
assert_eq!(
summary.selected_company_stock_issue_current_cash,
Some(250_000)
);
assert_eq!(
summary.selected_company_stock_issue_highest_coupon_live_bond_principal,
Some(300_000)
);
assert_eq!(
summary.selected_company_stock_issue_highest_coupon_live_bond_rate_basis_points,
Some(1_100)
);
assert_eq!(
summary.selected_company_stock_issue_current_issue_age_absolute_counter_delta,
Some(967_680)
);
assert_eq!(
summary.selected_company_stock_issue_current_issue_cooldown_floor,
Some(483_840)
);
assert_eq!(
summary.selected_company_stock_issue_minimum_price_to_book_ratio_basis_points,
Some(8_000)
);
assert_eq!(
summary.selected_company_stock_issue_passes_share_price_floor,
Some(true)
);
assert_eq!(
summary.selected_company_stock_issue_passes_proceeds_floor,
Some(true)
);
assert_eq!(
summary.selected_company_stock_issue_passes_cash_gate,
Some(true)
);
assert_eq!(
summary.selected_company_stock_issue_passes_issue_cooldown_gate,
Some(true)
);
assert_eq!(
summary.selected_company_stock_issue_passes_coupon_price_to_book_gate,
Some(true)
);
assert_eq!(
summary.selected_company_stock_issue_eligible_for_double_tranche,
Some(true)
);
}
}

View file

@ -134,10 +134,12 @@ 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
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
owner-state surface; the same fixed-world save block now also carries the raw stock, bond,
owner-state surface, plus the highest live coupon rate for the stock-capital approval ladder;
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
later deep-distress bankruptcy fallback now runs on that same save-native cash and trailing-
profit seam; the annual stock-repurchase and stock-capital issue branches now do too
net-profit surface too; the same owner seam now also carries the fixed-world building-density
growth setting plus the linked chairman personality byte, which is enough to run the annual
stock-repurchase gate headlessly as another pure reader

View file

@ -224,8 +224,9 @@ annual finance-policy gates in the atlas. Live bond-slot count now also flows th
owned company market and annual-finance state, matching the stock-capital branch gate that needs
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
annual-finance state, so later stock-capital gates can extend a rehosted owner-state seam instead
of guessing another finance leaf. The same fixed-world save block now also carries the raw stock,
annual-finance state, and now also exposes the highest live coupon rate, so the stock-capital
price-to-book ladder can extend a rehosted owner-state seam instead 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. The later
@ -233,7 +234,9 @@ deep-distress bankruptcy fallback now rides the same owner-state seam too, using
cash reader plus the first three trailing net-profit years instead of a parallel raw-offset guess.
That same seam now also carries the fixed-world building-density growth setting plus the linked
chairman personality byte, which is enough to rehost the annual stock-repurchase gate on owned
save/runtime state instead of another threshold-only note.
save/runtime state instead of another threshold-only note. The stock-capital issue branch now
rides that same seam too, with share-pressure, cooldown, and price-to-book gate state exposed as
normal runtime readers.
## Why This Boundary