Rehost annual bond repayment and compaction
This commit is contained in:
parent
ad048f1528
commit
fc1ba28109
5 changed files with 686 additions and 105 deletions
|
|
@ -99,7 +99,8 @@ distress bankruptcy fallback is now rehosted on that same owner surface too, usi
|
||||||
cash reader seam plus the first three trailing net-profit years instead of another ad hoc probe.
|
cash reader seam plus the first three trailing net-profit years instead of another ad hoc probe.
|
||||||
The annual bond lane now runs on that same owner surface too, using the simulated post-repayment
|
The annual bond lane now runs on that same owner surface too, using the simulated post-repayment
|
||||||
cash window plus the linked-transit threshold split to stage `500000` principal issue counts as a
|
cash window plus the linked-transit threshold split to stage `500000` principal issue counts as a
|
||||||
pure runtime reader. The annual dividend lane now runs there too: the runtime now rehosts the
|
pure runtime reader, and periodic boundary service now commits the same shellless matured-bond repayment-and-
|
||||||
|
compaction path before issuing the exact staged count. The annual dividend lane now runs there too: the runtime now rehosts the
|
||||||
shared year-or-control-transfer metric seam, the board-approved dividend ceiling helper, and the
|
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,
|
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
|
building-growth policy, and recent profit history instead of leaving that policy on shell-side
|
||||||
|
|
|
||||||
|
|
@ -3298,7 +3298,8 @@ pub fn runtime_company_annual_bond_policy_state(
|
||||||
let eligible_for_bond_issue_branch = runtime_world_annual_finance_mode_active(state)
|
let eligible_for_bond_issue_branch = runtime_world_annual_finance_mode_active(state)
|
||||||
== Some(true)
|
== Some(true)
|
||||||
&& runtime_world_bond_issue_and_repayment_allowed(state) == Some(true)
|
&& runtime_world_bond_issue_and_repayment_allowed(state) == Some(true)
|
||||||
&& proposed_issue_bond_count.is_some_and(|count| count > 0);
|
&& (matured_live_bond_principal_total.is_some_and(|principal| principal > 0)
|
||||||
|
|| proposed_issue_bond_count.is_some_and(|count| count > 0));
|
||||||
Some(RuntimeCompanyAnnualBondPolicyState {
|
Some(RuntimeCompanyAnnualBondPolicyState {
|
||||||
company_id,
|
company_id,
|
||||||
annual_mode_active: runtime_world_annual_finance_mode_active(state),
|
annual_mode_active: runtime_world_annual_finance_mode_active(state),
|
||||||
|
|
@ -8350,6 +8351,116 @@ mod tests {
|
||||||
assert!(bond_state.eligible_for_bond_issue_branch);
|
assert!(bond_state.eligible_for_bond_issue_branch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn annual_bond_policy_stays_eligible_for_repayment_without_new_issue() {
|
||||||
|
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();
|
||||||
|
};
|
||||||
|
write_current_value(&mut year_stat_family_qword_bits, 0x0d, 900_000.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 {
|
||||||
|
partial_year_progress_raw_u8: Some(0x0c),
|
||||||
|
bond_issue_and_repayment_policy_raw_u8: Some(0),
|
||||||
|
bond_issue_and_repayment_allowed: Some(true),
|
||||||
|
..RuntimeWorldRestoreState::default()
|
||||||
|
},
|
||||||
|
metadata: BTreeMap::new(),
|
||||||
|
companies: vec![RuntimeCompany {
|
||||||
|
company_id: 12,
|
||||||
|
current_cash: 0,
|
||||||
|
debt: 0,
|
||||||
|
credit_rating_score: None,
|
||||||
|
prime_rate: None,
|
||||||
|
active: true,
|
||||||
|
available_track_laying_capacity: None,
|
||||||
|
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||||
|
linked_chairman_profile_id: None,
|
||||||
|
book_value_per_share: 0,
|
||||||
|
investor_confidence: 0,
|
||||||
|
management_attitude: 0,
|
||||||
|
takeover_cooldown_year: None,
|
||||||
|
merger_cooldown_year: None,
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
|
}],
|
||||||
|
selected_company_id: None,
|
||||||
|
players: Vec::new(),
|
||||||
|
selected_player_id: None,
|
||||||
|
chairman_profiles: Vec::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: RuntimeServiceState {
|
||||||
|
company_market_state: BTreeMap::from([(
|
||||||
|
12,
|
||||||
|
RuntimeCompanyMarketState {
|
||||||
|
bond_count: 2,
|
||||||
|
live_bond_slots: vec![
|
||||||
|
RuntimeCompanyBondSlot {
|
||||||
|
slot_index: 0,
|
||||||
|
principal: 200_000,
|
||||||
|
maturity_year: 1845,
|
||||||
|
coupon_rate_raw_u32: 0.09f32.to_bits(),
|
||||||
|
},
|
||||||
|
RuntimeCompanyBondSlot {
|
||||||
|
slot_index: 1,
|
||||||
|
principal: 150_000,
|
||||||
|
maturity_year: 1847,
|
||||||
|
coupon_rate_raw_u32: 0.08f32.to_bits(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
year_stat_family_qword_bits,
|
||||||
|
..RuntimeCompanyMarketState::default()
|
||||||
|
},
|
||||||
|
)]),
|
||||||
|
..RuntimeServiceState::default()
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let bond_state =
|
||||||
|
runtime_company_annual_bond_policy_state(&state, 12).expect("bond policy state");
|
||||||
|
assert_eq!(bond_state.live_bond_principal_total, Some(350_000));
|
||||||
|
assert_eq!(bond_state.cash_after_full_repayment, Some(550_000));
|
||||||
|
assert_eq!(bond_state.proposed_issue_bond_count, Some(0));
|
||||||
|
assert!(bond_state.eligible_for_bond_issue_branch);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn derives_annual_stock_issue_state_from_rehosted_owner_state() {
|
fn derives_annual_stock_issue_state_from_rehosted_owner_state() {
|
||||||
let mut year_stat_family_qword_bits = vec![
|
let mut year_stat_family_qword_bits = vec![
|
||||||
|
|
|
||||||
|
|
@ -2,25 +2,25 @@ use std::collections::BTreeSet;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
|
||||||
RuntimeCargoClass, RuntimeCargoPriceTarget, RuntimeCargoProductionTarget,
|
|
||||||
RuntimeChairmanMetric, RuntimeChairmanTarget, RuntimeCompanyControllerKind,
|
|
||||||
RuntimeCompanyMetric, RuntimeCompanyTarget, RuntimeCondition, RuntimeConditionComparator,
|
|
||||||
RuntimeEffect, RuntimeEventRecordTemplate, RuntimePlayerTarget, RuntimeState, RuntimeSummary,
|
|
||||||
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_STAT_SLOT_COUNT,
|
|
||||||
RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH, RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN,
|
|
||||||
};
|
|
||||||
use crate::runtime::{
|
use crate::runtime::{
|
||||||
runtime_company_bond_interest_rate_quote_f64,
|
runtime_company_bond_interest_rate_quote_f64,
|
||||||
runtime_company_support_adjusted_share_price_scalar_with_pressure_f64,
|
runtime_company_support_adjusted_share_price_scalar_with_pressure_f64,
|
||||||
runtime_round_f64_to_i64,
|
runtime_round_f64_to_i64,
|
||||||
};
|
};
|
||||||
|
use crate::{
|
||||||
|
RUNTIME_COMPANY_STAT_SLOT_COUNT, RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH,
|
||||||
|
RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN, RuntimeCargoClass, RuntimeCargoPriceTarget,
|
||||||
|
RuntimeCargoProductionTarget, RuntimeChairmanMetric, RuntimeChairmanTarget,
|
||||||
|
RuntimeCompanyControllerKind, RuntimeCompanyMetric, RuntimeCompanyTarget, RuntimeCondition,
|
||||||
|
RuntimeConditionComparator, RuntimeEffect, RuntimeEventRecordTemplate, RuntimePlayerTarget,
|
||||||
|
RuntimeState, RuntimeSummary, 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,
|
||||||
|
};
|
||||||
|
|
||||||
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;
|
||||||
|
|
@ -219,8 +219,8 @@ fn service_ensure_company_stat_post_capacity(
|
||||||
.checked_mul(RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN)?
|
.checked_mul(RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN)?
|
||||||
.try_into()
|
.try_into()
|
||||||
.ok()?;
|
.ok()?;
|
||||||
let required_year_len = ((RUNTIME_COMPANY_STAT_SLOT_COUNT + 2)
|
let required_year_len =
|
||||||
* RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) as usize;
|
((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 {
|
if market_state.year_stat_family_qword_bits.len() < required_year_len {
|
||||||
market_state
|
market_state
|
||||||
.year_stat_family_qword_bits
|
.year_stat_family_qword_bits
|
||||||
|
|
@ -247,7 +247,11 @@ fn service_post_company_stat_delta(
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(refreshed_current_cash) = ({
|
let Some(refreshed_current_cash) = ({
|
||||||
let Some(market_state) = state.service_state.company_market_state.get_mut(&company_id) else {
|
let Some(market_state) = state
|
||||||
|
.service_state
|
||||||
|
.company_market_state
|
||||||
|
.get_mut(&company_id)
|
||||||
|
else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
let Some(index) = service_ensure_company_stat_post_capacity(market_state, slot_id) else {
|
let Some(index) = service_ensure_company_stat_post_capacity(market_state, slot_id) else {
|
||||||
|
|
@ -285,8 +289,10 @@ fn service_post_company_stat_delta(
|
||||||
|
|
||||||
market_state
|
market_state
|
||||||
.year_stat_family_qword_bits
|
.year_stat_family_qword_bits
|
||||||
.get((RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN)
|
.get(
|
||||||
as usize)
|
(RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN)
|
||||||
|
as usize,
|
||||||
|
)
|
||||||
.copied()
|
.copied()
|
||||||
.and_then(service_decode_saved_f64_bits)
|
.and_then(service_decode_saved_f64_bits)
|
||||||
.and_then(runtime_round_f64_to_i64)
|
.and_then(runtime_round_f64_to_i64)
|
||||||
|
|
@ -317,15 +323,24 @@ fn service_apply_company_bankruptcy(state: &mut RuntimeState, company_id: u32) -
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut company_mutated = false;
|
let mut company_mutated = false;
|
||||||
if let Some(market_state) = state.service_state.company_market_state.get_mut(&company_id) {
|
if let Some(market_state) = state
|
||||||
|
.service_state
|
||||||
|
.company_market_state
|
||||||
|
.get_mut(&company_id)
|
||||||
|
{
|
||||||
market_state.last_bankruptcy_year = bankruptcy_year;
|
market_state.last_bankruptcy_year = bankruptcy_year;
|
||||||
for slot in &mut market_state.live_bond_slots {
|
for slot in &mut market_state.live_bond_slots {
|
||||||
slot.principal /= 2;
|
slot.principal /= 2;
|
||||||
}
|
}
|
||||||
market_state.live_bond_slots.retain(|slot| slot.principal > 0);
|
market_state
|
||||||
|
.live_bond_slots
|
||||||
|
.retain(|slot| slot.principal > 0);
|
||||||
market_state.bond_count = market_state.live_bond_slots.len().min(u8::MAX as usize) as u8;
|
market_state.bond_count = market_state.live_bond_slots.len().min(u8::MAX as usize) as u8;
|
||||||
market_state.largest_live_bond_principal =
|
market_state.largest_live_bond_principal = market_state
|
||||||
market_state.live_bond_slots.iter().map(|slot| slot.principal).max();
|
.live_bond_slots
|
||||||
|
.iter()
|
||||||
|
.map(|slot| slot.principal)
|
||||||
|
.max();
|
||||||
market_state.highest_coupon_live_bond_principal = market_state
|
market_state.highest_coupon_live_bond_principal = market_state
|
||||||
.live_bond_slots
|
.live_bond_slots
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -367,6 +382,108 @@ fn service_apply_company_bankruptcy(state: &mut RuntimeState, company_id: u32) -
|
||||||
company_mutated
|
company_mutated
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn service_repay_matured_company_live_bonds_and_compact(
|
||||||
|
state: &mut RuntimeState,
|
||||||
|
company_id: u32,
|
||||||
|
) -> bool {
|
||||||
|
let Some(current_year_word) = state
|
||||||
|
.world_restore
|
||||||
|
.packed_year_word_raw_u16
|
||||||
|
.map(u32::from)
|
||||||
|
.or_else(|| Some(state.calendar.year))
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let retired_principal_total = state
|
||||||
|
.service_state
|
||||||
|
.company_market_state
|
||||||
|
.get(&company_id)
|
||||||
|
.map(|market_state| {
|
||||||
|
market_state
|
||||||
|
.live_bond_slots
|
||||||
|
.iter()
|
||||||
|
.filter(|slot| slot.maturity_year != 0 && slot.maturity_year <= current_year_word)
|
||||||
|
.map(|slot| u64::from(slot.principal))
|
||||||
|
.sum::<u64>()
|
||||||
|
})
|
||||||
|
.unwrap_or(0);
|
||||||
|
if retired_principal_total == 0 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let retired_principal_total_f64 = retired_principal_total as f64;
|
||||||
|
let mut company_mutated = false;
|
||||||
|
company_mutated |= service_post_company_stat_delta(
|
||||||
|
state,
|
||||||
|
company_id,
|
||||||
|
RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH,
|
||||||
|
-retired_principal_total_f64,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
company_mutated |= service_post_company_stat_delta(
|
||||||
|
state,
|
||||||
|
company_id,
|
||||||
|
0x12,
|
||||||
|
retired_principal_total_f64,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(market_state) = state
|
||||||
|
.service_state
|
||||||
|
.company_market_state
|
||||||
|
.get_mut(&company_id)
|
||||||
|
{
|
||||||
|
market_state
|
||||||
|
.live_bond_slots
|
||||||
|
.retain(|slot| slot.maturity_year == 0 || slot.maturity_year > current_year_word);
|
||||||
|
for (slot_index, slot) in market_state.live_bond_slots.iter_mut().enumerate() {
|
||||||
|
slot.slot_index = slot_index as u32;
|
||||||
|
}
|
||||||
|
market_state.bond_count = market_state.live_bond_slots.len().min(u8::MAX as usize) as u8;
|
||||||
|
market_state.largest_live_bond_principal = market_state
|
||||||
|
.live_bond_slots
|
||||||
|
.iter()
|
||||||
|
.map(|slot| slot.principal)
|
||||||
|
.max();
|
||||||
|
market_state.highest_coupon_live_bond_principal = market_state
|
||||||
|
.live_bond_slots
|
||||||
|
.iter()
|
||||||
|
.filter_map(|slot| {
|
||||||
|
let coupon = f32::from_bits(slot.coupon_rate_raw_u32) as f64;
|
||||||
|
coupon.is_finite().then_some((coupon, slot.principal))
|
||||||
|
})
|
||||||
|
.max_by(|left, right| {
|
||||||
|
left.0
|
||||||
|
.partial_cmp(&right.0)
|
||||||
|
.unwrap_or(std::cmp::Ordering::Equal)
|
||||||
|
})
|
||||||
|
.map(|(_, principal)| principal);
|
||||||
|
company_mutated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(company) = state
|
||||||
|
.companies
|
||||||
|
.iter_mut()
|
||||||
|
.find(|company| company.company_id == company_id)
|
||||||
|
{
|
||||||
|
company.debt = state
|
||||||
|
.service_state
|
||||||
|
.company_market_state
|
||||||
|
.get(&company_id)
|
||||||
|
.map(|market_state| {
|
||||||
|
market_state
|
||||||
|
.live_bond_slots
|
||||||
|
.iter()
|
||||||
|
.map(|slot| u64::from(slot.principal))
|
||||||
|
.sum::<u64>()
|
||||||
|
})
|
||||||
|
.unwrap_or(0);
|
||||||
|
company_mutated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
company_mutated
|
||||||
|
}
|
||||||
|
|
||||||
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>,
|
||||||
|
|
@ -515,10 +632,21 @@ fn service_company_annual_finance_policy(
|
||||||
if !bond_state.eligible_for_bond_issue_branch {
|
if !bond_state.eligible_for_bond_issue_branch {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
mutated |= service_repay_matured_company_live_bonds_and_compact(state, company_id);
|
||||||
|
|
||||||
|
let issue_bond_count = bond_state.proposed_issue_bond_count.unwrap_or(0);
|
||||||
let Some(principal) = bond_state.issue_principal_step else {
|
let Some(principal) = bond_state.issue_principal_step else {
|
||||||
|
if mutated {
|
||||||
|
applied_effect_count += 1;
|
||||||
|
mutated_company_ids.insert(company_id);
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let Some(years_to_maturity) = bond_state.proposed_issue_years_to_maturity else {
|
let Some(years_to_maturity) = bond_state.proposed_issue_years_to_maturity else {
|
||||||
|
if mutated {
|
||||||
|
applied_effect_count += 1;
|
||||||
|
mutated_company_ids.insert(company_id);
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let Some(maturity_year) = state
|
let Some(maturity_year) = state
|
||||||
|
|
@ -527,77 +655,104 @@ fn service_company_annual_finance_policy(
|
||||||
.map(u32::from)
|
.map(u32::from)
|
||||||
.and_then(|year| year.checked_add(years_to_maturity))
|
.and_then(|year| year.checked_add(years_to_maturity))
|
||||||
else {
|
else {
|
||||||
continue;
|
if mutated {
|
||||||
};
|
applied_effect_count += 1;
|
||||||
let Some(quote_rate) = runtime_company_bond_interest_rate_quote_f64(
|
mutated_company_ids.insert(company_id);
|
||||||
state,
|
}
|
||||||
company_id,
|
|
||||||
principal,
|
|
||||||
years_to_maturity,
|
|
||||||
) else {
|
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
mutated |= service_post_company_stat_delta(
|
for _ in 0..issue_bond_count {
|
||||||
state,
|
let Some(quote_rate) = runtime_company_bond_interest_rate_quote_f64(
|
||||||
company_id,
|
state,
|
||||||
0x0c,
|
company_id,
|
||||||
(principal as f64) * COMPANY_STOCK_AND_BOND_CAPITAL_POST_SCALE,
|
principal,
|
||||||
true,
|
years_to_maturity,
|
||||||
);
|
) else {
|
||||||
mutated |= service_post_company_stat_delta(
|
break;
|
||||||
state,
|
};
|
||||||
company_id,
|
|
||||||
0x12,
|
|
||||||
-(principal as f64),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
mutated |= service_post_company_stat_delta(
|
|
||||||
state,
|
|
||||||
company_id,
|
|
||||||
RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH,
|
|
||||||
principal as f64,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
let Some(market_state) = state
|
mutated |= service_post_company_stat_delta(
|
||||||
.service_state
|
state,
|
||||||
.company_market_state
|
company_id,
|
||||||
.get_mut(&company_id)
|
0x0c,
|
||||||
else {
|
(principal as f64) * COMPANY_STOCK_AND_BOND_CAPITAL_POST_SCALE,
|
||||||
continue;
|
true,
|
||||||
};
|
);
|
||||||
let slot_index = market_state.bond_count as u32;
|
mutated |= service_post_company_stat_delta(
|
||||||
if market_state.bond_count == u8::MAX {
|
state,
|
||||||
continue;
|
company_id,
|
||||||
}
|
0x12,
|
||||||
market_state.live_bond_slots.push(crate::RuntimeCompanyBondSlot {
|
-(principal as f64),
|
||||||
slot_index,
|
false,
|
||||||
principal,
|
);
|
||||||
maturity_year,
|
mutated |= service_post_company_stat_delta(
|
||||||
coupon_rate_raw_u32: (quote_rate as f32).to_bits(),
|
state,
|
||||||
});
|
company_id,
|
||||||
market_state.bond_count = market_state.bond_count.saturating_add(1);
|
RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH,
|
||||||
market_state.largest_live_bond_principal = Some(
|
principal as f64,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
let Some(market_state) = state
|
||||||
|
.service_state
|
||||||
|
.company_market_state
|
||||||
|
.get_mut(&company_id)
|
||||||
|
else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
let slot_index = market_state.bond_count as u32;
|
||||||
|
if market_state.bond_count == u8::MAX {
|
||||||
|
break;
|
||||||
|
}
|
||||||
market_state
|
market_state
|
||||||
.largest_live_bond_principal
|
.live_bond_slots
|
||||||
.unwrap_or(0)
|
.push(crate::RuntimeCompanyBondSlot {
|
||||||
.max(principal),
|
slot_index,
|
||||||
);
|
principal,
|
||||||
let highest_coupon_live_principal = market_state
|
maturity_year,
|
||||||
.live_bond_slots
|
coupon_rate_raw_u32: (quote_rate as f32).to_bits(),
|
||||||
.iter()
|
});
|
||||||
.filter_map(|slot| {
|
market_state.bond_count = market_state.bond_count.saturating_add(1);
|
||||||
let coupon = f32::from_bits(slot.coupon_rate_raw_u32) as f64;
|
market_state.largest_live_bond_principal = Some(
|
||||||
coupon.is_finite().then_some((coupon, slot.principal))
|
market_state
|
||||||
})
|
.largest_live_bond_principal
|
||||||
.max_by(|left, right| {
|
.unwrap_or(0)
|
||||||
left.0
|
.max(principal),
|
||||||
.partial_cmp(&right.0)
|
);
|
||||||
.unwrap_or(std::cmp::Ordering::Equal)
|
let highest_coupon_live_principal = market_state
|
||||||
})
|
.live_bond_slots
|
||||||
.map(|(_, principal)| principal);
|
.iter()
|
||||||
market_state.highest_coupon_live_bond_principal = highest_coupon_live_principal;
|
.filter_map(|slot| {
|
||||||
|
let coupon = f32::from_bits(slot.coupon_rate_raw_u32) as f64;
|
||||||
|
coupon.is_finite().then_some((coupon, slot.principal))
|
||||||
|
})
|
||||||
|
.max_by(|left, right| {
|
||||||
|
left.0
|
||||||
|
.partial_cmp(&right.0)
|
||||||
|
.unwrap_or(std::cmp::Ordering::Equal)
|
||||||
|
})
|
||||||
|
.map(|(_, principal)| principal);
|
||||||
|
market_state.highest_coupon_live_bond_principal = highest_coupon_live_principal;
|
||||||
|
}
|
||||||
|
if let Some(company) = state
|
||||||
|
.companies
|
||||||
|
.iter_mut()
|
||||||
|
.find(|company| company.company_id == company_id)
|
||||||
|
{
|
||||||
|
company.debt = state
|
||||||
|
.service_state
|
||||||
|
.company_market_state
|
||||||
|
.get(&company_id)
|
||||||
|
.map(|market_state| {
|
||||||
|
market_state
|
||||||
|
.live_bond_slots
|
||||||
|
.iter()
|
||||||
|
.map(|slot| u64::from(slot.principal))
|
||||||
|
.sum::<u64>()
|
||||||
|
})
|
||||||
|
.unwrap_or(company.debt);
|
||||||
|
}
|
||||||
if mutated {
|
if mutated {
|
||||||
applied_effect_count += 1;
|
applied_effect_count += 1;
|
||||||
mutated_company_ids.insert(company_id);
|
mutated_company_ids.insert(company_id);
|
||||||
|
|
@ -617,9 +772,9 @@ fn service_company_annual_finance_policy(
|
||||||
let Some(batch_size) = repurchase_state.repurchase_batch_size else {
|
let Some(batch_size) = repurchase_state.repurchase_batch_size else {
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
let Some(pressure_shares) =
|
let Some(pressure_shares) = runtime_round_f64_to_i64(
|
||||||
runtime_round_f64_to_i64(batch_size as f64 * COMPANY_REPURCHASE_PRESSURE_SCALE)
|
batch_size as f64 * COMPANY_REPURCHASE_PRESSURE_SCALE,
|
||||||
else {
|
) else {
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
let Some(share_price_scalar) =
|
let Some(share_price_scalar) =
|
||||||
|
|
@ -2872,9 +3027,11 @@ mod tests {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let pressured_share_price = crate::runtime::
|
let pressured_share_price =
|
||||||
runtime_company_support_adjusted_share_price_scalar_with_pressure_f64(
|
crate::runtime::runtime_company_support_adjusted_share_price_scalar_with_pressure_f64(
|
||||||
&base_state, 23, 700,
|
&base_state,
|
||||||
|
23,
|
||||||
|
700,
|
||||||
)
|
)
|
||||||
.expect("repurchase share price");
|
.expect("repurchase share price");
|
||||||
let expected_repurchase_total =
|
let expected_repurchase_total =
|
||||||
|
|
@ -2889,7 +3046,10 @@ mod tests {
|
||||||
state.service_state.annual_finance_last_actions.get(&23),
|
state.service_state.annual_finance_last_actions.get(&23),
|
||||||
Some(&crate::RuntimeCompanyAnnualFinancePolicyAction::StockRepurchase)
|
Some(&crate::RuntimeCompanyAnnualFinancePolicyAction::StockRepurchase)
|
||||||
);
|
);
|
||||||
assert_eq!(state.companies[0].current_cash, 1_600_000 - expected_repurchase_total);
|
assert_eq!(
|
||||||
|
state.companies[0].current_cash,
|
||||||
|
1_600_000 - expected_repurchase_total
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
state.service_state.company_market_state[&23].outstanding_shares,
|
state.service_state.company_market_state[&23].outstanding_shares,
|
||||||
9_000
|
9_000
|
||||||
|
|
@ -3032,12 +3192,318 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn periodic_boundary_retires_live_bonds_when_annual_bond_lane_needs_no_reissue() {
|
||||||
|
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();
|
||||||
|
};
|
||||||
|
write_current_value(
|
||||||
|
&mut year_stat_family_qword_bits,
|
||||||
|
crate::RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH,
|
||||||
|
900_000.0,
|
||||||
|
);
|
||||||
|
write_current_value(&mut year_stat_family_qword_bits, 0x12, -350_000.0);
|
||||||
|
|
||||||
|
let mut state = crate::RuntimeState {
|
||||||
|
calendar: crate::CalendarPoint {
|
||||||
|
year: 1845,
|
||||||
|
month_slot: 0,
|
||||||
|
phase_slot: 0,
|
||||||
|
tick_slot: 0,
|
||||||
|
},
|
||||||
|
world_restore: crate::RuntimeWorldRestoreState {
|
||||||
|
packed_year_word_raw_u16: Some(1845),
|
||||||
|
partial_year_progress_raw_u8: Some(0x0c),
|
||||||
|
bond_issue_and_repayment_policy_raw_u8: Some(0),
|
||||||
|
bond_issue_and_repayment_allowed: Some(true),
|
||||||
|
stock_issue_and_buyback_policy_raw_u8: Some(0),
|
||||||
|
stock_issue_and_buyback_allowed: Some(true),
|
||||||
|
bankruptcy_policy_raw_u8: Some(0),
|
||||||
|
bankruptcy_allowed: Some(true),
|
||||||
|
..crate::RuntimeWorldRestoreState::default()
|
||||||
|
},
|
||||||
|
world_flags: BTreeMap::new(),
|
||||||
|
save_profile: crate::RuntimeSaveProfileState::default(),
|
||||||
|
metadata: BTreeMap::new(),
|
||||||
|
companies: vec![crate::RuntimeCompany {
|
||||||
|
company_id: 25,
|
||||||
|
current_cash: 900_000,
|
||||||
|
debt: 350_000,
|
||||||
|
active: true,
|
||||||
|
credit_rating_score: Some(7),
|
||||||
|
prime_rate: Some(6),
|
||||||
|
controller_kind: crate::RuntimeCompanyControllerKind::Unknown,
|
||||||
|
linked_chairman_profile_id: None,
|
||||||
|
available_track_laying_capacity: None,
|
||||||
|
book_value_per_share: 0,
|
||||||
|
investor_confidence: 0,
|
||||||
|
management_attitude: 0,
|
||||||
|
takeover_cooldown_year: None,
|
||||||
|
merger_cooldown_year: None,
|
||||||
|
track_piece_counts: crate::RuntimeTrackPieceCounts::default(),
|
||||||
|
}],
|
||||||
|
selected_company_id: Some(25),
|
||||||
|
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 {
|
||||||
|
company_market_state: BTreeMap::from([(
|
||||||
|
25,
|
||||||
|
crate::RuntimeCompanyMarketState {
|
||||||
|
bond_count: 2,
|
||||||
|
live_bond_slots: vec![
|
||||||
|
crate::RuntimeCompanyBondSlot {
|
||||||
|
slot_index: 0,
|
||||||
|
principal: 200_000,
|
||||||
|
maturity_year: 1845,
|
||||||
|
coupon_rate_raw_u32: 0.09f32.to_bits(),
|
||||||
|
},
|
||||||
|
crate::RuntimeCompanyBondSlot {
|
||||||
|
slot_index: 1,
|
||||||
|
principal: 150_000,
|
||||||
|
maturity_year: 1845,
|
||||||
|
coupon_rate_raw_u32: 0.08f32.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 bond repayment lane");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
state.service_state.annual_finance_last_actions.get(&25),
|
||||||
|
Some(&crate::RuntimeCompanyAnnualFinancePolicyAction::BondIssue)
|
||||||
|
);
|
||||||
|
assert_eq!(state.companies[0].current_cash, 550_000);
|
||||||
|
assert_eq!(state.companies[0].debt, 0);
|
||||||
|
assert_eq!(state.service_state.company_market_state[&25].bond_count, 0);
|
||||||
|
assert!(
|
||||||
|
state.service_state.company_market_state[&25]
|
||||||
|
.live_bond_slots
|
||||||
|
.is_empty()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
state.service_state.company_market_state[&25].largest_live_bond_principal,
|
||||||
|
None
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
state.service_state.company_market_state[&25].highest_coupon_live_bond_principal,
|
||||||
|
None
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
result
|
||||||
|
.service_events
|
||||||
|
.iter()
|
||||||
|
.any(|event| event.kind == "annual_finance_policy"
|
||||||
|
&& event.applied_effect_count == 1
|
||||||
|
&& event.mutated_company_ids == vec![25])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn periodic_boundary_retires_then_reissues_exact_annual_bond_count() {
|
||||||
|
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();
|
||||||
|
};
|
||||||
|
write_current_value(
|
||||||
|
&mut year_stat_family_qword_bits,
|
||||||
|
crate::RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH,
|
||||||
|
-400_000.0,
|
||||||
|
);
|
||||||
|
write_current_value(&mut year_stat_family_qword_bits, 0x12, -350_000.0);
|
||||||
|
|
||||||
|
let mut state = crate::RuntimeState {
|
||||||
|
calendar: crate::CalendarPoint {
|
||||||
|
year: 1845,
|
||||||
|
month_slot: 0,
|
||||||
|
phase_slot: 0,
|
||||||
|
tick_slot: 0,
|
||||||
|
},
|
||||||
|
world_restore: crate::RuntimeWorldRestoreState {
|
||||||
|
packed_year_word_raw_u16: Some(1845),
|
||||||
|
partial_year_progress_raw_u8: Some(0x0c),
|
||||||
|
bond_issue_and_repayment_policy_raw_u8: Some(0),
|
||||||
|
bond_issue_and_repayment_allowed: Some(true),
|
||||||
|
stock_issue_and_buyback_policy_raw_u8: Some(0),
|
||||||
|
stock_issue_and_buyback_allowed: Some(true),
|
||||||
|
bankruptcy_policy_raw_u8: Some(0),
|
||||||
|
bankruptcy_allowed: Some(true),
|
||||||
|
..crate::RuntimeWorldRestoreState::default()
|
||||||
|
},
|
||||||
|
world_flags: BTreeMap::new(),
|
||||||
|
save_profile: crate::RuntimeSaveProfileState::default(),
|
||||||
|
metadata: BTreeMap::new(),
|
||||||
|
companies: vec![crate::RuntimeCompany {
|
||||||
|
company_id: 26,
|
||||||
|
current_cash: -400_000,
|
||||||
|
debt: 350_000,
|
||||||
|
active: true,
|
||||||
|
credit_rating_score: Some(7),
|
||||||
|
prime_rate: Some(6),
|
||||||
|
controller_kind: crate::RuntimeCompanyControllerKind::Unknown,
|
||||||
|
linked_chairman_profile_id: None,
|
||||||
|
available_track_laying_capacity: None,
|
||||||
|
book_value_per_share: 0,
|
||||||
|
investor_confidence: 0,
|
||||||
|
management_attitude: 0,
|
||||||
|
takeover_cooldown_year: None,
|
||||||
|
merger_cooldown_year: None,
|
||||||
|
track_piece_counts: crate::RuntimeTrackPieceCounts::default(),
|
||||||
|
}],
|
||||||
|
selected_company_id: Some(26),
|
||||||
|
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 {
|
||||||
|
company_market_state: BTreeMap::from([(
|
||||||
|
26,
|
||||||
|
crate::RuntimeCompanyMarketState {
|
||||||
|
bond_count: 2,
|
||||||
|
linked_transit_latch: true,
|
||||||
|
live_bond_slots: vec![
|
||||||
|
crate::RuntimeCompanyBondSlot {
|
||||||
|
slot_index: 0,
|
||||||
|
principal: 200_000,
|
||||||
|
maturity_year: 1845,
|
||||||
|
coupon_rate_raw_u32: 0.09f32.to_bits(),
|
||||||
|
},
|
||||||
|
crate::RuntimeCompanyBondSlot {
|
||||||
|
slot_index: 1,
|
||||||
|
principal: 150_000,
|
||||||
|
maturity_year: 1845,
|
||||||
|
coupon_rate_raw_u32: 0.08f32.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 bond restructure lane");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
state.service_state.annual_finance_last_actions.get(&26),
|
||||||
|
Some(&crate::RuntimeCompanyAnnualFinancePolicyAction::BondIssue)
|
||||||
|
);
|
||||||
|
assert_eq!(state.companies[0].current_cash, 250_000);
|
||||||
|
assert_eq!(state.companies[0].debt, 1_000_000);
|
||||||
|
assert_eq!(state.service_state.company_market_state[&26].bond_count, 2);
|
||||||
|
assert_eq!(
|
||||||
|
state.service_state.company_market_state[&26].largest_live_bond_principal,
|
||||||
|
Some(500_000)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
state.service_state.company_market_state[&26].highest_coupon_live_bond_principal,
|
||||||
|
Some(500_000)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
state.service_state.company_market_state[&26].live_bond_slots,
|
||||||
|
vec![
|
||||||
|
crate::RuntimeCompanyBondSlot {
|
||||||
|
slot_index: 0,
|
||||||
|
principal: 500_000,
|
||||||
|
maturity_year: 1875,
|
||||||
|
coupon_rate_raw_u32: 0.09f32.to_bits(),
|
||||||
|
},
|
||||||
|
crate::RuntimeCompanyBondSlot {
|
||||||
|
slot_index: 1,
|
||||||
|
principal: 500_000,
|
||||||
|
maturity_year: 1875,
|
||||||
|
coupon_rate_raw_u32: 0.09f32.to_bits(),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
result
|
||||||
|
.service_events
|
||||||
|
.iter()
|
||||||
|
.any(|event| event.kind == "annual_finance_policy"
|
||||||
|
&& event.applied_effect_count == 1
|
||||||
|
&& event.mutated_company_ids == vec![26])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn periodic_boundary_applies_creditor_pressure_bankruptcy_from_annual_finance_policy() {
|
fn periodic_boundary_applies_creditor_pressure_bankruptcy_from_annual_finance_policy() {
|
||||||
let mut year_stat_family_qword_bits = vec![
|
let mut year_stat_family_qword_bits = vec![
|
||||||
0u64;
|
0u64;
|
||||||
((crate::RUNTIME_COMPANY_STAT_SLOT_COUNT + 2)
|
((crate::RUNTIME_COMPANY_STAT_SLOT_COUNT + 2)
|
||||||
* crate::RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) as usize
|
* crate::RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN)
|
||||||
|
as usize
|
||||||
];
|
];
|
||||||
let write_current_value = |bits: &mut Vec<u64>, slot_id: u32, value: f64| {
|
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;
|
let index = (slot_id * crate::RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) as usize;
|
||||||
|
|
@ -3207,7 +3673,8 @@ mod tests {
|
||||||
let mut year_stat_family_qword_bits = vec![
|
let mut year_stat_family_qword_bits = vec![
|
||||||
0u64;
|
0u64;
|
||||||
((crate::RUNTIME_COMPANY_STAT_SLOT_COUNT + 2)
|
((crate::RUNTIME_COMPANY_STAT_SLOT_COUNT + 2)
|
||||||
* crate::RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) as usize
|
* crate::RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN)
|
||||||
|
as usize
|
||||||
];
|
];
|
||||||
let write_current_value = |bits: &mut Vec<u64>, slot_id: u32, value: f64| {
|
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;
|
let index = (slot_id * crate::RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) as usize;
|
||||||
|
|
|
||||||
|
|
@ -150,12 +150,12 @@ The highest-value next passes are now:
|
||||||
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
|
||||||
creditor-pressure-bankruptcy, deep-distress-bankruptcy, dividend-adjustment, stock-repurchase,
|
creditor-pressure-bankruptcy, deep-distress-bankruptcy, dividend-adjustment, stock-repurchase,
|
||||||
stock-issue, and bond-issue branches against owned runtime state, with bankruptcy now following
|
stock-issue, and annual bond-restructure branches against owned runtime state, with bankruptcy now following
|
||||||
the grounded “halve live bond debt and stamp the year” path rather than a liquidation shortcut;
|
the grounded “halve live bond debt and stamp the year” path rather than a liquidation shortcut;
|
||||||
the same live bond-slot owner
|
the same live bond-slot owner
|
||||||
surface now also carries save-native maturity years into annual bond policy summaries as the
|
surface now also carries save-native maturity years into annual bond policy summaries, derives the current live coupon burden
|
||||||
next seam for shellless repayment work, and now also derives the current live coupon burden
|
directly from owned bond slots, and now also commits the shellless “repay matured live bonds,
|
||||||
directly from owned bond slots
|
compact the table, then issue the exact staged count” path during periodic service
|
||||||
- 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
|
||||||
|
|
|
||||||
|
|
@ -233,7 +233,9 @@ adjusted share price, and those policy bytes rather than staying in atlas prose
|
||||||
deep-distress bankruptcy fallback now rides the same owner-state seam too, using the save-native
|
deep-distress bankruptcy fallback now rides the same owner-state seam too, using the save-native
|
||||||
cash reader plus the first three trailing net-profit years instead of a parallel raw-offset guess.
|
cash reader plus the first three trailing net-profit years instead of a parallel raw-offset guess.
|
||||||
The annual bond lane now rides it as well, using the simulated post-repayment cash window plus the
|
The annual bond lane now rides it as well, using the simulated post-repayment cash window plus the
|
||||||
linked-transit threshold split to stage `500000` principal issue counts without shell ownership.
|
linked-transit threshold split to stage `500000` principal issue counts without shell ownership,
|
||||||
|
and periodic boundary service now commits the same shellless matured-bond repay/compact/issue path instead of
|
||||||
|
stopping at the staging reader.
|
||||||
The annual dividend-adjustment lane now rides that same seam too: the runtime now rehosts the
|
The annual dividend-adjustment lane now rides that same seam too: the runtime now rehosts the
|
||||||
shared year-or-control-transfer metric reader, the board-approved dividend ceiling helper, and the
|
shared year-or-control-transfer metric reader, the board-approved dividend ceiling helper, and the
|
||||||
full annual dividend branch over owned cash, public float, current dividend, and building-growth
|
full annual dividend branch over owned cash, public float, current dividend, and building-growth
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue