Carry bond maturity through runtime state

This commit is contained in:
Jan Petykiewicz 2026-04-18 01:43:35 -07:00
commit 3d31b0b65e
7 changed files with 133 additions and 1 deletions

View file

@ -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.

View file

@ -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(),
},
],

View file

@ -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()

View file

@ -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()

View file

@ -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(),
},
],

View file

@ -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

View file

@ -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