Derive live bond burden from owned slots

This commit is contained in:
Jan Petykiewicz 2026-04-18 01:46:08 -07:00
commit ec734c5d92
5 changed files with 49 additions and 1 deletions

View file

@ -112,6 +112,9 @@ 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.
That same seam now also derives the current live coupon burden directly from owned bond slots, so
later finance service work can consume a runtime reader instead of recomputing from scattered raw
fields.
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

@ -136,6 +136,8 @@ pub struct RuntimeCompanyAnnualFinanceState {
pub largest_live_bond_principal: Option<u32>,
#[serde(default)]
pub highest_coupon_live_bond_principal: Option<u32>,
#[serde(default)]
pub live_bond_coupon_burden_total: Option<i64>,
pub assigned_share_pool: u32,
pub unassigned_share_pool: u32,
#[serde(default)]
@ -331,6 +333,8 @@ pub struct RuntimeCompanyAnnualBondPolicyState {
#[serde(default)]
pub next_live_bond_maturity_year: Option<u32>,
#[serde(default)]
pub live_bond_coupon_burden_total: Option<i64>,
#[serde(default)]
pub current_cash: Option<i64>,
#[serde(default)]
pub cash_after_full_repayment: Option<i64>,
@ -3104,6 +3108,24 @@ pub fn runtime_company_average_live_bond_coupon(
Some(weighted_coupon_sum / total_principal as f64)
}
pub fn runtime_company_live_bond_coupon_burden_total(
state: &RuntimeState,
company_id: u32,
) -> Option<i64> {
let market_state = state.service_state.company_market_state.get(&company_id)?;
let mut total = 0i64;
for slot in &market_state.live_bond_slots {
let coupon_rate = f32::from_bits(slot.coupon_rate_raw_u32) as f64;
if !coupon_rate.is_finite() {
continue;
}
let coupon_burden =
runtime_round_f64_to_i64((slot.principal as f64) * coupon_rate).unwrap_or(0);
total = total.checked_add(coupon_burden)?;
}
Some(total)
}
pub fn runtime_company_bond_interest_rate_quote_f64(
state: &RuntimeState,
company_id: u32,
@ -3287,6 +3309,7 @@ pub fn runtime_company_annual_bond_policy_state(
matured_live_bond_count,
matured_live_bond_principal_total,
next_live_bond_maturity_year,
live_bond_coupon_burden_total: annual_finance_state.live_bond_coupon_burden_total,
current_cash,
cash_after_full_repayment,
issue_cash_floor,
@ -4037,6 +4060,9 @@ pub fn runtime_company_annual_finance_state(
bond_count: market_state.bond_count,
largest_live_bond_principal: market_state.largest_live_bond_principal,
highest_coupon_live_bond_principal: market_state.highest_coupon_live_bond_principal,
live_bond_coupon_burden_total: runtime_company_live_bond_coupon_burden_total(
state, company_id,
),
assigned_share_pool,
unassigned_share_pool,
cached_share_price: rounded_cached_share_price_i64(market_state.cached_share_price_raw_u32),
@ -7421,6 +7447,10 @@ mod tests {
let average_live_bond_coupon =
runtime_company_average_live_bond_coupon(&state, 7).expect("average coupon");
assert!((average_live_bond_coupon - 0.05).abs() < 1e-6);
assert_eq!(
annual_finance_state.live_bond_coupon_burden_total,
Some(5_000)
);
assert_eq!(runtime_world_prime_rate_baseline(&state), Some(5.0));
assert_eq!(
runtime_world_issue_opinion_term_sum_raw(
@ -7801,6 +7831,7 @@ mod tests {
bond_count: 3,
largest_live_bond_principal: Some(650_000),
highest_coupon_live_bond_principal: Some(500_000),
live_bond_coupon_burden_total: Some(0),
assigned_share_pool: 15_500,
unassigned_share_pool: 4_500,
cached_share_price: Some(40),
@ -8308,6 +8339,7 @@ mod tests {
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.live_bond_coupon_burden_total, Some(30_000));
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));

View file

@ -122,6 +122,7 @@ pub struct RuntimeSummary {
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_live_bond_coupon_burden_total: Option<i64>,
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>,
@ -592,6 +593,10 @@ impl RuntimeSummary {
selected_company_annual_bond_state
.as_ref()
.and_then(|bond_state| bond_state.next_live_bond_maturity_year),
selected_company_annual_bond_live_bond_coupon_burden_total:
selected_company_annual_bond_state
.as_ref()
.and_then(|bond_state| bond_state.live_bond_coupon_burden_total),
selected_company_annual_bond_current_cash: selected_company_annual_bond_state
.as_ref()
.and_then(|bond_state| bond_state.current_cash),
@ -3215,6 +3220,10 @@ mod tests {
summary.selected_company_annual_bond_next_live_bond_maturity_year,
None
);
assert_eq!(
summary.selected_company_annual_bond_live_bond_coupon_burden_total,
Some(30_000)
);
assert_eq!(
summary.selected_company_annual_bond_current_cash,
Some(-400_000)

View file

@ -152,7 +152,8 @@ The highest-value next passes are now:
creditor-pressure-bankruptcy, deep-distress-bankruptcy, dividend-adjustment, stock-repurchase,
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
next seam for shellless repayment work, and now also derives the current live coupon burden
directly from owned bond slots
- 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

@ -250,6 +250,9 @@ 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.
That same owner seam now also derives live coupon burden totals directly from saved bond slots,
which gives later finance service work a bounded runtime reader instead of another synthetic
finance leaf.
## Why This Boundary