Rehost company annual finance reader

This commit is contained in:
Jan Petykiewicz 2026-04-17 20:58:41 -07:00
commit 4599976b17
7 changed files with 249 additions and 17 deletions

View file

@ -63,7 +63,9 @@ reader seam is now also rehosted for the grounded `0x37` investor-confidence lan
save-native world-restore state. The selected-company summary path now also exposes the
unassigned share pool derived from outstanding shares minus chairman-held shares, so later
dividend / stock-capital logic can extend one owned market reader instead of another ad hoc
counter. A checked-in
counter. The next bundled annual-finance reader seam is now rehosted on top of that same market
state too, deriving assigned shares, public float, and rounded cached share price from one shared
company market reader instead of scattering more finance helpers across the runtime. 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

@ -92,8 +92,12 @@ pub struct ExpectedRuntimeSummary {
#[serde(default)]
pub selected_company_outstanding_shares: Option<u32>,
#[serde(default)]
pub selected_company_assigned_share_pool: Option<u32>,
#[serde(default)]
pub selected_company_unassigned_share_pool: Option<u32>,
#[serde(default)]
pub selected_company_cached_share_price: Option<i64>,
#[serde(default)]
pub selected_company_cached_share_price_value_f32_text: Option<String>,
#[serde(default)]
pub selected_company_mutable_support_scalar_value_f32_text: Option<String>,
@ -593,6 +597,14 @@ impl ExpectedRuntimeSummary {
));
}
}
if let Some(value) = self.selected_company_assigned_share_pool {
if actual.selected_company_assigned_share_pool != Some(value) {
mismatches.push(format!(
"selected_company_assigned_share_pool mismatch: expected {value}, got {:?}",
actual.selected_company_assigned_share_pool
));
}
}
if let Some(value) = self.selected_company_unassigned_share_pool {
if actual.selected_company_unassigned_share_pool != Some(value) {
mismatches.push(format!(
@ -601,6 +613,14 @@ impl ExpectedRuntimeSummary {
));
}
}
if let Some(value) = self.selected_company_cached_share_price {
if actual.selected_company_cached_share_price != Some(value) {
mismatches.push(format!(
"selected_company_cached_share_price mismatch: expected {value}, got {:?}",
actual.selected_company_cached_share_price
));
}
}
if let Some(value) = &self.selected_company_cached_share_price_value_f32_text {
if actual
.selected_company_cached_share_price_value_f32_text

View file

@ -46,12 +46,12 @@ pub use pk4::{
pub use runtime::{
RuntimeCargoCatalogEntry, RuntimeCargoClass, RuntimeCargoPriceTarget,
RuntimeCargoProductionTarget, RuntimeChairmanMetric, RuntimeChairmanProfile,
RuntimeChairmanTarget, RuntimeCompany, RuntimeCompanyConditionTestScope,
RuntimeCompanyControllerKind, RuntimeCompanyMarketState, RuntimeCompanyMetric,
RuntimeCompanyStatBandCandidate, RuntimeCompanyStatSelector, RuntimeCompanyTarget,
RuntimeCompanyTerritoryAccess, RuntimeCompanyTerritoryTrackPieceCount, RuntimeCondition,
RuntimeConditionComparator, RuntimeEffect, RuntimeEventRecord, RuntimeEventRecordTemplate,
RuntimeLocomotiveCatalogEntry, RuntimePackedEventCollectionSummary,
RuntimeChairmanTarget, RuntimeCompany, RuntimeCompanyAnnualFinanceState,
RuntimeCompanyConditionTestScope, RuntimeCompanyControllerKind, RuntimeCompanyMarketState,
RuntimeCompanyMetric, RuntimeCompanyStatBandCandidate, RuntimeCompanyStatSelector,
RuntimeCompanyTarget, RuntimeCompanyTerritoryAccess, RuntimeCompanyTerritoryTrackPieceCount,
RuntimeCondition, RuntimeConditionComparator, RuntimeEffect, RuntimeEventRecord,
RuntimeEventRecordTemplate, RuntimeLocomotiveCatalogEntry, RuntimePackedEventCollectionSummary,
RuntimePackedEventCompactControlSummary, RuntimePackedEventConditionRowSummary,
RuntimePackedEventGroupedEffectRowSummary, RuntimePackedEventNegativeSentinelScopeSummary,
RuntimePackedEventRecordSummary, RuntimePackedEventTextBandSummary, RuntimePlayer,
@ -61,8 +61,8 @@ pub use runtime::{
RuntimeWorldFinanceNeighborhoodCandidate, RuntimeWorldIssueState, RuntimeWorldRestoreState,
RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER, RUNTIME_COMPANY_STAT_SLOT_BOOK_VALUE_PER_SHARE,
RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH, RUNTIME_WORLD_ISSUE_INVESTOR_CONFIDENCE,
runtime_company_stat_value, runtime_company_unassigned_share_pool,
runtime_world_issue_state,
runtime_company_annual_finance_state, runtime_company_assigned_share_pool,
runtime_company_stat_value, runtime_company_unassigned_share_pool, runtime_world_issue_state,
};
pub use smp::{
SMP_FOUR_SIDECAR_BYTE_PLANES_MIN_BUNDLE_VERSION, SmpAlignedRuntimeRuleBandLane,

View file

@ -92,6 +92,27 @@ pub struct RuntimeCompanyMarketState {
pub stat_band_root_1c47_candidates: Vec<RuntimeCompanyStatBandCandidate>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RuntimeCompanyAnnualFinanceState {
pub company_id: u32,
pub outstanding_shares: u32,
pub assigned_share_pool: u32,
pub unassigned_share_pool: u32,
#[serde(default)]
pub cached_share_price: Option<i64>,
pub chairman_salary_baseline: u32,
pub chairman_salary_current: u32,
pub chairman_bonus_year: u32,
pub chairman_bonus_amount: i32,
pub founding_year: u32,
pub last_bankruptcy_year: u32,
pub last_dividend_year: u32,
pub current_issue_calendar_word: u32,
pub prior_issue_calendar_word: u32,
pub city_connection_latch: bool,
pub linked_transit_latch: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct RuntimeTrackPieceCounts {
#[serde(default)]
@ -1844,14 +1865,51 @@ pub fn runtime_company_unassigned_share_pool(state: &RuntimeState, company_id: u
.company_market_state
.get(&company_id)?
.outstanding_shares;
let assigned_shares = state
.chairman_profiles
.iter()
.filter_map(|profile| profile.company_holdings.get(&company_id).copied())
.sum::<u32>();
let assigned_shares = runtime_company_assigned_share_pool(state, company_id)?;
Some(outstanding_shares.saturating_sub(assigned_shares))
}
pub fn runtime_company_assigned_share_pool(state: &RuntimeState, company_id: u32) -> Option<u32> {
state
.service_state
.company_market_state
.get(&company_id)?;
Some(
state
.chairman_profiles
.iter()
.filter_map(|profile| profile.company_holdings.get(&company_id).copied())
.sum::<u32>(),
)
}
pub fn runtime_company_annual_finance_state(
state: &RuntimeState,
company_id: u32,
) -> Option<RuntimeCompanyAnnualFinanceState> {
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)?;
Some(RuntimeCompanyAnnualFinanceState {
company_id,
outstanding_shares: market_state.outstanding_shares,
assigned_share_pool,
unassigned_share_pool,
cached_share_price: rounded_cached_share_price_i64(market_state.cached_share_price_raw_u32),
chairman_salary_baseline: market_state.chairman_salary_baseline,
chairman_salary_current: market_state.chairman_salary_current,
chairman_bonus_year: market_state.chairman_bonus_year,
chairman_bonus_amount: market_state.chairman_bonus_amount,
founding_year: market_state.founding_year,
last_bankruptcy_year: market_state.last_bankruptcy_year,
last_dividend_year: market_state.last_dividend_year,
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,
linked_transit_latch: market_state.linked_transit_latch,
})
}
fn rounded_cached_share_price_i64(raw_u32: u32) -> Option<i64> {
let value = f32::from_bits(raw_u32);
if !value.is_finite() {
@ -4001,4 +4059,136 @@ mod tests {
assert_eq!(runtime_company_unassigned_share_pool(&state, 4), Some(4_500));
assert_eq!(runtime_company_unassigned_share_pool(&state, 99), None);
}
#[test]
fn derives_company_annual_finance_state_from_owned_runtime_market_state() {
let state = RuntimeState {
calendar: CalendarPoint {
year: 1830,
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: 4,
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![
RuntimeChairmanProfile {
profile_id: 1,
name: "Chairman One".to_string(),
active: true,
current_cash: 0,
linked_company_id: Some(4),
company_holdings: BTreeMap::from([(4, 8_000)]),
holdings_value_total: 0,
net_worth_total: 0,
purchasing_power_total: 0,
},
RuntimeChairmanProfile {
profile_id: 2,
name: "Chairman Two".to_string(),
active: true,
current_cash: 0,
linked_company_id: None,
company_holdings: BTreeMap::from([(4, 7_500)]),
holdings_value_total: 0,
net_worth_total: 0,
purchasing_power_total: 0,
},
],
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([(
4,
RuntimeCompanyMarketState {
outstanding_shares: 20_000,
cached_share_price_raw_u32: 0x42200000,
chairman_salary_baseline: 24,
chairman_salary_current: 30,
chairman_bonus_year: 1842,
chairman_bonus_amount: 750,
founding_year: 1831,
last_bankruptcy_year: 0,
last_dividend_year: 1841,
current_issue_calendar_word: 5,
prior_issue_calendar_word: 4,
city_connection_latch: true,
linked_transit_latch: false,
..RuntimeCompanyMarketState::default()
},
)]),
..RuntimeServiceState::default()
},
};
assert_eq!(runtime_company_assigned_share_pool(&state, 4), Some(15_500));
assert_eq!(
runtime_company_annual_finance_state(&state, 4),
Some(RuntimeCompanyAnnualFinanceState {
company_id: 4,
outstanding_shares: 20_000,
assigned_share_pool: 15_500,
unassigned_share_pool: 4_500,
cached_share_price: Some(40),
chairman_salary_baseline: 24,
chairman_salary_current: 30,
chairman_bonus_year: 1842,
chairman_bonus_amount: 750,
founding_year: 1831,
last_bankruptcy_year: 0,
last_dividend_year: 1841,
current_issue_calendar_word: 5,
prior_issue_calendar_word: 4,
city_connection_latch: true,
linked_transit_latch: false,
})
);
assert_eq!(runtime_company_assigned_share_pool(&state, 99), None);
assert_eq!(runtime_company_annual_finance_state(&state, 99), None);
}
}

View file

@ -1,6 +1,9 @@
use serde::{Deserialize, Serialize};
use crate::{runtime_company_unassigned_share_pool, CalendarPoint, RuntimeState};
use crate::{
runtime_company_annual_finance_state, runtime_company_unassigned_share_pool, CalendarPoint,
RuntimeState,
};
fn raw_u32_to_f32_text(raw: u32) -> String {
format!("{:.6}", f32::from_bits(raw))
@ -47,7 +50,9 @@ pub struct RuntimeSummary {
pub active_company_count: usize,
pub company_market_state_owner_count: usize,
pub selected_company_outstanding_shares: Option<u32>,
pub selected_company_assigned_share_pool: Option<u32>,
pub selected_company_unassigned_share_pool: Option<u32>,
pub selected_company_cached_share_price: Option<i64>,
pub selected_company_cached_share_price_value_f32_text: Option<String>,
pub selected_company_mutable_support_scalar_value_f32_text: Option<String>,
pub selected_company_stat_band_root_0cfb_count: usize,
@ -142,6 +147,9 @@ impl RuntimeSummary {
let selected_company_market_state = state
.selected_company_id
.and_then(|company_id| state.service_state.company_market_state.get(&company_id));
let selected_company_annual_finance_state = state
.selected_company_id
.and_then(|company_id| runtime_company_annual_finance_state(state, company_id));
Self {
calendar: state.calendar,
calendar_projection_source: state.metadata.get("save_slice.calendar_source").cloned(),
@ -247,9 +255,15 @@ impl RuntimeSummary {
company_market_state_owner_count: state.service_state.company_market_state.len(),
selected_company_outstanding_shares: selected_company_market_state
.map(|market_state| market_state.outstanding_shares),
selected_company_assigned_share_pool: selected_company_annual_finance_state
.as_ref()
.map(|finance_state| finance_state.assigned_share_pool),
selected_company_unassigned_share_pool: state
.selected_company_id
.and_then(|company_id| runtime_company_unassigned_share_pool(state, company_id)),
selected_company_cached_share_price: selected_company_annual_finance_state
.as_ref()
.and_then(|finance_state| finance_state.cached_share_price),
selected_company_cached_share_price_value_f32_text: selected_company_market_state
.map(|market_state| raw_u32_to_f32_text(market_state.cached_share_price_raw_u32)),
selected_company_mutable_support_scalar_value_f32_text: selected_company_market_state
@ -1997,7 +2011,9 @@ mod tests {
let summary = RuntimeSummary::from_state(&state);
assert_eq!(summary.company_market_state_owner_count, 1);
assert_eq!(summary.selected_company_outstanding_shares, Some(20_000));
assert_eq!(summary.selected_company_assigned_share_pool, Some(5_000));
assert_eq!(summary.selected_company_unassigned_share_pool, Some(15_000));
assert_eq!(summary.selected_company_cached_share_price, Some(40));
assert_eq!(
summary.selected_company_cached_share_price_value_f32_text,
Some("40.000000".to_string())

View file

@ -120,7 +120,8 @@ The highest-value next passes are now:
stat-band windows themselves now carry 16 dwords per root; the matching world-side issue reader
seam is now rehosted for the grounded `0x37` lane, and selected-company summaries now expose the
unassigned share pool derived from outstanding shares minus chairman-held shares for later annual
finance logic
finance logic; that same owned company market state now also backs a bundled annual-finance
reader seam for assigned shares, public float, and rounded cached share price
- 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

@ -206,7 +206,10 @@ runtime-owned company market state, and the matching world-side issue reader sea
for the grounded `0x37` lane over save-native world restore state. The selected-company summary
surface now also carries the unassigned share pool derived from outstanding shares minus
chairman-held shares, so later dividend / stock-capital work can extend a shared owned-state
reader instead of guessing another finance leaf.
reader instead of guessing another finance leaf. The same owned company market state now also
supports a bundled annual-finance reader seam for assigned shares, public float, and rounded
cached share price, which is a better base for later dividend / issue-calendar simulation than
scattered single-field helpers.
## Why This Boundary