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 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 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 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 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 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 guessing one more derived leaf field from nearby offsets. A checked-in

View file

@ -108,6 +108,18 @@ pub struct ExpectedRuntimeSummary {
#[serde(default)] #[serde(default)]
pub selected_company_stat_band_root_1c47_count: Option<usize>, pub selected_company_stat_band_root_1c47_count: Option<usize>,
#[serde(default)] #[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>, pub player_count: Option<usize>,
#[serde(default)] #[serde(default)]
pub chairman_profile_count: Option<usize>, 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 let Some(count) = self.player_count {
if actual.player_count != count { if actual.player_count != count {
mismatches.push(format!( mismatches.push(format!(

View file

@ -107,6 +107,12 @@ pub struct RuntimeCompanyAnnualFinanceState {
pub founding_year: u32, pub founding_year: u32,
pub last_bankruptcy_year: u32, pub last_bankruptcy_year: u32,
pub last_dividend_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 current_issue_calendar_word: u32,
pub prior_issue_calendar_word: u32, pub prior_issue_calendar_word: u32,
pub city_connection_latch: bool, 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 market_state = state.service_state.company_market_state.get(&company_id)?;
let assigned_share_pool = runtime_company_assigned_share_pool(state, 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 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 { Some(RuntimeCompanyAnnualFinanceState {
company_id, company_id,
outstanding_shares: market_state.outstanding_shares, outstanding_shares: market_state.outstanding_shares,
@ -1917,6 +1933,9 @@ pub fn runtime_company_annual_finance_state(
founding_year: market_state.founding_year, founding_year: market_state.founding_year,
last_bankruptcy_year: market_state.last_bankruptcy_year, last_bankruptcy_year: market_state.last_bankruptcy_year,
last_dividend_year: market_state.last_dividend_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, current_issue_calendar_word: market_state.current_issue_calendar_word,
prior_issue_calendar_word: market_state.prior_issue_calendar_word, prior_issue_calendar_word: market_state.prior_issue_calendar_word,
city_connection_latch: market_state.city_connection_latch, 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) 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( fn derive_runtime_chairman_holdings_share_price_total(
holdings_by_company: &BTreeMap<u32, u32>, holdings_by_company: &BTreeMap<u32, u32>,
company_share_prices: &BTreeMap<u32, i64>, company_share_prices: &BTreeMap<u32, i64>,
@ -4231,6 +4257,9 @@ mod tests {
founding_year: 1831, founding_year: 1831,
last_bankruptcy_year: 0, last_bankruptcy_year: 0,
last_dividend_year: 1841, last_dividend_year: 1841,
years_since_founding: None,
years_since_last_bankruptcy: None,
years_since_last_dividend: None,
current_issue_calendar_word: 5, current_issue_calendar_word: 5,
prior_issue_calendar_word: 4, prior_issue_calendar_word: 4,
city_connection_latch: true, city_connection_latch: true,
@ -4385,4 +4414,85 @@ mod tests {
None 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_0d7f_count: usize,
pub selected_company_stat_band_root_1c47_count: usize, pub selected_company_stat_band_root_1c47_count: usize,
pub selected_company_last_dividend_year: Option<u32>, 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_year: Option<u32>,
pub selected_company_chairman_bonus_amount: Option<i32>, pub selected_company_chairman_bonus_amount: Option<i32>,
pub player_count: usize, pub player_count: usize,
@ -282,6 +285,15 @@ impl RuntimeSummary {
selected_company_last_dividend_year: selected_company_market_state selected_company_last_dividend_year: selected_company_market_state
.map(|market_state| market_state.last_dividend_year) .map(|market_state| market_state.last_dividend_year)
.filter(|year| *year != 0), .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 selected_company_chairman_bonus_year: selected_company_market_state
.map(|market_state| market_state.chairman_bonus_year) .map(|market_state| market_state.chairman_bonus_year)
.filter(|year| *year != 0), .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_0d7f_count, 1);
assert_eq!(summary.selected_company_stat_band_root_1c47_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_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_year, Some(1842));
assert_eq!(summary.selected_company_chairman_bonus_amount, Some(750)); 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 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 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 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 - 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

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 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 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 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 ## Why This Boundary