Rehost annual dividend policy branch

This commit is contained in:
Jan Petykiewicz 2026-04-18 01:00:21 -07:00
commit 90d213c9ed
6 changed files with 700 additions and 21 deletions

View file

@ -99,7 +99,11 @@ distress bankruptcy fallback is now rehosted on that same owner surface too, usi
cash reader seam plus the first three trailing net-profit years instead of another ad hoc probe.
The annual bond lane now runs on that same owner surface too, using the simulated post-repayment
cash window plus the linked-transit threshold split to stage `500000` principal issue counts as a
pure runtime reader.
pure runtime reader. The annual dividend lane now runs there too: the runtime now rehosts the
shared year-or-control-transfer metric seam, the board-approved dividend ceiling helper, and the
full annual dividend adjustment branch over owned current cash, public float, current dividend,
building-growth policy, and recent profit history instead of leaving that policy on shell-side
dialog notes.
The same 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 as another
pure reader over owned save-native state instead of a guessed finance-side approximation.

View file

@ -53,23 +53,24 @@ pub use runtime::{
RuntimeCargoPriceTarget, RuntimeCargoProductionTarget, RuntimeChairmanMetric,
RuntimeChairmanProfile, RuntimeChairmanTarget, RuntimeCompany,
RuntimeCompanyAnnualBondPolicyState, RuntimeCompanyAnnualCreditorPressureState,
RuntimeCompanyAnnualDeepDistressState, 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_bond_policy_state, runtime_company_annual_creditor_pressure_state,
runtime_company_annual_deep_distress_state, runtime_company_annual_finance_state,
RuntimeCompanyAnnualDeepDistressState, RuntimeCompanyAnnualDividendPolicyState,
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_bond_policy_state,
runtime_company_annual_creditor_pressure_state, runtime_company_annual_deep_distress_state,
runtime_company_annual_dividend_policy_state, 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,

View file

@ -339,6 +339,43 @@ pub struct RuntimeCompanyAnnualBondPolicyState {
pub eligible_for_bond_issue_branch: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RuntimeCompanyAnnualDividendPolicyState {
pub company_id: u32,
#[serde(default)]
pub annual_mode_active: Option<bool>,
#[serde(default)]
pub dividend_adjustment_allowed: Option<bool>,
#[serde(default)]
pub years_since_last_dividend: Option<u32>,
#[serde(default)]
pub years_since_founding: Option<u32>,
#[serde(default)]
pub outstanding_shares: Option<u32>,
#[serde(default)]
pub unassigned_share_pool: Option<u32>,
#[serde(default)]
pub weighted_recent_net_profit_total: Option<i64>,
#[serde(default)]
pub weighted_recent_net_profit_average: Option<i64>,
#[serde(default)]
pub current_cash: Option<i64>,
pub tiny_unassigned_share_cash_supplement_branch: bool,
#[serde(default)]
pub tentative_target_dividend_per_share_tenths: Option<i64>,
#[serde(default)]
pub current_dividend_per_share_tenths: Option<i64>,
#[serde(default)]
pub building_density_growth_setting: Option<u32>,
#[serde(default)]
pub growth_adjusted_current_dividend_per_share_tenths: Option<i64>,
#[serde(default)]
pub board_approved_dividend_rate_ceiling_tenths: Option<i64>,
#[serde(default)]
pub proposed_dividend_per_share_tenths: Option<i64>,
pub eligible_for_dividend_adjustment_branch: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct RuntimeTrackPieceCounts {
#[serde(default)]
@ -2615,6 +2652,34 @@ fn runtime_company_trailing_full_year_stat_series(
Some((year_words, values))
}
fn runtime_company_year_or_control_transfer_metric_value_f64(
state: &RuntimeState,
company_id: u32,
year_word: u32,
slot_id: u32,
) -> Option<f64> {
let current_year_word = u32::from(state.world_restore.packed_year_word_raw_u16?);
if year_word == current_year_word {
runtime_company_stat_value_f64(
state,
company_id,
RuntimeCompanyStatSelector {
family_id: RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER,
slot_id,
},
)
} else {
runtime_company_stat_value_f64(
state,
company_id,
RuntimeCompanyStatSelector {
family_id: year_word,
slot_id,
},
)
}
}
pub fn runtime_world_issue_opinion_term_sum_raw(
state: &RuntimeState,
issue_id: u32,
@ -3115,6 +3180,224 @@ pub fn runtime_company_annual_bond_policy_state(
})
}
fn runtime_company_board_approved_dividend_rate_ceiling_f64(
state: &RuntimeState,
company_id: u32,
) -> Option<f64> {
const REVENUE_GUARD_DIVISOR: f64 = 2.0;
const EARLY_SUPPORT_MULTIPLIER: f64 = 0.05;
const HISTORICAL_GUARD_SCALE: f64 = 1.25;
const ANCHOR_SCALE: f64 = 0.35;
let market_state = state.service_state.company_market_state.get(&company_id)?;
let current_cash = runtime_company_control_transfer_stat_value_f64(
state,
company_id,
RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH,
)?;
let shares_plus_one = market_state.outstanding_shares.checked_add(1)?;
let shares_plus_one_f64 = shares_plus_one as f64;
let current_cash_per_share_ceiling = current_cash / shares_plus_one_f64;
let current_year_word = u32::from(state.world_restore.packed_year_word_raw_u16?);
let years_since_founding = current_year_word
.checked_sub(market_state.founding_year)
.unwrap_or(0)
.min(3);
let start_year_offset = if state.world_restore.partial_year_progress_raw_u8 == Some(0x0c) {
0
} else {
1
};
let mut strongest_net_profit_guard = 0.0f64;
let mut strongest_revenue_guard = 0.0f64;
if start_year_offset <= years_since_founding {
for year_offset in start_year_offset..=years_since_founding {
let year_word = current_year_word.checked_sub(year_offset)?;
let net_profit = runtime_company_year_or_control_transfer_metric_value_f64(
state, company_id, year_word, 0x2b,
)?;
strongest_net_profit_guard = strongest_net_profit_guard.max(net_profit);
let revenue = runtime_company_year_or_control_transfer_metric_value_f64(
state, company_id, year_word, 0x2c,
)?;
strongest_revenue_guard = strongest_revenue_guard.max(revenue);
}
}
let mut historical_guard_total =
strongest_net_profit_guard.min(strongest_revenue_guard / REVENUE_GUARD_DIVISOR);
if years_since_founding <= 1 {
let early_support_guard = market_state.outstanding_shares as f64
* runtime_decode_saved_f32_value_f64(
market_state.young_company_support_scalar_raw_u32,
)?
* EARLY_SUPPORT_MULTIPLIER;
historical_guard_total = historical_guard_total.max(early_support_guard);
}
let historical_guard_per_share_ceiling =
historical_guard_total / shares_plus_one_f64 * HISTORICAL_GUARD_SCALE;
let mut ceiling = current_cash_per_share_ceiling.min(historical_guard_per_share_ceiling);
let anchor_value = if years_since_founding == 0 {
runtime_decode_saved_f32_value_f64(market_state.young_company_support_scalar_raw_u32)?
} else {
runtime_company_year_or_control_transfer_metric_value_f64(
state,
company_id,
current_year_word.checked_sub(1)?,
0x1c,
)?
};
ceiling = ceiling.min(anchor_value * ANCHOR_SCALE);
Some(ceiling.max(0.0))
}
pub fn runtime_company_annual_dividend_policy_state(
state: &RuntimeState,
company_id: u32,
) -> Option<RuntimeCompanyAnnualDividendPolicyState> {
const WEIGHTED_NET_PROFIT_DIVISOR: f64 = 6.0;
const CASH_SUPPLEMENT_DIVISOR: f64 = 3.0;
const STANDARD_TARGET_DIVISOR: f64 = 6.0;
const DIVIDEND_DELTA_COLLAPSE_THRESHOLD: f64 = 0.1;
const GROWTH_SETTING_ONE_DIVIDEND_SCALE: f64 = 0.66;
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 current_year_word = u32::from(state.world_restore.packed_year_word_raw_u16?);
let current_dividend_per_share =
runtime_company_control_transfer_stat_value_f64(state, company_id, 0x20)?;
let building_density_growth_setting = runtime_world_building_density_growth_setting(state);
let weighted_recent_net_profit_total = Some(
runtime_company_year_or_control_transfer_metric_value_f64(
state,
company_id,
current_year_word,
0x2b,
)
.and_then(runtime_round_f64_to_i64)?
.checked_mul(3)?
.checked_add(
runtime_company_year_or_control_transfer_metric_value_f64(
state,
company_id,
current_year_word.checked_sub(1)?,
0x2b,
)
.and_then(runtime_round_f64_to_i64)?
.checked_mul(2)?,
)?
.checked_add(
runtime_company_year_or_control_transfer_metric_value_f64(
state,
company_id,
current_year_word.checked_sub(2)?,
0x2b,
)
.and_then(runtime_round_f64_to_i64)?,
)?,
);
let weighted_recent_net_profit_average = weighted_recent_net_profit_total
.and_then(|value| runtime_round_f64_to_i64(value as f64 / WEIGHTED_NET_PROFIT_DIVISOR));
let tiny_unassigned_share_cash_supplement_branch =
annual_finance_state.unassigned_share_pool <= 1_000;
let tentative_target_dividend_per_share =
weighted_recent_net_profit_average.and_then(|value| {
if annual_finance_state.outstanding_shares == 0 {
return None;
}
let shares = annual_finance_state.outstanding_shares as f64;
if tiny_unassigned_share_cash_supplement_branch {
let cash_component = current_cash.unwrap_or(0).max(0) as f64;
Some(
((value as f64 / CASH_SUPPLEMENT_DIVISOR)
+ cash_component / CASH_SUPPLEMENT_DIVISOR)
/ shares,
)
} else {
Some((value as f64 / STANDARD_TARGET_DIVISOR) / shares)
}
});
let growth_adjusted_current_dividend_per_share = Some(match building_density_growth_setting {
Some(1) => current_dividend_per_share * GROWTH_SETTING_ONE_DIVIDEND_SCALE,
Some(2) => 0.0,
_ => current_dividend_per_share,
});
let proposed_dividend_per_share = if tentative_target_dividend_per_share
.is_some_and(|value| value <= DIVIDEND_DELTA_COLLAPSE_THRESHOLD)
{
Some(0.0)
} else {
growth_adjusted_current_dividend_per_share
.zip(tentative_target_dividend_per_share)
.map(|(current_dividend, target)| {
((current_dividend + target + DIVIDEND_DELTA_COLLAPSE_THRESHOLD) / 2.0 * 10.0)
.round()
/ 10.0
})
};
let board_approved_dividend_rate_ceiling =
runtime_company_board_approved_dividend_rate_ceiling_f64(state, company_id);
let proposed_dividend_per_share = proposed_dividend_per_share
.zip(board_approved_dividend_rate_ceiling)
.map(|(proposed, ceiling)| proposed.min(ceiling));
let current_dividend_per_share_tenths =
runtime_round_f64_to_i64(current_dividend_per_share * 10.0);
let eligible_for_dividend_adjustment_branch = runtime_world_annual_finance_mode_active(state)
== Some(true)
&& runtime_world_dividend_adjustment_allowed(state) == Some(true)
&& annual_finance_state
.years_since_last_dividend
.is_some_and(|years| years >= 1)
&& annual_finance_state
.years_since_founding
.is_some_and(|years| years >= 2)
&& !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_bond_policy_state(state, company_id)?
.eligible_for_bond_issue_branch
&& !runtime_company_annual_stock_repurchase_state(state, company_id)?
.eligible_for_single_batch_repurchase
&& !runtime_company_annual_stock_issue_state(state, company_id)?
.eligible_for_double_tranche_issue
&& proposed_dividend_per_share.and_then(|value| runtime_round_f64_to_i64(value * 10.0))
!= current_dividend_per_share_tenths;
Some(RuntimeCompanyAnnualDividendPolicyState {
company_id,
annual_mode_active: runtime_world_annual_finance_mode_active(state),
dividend_adjustment_allowed: runtime_world_dividend_adjustment_allowed(state),
years_since_last_dividend: annual_finance_state.years_since_last_dividend,
years_since_founding: annual_finance_state.years_since_founding,
outstanding_shares: Some(annual_finance_state.outstanding_shares),
unassigned_share_pool: Some(annual_finance_state.unassigned_share_pool),
weighted_recent_net_profit_total,
weighted_recent_net_profit_average,
current_cash,
tiny_unassigned_share_cash_supplement_branch,
tentative_target_dividend_per_share_tenths: tentative_target_dividend_per_share
.and_then(|value| runtime_round_f64_to_i64(value * 10.0)),
current_dividend_per_share_tenths,
building_density_growth_setting,
growth_adjusted_current_dividend_per_share_tenths:
growth_adjusted_current_dividend_per_share
.and_then(|value| runtime_round_f64_to_i64(value * 10.0)),
board_approved_dividend_rate_ceiling_tenths: board_approved_dividend_rate_ceiling
.and_then(|value| runtime_round_f64_to_i64(value * 10.0)),
proposed_dividend_per_share_tenths: proposed_dividend_per_share
.and_then(|value| runtime_round_f64_to_i64(value * 10.0)),
eligible_for_dividend_adjustment_branch,
})
}
fn runtime_company_stock_issue_price_to_book_ratio_f64(
pressured_support_adjusted_share_price_scalar: f64,
book_value_per_share: f64,
@ -8019,6 +8302,163 @@ mod tests {
assert!(stock_issue_state.eligible_for_double_tranche_issue);
}
#[test]
fn derives_annual_dividend_policy_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();
};
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, 0x0d, 300_000.0);
write_current_value(&mut year_stat_family_qword_bits, 0x01, 300_000.0);
write_current_value(&mut year_stat_family_qword_bits, 0x09, -180_000.0);
write_prior_year_value(&mut year_stat_family_qword_bits, 0x01, 1, 280_000.0);
write_prior_year_value(&mut year_stat_family_qword_bits, 0x09, 1, -190_000.0);
write_prior_year_value(&mut year_stat_family_qword_bits, 0x01, 2, 260_000.0);
write_prior_year_value(&mut year_stat_family_qword_bits, 0x09, 2, -200_000.0);
write_prior_year_value(&mut year_stat_family_qword_bits, 0x1c, 1, 5.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),
dividend_policy_raw_u8: Some(0),
dividend_adjustment_allowed: Some(true),
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),
bankruptcy_policy_raw_u8: Some(0),
bankruptcy_allowed: Some(true),
building_density_growth_setting_raw_u32: Some(1),
..RuntimeWorldRestoreState::default()
},
metadata: BTreeMap::new(),
companies: vec![RuntimeCompany {
company_id: 15,
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![RuntimeChairmanProfile {
profile_id: 3,
name: "Chairman Three".to_string(),
active: true,
current_cash: 0,
linked_company_id: Some(15),
company_holdings: BTreeMap::from([(15, 9_500)]),
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 {
company_market_state: BTreeMap::from([(
15,
RuntimeCompanyMarketState {
outstanding_shares: 10_000,
founding_year: 1840,
last_dividend_year: 1844,
year_stat_family_qword_bits,
direct_control_transfer_float_fields_raw_u32: BTreeMap::from([(
0x33f,
0.4f32.to_bits(),
)]),
..RuntimeCompanyMarketState::default()
},
)]),
..RuntimeServiceState::default()
},
};
let dividend_state = runtime_company_annual_dividend_policy_state(&state, 15)
.expect("annual dividend policy state");
assert_eq!(dividend_state.years_since_last_dividend, Some(1));
assert_eq!(dividend_state.years_since_founding, Some(5));
assert_eq!(dividend_state.outstanding_shares, Some(10_000));
assert_eq!(dividend_state.unassigned_share_pool, Some(500));
assert_eq!(
dividend_state.weighted_recent_net_profit_total,
Some(600_000)
);
assert_eq!(
dividend_state.weighted_recent_net_profit_average,
Some(100_000)
);
assert_eq!(dividend_state.current_cash, Some(300_000));
assert!(dividend_state.tiny_unassigned_share_cash_supplement_branch);
assert_eq!(
dividend_state.tentative_target_dividend_per_share_tenths,
Some(133)
);
assert_eq!(dividend_state.current_dividend_per_share_tenths, Some(4));
assert_eq!(
dividend_state.growth_adjusted_current_dividend_per_share_tenths,
Some(3)
);
assert_eq!(
dividend_state.board_approved_dividend_rate_ceiling_tenths,
Some(18)
);
assert_eq!(dividend_state.proposed_dividend_per_share_tenths, Some(18));
assert!(dividend_state.eligible_for_dividend_adjustment_branch);
}
#[test]
fn reads_company_market_metrics_from_annual_finance_reader() {
let current_issue_calendar_word = 0x0101_0726;

View file

@ -3,8 +3,9 @@ use serde::{Deserialize, Serialize};
use crate::{
CalendarPoint, RuntimeState, runtime_company_annual_bond_policy_state,
runtime_company_annual_creditor_pressure_state, runtime_company_annual_deep_distress_state,
runtime_company_annual_finance_state, runtime_company_annual_stock_issue_state,
runtime_company_annual_stock_repurchase_state, runtime_company_unassigned_share_pool,
runtime_company_annual_dividend_policy_state, runtime_company_annual_finance_state,
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 {
@ -156,6 +157,16 @@ pub struct RuntimeSummary {
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 selected_company_dividend_weighted_recent_net_profit_total: Option<i64>,
pub selected_company_dividend_weighted_recent_net_profit_average: Option<i64>,
pub selected_company_dividend_current_cash: Option<i64>,
pub selected_company_dividend_tiny_unassigned_share_cash_supplement_branch: Option<bool>,
pub selected_company_dividend_tentative_target_per_share_tenths: Option<i64>,
pub selected_company_dividend_current_per_share_tenths: Option<i64>,
pub selected_company_dividend_growth_adjusted_current_per_share_tenths: Option<i64>,
pub selected_company_dividend_board_approved_ceiling_tenths: Option<i64>,
pub selected_company_dividend_proposed_per_share_tenths: Option<i64>,
pub selected_company_dividend_eligible_for_adjustment_branch: Option<bool>,
pub player_count: usize,
pub chairman_profile_count: usize,
pub active_chairman_profile_count: usize,
@ -262,6 +273,9 @@ impl RuntimeSummary {
let selected_company_stock_issue_state = state
.selected_company_id
.and_then(|company_id| runtime_company_annual_stock_issue_state(state, company_id));
let selected_company_dividend_state = state
.selected_company_id
.and_then(|company_id| runtime_company_annual_dividend_policy_state(state, company_id));
Self {
calendar: state.calendar,
calendar_projection_source: state.metadata.get("save_slice.calendar_source").cloned(),
@ -702,6 +716,51 @@ impl RuntimeSummary {
selected_company_stock_issue_state
.as_ref()
.map(|issue_state| issue_state.eligible_for_double_tranche_issue),
selected_company_dividend_weighted_recent_net_profit_total:
selected_company_dividend_state
.as_ref()
.and_then(|dividend_state| dividend_state.weighted_recent_net_profit_total),
selected_company_dividend_weighted_recent_net_profit_average:
selected_company_dividend_state
.as_ref()
.and_then(|dividend_state| dividend_state.weighted_recent_net_profit_average),
selected_company_dividend_current_cash: selected_company_dividend_state
.as_ref()
.and_then(|dividend_state| dividend_state.current_cash),
selected_company_dividend_tiny_unassigned_share_cash_supplement_branch:
selected_company_dividend_state
.as_ref()
.map(|dividend_state| {
dividend_state.tiny_unassigned_share_cash_supplement_branch
}),
selected_company_dividend_tentative_target_per_share_tenths:
selected_company_dividend_state
.as_ref()
.and_then(|dividend_state| {
dividend_state.tentative_target_dividend_per_share_tenths
}),
selected_company_dividend_current_per_share_tenths: selected_company_dividend_state
.as_ref()
.and_then(|dividend_state| dividend_state.current_dividend_per_share_tenths),
selected_company_dividend_growth_adjusted_current_per_share_tenths:
selected_company_dividend_state
.as_ref()
.and_then(|dividend_state| {
dividend_state.growth_adjusted_current_dividend_per_share_tenths
}),
selected_company_dividend_board_approved_ceiling_tenths:
selected_company_dividend_state
.as_ref()
.and_then(|dividend_state| {
dividend_state.board_approved_dividend_rate_ceiling_tenths
}),
selected_company_dividend_proposed_per_share_tenths: selected_company_dividend_state
.as_ref()
.and_then(|dividend_state| dividend_state.proposed_dividend_per_share_tenths),
selected_company_dividend_eligible_for_adjustment_branch:
selected_company_dividend_state
.as_ref()
.map(|dividend_state| dividend_state.eligible_for_dividend_adjustment_branch),
player_count: state.players.len(),
chairman_profile_count: state.chairman_profiles.len(),
active_chairman_profile_count: state
@ -3322,4 +3381,173 @@ mod tests {
Some(true)
);
}
#[test]
fn summarizes_selected_company_annual_dividend_policy_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, 0x0d, 300_000.0);
write_current_value(&mut year_stat_family_qword_bits, 0x01, 300_000.0);
write_current_value(&mut year_stat_family_qword_bits, 0x09, -180_000.0);
write_prior_year_value(&mut year_stat_family_qword_bits, 0x01, 1, 280_000.0);
write_prior_year_value(&mut year_stat_family_qword_bits, 0x09, 1, -190_000.0);
write_prior_year_value(&mut year_stat_family_qword_bits, 0x01, 2, 260_000.0);
write_prior_year_value(&mut year_stat_family_qword_bits, 0x09, 2, -200_000.0);
write_prior_year_value(&mut year_stat_family_qword_bits, 0x1c, 1, 5.0);
let state = RuntimeState {
calendar: CalendarPoint {
year: 1845,
month_slot: 0,
phase_slot: 0,
tick_slot: 0,
},
world_flags: BTreeMap::new(),
save_profile: crate::RuntimeSaveProfileState::default(),
world_restore: crate::RuntimeWorldRestoreState {
packed_year_word_raw_u16: Some(1845),
partial_year_progress_raw_u8: Some(0x0c),
dividend_policy_raw_u8: Some(0),
dividend_adjustment_allowed: Some(true),
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),
bankruptcy_policy_raw_u8: Some(0),
bankruptcy_allowed: Some(true),
building_density_growth_setting_raw_u32: Some(1),
..crate::RuntimeWorldRestoreState::default()
},
metadata: BTreeMap::new(),
companies: vec![crate::RuntimeCompany {
company_id: 15,
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: crate::RuntimeTrackPieceCounts::default(),
}],
selected_company_id: Some(15),
players: Vec::new(),
selected_player_id: None,
chairman_profiles: vec![crate::RuntimeChairmanProfile {
profile_id: 3,
name: "Chairman Three".to_string(),
active: true,
current_cash: 0,
linked_company_id: Some(15),
company_holdings: BTreeMap::from([(15, 9_500)]),
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: crate::RuntimeServiceState {
company_market_state: BTreeMap::from([(
15,
crate::RuntimeCompanyMarketState {
outstanding_shares: 10_000,
founding_year: 1840,
last_dividend_year: 1844,
year_stat_family_qword_bits,
direct_control_transfer_float_fields_raw_u32: BTreeMap::from([(
0x33f,
0.4f32.to_bits(),
)]),
..crate::RuntimeCompanyMarketState::default()
},
)]),
..crate::RuntimeServiceState::default()
},
};
let summary = RuntimeSummary::from_state(&state);
assert_eq!(
summary.selected_company_dividend_weighted_recent_net_profit_total,
Some(600_000)
);
assert_eq!(
summary.selected_company_dividend_weighted_recent_net_profit_average,
Some(100_000)
);
assert_eq!(
summary.selected_company_dividend_current_cash,
Some(300_000)
);
assert_eq!(
summary.selected_company_dividend_tiny_unassigned_share_cash_supplement_branch,
Some(true)
);
assert_eq!(
summary.selected_company_dividend_tentative_target_per_share_tenths,
Some(133)
);
assert_eq!(
summary.selected_company_dividend_current_per_share_tenths,
Some(4)
);
assert_eq!(
summary.selected_company_dividend_growth_adjusted_current_per_share_tenths,
Some(3)
);
assert_eq!(
summary.selected_company_dividend_board_approved_ceiling_tenths,
Some(18)
);
assert_eq!(
summary.selected_company_dividend_proposed_per_share_tenths,
Some(18)
);
assert_eq!(
summary.selected_company_dividend_eligible_for_adjustment_branch,
Some(true)
);
}
}

View file

@ -140,7 +140,9 @@ The highest-value next passes are 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 bond, 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
net-profit surface too; the annual dividend-adjustment branch now does as well through the
shared year-or-control-transfer reader and board-approved dividend ceiling helper; 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
- the project rule on the remaining closure work is now explicit too: when one runtime-facing field

View file

@ -234,6 +234,10 @@ 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.
The annual bond lane now rides it as well, using the simulated post-repayment cash window plus the
linked-transit threshold split to stage `500000` principal issue counts without shell ownership.
The annual dividend-adjustment lane now rides that same seam too: the runtime now rehosts the
shared year-or-control-transfer metric reader, the board-approved dividend ceiling helper, and the
full annual dividend branch over owned cash, public float, current dividend, and building-growth
policy instead of treating dividend changes as shell-dialog-only logic.
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. The stock-capital issue branch now