Derive annual finance timing inputs

This commit is contained in:
Jan Petykiewicz 2026-04-17 21:07:41 -07:00
commit 08f44debc7
6 changed files with 194 additions and 3 deletions

View file

@ -71,7 +71,9 @@ so future issue-`0x38/0x39` closure can build on a broader owned restore-state w
another narrow one-off probe. The next company-side seam is now bundled too: a shared company
market reader now exposes outstanding shares, assigned shares, public float, rounded cached share
price, salary lanes, bonus amount, and issue-calendar words from the owned annual-finance state
instead of leaving that logic spread across summary helpers. A checked-in
instead of leaving that logic spread across summary helpers. The same annual-finance state now also
derives elapsed years since founding, last dividend, and last bankruptcy from the runtime calendar,
which lines up directly with the grounded annual finance-policy gates in the atlas. A checked-in
The working rule on the remaining frontier is explicit now too: when a lane is still ambiguous, we
should prefer rehosting the owning source state or the real reader/setter family rather than
guessing one more derived leaf field from nearby offsets. A checked-in

View file

@ -108,6 +108,18 @@ pub struct ExpectedRuntimeSummary {
#[serde(default)]
pub selected_company_stat_band_root_1c47_count: Option<usize>,
#[serde(default)]
pub selected_company_last_dividend_year: Option<u32>,
#[serde(default)]
pub selected_company_years_since_founding: Option<u32>,
#[serde(default)]
pub selected_company_years_since_last_bankruptcy: Option<u32>,
#[serde(default)]
pub selected_company_years_since_last_dividend: Option<u32>,
#[serde(default)]
pub selected_company_chairman_bonus_year: Option<u32>,
#[serde(default)]
pub selected_company_chairman_bonus_amount: Option<i32>,
#[serde(default)]
pub player_count: Option<usize>,
#[serde(default)]
pub chairman_profile_count: Option<usize>,
@ -669,6 +681,54 @@ impl ExpectedRuntimeSummary {
));
}
}
if let Some(value) = self.selected_company_last_dividend_year {
if actual.selected_company_last_dividend_year != Some(value) {
mismatches.push(format!(
"selected_company_last_dividend_year mismatch: expected {value}, got {:?}",
actual.selected_company_last_dividend_year
));
}
}
if let Some(value) = self.selected_company_years_since_founding {
if actual.selected_company_years_since_founding != Some(value) {
mismatches.push(format!(
"selected_company_years_since_founding mismatch: expected {value}, got {:?}",
actual.selected_company_years_since_founding
));
}
}
if let Some(value) = self.selected_company_years_since_last_bankruptcy {
if actual.selected_company_years_since_last_bankruptcy != Some(value) {
mismatches.push(format!(
"selected_company_years_since_last_bankruptcy mismatch: expected {value}, got {:?}",
actual.selected_company_years_since_last_bankruptcy
));
}
}
if let Some(value) = self.selected_company_years_since_last_dividend {
if actual.selected_company_years_since_last_dividend != Some(value) {
mismatches.push(format!(
"selected_company_years_since_last_dividend mismatch: expected {value}, got {:?}",
actual.selected_company_years_since_last_dividend
));
}
}
if let Some(value) = self.selected_company_chairman_bonus_year {
if actual.selected_company_chairman_bonus_year != Some(value) {
mismatches.push(format!(
"selected_company_chairman_bonus_year mismatch: expected {value}, got {:?}",
actual.selected_company_chairman_bonus_year
));
}
}
if let Some(value) = self.selected_company_chairman_bonus_amount {
if actual.selected_company_chairman_bonus_amount != Some(value) {
mismatches.push(format!(
"selected_company_chairman_bonus_amount mismatch: expected {value}, got {:?}",
actual.selected_company_chairman_bonus_amount
));
}
}
if let Some(count) = self.player_count {
if actual.player_count != count {
mismatches.push(format!(

View file

@ -107,6 +107,12 @@ pub struct RuntimeCompanyAnnualFinanceState {
pub founding_year: u32,
pub last_bankruptcy_year: u32,
pub last_dividend_year: u32,
#[serde(default)]
pub years_since_founding: Option<u32>,
#[serde(default)]
pub years_since_last_bankruptcy: Option<u32>,
#[serde(default)]
pub years_since_last_dividend: Option<u32>,
pub current_issue_calendar_word: u32,
pub prior_issue_calendar_word: u32,
pub city_connection_latch: bool,
@ -1904,6 +1910,16 @@ pub fn runtime_company_annual_finance_state(
let market_state = state.service_state.company_market_state.get(&company_id)?;
let assigned_share_pool = runtime_company_assigned_share_pool(state, company_id)?;
let unassigned_share_pool = runtime_company_unassigned_share_pool(state, company_id)?;
let years_since_founding =
derive_runtime_company_elapsed_years(state.calendar.year, market_state.founding_year);
let years_since_last_bankruptcy = derive_runtime_company_elapsed_years(
state.calendar.year,
market_state.last_bankruptcy_year,
);
let years_since_last_dividend = derive_runtime_company_elapsed_years(
state.calendar.year,
market_state.last_dividend_year,
);
Some(RuntimeCompanyAnnualFinanceState {
company_id,
outstanding_shares: market_state.outstanding_shares,
@ -1917,6 +1933,9 @@ pub fn runtime_company_annual_finance_state(
founding_year: market_state.founding_year,
last_bankruptcy_year: market_state.last_bankruptcy_year,
last_dividend_year: market_state.last_dividend_year,
years_since_founding,
years_since_last_bankruptcy,
years_since_last_dividend,
current_issue_calendar_word: market_state.current_issue_calendar_word,
prior_issue_calendar_word: market_state.prior_issue_calendar_word,
city_connection_latch: market_state.city_connection_latch,
@ -1970,6 +1989,13 @@ fn rounded_cached_share_price_i64(raw_u32: u32) -> Option<i64> {
Some(value.round() as i64)
}
fn derive_runtime_company_elapsed_years(current_year: u32, prior_year: u32) -> Option<u32> {
if prior_year == 0 || prior_year > current_year {
return None;
}
Some(current_year - prior_year)
}
fn derive_runtime_chairman_holdings_share_price_total(
holdings_by_company: &BTreeMap<u32, u32>,
company_share_prices: &BTreeMap<u32, i64>,
@ -4231,6 +4257,9 @@ mod tests {
founding_year: 1831,
last_bankruptcy_year: 0,
last_dividend_year: 1841,
years_since_founding: None,
years_since_last_bankruptcy: None,
years_since_last_dividend: None,
current_issue_calendar_word: 5,
prior_issue_calendar_word: 4,
city_connection_latch: true,
@ -4385,4 +4414,85 @@ mod tests {
None
);
}
#[test]
fn derives_elapsed_company_finance_years_from_calendar_and_saved_market_state() {
let state = RuntimeState {
calendar: CalendarPoint {
year: 1844,
month_slot: 0,
phase_slot: 0,
tick_slot: 0,
},
world_flags: BTreeMap::new(),
save_profile: RuntimeSaveProfileState::default(),
world_restore: RuntimeWorldRestoreState::default(),
metadata: BTreeMap::new(),
companies: vec![RuntimeCompany {
company_id: 3,
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([(
3,
RuntimeCompanyMarketState {
outstanding_shares: 10_000,
founding_year: 1838,
last_bankruptcy_year: 1841,
last_dividend_year: 1843,
..RuntimeCompanyMarketState::default()
},
)]),
..RuntimeServiceState::default()
},
};
let finance_state =
runtime_company_annual_finance_state(&state, 3).expect("finance state should derive");
assert_eq!(finance_state.years_since_founding, Some(6));
assert_eq!(finance_state.years_since_last_bankruptcy, Some(3));
assert_eq!(finance_state.years_since_last_dividend, Some(1));
}
}

View file

@ -59,6 +59,9 @@ pub struct RuntimeSummary {
pub selected_company_stat_band_root_0d7f_count: usize,
pub selected_company_stat_band_root_1c47_count: usize,
pub selected_company_last_dividend_year: Option<u32>,
pub selected_company_years_since_founding: Option<u32>,
pub selected_company_years_since_last_bankruptcy: Option<u32>,
pub selected_company_years_since_last_dividend: Option<u32>,
pub selected_company_chairman_bonus_year: Option<u32>,
pub selected_company_chairman_bonus_amount: Option<i32>,
pub player_count: usize,
@ -282,6 +285,15 @@ impl RuntimeSummary {
selected_company_last_dividend_year: selected_company_market_state
.map(|market_state| market_state.last_dividend_year)
.filter(|year| *year != 0),
selected_company_years_since_founding: selected_company_annual_finance_state
.as_ref()
.and_then(|finance_state| finance_state.years_since_founding),
selected_company_years_since_last_bankruptcy: selected_company_annual_finance_state
.as_ref()
.and_then(|finance_state| finance_state.years_since_last_bankruptcy),
selected_company_years_since_last_dividend: selected_company_annual_finance_state
.as_ref()
.and_then(|finance_state| finance_state.years_since_last_dividend),
selected_company_chairman_bonus_year: selected_company_market_state
.map(|market_state| market_state.chairman_bonus_year)
.filter(|year| *year != 0),
@ -2026,6 +2038,9 @@ mod tests {
assert_eq!(summary.selected_company_stat_band_root_0d7f_count, 1);
assert_eq!(summary.selected_company_stat_band_root_1c47_count, 1);
assert_eq!(summary.selected_company_last_dividend_year, Some(1841));
assert_eq!(summary.selected_company_years_since_founding, None);
assert_eq!(summary.selected_company_years_since_last_bankruptcy, None);
assert_eq!(summary.selected_company_years_since_last_dividend, None);
assert_eq!(summary.selected_company_chairman_bonus_year, Some(1842));
assert_eq!(summary.selected_company_chairman_bonus_amount, Some(750));
}

View file

@ -124,7 +124,9 @@ The highest-value next passes are now:
reader seam for assigned shares, public float, and rounded cached share price; the fixed-world
finance neighborhood is now widened to 16 dwords rooted at `[world+0x11]` so later issue-family
closure can target a broader owned restore-state window; the same annual-finance state now also
feeds a shared company market reader for stock-capital, salary, bonus, and issue-calendar values
feeds a shared company market reader for stock-capital, salary, bonus, and issue-calendar values,
and now derives elapsed years since founding, last dividend, and last bankruptcy for later annual
finance-policy rehosting
- 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

@ -214,7 +214,9 @@ rooted at `[world+0x11]`, so later issue-`0x38/0x39` closure can build on a broa
restore-state window instead of another narrow probe. The same owned company annual-finance state
now also drives a shared company market reader seam for stock-capital, salary, bonus, and
issue-calendar values, which is a better base for shellless finance simulation than summary-only
helpers.
helpers. That same owned annual-finance state now also derives elapsed years since founding, last
dividend, and last bankruptcy from the runtime calendar, which lines up directly with the grounded
annual finance-policy gates in the atlas.
## Why This Boundary