Rehost annual bond issue service branch
This commit is contained in:
parent
bb3c968cec
commit
c5c14d71b0
5 changed files with 240 additions and 8 deletions
|
|
@ -105,9 +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, stock-repurchase, and stock-issue branches by mutating owned
|
the shellless dividend-adjustment, stock-repurchase, stock-issue, and bond-issue branches by
|
||||||
dividend, company stat-post, outstanding-share, and issue-calendar state instead of stopping at
|
mutating owned dividend, company stat-post, outstanding-share, issue-calendar, and live bond-slot
|
||||||
reader-only diagnostics.
|
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.
|
||||||
|
|
|
||||||
|
|
@ -3053,6 +3053,18 @@ pub fn runtime_company_average_live_bond_coupon(
|
||||||
Some(weighted_coupon_sum / total_principal as f64)
|
Some(weighted_coupon_sum / total_principal as f64)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn runtime_company_bond_interest_rate_quote_f64(
|
||||||
|
state: &RuntimeState,
|
||||||
|
company_id: u32,
|
||||||
|
_principal: u32,
|
||||||
|
_years_to_maturity: u32,
|
||||||
|
) -> Option<f64> {
|
||||||
|
let credit_rating = runtime_company_credit_rating(state, company_id)? as f64;
|
||||||
|
let prime_rate_percent = runtime_company_prime_rate(state, company_id)? as f64;
|
||||||
|
let quote = (prime_rate_percent + (10.0 - credit_rating)) / 100.0;
|
||||||
|
quote.is_finite().then_some(quote)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn runtime_world_annual_finance_mode_active(state: &RuntimeState) -> Option<bool> {
|
pub fn runtime_world_annual_finance_mode_active(state: &RuntimeState) -> Option<bool> {
|
||||||
Some(state.world_restore.partial_year_progress_raw_u8? == 0x0c)
|
Some(state.world_restore.partial_year_progress_raw_u8? == 0x0c)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ use crate::{
|
||||||
RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH, RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN,
|
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_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,
|
||||||
};
|
};
|
||||||
|
|
@ -436,6 +437,95 @@ fn service_company_annual_finance_policy(
|
||||||
mutated_company_ids.insert(company_id);
|
mutated_company_ids.insert(company_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
crate::RuntimeCompanyAnnualFinancePolicyAction::BondIssue => {
|
||||||
|
let mut mutated = false;
|
||||||
|
let Some(bond_state) =
|
||||||
|
crate::runtime::runtime_company_annual_bond_policy_state(state, company_id)
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if !bond_state.eligible_for_bond_issue_branch {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let Some(principal) = bond_state.issue_principal_step else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Some(years_to_maturity) = bond_state.proposed_issue_years_to_maturity else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Some(quote_rate) = runtime_company_bond_interest_rate_quote_f64(
|
||||||
|
state,
|
||||||
|
company_id,
|
||||||
|
principal,
|
||||||
|
years_to_maturity,
|
||||||
|
) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
mutated |= service_post_company_stat_delta(
|
||||||
|
state,
|
||||||
|
company_id,
|
||||||
|
0x0c,
|
||||||
|
(principal as f64) * COMPANY_STOCK_AND_BOND_CAPITAL_POST_SCALE,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
mutated |= service_post_company_stat_delta(
|
||||||
|
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
|
||||||
|
.service_state
|
||||||
|
.company_market_state
|
||||||
|
.get_mut(&company_id)
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let slot_index = market_state.bond_count as u32;
|
||||||
|
if market_state.bond_count == u8::MAX {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
market_state.live_bond_slots.push(crate::RuntimeCompanyBondSlot {
|
||||||
|
slot_index,
|
||||||
|
principal,
|
||||||
|
coupon_rate_raw_u32: (quote_rate as f32).to_bits(),
|
||||||
|
});
|
||||||
|
market_state.bond_count = market_state.bond_count.saturating_add(1);
|
||||||
|
market_state.largest_live_bond_principal = Some(
|
||||||
|
market_state
|
||||||
|
.largest_live_bond_principal
|
||||||
|
.unwrap_or(0)
|
||||||
|
.max(principal),
|
||||||
|
);
|
||||||
|
let highest_coupon_live_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);
|
||||||
|
market_state.highest_coupon_live_bond_principal = highest_coupon_live_principal;
|
||||||
|
if mutated {
|
||||||
|
applied_effect_count += 1;
|
||||||
|
mutated_company_ids.insert(company_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
crate::RuntimeCompanyAnnualFinancePolicyAction::StockRepurchase => {
|
crate::RuntimeCompanyAnnualFinancePolicyAction::StockRepurchase => {
|
||||||
let mut mutated = false;
|
let mut mutated = false;
|
||||||
for _ in 0..128 {
|
for _ in 0..128 {
|
||||||
|
|
@ -2735,6 +2825,133 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn periodic_boundary_applies_bond_issue_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] = (-400_000.0f64).to_bits();
|
||||||
|
|
||||||
|
let mut 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),
|
||||||
|
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()
|
||||||
|
},
|
||||||
|
metadata: BTreeMap::new(),
|
||||||
|
companies: vec![crate::RuntimeCompany {
|
||||||
|
company_id: 24,
|
||||||
|
controller_kind: crate::RuntimeCompanyControllerKind::Unknown,
|
||||||
|
current_cash: 0,
|
||||||
|
debt: 0,
|
||||||
|
credit_rating_score: Some(6),
|
||||||
|
prime_rate: Some(5),
|
||||||
|
track_piece_counts: crate::RuntimeTrackPieceCounts::default(),
|
||||||
|
active: true,
|
||||||
|
available_track_laying_capacity: None,
|
||||||
|
linked_chairman_profile_id: None,
|
||||||
|
book_value_per_share: 0,
|
||||||
|
investor_confidence: 0,
|
||||||
|
management_attitude: 0,
|
||||||
|
takeover_cooldown_year: None,
|
||||||
|
merger_cooldown_year: None,
|
||||||
|
}],
|
||||||
|
selected_company_id: Some(24),
|
||||||
|
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([(
|
||||||
|
24,
|
||||||
|
crate::RuntimeCompanyMarketState {
|
||||||
|
outstanding_shares: 10_000,
|
||||||
|
founding_year: 1840,
|
||||||
|
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 issue policy");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
state.service_state.annual_finance_last_actions.get(&24),
|
||||||
|
Some(&crate::RuntimeCompanyAnnualFinancePolicyAction::BondIssue)
|
||||||
|
);
|
||||||
|
assert_eq!(state.companies[0].current_cash, 100_000);
|
||||||
|
assert_eq!(state.service_state.company_market_state[&24].bond_count, 1);
|
||||||
|
assert_eq!(
|
||||||
|
state.service_state.company_market_state[&24].largest_live_bond_principal,
|
||||||
|
Some(500_000)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
state.service_state.company_market_state[&24].highest_coupon_live_bond_principal,
|
||||||
|
Some(500_000)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
state.service_state.company_market_state[&24].live_bond_slots,
|
||||||
|
vec![crate::RuntimeCompanyBondSlot {
|
||||||
|
slot_index: 0,
|
||||||
|
principal: 500_000,
|
||||||
|
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![24])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn applies_company_effects_for_specific_targets() {
|
fn applies_company_effects_for_specific_targets() {
|
||||||
let mut state = RuntimeState {
|
let mut state = RuntimeState {
|
||||||
|
|
|
||||||
|
|
@ -139,14 +139,17 @@ The highest-value next passes are now:
|
||||||
bankruptcy, and dividend finance-policy bytes, and the first annual creditor-pressure branch now
|
bankruptcy, and dividend finance-policy bytes, and the first annual creditor-pressure branch now
|
||||||
executes as a pure runtime reader over that owner state instead of remaining atlas-only; the
|
executes as a pure runtime reader over that owner state instead of remaining atlas-only; the
|
||||||
later deep-distress bankruptcy fallback now runs on that same save-native cash and trailing-
|
later deep-distress bankruptcy fallback now runs on that same save-native cash and trailing-
|
||||||
profit seam; the annual bond, stock-repurchase, and stock-capital issue branches now do too
|
profit seam; the annual bond, stock-repurchase, and stock-capital issue branches now do too,
|
||||||
net-profit surface too; the annual dividend-adjustment branch now does as well through the
|
and periodic boundary service now also commits the bond-issue branch through that owned
|
||||||
|
stat-post and live-bond seam instead of leaving it as a reader-only finance lane; the rehosted
|
||||||
|
company market reader now carries the first bundled annual net-profit surface too; the annual
|
||||||
|
dividend-adjustment branch now does as well through the
|
||||||
shared year-or-control-transfer reader and board-approved dividend ceiling helper; the same
|
shared year-or-control-transfer reader and board-approved dividend ceiling helper; the same
|
||||||
owner seam now also carries the fixed-world building-density
|
owner seam now also carries the fixed-world building-density
|
||||||
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, stock-repurchase, and stock-issue branches against owned runtime state
|
dividend-adjustment, stock-repurchase, stock-issue, and bond-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, stock-repurchase, and stock-issue branches directly into owned dividend,
|
dividend-adjustment, stock-repurchase, stock-issue, and bond-issue branches directly into owned
|
||||||
company stat-post, outstanding-share, and issue-calendar state.
|
dividend, company stat-post, outstanding-share, issue-calendar, and live bond-slot state.
|
||||||
|
|
||||||
## Why This Boundary
|
## Why This Boundary
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue