Rehost annual stock repurchase service branch

This commit is contained in:
Jan Petykiewicz 2026-04-18 01:27:13 -07:00
commit bb3c968cec
5 changed files with 359 additions and 30 deletions

View file

@ -105,8 +105,9 @@ full annual dividend adjustment branch over owned current cash, public float, cu
building-growth policy, and recent profit history instead of leaving that policy on shell-side
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 shellless dividend-adjustment, stock-repurchase, and stock-issue branches by mutating owned
dividend, company stat-post, 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

@ -2353,7 +2353,7 @@ fn runtime_company_total_live_bond_principal(state: &RuntimeState, company_id: u
)
}
fn runtime_company_support_adjusted_share_price_scalar_with_pressure_f64(
pub(crate) fn runtime_company_support_adjusted_share_price_scalar_with_pressure_f64(
state: &RuntimeState,
company_id: u32,
share_pressure_shares: i64,
@ -4066,7 +4066,7 @@ fn runtime_decode_saved_f64_bits(bits: u64) -> Option<f64> {
Some(value)
}
fn runtime_round_f64_to_i64(value: f64) -> Option<i64> {
pub(crate) fn runtime_round_f64_to_i64(value: f64) -> Option<i64> {
if !value.is_finite() {
return None;
}

View file

@ -10,13 +10,21 @@ use crate::{
RuntimeTerritoryMetric, RuntimeTerritoryTarget, RuntimeTrackMetric, RuntimeTrackPieceCounts,
calendar::BoundaryEventKind, runtime_company_annual_dividend_policy_state,
runtime_company_annual_finance_policy_state, runtime_company_annual_stock_issue_state,
runtime_company_annual_stock_repurchase_state,
runtime_company_book_value_per_share, runtime_company_credit_rating,
runtime_company_investor_confidence, runtime_company_management_attitude,
runtime_company_prime_rate,
runtime_company_prime_rate, RUNTIME_COMPANY_STAT_SLOT_COUNT,
RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH, RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN,
};
use crate::runtime::{
runtime_company_support_adjusted_share_price_scalar_with_pressure_f64,
runtime_round_f64_to_i64,
};
const PERIODIC_TRIGGER_KIND_ORDER: [u8; 6] = [1, 0, 3, 2, 5, 4];
const COMPANY_DIRECT_DIVIDEND_RATE_FIELD_SLOT: u32 = 0x33f;
const COMPANY_STOCK_AND_BOND_CAPITAL_POST_SCALE: f64 = -0.02;
const COMPANY_REPURCHASE_PRESSURE_SCALE: f64 = 0.7;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
@ -197,6 +205,106 @@ fn service_periodic_boundary(
Ok(())
}
fn service_decode_saved_f64_bits(raw_bits: u64) -> Option<f64> {
let value = f64::from_bits(raw_bits);
value.is_finite().then_some(value)
}
fn service_ensure_company_stat_post_capacity(
market_state: &mut crate::RuntimeCompanyMarketState,
slot_id: u32,
) -> Option<usize> {
let index = slot_id
.checked_mul(RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN)?
.try_into()
.ok()?;
let required_year_len = ((RUNTIME_COMPANY_STAT_SLOT_COUNT + 2)
* RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) as usize;
if market_state.year_stat_family_qword_bits.len() < required_year_len {
market_state
.year_stat_family_qword_bits
.resize(required_year_len, 0.0f64.to_bits());
}
let required_special_len = RUNTIME_COMPANY_STAT_SLOT_COUNT as usize;
if market_state.special_stat_family_232a_qword_bits.len() < required_special_len {
market_state
.special_stat_family_232a_qword_bits
.resize(required_special_len, 0.0f64.to_bits());
}
Some(index)
}
fn service_post_company_stat_delta(
state: &mut RuntimeState,
company_id: u32,
slot_id: u32,
delta: f64,
mirror_cash_totals: bool,
) -> bool {
if !delta.is_finite() {
return false;
}
let Some(refreshed_current_cash) = ({
let Some(market_state) = state.service_state.company_market_state.get_mut(&company_id) else {
return false;
};
let Some(index) = service_ensure_company_stat_post_capacity(market_state, slot_id) else {
return false;
};
let prior_year_value = market_state
.year_stat_family_qword_bits
.get(index)
.copied()
.and_then(service_decode_saved_f64_bits)
.unwrap_or(0.0);
market_state.year_stat_family_qword_bits[index] = (prior_year_value + delta).to_bits();
let special_index = slot_id as usize;
let prior_special_value = market_state
.special_stat_family_232a_qword_bits
.get(special_index)
.copied()
.and_then(service_decode_saved_f64_bits)
.unwrap_or(0.0);
market_state.special_stat_family_232a_qword_bits[special_index] =
(prior_special_value + delta).to_bits();
if mirror_cash_totals {
let cash_index = RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH as usize;
let prior_cash_shadow_value = market_state
.special_stat_family_232a_qword_bits
.get(cash_index)
.copied()
.and_then(service_decode_saved_f64_bits)
.unwrap_or(0.0);
market_state.special_stat_family_232a_qword_bits[cash_index] =
(prior_cash_shadow_value + delta).to_bits();
}
market_state
.year_stat_family_qword_bits
.get((RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN)
as usize)
.copied()
.and_then(service_decode_saved_f64_bits)
.and_then(runtime_round_f64_to_i64)
}) else {
return false;
};
if let Some(company) = state
.companies
.iter_mut()
.find(|company| company.company_id == company_id)
{
company.current_cash = refreshed_current_cash;
true
} else {
false
}
}
fn service_company_annual_finance_policy(
state: &mut RuntimeState,
service_events: &mut Vec<ServiceEvent>,
@ -271,9 +379,6 @@ fn service_company_annual_finance_policy(
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 {
@ -287,19 +392,32 @@ fn service_company_annual_finance_policy(
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)
let Some(next_outstanding_shares) = state
.service_state
.company_market_state
.get(&company_id)
.map(|market_state| market_state.outstanding_shares)
.and_then(|value| value.checked_add(total_share_delta))
else {
continue;
};
let mut mutated = false;
for _ in 0..2 {
mutated |= service_post_company_stat_delta(
state,
company_id,
0x0c,
(batch_size as f64) * COMPANY_STOCK_AND_BOND_CAPITAL_POST_SCALE,
true,
);
mutated |= service_post_company_stat_delta(
state,
company_id,
RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH,
proceeds_per_tranche as f64,
false,
);
}
let Some(market_state) = state
.service_state
.company_market_state
@ -307,21 +425,89 @@ fn service_company_annual_finance_policy(
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);
if mutated {
applied_effect_count += 1;
mutated_company_ids.insert(company_id);
}
}
crate::RuntimeCompanyAnnualFinancePolicyAction::StockRepurchase => {
let mut mutated = false;
for _ in 0..128 {
let Some(repurchase_state) =
runtime_company_annual_stock_repurchase_state(state, company_id)
else {
break;
};
if !repurchase_state.eligible_for_single_batch_repurchase {
break;
}
let Some(batch_size) = repurchase_state.repurchase_batch_size else {
break;
};
let Some(pressure_shares) =
runtime_round_f64_to_i64(batch_size as f64 * COMPANY_REPURCHASE_PRESSURE_SCALE)
else {
break;
};
let Some(share_price_scalar) =
runtime_company_support_adjusted_share_price_scalar_with_pressure_f64(
state,
company_id,
pressure_shares,
)
else {
break;
};
let Some(repurchase_total) =
runtime_round_f64_to_i64(share_price_scalar * batch_size as f64)
else {
break;
};
if repurchase_total <= 0 {
break;
}
let Some(next_outstanding_shares) = state
.service_state
.company_market_state
.get(&company_id)
.map(|market_state| market_state.outstanding_shares)
.and_then(|value| value.checked_sub(batch_size))
else {
break;
};
mutated |= service_post_company_stat_delta(
state,
company_id,
0x0c,
(repurchase_total as f64) * COMPANY_STOCK_AND_BOND_CAPITAL_POST_SCALE,
true,
);
mutated |= service_post_company_stat_delta(
state,
company_id,
RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH,
-(repurchase_total as f64),
false,
);
let Some(market_state) = state
.service_state
.company_market_state
.get_mut(&company_id)
else {
break;
};
market_state.outstanding_shares = next_outstanding_shares;
}
if mutated {
applied_effect_count += 1;
mutated_company_ids.insert(company_id);
}
}
_ => {}
}
@ -2407,6 +2593,148 @@ mod tests {
);
}
#[test]
fn periodic_boundary_applies_stock_repurchase_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] = 1_600_000.0f64.to_bits();
let base_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),
absolute_counter_raw_u32: Some(1_000),
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(0),
..crate::RuntimeWorldRestoreState::default()
},
metadata: BTreeMap::new(),
companies: vec![crate::RuntimeCompany {
company_id: 23,
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(8),
book_value_per_share: 0,
investor_confidence: 0,
management_attitude: 0,
takeover_cooldown_year: None,
merger_cooldown_year: None,
}],
selected_company_id: Some(23),
players: Vec::new(),
selected_player_id: None,
chairman_profiles: vec![crate::RuntimeChairmanProfile {
profile_id: 8,
name: "Chairman".to_string(),
active: true,
current_cash: 0,
linked_company_id: Some(23),
company_holdings: BTreeMap::from([(23, 9_000)]),
holdings_value_total: 0,
net_worth_total: 0,
purchasing_power_total: 0,
}],
selected_chairman_profile_id: Some(8),
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([(
23,
crate::RuntimeCompanyMarketState {
outstanding_shares: 10_000,
founding_year: 1840,
city_connection_latch: true,
recent_per_share_cache_absolute_counter: 1_000,
recent_per_share_cached_value_bits: 50.0f64.to_bits(),
mutable_support_scalar_raw_u32: 0.0f32.to_bits(),
young_company_support_scalar_raw_u32: 0.0f32.to_bits(),
year_stat_family_qword_bits,
..crate::RuntimeCompanyMarketState::default()
},
)]),
..crate::RuntimeServiceState::default()
},
};
let pressured_share_price = crate::runtime::
runtime_company_support_adjusted_share_price_scalar_with_pressure_f64(
&base_state, 23, 700,
)
.expect("repurchase share price");
let expected_repurchase_total =
crate::runtime::runtime_round_f64_to_i64(pressured_share_price * 1_000.0)
.expect("repurchase total should round");
let mut state = base_state;
let result = execute_step_command(&mut state, &StepCommand::ServicePeriodicBoundary)
.expect("periodic boundary should apply annual stock repurchase policy");
assert_eq!(
state.service_state.annual_finance_last_actions.get(&23),
Some(&crate::RuntimeCompanyAnnualFinancePolicyAction::StockRepurchase)
);
assert_eq!(state.companies[0].current_cash, 1_600_000 - expected_repurchase_total);
assert_eq!(
state.service_state.company_market_state[&23].outstanding_shares,
9_000
);
assert!(
result
.service_events
.iter()
.any(|event| event.kind == "annual_finance_policy"
&& event.applied_effect_count == 1
&& event.mutated_company_ids == vec![23])
);
}
#[test]
fn applies_company_effects_for_specific_targets() {
let mut state = RuntimeState {

View file

@ -146,7 +146,7 @@ The highest-value next passes are now:
growth setting plus the linked chairman personality byte, which is enough to run the annual
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
dividend-adjustment, stock-repurchase, 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

@ -244,8 +244,8 @@ save/runtime state instead of another threshold-only note. The stock-capital iss
rides that same seam too, with share-pressure, cooldown, and price-to-book gate state exposed as
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.
dividend-adjustment, stock-repurchase, and stock-issue branches directly into owned dividend,
company stat-post, outstanding-share, and issue-calendar state.
## Why This Boundary