Rehost annual finance policy service branch

This commit is contained in:
Jan Petykiewicz 2026-04-18 01:12:36 -07:00
commit 6f36f6269d
8 changed files with 811 additions and 9 deletions

View file

@ -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.

View file

@ -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(),
},

View file

@ -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,

View file

@ -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;

View file

@ -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]

View file

@ -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)
);
}
}

View file

@ -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

View file

@ -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