Rehost annual dividend policy branch
This commit is contained in:
parent
fa86a95f0d
commit
90d213c9ed
6 changed files with 700 additions and 21 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue