Carry bond maturity through runtime state
This commit is contained in:
parent
b87216a556
commit
3d31b0b65e
7 changed files with 133 additions and 1 deletions
|
|
@ -109,6 +109,9 @@ the shellless creditor-pressure-bankruptcy, deep-distress-bankruptcy, dividend-a
|
|||
stock-repurchase, stock-issue, and bond-issue branches by mutating owned company activity,
|
||||
dividend, company stat-post, outstanding-share, issue-calendar, and live bond-slot state instead
|
||||
of stopping at reader-only diagnostics.
|
||||
The same save-native live bond-slot surface now also carries per-slot maturity years all the way
|
||||
through runtime summaries and annual bond policy state, which is the next owner seam needed for
|
||||
shellless repayment and bond-burden simulation instead of another round of raw-slot guessing.
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -122,6 +122,8 @@ pub struct RuntimeCompanyMarketState {
|
|||
pub struct RuntimeCompanyBondSlot {
|
||||
pub slot_index: u32,
|
||||
pub principal: u32,
|
||||
#[serde(default)]
|
||||
pub maturity_year: u32,
|
||||
pub coupon_rate_raw_u32: u32,
|
||||
}
|
||||
|
||||
|
|
@ -323,6 +325,12 @@ pub struct RuntimeCompanyAnnualBondPolicyState {
|
|||
#[serde(default)]
|
||||
pub live_bond_principal_total: Option<u32>,
|
||||
#[serde(default)]
|
||||
pub matured_live_bond_count: Option<u32>,
|
||||
#[serde(default)]
|
||||
pub matured_live_bond_principal_total: Option<u32>,
|
||||
#[serde(default)]
|
||||
pub next_live_bond_maturity_year: Option<u32>,
|
||||
#[serde(default)]
|
||||
pub current_cash: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub cash_after_full_repayment: Option<i64>,
|
||||
|
|
@ -2353,6 +2361,49 @@ fn runtime_company_total_live_bond_principal(state: &RuntimeState, company_id: u
|
|||
)
|
||||
}
|
||||
|
||||
fn runtime_company_matured_live_bond_count(
|
||||
state: &RuntimeState,
|
||||
company_id: u32,
|
||||
current_year_word: u32,
|
||||
) -> Option<u32> {
|
||||
let market_state = state.service_state.company_market_state.get(&company_id)?;
|
||||
Some(
|
||||
market_state
|
||||
.live_bond_slots
|
||||
.iter()
|
||||
.filter(|slot| slot.maturity_year != 0 && slot.maturity_year <= current_year_word)
|
||||
.count() as u32,
|
||||
)
|
||||
}
|
||||
|
||||
fn runtime_company_matured_live_bond_principal_total(
|
||||
state: &RuntimeState,
|
||||
company_id: u32,
|
||||
current_year_word: u32,
|
||||
) -> Option<u32> {
|
||||
let market_state = state.service_state.company_market_state.get(&company_id)?;
|
||||
Some(
|
||||
market_state
|
||||
.live_bond_slots
|
||||
.iter()
|
||||
.filter(|slot| slot.maturity_year != 0 && slot.maturity_year <= current_year_word)
|
||||
.map(|slot| slot.principal)
|
||||
.sum(),
|
||||
)
|
||||
}
|
||||
|
||||
fn runtime_company_next_live_bond_maturity_year(
|
||||
state: &RuntimeState,
|
||||
company_id: u32,
|
||||
) -> Option<u32> {
|
||||
let market_state = state.service_state.company_market_state.get(&company_id)?;
|
||||
market_state
|
||||
.live_bond_slots
|
||||
.iter()
|
||||
.filter_map(|slot| (slot.maturity_year != 0).then_some(slot.maturity_year))
|
||||
.min()
|
||||
}
|
||||
|
||||
pub(crate) fn runtime_company_support_adjusted_share_price_scalar_with_pressure_f64(
|
||||
state: &RuntimeState,
|
||||
company_id: u32,
|
||||
|
|
@ -3184,6 +3235,11 @@ pub fn runtime_company_annual_bond_policy_state(
|
|||
const ISSUE_YEARS_TO_MATURITY: u32 = 30;
|
||||
|
||||
let annual_finance_state = runtime_company_annual_finance_state(state, company_id)?;
|
||||
let current_year_word = state
|
||||
.world_restore
|
||||
.packed_year_word_raw_u16
|
||||
.map(u32::from)
|
||||
.unwrap_or(state.calendar.year);
|
||||
let current_cash = runtime_company_control_transfer_stat_value_f64(
|
||||
state,
|
||||
company_id,
|
||||
|
|
@ -3191,6 +3247,12 @@ pub fn runtime_company_annual_bond_policy_state(
|
|||
)
|
||||
.and_then(runtime_round_f64_to_i64);
|
||||
let live_bond_principal_total = runtime_company_total_live_bond_principal(state, company_id);
|
||||
let matured_live_bond_count =
|
||||
runtime_company_matured_live_bond_count(state, company_id, current_year_word);
|
||||
let matured_live_bond_principal_total =
|
||||
runtime_company_matured_live_bond_principal_total(state, company_id, current_year_word);
|
||||
let next_live_bond_maturity_year =
|
||||
runtime_company_next_live_bond_maturity_year(state, company_id);
|
||||
let cash_after_full_repayment = current_cash
|
||||
.zip(live_bond_principal_total)
|
||||
.map(|(cash, principal)| cash - i64::from(principal));
|
||||
|
|
@ -3222,6 +3284,9 @@ pub fn runtime_company_annual_bond_policy_state(
|
|||
linked_transit_latch: annual_finance_state.linked_transit_latch,
|
||||
live_bond_count: Some(annual_finance_state.bond_count),
|
||||
live_bond_principal_total,
|
||||
matured_live_bond_count,
|
||||
matured_live_bond_principal_total,
|
||||
next_live_bond_maturity_year,
|
||||
current_cash,
|
||||
cash_after_full_repayment,
|
||||
issue_cash_floor,
|
||||
|
|
@ -7315,6 +7380,7 @@ mod tests {
|
|||
live_bond_slots: vec![RuntimeCompanyBondSlot {
|
||||
slot_index: 0,
|
||||
principal: 100_000,
|
||||
maturity_year: 0,
|
||||
coupon_rate_raw_u32: 0.05f32.to_bits(),
|
||||
}],
|
||||
..RuntimeCompanyMarketState::default()
|
||||
|
|
@ -7459,11 +7525,13 @@ mod tests {
|
|||
RuntimeCompanyBondSlot {
|
||||
slot_index: 0,
|
||||
principal: 100_000,
|
||||
maturity_year: 0,
|
||||
coupon_rate_raw_u32: 0.04f32.to_bits(),
|
||||
},
|
||||
RuntimeCompanyBondSlot {
|
||||
slot_index: 1,
|
||||
principal: 300_000,
|
||||
maturity_year: 0,
|
||||
coupon_rate_raw_u32: 0.08f32.to_bits(),
|
||||
},
|
||||
],
|
||||
|
|
@ -8215,11 +8283,13 @@ mod tests {
|
|||
RuntimeCompanyBondSlot {
|
||||
slot_index: 0,
|
||||
principal: 200_000,
|
||||
maturity_year: 0,
|
||||
coupon_rate_raw_u32: 0.09f32.to_bits(),
|
||||
},
|
||||
RuntimeCompanyBondSlot {
|
||||
slot_index: 1,
|
||||
principal: 150_000,
|
||||
maturity_year: 0,
|
||||
coupon_rate_raw_u32: 0.08f32.to_bits(),
|
||||
},
|
||||
],
|
||||
|
|
@ -8235,6 +8305,9 @@ mod tests {
|
|||
runtime_company_annual_bond_policy_state(&state, 11).expect("bond policy state");
|
||||
assert_eq!(bond_state.live_bond_count, Some(2));
|
||||
assert_eq!(bond_state.live_bond_principal_total, Some(350_000));
|
||||
assert_eq!(bond_state.matured_live_bond_count, Some(0));
|
||||
assert_eq!(bond_state.matured_live_bond_principal_total, Some(0));
|
||||
assert_eq!(bond_state.next_live_bond_maturity_year, None);
|
||||
assert_eq!(bond_state.current_cash, Some(-400_000));
|
||||
assert_eq!(bond_state.cash_after_full_repayment, Some(-750_000));
|
||||
assert_eq!(bond_state.issue_cash_floor, Some(-30_000));
|
||||
|
|
@ -8356,11 +8429,13 @@ mod tests {
|
|||
RuntimeCompanyBondSlot {
|
||||
slot_index: 0,
|
||||
principal: 300_000,
|
||||
maturity_year: 0,
|
||||
coupon_rate_raw_u32: 0.11f32.to_bits(),
|
||||
},
|
||||
RuntimeCompanyBondSlot {
|
||||
slot_index: 1,
|
||||
principal: 200_000,
|
||||
maturity_year: 0,
|
||||
coupon_rate_raw_u32: 0.07f32.to_bits(),
|
||||
},
|
||||
],
|
||||
|
|
|
|||
|
|
@ -4180,6 +4180,7 @@ fn parse_save_company_live_bond_slots(
|
|||
if principal <= 0 {
|
||||
continue;
|
||||
}
|
||||
let maturity_year = read_u32_at(bytes, slot_offset + 4)?;
|
||||
let coupon_rate_raw_u32 = read_u32_at(bytes, slot_offset + 8)?;
|
||||
let coupon_rate = f32::from_bits(coupon_rate_raw_u32);
|
||||
if !coupon_rate.is_finite() {
|
||||
|
|
@ -4188,6 +4189,7 @@ fn parse_save_company_live_bond_slots(
|
|||
slots.push(crate::RuntimeCompanyBondSlot {
|
||||
slot_index: slot_index as u32,
|
||||
principal: principal as u32,
|
||||
maturity_year,
|
||||
coupon_rate_raw_u32,
|
||||
});
|
||||
}
|
||||
|
|
@ -16663,6 +16665,7 @@ mod tests {
|
|||
assert_eq!(market_state.outstanding_shares, 20_000);
|
||||
assert_eq!(market_state.live_bond_slots.len(), 2);
|
||||
assert_eq!(market_state.live_bond_slots[0].principal, 900_000);
|
||||
assert_eq!(market_state.live_bond_slots[0].maturity_year, 1894);
|
||||
assert_eq!(
|
||||
market_state.live_bond_slots[1].coupon_rate_raw_u32,
|
||||
0.12f32.to_bits()
|
||||
|
|
|
|||
|
|
@ -499,6 +499,14 @@ fn service_company_annual_finance_policy(
|
|||
let Some(years_to_maturity) = bond_state.proposed_issue_years_to_maturity else {
|
||||
continue;
|
||||
};
|
||||
let Some(maturity_year) = state
|
||||
.world_restore
|
||||
.packed_year_word_raw_u16
|
||||
.map(u32::from)
|
||||
.and_then(|year| year.checked_add(years_to_maturity))
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Some(quote_rate) = runtime_company_bond_interest_rate_quote_f64(
|
||||
state,
|
||||
company_id,
|
||||
|
|
@ -544,6 +552,7 @@ fn service_company_annual_finance_policy(
|
|||
market_state.live_bond_slots.push(crate::RuntimeCompanyBondSlot {
|
||||
slot_index,
|
||||
principal,
|
||||
maturity_year,
|
||||
coupon_rate_raw_u32: (quote_rate as f32).to_bits(),
|
||||
});
|
||||
market_state.bond_count = market_state.bond_count.saturating_add(1);
|
||||
|
|
@ -2671,11 +2680,13 @@ mod tests {
|
|||
crate::RuntimeCompanyBondSlot {
|
||||
slot_index: 0,
|
||||
principal: 300_000,
|
||||
maturity_year: 0,
|
||||
coupon_rate_raw_u32: 0.11f32.to_bits(),
|
||||
},
|
||||
crate::RuntimeCompanyBondSlot {
|
||||
slot_index: 1,
|
||||
principal: 200_000,
|
||||
maturity_year: 0,
|
||||
coupon_rate_raw_u32: 0.07f32.to_bits(),
|
||||
},
|
||||
],
|
||||
|
|
@ -2985,6 +2996,7 @@ mod tests {
|
|||
vec![crate::RuntimeCompanyBondSlot {
|
||||
slot_index: 0,
|
||||
principal: 500_000,
|
||||
maturity_year: 1875,
|
||||
coupon_rate_raw_u32: 0.09f32.to_bits(),
|
||||
}]
|
||||
);
|
||||
|
|
@ -3114,6 +3126,7 @@ mod tests {
|
|||
live_bond_slots: vec![crate::RuntimeCompanyBondSlot {
|
||||
slot_index: 0,
|
||||
principal: 500_000,
|
||||
maturity_year: 0,
|
||||
coupon_rate_raw_u32: 0.08f32.to_bits(),
|
||||
}],
|
||||
..crate::RuntimeCompanyMarketState::default()
|
||||
|
|
@ -3268,6 +3281,7 @@ mod tests {
|
|||
live_bond_slots: vec![crate::RuntimeCompanyBondSlot {
|
||||
slot_index: 0,
|
||||
principal: 250_000,
|
||||
maturity_year: 0,
|
||||
coupon_rate_raw_u32: 0.07f32.to_bits(),
|
||||
}],
|
||||
..crate::RuntimeCompanyMarketState::default()
|
||||
|
|
@ -5808,6 +5822,7 @@ mod tests {
|
|||
live_bond_slots: vec![crate::RuntimeCompanyBondSlot {
|
||||
slot_index: 0,
|
||||
principal: 100_000,
|
||||
maturity_year: 0,
|
||||
coupon_rate_raw_u32: 0.05f32.to_bits(),
|
||||
}],
|
||||
..crate::RuntimeCompanyMarketState::default()
|
||||
|
|
|
|||
|
|
@ -119,6 +119,9 @@ pub struct RuntimeSummary {
|
|||
pub selected_company_annual_bond_linked_transit_latch: Option<bool>,
|
||||
pub selected_company_annual_bond_live_bond_count: Option<u8>,
|
||||
pub selected_company_annual_bond_live_bond_principal_total: Option<u32>,
|
||||
pub selected_company_annual_bond_matured_live_bond_count: Option<u32>,
|
||||
pub selected_company_annual_bond_matured_live_bond_principal_total: Option<u32>,
|
||||
pub selected_company_annual_bond_next_live_bond_maturity_year: Option<u32>,
|
||||
pub selected_company_annual_bond_current_cash: Option<i64>,
|
||||
pub selected_company_annual_bond_cash_after_full_repayment: Option<i64>,
|
||||
pub selected_company_annual_bond_issue_cash_floor: Option<i64>,
|
||||
|
|
@ -577,6 +580,18 @@ impl RuntimeSummary {
|
|||
selected_company_annual_bond_state
|
||||
.as_ref()
|
||||
.and_then(|bond_state| bond_state.live_bond_principal_total),
|
||||
selected_company_annual_bond_matured_live_bond_count:
|
||||
selected_company_annual_bond_state
|
||||
.as_ref()
|
||||
.and_then(|bond_state| bond_state.matured_live_bond_count),
|
||||
selected_company_annual_bond_matured_live_bond_principal_total:
|
||||
selected_company_annual_bond_state
|
||||
.as_ref()
|
||||
.and_then(|bond_state| bond_state.matured_live_bond_principal_total),
|
||||
selected_company_annual_bond_next_live_bond_maturity_year:
|
||||
selected_company_annual_bond_state
|
||||
.as_ref()
|
||||
.and_then(|bond_state| bond_state.next_live_bond_maturity_year),
|
||||
selected_company_annual_bond_current_cash: selected_company_annual_bond_state
|
||||
.as_ref()
|
||||
.and_then(|bond_state| bond_state.current_cash),
|
||||
|
|
@ -3157,11 +3172,13 @@ mod tests {
|
|||
crate::RuntimeCompanyBondSlot {
|
||||
slot_index: 0,
|
||||
principal: 200_000,
|
||||
maturity_year: 0,
|
||||
coupon_rate_raw_u32: 0.09f32.to_bits(),
|
||||
},
|
||||
crate::RuntimeCompanyBondSlot {
|
||||
slot_index: 1,
|
||||
principal: 150_000,
|
||||
maturity_year: 0,
|
||||
coupon_rate_raw_u32: 0.08f32.to_bits(),
|
||||
},
|
||||
],
|
||||
|
|
@ -3186,6 +3203,18 @@ mod tests {
|
|||
summary.selected_company_annual_bond_live_bond_principal_total,
|
||||
Some(350_000)
|
||||
);
|
||||
assert_eq!(
|
||||
summary.selected_company_annual_bond_matured_live_bond_count,
|
||||
Some(0)
|
||||
);
|
||||
assert_eq!(
|
||||
summary.selected_company_annual_bond_matured_live_bond_principal_total,
|
||||
Some(0)
|
||||
);
|
||||
assert_eq!(
|
||||
summary.selected_company_annual_bond_next_live_bond_maturity_year,
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
summary.selected_company_annual_bond_current_cash,
|
||||
Some(-400_000)
|
||||
|
|
@ -3329,11 +3358,13 @@ mod tests {
|
|||
crate::RuntimeCompanyBondSlot {
|
||||
slot_index: 0,
|
||||
principal: 300_000,
|
||||
maturity_year: 0,
|
||||
coupon_rate_raw_u32: 0.11f32.to_bits(),
|
||||
},
|
||||
crate::RuntimeCompanyBondSlot {
|
||||
slot_index: 1,
|
||||
principal: 200_000,
|
||||
maturity_year: 0,
|
||||
coupon_rate_raw_u32: 0.07f32.to_bits(),
|
||||
},
|
||||
],
|
||||
|
|
|
|||
|
|
@ -150,7 +150,9 @@ The highest-value next passes are now:
|
|||
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
|
||||
creditor-pressure-bankruptcy, deep-distress-bankruptcy, dividend-adjustment, stock-repurchase,
|
||||
stock-issue, and bond-issue branches against owned runtime state
|
||||
stock-issue, and bond-issue branches against owned runtime state; the same live bond-slot owner
|
||||
surface now also carries save-native maturity years into annual bond policy summaries as the
|
||||
next seam for shellless repayment work
|
||||
- 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
|
||||
|
|
|
|||
|
|
@ -247,6 +247,9 @@ the runtime selects one annual-finance action per active company and already com
|
|||
creditor-pressure-bankruptcy, deep-distress-bankruptcy, dividend-adjustment, stock-repurchase,
|
||||
stock-issue, and bond-issue branches directly into owned dividend, company stat-post,
|
||||
outstanding-share, issue-calendar, live bond-slot, and company activity state.
|
||||
The same owned live bond-slot surface now also carries maturity years through save import,
|
||||
runtime state, and annual bond summaries, which is the right next base for shellless repayment and
|
||||
bond-service simulation.
|
||||
|
||||
## Why This Boundary
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue