diff --git a/README.md b/README.md index f5d7388..120d6fb 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,9 @@ runtime-side `0x2329` stat-family reader seam is now rehosted too for the curren `0x0d` (`current_cash`) and `0x1d` (`book_value_per_share`), so later annual-finance logic can extend one shared reader family instead of hard-coding more direct field accesses. Those saved stat-band windows are now widened to 16 dwords per root in save-slice/runtime state so later -year-series finance closure has enough owned raw state to attach to. A checked-in +year-series finance closure has enough owned raw state to attach to. The matching world-side issue +reader seam is now also rehosted for the grounded `0x37` investor-confidence lane on top of the +save-native world-restore state. 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 diff --git a/crates/rrt-runtime/src/lib.rs b/crates/rrt-runtime/src/lib.rs index 00fdbac..bf87157 100644 --- a/crates/rrt-runtime/src/lib.rs +++ b/crates/rrt-runtime/src/lib.rs @@ -58,9 +58,10 @@ pub use runtime::{ RuntimePlayerConditionTestScope, RuntimePlayerTarget, RuntimeSaveProfileState, RuntimeServiceState, RuntimeState, RuntimeTerritory, RuntimeTerritoryMetric, RuntimeTerritoryTarget, RuntimeTrackMetric, RuntimeTrackPieceCounts, RuntimeTrain, - RuntimeWorldFinanceNeighborhoodCandidate, RuntimeWorldRestoreState, + RuntimeWorldFinanceNeighborhoodCandidate, RuntimeWorldIssueState, RuntimeWorldRestoreState, RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER, RUNTIME_COMPANY_STAT_SLOT_BOOK_VALUE_PER_SHARE, - RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH, runtime_company_stat_value, + RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH, RUNTIME_WORLD_ISSUE_INVESTOR_CONFIDENCE, + runtime_company_stat_value, runtime_world_issue_state, }; pub use smp::{ SMP_FOUR_SIDECAR_BYTE_PLANES_MIN_BUNDLE_VERSION, SmpAlignedRuntimeRuleBandLane, diff --git a/crates/rrt-runtime/src/runtime.rs b/crates/rrt-runtime/src/runtime.rs index 0ca4387..e7577a5 100644 --- a/crates/rrt-runtime/src/runtime.rs +++ b/crates/rrt-runtime/src/runtime.rs @@ -339,6 +339,15 @@ pub struct RuntimeCompanyStatSelector { pub const RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER: u32 = 0x2329; pub const RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH: u32 = 0x0d; pub const RUNTIME_COMPANY_STAT_SLOT_BOOK_VALUE_PER_SHARE: u32 = 0x1d; +pub const RUNTIME_WORLD_ISSUE_INVESTOR_CONFIDENCE: u32 = 0x37; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct RuntimeWorldIssueState { + pub issue_id: u32, + pub raw_value_u32: u32, + pub multiplier_raw_u32: u32, + pub multiplier_value_f32_text: String, +} #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] @@ -1814,6 +1823,21 @@ pub fn runtime_company_stat_value( } } +pub fn runtime_world_issue_state(state: &RuntimeState, issue_id: u32) -> Option { + match issue_id { + RUNTIME_WORLD_ISSUE_INVESTOR_CONFIDENCE => Some(RuntimeWorldIssueState { + issue_id, + raw_value_u32: state.world_restore.issue_37_value?, + multiplier_raw_u32: state.world_restore.issue_37_multiplier_raw_u32?, + multiplier_value_f32_text: state + .world_restore + .issue_37_multiplier_value_f32_text + .clone()?, + }), + _ => None, + } +} + fn rounded_cached_share_price_i64(raw_u32: u32) -> Option { let value = f32::from_bits(raw_u32); if !value.is_finite() { @@ -3805,4 +3829,64 @@ mod tests { None ); } + + #[test] + fn reads_grounded_world_issue_state_from_runtime_restore_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 { + issue_37_value: Some(3), + issue_37_multiplier_raw_u32: Some(0x3d75c28f), + issue_37_multiplier_value_f32_text: Some("0.060000".to_string()), + ..RuntimeWorldRestoreState::default() + }, + metadata: BTreeMap::new(), + companies: Vec::new(), + 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::default(), + }; + + let issue = runtime_world_issue_state(&state, RUNTIME_WORLD_ISSUE_INVESTOR_CONFIDENCE) + .expect("grounded issue 0x37 state"); + assert_eq!(issue.issue_id, RUNTIME_WORLD_ISSUE_INVESTOR_CONFIDENCE); + assert_eq!(issue.raw_value_u32, 3); + assert_eq!(issue.multiplier_raw_u32, 0x3d75c28f); + assert_eq!(issue.multiplier_value_f32_text, "0.060000"); + assert_eq!(runtime_world_issue_state(&state, 0x39), None); + } } diff --git a/docs/README.md b/docs/README.md index 9b98c67..84e71d1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -117,7 +117,8 @@ The highest-value next passes are now: and `[company+0x1c47]` for each live company, so later finance/stat-family readers can attach to owned runtime data instead of one more guessed save offset; the first runtime-side `0x2329` stat-family reader seam is now also rehosted for slots `0x0d` and `0x1d`, and the saved - stat-band windows themselves now carry 16 dwords per root + 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 - 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 diff --git a/docs/runtime-rehost-plan.md b/docs/runtime-rehost-plan.md index 315118d..882a778 100644 --- a/docs/runtime-rehost-plan.md +++ b/docs/runtime-rehost-plan.md @@ -202,7 +202,8 @@ the first grounded stat-band root windows at `[company+0x0cfb]`, `[company+0x0d7 `[company+0x1c47]`, and the first runtime-side `0x2329` stat-family reader seam is now rehosted for slots `0x0d` and `0x1d`, so later finance readers can target saved owner state and one shared reader family directly. Those stat-band windows now carry 16 dwords per root in the save-slice and -runtime-owned company market state. +runtime-owned company market state, and the matching world-side issue reader seam is now rehosted +for the grounded `0x37` lane over save-native world restore state. ## Why This Boundary