Rehost annual finance policy service branch
This commit is contained in:
parent
90d213c9ed
commit
6f36f6269d
8 changed files with 811 additions and 9 deletions
|
|
@ -103,7 +103,10 @@ pure runtime reader. The annual dividend lane now runs there too: the runtime no
|
|||
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.
|
||||
dialog notes. `simulation_service_periodic_boundary_work` is now beginning to use that same owner
|
||||
surface too: the runtime chooses one annual-finance action per active company and already commits
|
||||
the shellless dividend-adjustment and stock-issue branches by mutating owned dividend, cash,
|
||||
outstanding-share, and issue-calendar state instead of stopping at reader-only diagnostics.
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -13984,11 +13984,15 @@ mod tests {
|
|||
special_conditions: BTreeMap::new(),
|
||||
service_state: RuntimeServiceState {
|
||||
periodic_boundary_calls: 9,
|
||||
annual_finance_service_calls: 0,
|
||||
trigger_dispatch_counts: BTreeMap::new(),
|
||||
total_event_record_services: 4,
|
||||
dirty_rerun_count: 2,
|
||||
world_issue_opinion_base_terms_raw_i32: Vec::new(),
|
||||
company_market_state: BTreeMap::new(),
|
||||
annual_finance_last_actions: BTreeMap::new(),
|
||||
annual_finance_action_counts: BTreeMap::new(),
|
||||
annual_dividend_adjustment_commit_count: 0,
|
||||
chairman_issue_opinion_terms_raw_i32: BTreeMap::new(),
|
||||
chairman_personality_raw_u8: BTreeMap::new(),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ pub use runtime::{
|
|||
RuntimeChairmanProfile, RuntimeChairmanTarget, RuntimeCompany,
|
||||
RuntimeCompanyAnnualBondPolicyState, RuntimeCompanyAnnualCreditorPressureState,
|
||||
RuntimeCompanyAnnualDeepDistressState, RuntimeCompanyAnnualDividendPolicyState,
|
||||
RuntimeCompanyAnnualFinancePolicyAction, RuntimeCompanyAnnualFinancePolicyState,
|
||||
RuntimeCompanyAnnualFinanceState, RuntimeCompanyAnnualStockIssueState,
|
||||
RuntimeCompanyAnnualStockRepurchaseState, RuntimeCompanyBondSlot,
|
||||
RuntimeCompanyConditionTestScope, RuntimeCompanyControllerKind, RuntimeCompanyMarketMetric,
|
||||
|
|
@ -70,7 +71,9 @@ pub use runtime::{
|
|||
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_dividend_policy_state,
|
||||
runtime_company_annual_finance_policy_action_label,
|
||||
runtime_company_annual_finance_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,
|
||||
|
|
|
|||
|
|
@ -376,6 +376,31 @@ pub struct RuntimeCompanyAnnualDividendPolicyState {
|
|||
pub eligible_for_dividend_adjustment_branch: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default, PartialOrd, Ord)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum RuntimeCompanyAnnualFinancePolicyAction {
|
||||
#[default]
|
||||
None,
|
||||
CreditorPressureBankruptcy,
|
||||
DeepDistressBankruptcyFallback,
|
||||
BondIssue,
|
||||
StockRepurchase,
|
||||
StockIssue,
|
||||
DividendAdjustment,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct RuntimeCompanyAnnualFinancePolicyState {
|
||||
pub company_id: u32,
|
||||
pub action: RuntimeCompanyAnnualFinancePolicyAction,
|
||||
pub creditor_pressure_bankruptcy_eligible: bool,
|
||||
pub deep_distress_bankruptcy_fallback_eligible: bool,
|
||||
pub bond_issue_eligible: bool,
|
||||
pub stock_repurchase_eligible: bool,
|
||||
pub stock_issue_eligible: bool,
|
||||
pub dividend_adjustment_eligible: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
pub struct RuntimeTrackPieceCounts {
|
||||
#[serde(default)]
|
||||
|
|
@ -1182,6 +1207,8 @@ pub struct RuntimeServiceState {
|
|||
#[serde(default)]
|
||||
pub periodic_boundary_calls: u64,
|
||||
#[serde(default)]
|
||||
pub annual_finance_service_calls: u64,
|
||||
#[serde(default)]
|
||||
pub trigger_dispatch_counts: BTreeMap<u8, u64>,
|
||||
#[serde(default)]
|
||||
pub total_event_record_services: u64,
|
||||
|
|
@ -1192,6 +1219,12 @@ pub struct RuntimeServiceState {
|
|||
#[serde(default)]
|
||||
pub company_market_state: BTreeMap<u32, RuntimeCompanyMarketState>,
|
||||
#[serde(default)]
|
||||
pub annual_finance_last_actions: BTreeMap<u32, RuntimeCompanyAnnualFinancePolicyAction>,
|
||||
#[serde(default)]
|
||||
pub annual_finance_action_counts: BTreeMap<RuntimeCompanyAnnualFinancePolicyAction, u64>,
|
||||
#[serde(default)]
|
||||
pub annual_dividend_adjustment_commit_count: u64,
|
||||
#[serde(default)]
|
||||
pub chairman_issue_opinion_terms_raw_i32: BTreeMap<u32, Vec<i32>>,
|
||||
#[serde(default)]
|
||||
pub chairman_personality_raw_u8: BTreeMap<u32, u8>,
|
||||
|
|
@ -2094,6 +2127,14 @@ impl RuntimeState {
|
|||
));
|
||||
}
|
||||
}
|
||||
for company_id in self.service_state.annual_finance_last_actions.keys() {
|
||||
if !seen_company_ids.contains(company_id) {
|
||||
return Err(format!(
|
||||
"service_state.annual_finance_last_actions references unknown company_id {}",
|
||||
company_id
|
||||
));
|
||||
}
|
||||
}
|
||||
for chairman_profile_id in self
|
||||
.service_state
|
||||
.chairman_issue_opinion_terms_raw_i32
|
||||
|
|
@ -3398,6 +3439,78 @@ pub fn runtime_company_annual_dividend_policy_state(
|
|||
})
|
||||
}
|
||||
|
||||
pub fn runtime_company_annual_finance_policy_state(
|
||||
state: &RuntimeState,
|
||||
company_id: u32,
|
||||
) -> Option<RuntimeCompanyAnnualFinancePolicyState> {
|
||||
runtime_company_annual_finance_state(state, company_id)?;
|
||||
let creditor_pressure_bankruptcy_eligible =
|
||||
runtime_company_annual_creditor_pressure_state(state, company_id)
|
||||
.map(|state| state.eligible_for_bankruptcy_branch)
|
||||
.unwrap_or(false);
|
||||
let deep_distress_bankruptcy_fallback_eligible =
|
||||
runtime_company_annual_deep_distress_state(state, company_id)
|
||||
.map(|state| state.eligible_for_bankruptcy_fallback)
|
||||
.unwrap_or(false);
|
||||
let bond_issue_eligible = runtime_company_annual_bond_policy_state(state, company_id)
|
||||
.map(|state| state.eligible_for_bond_issue_branch)
|
||||
.unwrap_or(false);
|
||||
let stock_repurchase_eligible =
|
||||
runtime_company_annual_stock_repurchase_state(state, company_id)
|
||||
.map(|state| state.eligible_for_single_batch_repurchase)
|
||||
.unwrap_or(false);
|
||||
let stock_issue_eligible = runtime_company_annual_stock_issue_state(state, company_id)
|
||||
.map(|state| state.eligible_for_double_tranche_issue)
|
||||
.unwrap_or(false);
|
||||
let dividend_adjustment_eligible =
|
||||
runtime_company_annual_dividend_policy_state(state, company_id)
|
||||
.map(|state| state.eligible_for_dividend_adjustment_branch)
|
||||
.unwrap_or(false);
|
||||
let action = if creditor_pressure_bankruptcy_eligible {
|
||||
RuntimeCompanyAnnualFinancePolicyAction::CreditorPressureBankruptcy
|
||||
} else if deep_distress_bankruptcy_fallback_eligible {
|
||||
RuntimeCompanyAnnualFinancePolicyAction::DeepDistressBankruptcyFallback
|
||||
} else if bond_issue_eligible {
|
||||
RuntimeCompanyAnnualFinancePolicyAction::BondIssue
|
||||
} else if stock_repurchase_eligible {
|
||||
RuntimeCompanyAnnualFinancePolicyAction::StockRepurchase
|
||||
} else if stock_issue_eligible {
|
||||
RuntimeCompanyAnnualFinancePolicyAction::StockIssue
|
||||
} else if dividend_adjustment_eligible {
|
||||
RuntimeCompanyAnnualFinancePolicyAction::DividendAdjustment
|
||||
} else {
|
||||
RuntimeCompanyAnnualFinancePolicyAction::None
|
||||
};
|
||||
Some(RuntimeCompanyAnnualFinancePolicyState {
|
||||
company_id,
|
||||
action,
|
||||
creditor_pressure_bankruptcy_eligible,
|
||||
deep_distress_bankruptcy_fallback_eligible,
|
||||
bond_issue_eligible,
|
||||
stock_repurchase_eligible,
|
||||
stock_issue_eligible,
|
||||
dividend_adjustment_eligible,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn runtime_company_annual_finance_policy_action_label(
|
||||
action: RuntimeCompanyAnnualFinancePolicyAction,
|
||||
) -> &'static str {
|
||||
match action {
|
||||
RuntimeCompanyAnnualFinancePolicyAction::None => "none",
|
||||
RuntimeCompanyAnnualFinancePolicyAction::CreditorPressureBankruptcy => {
|
||||
"creditor_pressure_bankruptcy"
|
||||
}
|
||||
RuntimeCompanyAnnualFinancePolicyAction::DeepDistressBankruptcyFallback => {
|
||||
"deep_distress_bankruptcy_fallback"
|
||||
}
|
||||
RuntimeCompanyAnnualFinancePolicyAction::BondIssue => "bond_issue",
|
||||
RuntimeCompanyAnnualFinancePolicyAction::StockRepurchase => "stock_repurchase",
|
||||
RuntimeCompanyAnnualFinancePolicyAction::StockIssue => "stock_issue",
|
||||
RuntimeCompanyAnnualFinancePolicyAction::DividendAdjustment => "dividend_adjustment",
|
||||
}
|
||||
}
|
||||
|
||||
fn runtime_company_stock_issue_price_to_book_ratio_f64(
|
||||
pressured_support_adjusted_share_price_scalar: f64,
|
||||
book_value_per_share: f64,
|
||||
|
|
@ -8459,6 +8572,143 @@ mod tests {
|
|||
assert!(dividend_state.eligible_for_dividend_adjustment_branch);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derives_annual_finance_policy_action_from_branch_priority_order() {
|
||||
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: 16,
|
||||
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(4),
|
||||
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(16),
|
||||
players: Vec::new(),
|
||||
selected_player_id: None,
|
||||
chairman_profiles: vec![RuntimeChairmanProfile {
|
||||
profile_id: 4,
|
||||
name: "Chairman Four".to_string(),
|
||||
active: true,
|
||||
current_cash: 0,
|
||||
linked_company_id: Some(16),
|
||||
company_holdings: BTreeMap::from([(16, 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 {
|
||||
chairman_personality_raw_u8: BTreeMap::from([(4, 20)]),
|
||||
company_market_state: BTreeMap::from([(
|
||||
16,
|
||||
RuntimeCompanyMarketState {
|
||||
outstanding_shares: 10_000,
|
||||
founding_year: 1840,
|
||||
last_dividend_year: 1844,
|
||||
city_connection_latch: true,
|
||||
year_stat_family_qword_bits,
|
||||
direct_control_transfer_float_fields_raw_u32: BTreeMap::from([(
|
||||
0x33f,
|
||||
0.4f32.to_bits(),
|
||||
)]),
|
||||
..RuntimeCompanyMarketState::default()
|
||||
},
|
||||
)]),
|
||||
..RuntimeServiceState::default()
|
||||
},
|
||||
};
|
||||
|
||||
let policy_state = runtime_company_annual_finance_policy_state(&state, 16)
|
||||
.expect("annual finance policy state");
|
||||
assert_eq!(
|
||||
policy_state.action,
|
||||
RuntimeCompanyAnnualFinancePolicyAction::DividendAdjustment
|
||||
);
|
||||
assert!(!policy_state.stock_repurchase_eligible);
|
||||
assert!(!policy_state.stock_issue_eligible);
|
||||
assert!(policy_state.dividend_adjustment_eligible);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reads_company_market_metrics_from_annual_finance_reader() {
|
||||
let current_issue_calendar_word = 0x0101_0726;
|
||||
|
|
|
|||
|
|
@ -8,12 +8,15 @@ use crate::{
|
|||
RuntimeCompanyMetric, RuntimeCompanyTarget, RuntimeCondition, RuntimeConditionComparator,
|
||||
RuntimeEffect, RuntimeEventRecordTemplate, RuntimePlayerTarget, RuntimeState, RuntimeSummary,
|
||||
RuntimeTerritoryMetric, RuntimeTerritoryTarget, RuntimeTrackMetric, RuntimeTrackPieceCounts,
|
||||
calendar::BoundaryEventKind, runtime_company_book_value_per_share,
|
||||
runtime_company_credit_rating, runtime_company_investor_confidence,
|
||||
runtime_company_management_attitude, runtime_company_prime_rate,
|
||||
calendar::BoundaryEventKind, runtime_company_annual_dividend_policy_state,
|
||||
runtime_company_annual_finance_policy_state, runtime_company_annual_stock_issue_state,
|
||||
runtime_company_book_value_per_share, runtime_company_credit_rating,
|
||||
runtime_company_investor_confidence, runtime_company_management_attitude,
|
||||
runtime_company_prime_rate,
|
||||
};
|
||||
|
||||
const PERIODIC_TRIGGER_KIND_ORDER: [u8; 6] = [1, 0, 3, 2, 5, 4];
|
||||
const COMPANY_DIRECT_DIVIDEND_RATE_FIELD_SLOT: u32 = 0x33f;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||
|
|
@ -189,6 +192,154 @@ fn service_periodic_boundary(
|
|||
for trigger_kind in PERIODIC_TRIGGER_KIND_ORDER {
|
||||
service_trigger_kind(state, trigger_kind, service_events)?;
|
||||
}
|
||||
service_company_annual_finance_policy(state, service_events)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn service_company_annual_finance_policy(
|
||||
state: &mut RuntimeState,
|
||||
service_events: &mut Vec<ServiceEvent>,
|
||||
) -> Result<(), String> {
|
||||
let active_company_ids = state
|
||||
.companies
|
||||
.iter()
|
||||
.filter(|company| company.active)
|
||||
.map(|company| company.company_id)
|
||||
.collect::<Vec<_>>();
|
||||
let active_company_id_set = active_company_ids.iter().copied().collect::<BTreeSet<_>>();
|
||||
state
|
||||
.service_state
|
||||
.annual_finance_last_actions
|
||||
.retain(|company_id, _| active_company_id_set.contains(company_id));
|
||||
state.service_state.annual_finance_service_calls += 1;
|
||||
|
||||
let mut mutated_company_ids = BTreeSet::new();
|
||||
let mut applied_effect_count = 0u32;
|
||||
|
||||
for company_id in active_company_ids {
|
||||
let Some(policy_state) = runtime_company_annual_finance_policy_state(state, company_id)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
state
|
||||
.service_state
|
||||
.annual_finance_last_actions
|
||||
.insert(company_id, policy_state.action);
|
||||
*state
|
||||
.service_state
|
||||
.annual_finance_action_counts
|
||||
.entry(policy_state.action)
|
||||
.or_insert(0) += 1;
|
||||
|
||||
match policy_state.action {
|
||||
crate::RuntimeCompanyAnnualFinancePolicyAction::DividendAdjustment => {
|
||||
let Some(dividend_state) =
|
||||
runtime_company_annual_dividend_policy_state(state, company_id)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Some(proposed_tenths) = dividend_state.proposed_dividend_per_share_tenths
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Some(market_state) = state
|
||||
.service_state
|
||||
.company_market_state
|
||||
.get_mut(&company_id)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let raw_bits = ((proposed_tenths as f32) / 10.0).to_bits();
|
||||
let prior_bits = market_state
|
||||
.direct_control_transfer_float_fields_raw_u32
|
||||
.insert(COMPANY_DIRECT_DIVIDEND_RATE_FIELD_SLOT, raw_bits);
|
||||
if prior_bits != Some(raw_bits) {
|
||||
applied_effect_count += 1;
|
||||
state.service_state.annual_dividend_adjustment_commit_count += 1;
|
||||
mutated_company_ids.insert(company_id);
|
||||
}
|
||||
}
|
||||
crate::RuntimeCompanyAnnualFinancePolicyAction::StockIssue => {
|
||||
let Some(issue_state) = runtime_company_annual_stock_issue_state(state, company_id)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Some(batch_size) = issue_state.trimmed_issue_batch_size else {
|
||||
continue;
|
||||
};
|
||||
let Some(proceeds_per_tranche) = issue_state.pressured_proceeds else {
|
||||
continue;
|
||||
};
|
||||
let Some(current_cash) = issue_state.current_cash else {
|
||||
continue;
|
||||
};
|
||||
let Some(current_tuple_word_0) =
|
||||
state.world_restore.current_calendar_tuple_word_raw_u32
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Some(current_tuple_word_1) =
|
||||
state.world_restore.current_calendar_tuple_word_2_raw_u32
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Some(total_share_delta) = batch_size.checked_mul(2) else {
|
||||
continue;
|
||||
};
|
||||
let Some(total_cash_delta) = proceeds_per_tranche.checked_mul(2) else {
|
||||
continue;
|
||||
};
|
||||
let Some(next_cash) = current_cash.checked_add(total_cash_delta) else {
|
||||
continue;
|
||||
};
|
||||
let Some(company) = state
|
||||
.companies
|
||||
.iter_mut()
|
||||
.find(|company| company.company_id == company_id)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Some(market_state) = state
|
||||
.service_state
|
||||
.company_market_state
|
||||
.get_mut(&company_id)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Some(next_outstanding_shares) = market_state
|
||||
.outstanding_shares
|
||||
.checked_add(total_share_delta)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
company.current_cash = next_cash;
|
||||
market_state.outstanding_shares = next_outstanding_shares;
|
||||
market_state.prior_issue_calendar_word = market_state.current_issue_calendar_word;
|
||||
market_state.prior_issue_calendar_word_2 =
|
||||
market_state.current_issue_calendar_word_2;
|
||||
market_state.current_issue_calendar_word = current_tuple_word_0;
|
||||
market_state.current_issue_calendar_word_2 = current_tuple_word_1;
|
||||
applied_effect_count += 1;
|
||||
mutated_company_ids.insert(company_id);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
service_events.push(ServiceEvent {
|
||||
kind: "annual_finance_policy".to_string(),
|
||||
trigger_kind: None,
|
||||
serviced_record_ids: Vec::new(),
|
||||
applied_effect_count,
|
||||
mutated_company_ids: mutated_company_ids.into_iter().collect(),
|
||||
mutated_player_ids: Vec::new(),
|
||||
appended_record_ids: Vec::new(),
|
||||
activated_record_ids: Vec::new(),
|
||||
deactivated_record_ids: Vec::new(),
|
||||
removed_record_ids: Vec::new(),
|
||||
dirty_rerun: false,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -1899,7 +2050,7 @@ mod tests {
|
|||
state.service_state.trigger_dispatch_counts.get(&0x0a),
|
||||
Some(&1)
|
||||
);
|
||||
assert_eq!(result.service_events.len(), 7);
|
||||
assert_eq!(result.service_events.len(), 8);
|
||||
assert_eq!(result.service_events[0].applied_effect_count, 1);
|
||||
assert_eq!(
|
||||
result
|
||||
|
|
@ -1928,6 +2079,332 @@ mod tests {
|
|||
.mutated_company_ids,
|
||||
vec![1]
|
||||
);
|
||||
assert!(
|
||||
result
|
||||
.service_events
|
||||
.iter()
|
||||
.any(|event| event.kind == "annual_finance_policy")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn periodic_boundary_applies_dividend_adjustment_from_annual_finance_policy() {
|
||||
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 mut state = RuntimeState {
|
||||
calendar: crate::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: 21,
|
||||
controller_kind: crate::RuntimeCompanyControllerKind::Unknown,
|
||||
current_cash: 0,
|
||||
debt: 0,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: crate::RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: Some(7),
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
}],
|
||||
selected_company_id: Some(21),
|
||||
players: Vec::new(),
|
||||
selected_player_id: None,
|
||||
chairman_profiles: vec![crate::RuntimeChairmanProfile {
|
||||
profile_id: 7,
|
||||
name: "Chairman Seven".to_string(),
|
||||
active: true,
|
||||
current_cash: 0,
|
||||
linked_company_id: Some(21),
|
||||
company_holdings: BTreeMap::from([(21, 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([(
|
||||
21,
|
||||
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 result = execute_step_command(&mut state, &StepCommand::ServicePeriodicBoundary)
|
||||
.expect("periodic boundary should apply annual finance policy");
|
||||
|
||||
assert_eq!(state.service_state.annual_finance_service_calls, 1);
|
||||
assert_eq!(
|
||||
state.service_state.annual_dividend_adjustment_commit_count,
|
||||
1
|
||||
);
|
||||
assert_eq!(
|
||||
state.service_state.annual_finance_last_actions.get(&21),
|
||||
Some(&crate::RuntimeCompanyAnnualFinancePolicyAction::DividendAdjustment)
|
||||
);
|
||||
assert_eq!(
|
||||
state.service_state.company_market_state[&21]
|
||||
.direct_control_transfer_float_fields_raw_u32
|
||||
.get(&0x33f),
|
||||
Some(&1.8f32.to_bits())
|
||||
);
|
||||
assert!(
|
||||
result
|
||||
.service_events
|
||||
.iter()
|
||||
.any(|event| event.kind == "annual_finance_policy"
|
||||
&& event.applied_effect_count == 1
|
||||
&& event.mutated_company_ids == vec![21])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn periodic_boundary_applies_stock_issue_from_annual_finance_policy() {
|
||||
let mut year_stat_family_qword_bits = vec![
|
||||
0u64;
|
||||
(crate::RUNTIME_COMPANY_STAT_SLOT_COUNT * 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 current_issue_calendar_word = 0x0101_0725;
|
||||
let current_issue_calendar_word_2 = 0x0001_0001;
|
||||
let prior_issue_calendar_word = 0x0101_0701;
|
||||
let prior_issue_calendar_word_2 = 0x0001_0001;
|
||||
|
||||
let mut state = RuntimeState {
|
||||
calendar: crate::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),
|
||||
current_calendar_tuple_word_raw_u32: Some(0x0101_0726),
|
||||
current_calendar_tuple_word_2_raw_u32: Some(0x0001_0001),
|
||||
absolute_counter_raw_u32: Some(885_911_040),
|
||||
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),
|
||||
issue_37_value: Some(5.0f32.to_bits()),
|
||||
issue_38_value: Some(2),
|
||||
..crate::RuntimeWorldRestoreState::default()
|
||||
},
|
||||
metadata: BTreeMap::new(),
|
||||
companies: vec![crate::RuntimeCompany {
|
||||
company_id: 22,
|
||||
controller_kind: crate::RuntimeCompanyControllerKind::Unknown,
|
||||
current_cash: 0,
|
||||
debt: 0,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: crate::RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
linked_chairman_profile_id: None,
|
||||
book_value_per_share: 0,
|
||||
investor_confidence: 0,
|
||||
management_attitude: 0,
|
||||
takeover_cooldown_year: None,
|
||||
merger_cooldown_year: None,
|
||||
}],
|
||||
selected_company_id: Some(22),
|
||||
players: Vec::new(),
|
||||
selected_player_id: None,
|
||||
chairman_profiles: Vec::new(),
|
||||
selected_chairman_profile_id: None,
|
||||
trains: Vec::new(),
|
||||
locomotive_catalog: Vec::new(),
|
||||
cargo_catalog: Vec::new(),
|
||||
territories: Vec::new(),
|
||||
company_territory_track_piece_counts: Vec::new(),
|
||||
company_territory_access: Vec::new(),
|
||||
packed_event_collection: None,
|
||||
event_runtime_records: Vec::new(),
|
||||
candidate_availability: BTreeMap::new(),
|
||||
named_locomotive_availability: BTreeMap::new(),
|
||||
named_locomotive_cost: BTreeMap::new(),
|
||||
all_cargo_price_override: None,
|
||||
named_cargo_price_overrides: BTreeMap::new(),
|
||||
all_cargo_production_override: None,
|
||||
factory_cargo_production_override: None,
|
||||
farm_mine_cargo_production_override: None,
|
||||
named_cargo_production_overrides: BTreeMap::new(),
|
||||
cargo_production_overrides: BTreeMap::new(),
|
||||
world_runtime_variables: BTreeMap::new(),
|
||||
company_runtime_variables: BTreeMap::new(),
|
||||
player_runtime_variables: BTreeMap::new(),
|
||||
territory_runtime_variables: BTreeMap::new(),
|
||||
world_scalar_overrides: BTreeMap::new(),
|
||||
special_conditions: BTreeMap::new(),
|
||||
service_state: crate::RuntimeServiceState {
|
||||
world_issue_opinion_base_terms_raw_i32: vec![0; 0x3b],
|
||||
company_market_state: BTreeMap::from([(
|
||||
22,
|
||||
crate::RuntimeCompanyMarketState {
|
||||
outstanding_shares: 20_000,
|
||||
bond_count: 2,
|
||||
highest_coupon_live_bond_principal: Some(300_000),
|
||||
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(),
|
||||
current_issue_calendar_word,
|
||||
current_issue_calendar_word_2,
|
||||
prior_issue_calendar_word,
|
||||
prior_issue_calendar_word_2,
|
||||
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()
|
||||
},
|
||||
)]),
|
||||
..crate::RuntimeServiceState::default()
|
||||
},
|
||||
};
|
||||
|
||||
let result = execute_step_command(&mut state, &StepCommand::ServicePeriodicBoundary)
|
||||
.expect("periodic boundary should apply annual stock issue policy");
|
||||
|
||||
assert_eq!(
|
||||
state.service_state.annual_finance_last_actions.get(&22),
|
||||
Some(&crate::RuntimeCompanyAnnualFinancePolicyAction::StockIssue)
|
||||
);
|
||||
assert_eq!(state.companies[0].current_cash, 390_000);
|
||||
assert_eq!(
|
||||
state.service_state.company_market_state[&22].outstanding_shares,
|
||||
24_000
|
||||
);
|
||||
assert_eq!(
|
||||
state.service_state.company_market_state[&22].prior_issue_calendar_word,
|
||||
current_issue_calendar_word
|
||||
);
|
||||
assert_eq!(
|
||||
state.service_state.company_market_state[&22].prior_issue_calendar_word_2,
|
||||
current_issue_calendar_word_2
|
||||
);
|
||||
assert_eq!(
|
||||
state.service_state.company_market_state[&22].current_issue_calendar_word,
|
||||
0x0101_0726
|
||||
);
|
||||
assert_eq!(
|
||||
state.service_state.company_market_state[&22].current_issue_calendar_word_2,
|
||||
0x0001_0001
|
||||
);
|
||||
assert!(
|
||||
result
|
||||
.service_events
|
||||
.iter()
|
||||
.any(|event| event.kind == "annual_finance_policy"
|
||||
&& event.applied_effect_count == 1
|
||||
&& event.mutated_company_ids == vec![22])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -3,7 +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_dividend_policy_state, runtime_company_annual_finance_state,
|
||||
runtime_company_annual_dividend_policy_state,
|
||||
runtime_company_annual_finance_policy_action_label,
|
||||
runtime_company_annual_finance_policy_state, runtime_company_annual_finance_state,
|
||||
runtime_company_annual_stock_issue_state, runtime_company_annual_stock_repurchase_state,
|
||||
runtime_company_unassigned_share_pool,
|
||||
};
|
||||
|
|
@ -167,6 +169,14 @@ pub struct RuntimeSummary {
|
|||
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 selected_company_annual_finance_policy_action: Option<String>,
|
||||
pub selected_company_annual_finance_policy_creditor_pressure_bankruptcy_eligible: Option<bool>,
|
||||
pub selected_company_annual_finance_policy_deep_distress_bankruptcy_fallback_eligible:
|
||||
Option<bool>,
|
||||
pub selected_company_annual_finance_policy_bond_issue_eligible: Option<bool>,
|
||||
pub selected_company_annual_finance_policy_stock_repurchase_eligible: Option<bool>,
|
||||
pub selected_company_annual_finance_policy_stock_issue_eligible: Option<bool>,
|
||||
pub selected_company_annual_finance_policy_dividend_adjustment_eligible: Option<bool>,
|
||||
pub player_count: usize,
|
||||
pub chairman_profile_count: usize,
|
||||
pub active_chairman_profile_count: usize,
|
||||
|
|
@ -243,6 +253,8 @@ pub struct RuntimeSummary {
|
|||
pub save_profile_staged_profile_copy_on_restore: Option<bool>,
|
||||
pub total_event_record_service_count: u64,
|
||||
pub periodic_boundary_call_count: u64,
|
||||
pub annual_finance_service_call_count: u64,
|
||||
pub annual_dividend_adjustment_commit_count: u64,
|
||||
pub total_trigger_dispatch_count: u64,
|
||||
pub dirty_rerun_count: u64,
|
||||
pub total_company_cash: i64,
|
||||
|
|
@ -276,6 +288,9 @@ impl RuntimeSummary {
|
|||
let selected_company_dividend_state = state
|
||||
.selected_company_id
|
||||
.and_then(|company_id| runtime_company_annual_dividend_policy_state(state, company_id));
|
||||
let selected_company_annual_finance_policy_state = state
|
||||
.selected_company_id
|
||||
.and_then(|company_id| runtime_company_annual_finance_policy_state(state, company_id));
|
||||
Self {
|
||||
calendar: state.calendar,
|
||||
calendar_projection_source: state.metadata.get("save_slice.calendar_source").cloned(),
|
||||
|
|
@ -761,6 +776,37 @@ impl RuntimeSummary {
|
|||
selected_company_dividend_state
|
||||
.as_ref()
|
||||
.map(|dividend_state| dividend_state.eligible_for_dividend_adjustment_branch),
|
||||
selected_company_annual_finance_policy_action:
|
||||
selected_company_annual_finance_policy_state
|
||||
.as_ref()
|
||||
.map(|policy_state| {
|
||||
runtime_company_annual_finance_policy_action_label(policy_state.action)
|
||||
.to_string()
|
||||
}),
|
||||
selected_company_annual_finance_policy_creditor_pressure_bankruptcy_eligible:
|
||||
selected_company_annual_finance_policy_state
|
||||
.as_ref()
|
||||
.map(|policy_state| policy_state.creditor_pressure_bankruptcy_eligible),
|
||||
selected_company_annual_finance_policy_deep_distress_bankruptcy_fallback_eligible:
|
||||
selected_company_annual_finance_policy_state
|
||||
.as_ref()
|
||||
.map(|policy_state| policy_state.deep_distress_bankruptcy_fallback_eligible),
|
||||
selected_company_annual_finance_policy_bond_issue_eligible:
|
||||
selected_company_annual_finance_policy_state
|
||||
.as_ref()
|
||||
.map(|policy_state| policy_state.bond_issue_eligible),
|
||||
selected_company_annual_finance_policy_stock_repurchase_eligible:
|
||||
selected_company_annual_finance_policy_state
|
||||
.as_ref()
|
||||
.map(|policy_state| policy_state.stock_repurchase_eligible),
|
||||
selected_company_annual_finance_policy_stock_issue_eligible:
|
||||
selected_company_annual_finance_policy_state
|
||||
.as_ref()
|
||||
.map(|policy_state| policy_state.stock_issue_eligible),
|
||||
selected_company_annual_finance_policy_dividend_adjustment_eligible:
|
||||
selected_company_annual_finance_policy_state
|
||||
.as_ref()
|
||||
.map(|policy_state| policy_state.dividend_adjustment_eligible),
|
||||
player_count: state.players.len(),
|
||||
chairman_profile_count: state.chairman_profiles.len(),
|
||||
active_chairman_profile_count: state
|
||||
|
|
@ -1311,6 +1357,10 @@ impl RuntimeSummary {
|
|||
.staged_profile_copy_on_restore,
|
||||
total_event_record_service_count: state.service_state.total_event_record_services,
|
||||
periodic_boundary_call_count: state.service_state.periodic_boundary_calls,
|
||||
annual_finance_service_call_count: state.service_state.annual_finance_service_calls,
|
||||
annual_dividend_adjustment_commit_count: state
|
||||
.service_state
|
||||
.annual_dividend_adjustment_commit_count,
|
||||
total_trigger_dispatch_count: state
|
||||
.service_state
|
||||
.trigger_dispatch_counts
|
||||
|
|
@ -3549,5 +3599,15 @@ mod tests {
|
|||
summary.selected_company_dividend_eligible_for_adjustment_branch,
|
||||
Some(true)
|
||||
);
|
||||
assert_eq!(
|
||||
summary
|
||||
.selected_company_annual_finance_policy_action
|
||||
.as_deref(),
|
||||
Some("dividend_adjustment")
|
||||
);
|
||||
assert_eq!(
|
||||
summary.selected_company_annual_finance_policy_dividend_adjustment_eligible,
|
||||
Some(true)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -144,7 +144,9 @@ The highest-value next passes are now:
|
|||
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
|
||||
stock-repurchase gate headlessly as another pure reader; periodic boundary service now also
|
||||
chooses one annual-finance action per active company and already commits the shellless
|
||||
dividend-adjustment and stock-issue branches against owned runtime state
|
||||
- the project rule on the remaining closure work is now explicit too: when one runtime-facing field
|
||||
is still ambiguous, prefer rehosting the owning source state or real reader/setter family first
|
||||
instead of guessing another derived leaf field from neighboring raw offsets
|
||||
|
|
|
|||
|
|
@ -242,7 +242,10 @@ That same seam now also carries the fixed-world building-density growth setting
|
|||
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
|
||||
rides that same seam too, with share-pressure, cooldown, and price-to-book gate state exposed as
|
||||
normal runtime readers.
|
||||
normal runtime readers. Periodic boundary service now also uses that owner seam as a real chooser:
|
||||
the runtime selects one annual-finance action per active company and already commits the shellless
|
||||
dividend-adjustment and stock-issue branches directly into owned dividend, cash, outstanding-share,
|
||||
and issue-calendar state.
|
||||
|
||||
## Why This Boundary
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue