Rehost annual stock repurchase service branch
This commit is contained in:
parent
6f36f6269d
commit
bb3c968cec
5 changed files with 359 additions and 30 deletions
|
|
@ -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
|
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
|
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
|
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,
|
the shellless dividend-adjustment, stock-repurchase, and stock-issue branches by mutating owned
|
||||||
outstanding-share, and issue-calendar state instead of stopping at reader-only diagnostics.
|
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
|
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
|
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.
|
pure reader over owned save-native state instead of a guessed finance-side approximation.
|
||||||
|
|
|
||||||
|
|
@ -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,
|
state: &RuntimeState,
|
||||||
company_id: u32,
|
company_id: u32,
|
||||||
share_pressure_shares: i64,
|
share_pressure_shares: i64,
|
||||||
|
|
@ -4066,7 +4066,7 @@ fn runtime_decode_saved_f64_bits(bits: u64) -> Option<f64> {
|
||||||
Some(value)
|
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() {
|
if !value.is_finite() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,13 +10,21 @@ use crate::{
|
||||||
RuntimeTerritoryMetric, RuntimeTerritoryTarget, RuntimeTrackMetric, RuntimeTrackPieceCounts,
|
RuntimeTerritoryMetric, RuntimeTerritoryTarget, RuntimeTrackMetric, RuntimeTrackPieceCounts,
|
||||||
calendar::BoundaryEventKind, runtime_company_annual_dividend_policy_state,
|
calendar::BoundaryEventKind, runtime_company_annual_dividend_policy_state,
|
||||||
runtime_company_annual_finance_policy_state, runtime_company_annual_stock_issue_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_book_value_per_share, runtime_company_credit_rating,
|
||||||
runtime_company_investor_confidence, runtime_company_management_attitude,
|
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 PERIODIC_TRIGGER_KIND_ORDER: [u8; 6] = [1, 0, 3, 2, 5, 4];
|
||||||
const COMPANY_DIRECT_DIVIDEND_RATE_FIELD_SLOT: u32 = 0x33f;
|
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)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||||
|
|
@ -197,6 +205,106 @@ fn service_periodic_boundary(
|
||||||
Ok(())
|
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(
|
fn service_company_annual_finance_policy(
|
||||||
state: &mut RuntimeState,
|
state: &mut RuntimeState,
|
||||||
service_events: &mut Vec<ServiceEvent>,
|
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 {
|
let Some(proceeds_per_tranche) = issue_state.pressured_proceeds else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let Some(current_cash) = issue_state.current_cash else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let Some(current_tuple_word_0) =
|
let Some(current_tuple_word_0) =
|
||||||
state.world_restore.current_calendar_tuple_word_raw_u32
|
state.world_restore.current_calendar_tuple_word_raw_u32
|
||||||
else {
|
else {
|
||||||
|
|
@ -287,19 +392,32 @@ fn service_company_annual_finance_policy(
|
||||||
let Some(total_share_delta) = batch_size.checked_mul(2) else {
|
let Some(total_share_delta) = batch_size.checked_mul(2) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let Some(total_cash_delta) = proceeds_per_tranche.checked_mul(2) else {
|
let Some(next_outstanding_shares) = state
|
||||||
continue;
|
.service_state
|
||||||
};
|
.company_market_state
|
||||||
let Some(next_cash) = current_cash.checked_add(total_cash_delta) else {
|
.get(&company_id)
|
||||||
continue;
|
.map(|market_state| market_state.outstanding_shares)
|
||||||
};
|
.and_then(|value| value.checked_add(total_share_delta))
|
||||||
let Some(company) = state
|
|
||||||
.companies
|
|
||||||
.iter_mut()
|
|
||||||
.find(|company| company.company_id == company_id)
|
|
||||||
else {
|
else {
|
||||||
continue;
|
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
|
let Some(market_state) = state
|
||||||
.service_state
|
.service_state
|
||||||
.company_market_state
|
.company_market_state
|
||||||
|
|
@ -307,22 +425,90 @@ fn service_company_annual_finance_policy(
|
||||||
else {
|
else {
|
||||||
continue;
|
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.outstanding_shares = next_outstanding_shares;
|
||||||
market_state.prior_issue_calendar_word = market_state.current_issue_calendar_word;
|
market_state.prior_issue_calendar_word = market_state.current_issue_calendar_word;
|
||||||
market_state.prior_issue_calendar_word_2 =
|
market_state.prior_issue_calendar_word_2 =
|
||||||
market_state.current_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 = current_tuple_word_0;
|
||||||
market_state.current_issue_calendar_word_2 = current_tuple_word_1;
|
market_state.current_issue_calendar_word_2 = current_tuple_word_1;
|
||||||
|
if mutated {
|
||||||
applied_effect_count += 1;
|
applied_effect_count += 1;
|
||||||
mutated_company_ids.insert(company_id);
|
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]
|
#[test]
|
||||||
fn applies_company_effects_for_specific_targets() {
|
fn applies_company_effects_for_specific_targets() {
|
||||||
let mut state = RuntimeState {
|
let mut state = RuntimeState {
|
||||||
|
|
|
||||||
|
|
@ -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
|
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
|
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
|
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
|
- 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
|
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
|
instead of guessing another derived leaf field from neighboring raw offsets
|
||||||
|
|
|
||||||
|
|
@ -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
|
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:
|
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
|
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,
|
dividend-adjustment, stock-repurchase, and stock-issue branches directly into owned dividend,
|
||||||
and issue-calendar state.
|
company stat-post, outstanding-share, and issue-calendar state.
|
||||||
|
|
||||||
## Why This Boundary
|
## Why This Boundary
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue