diff --git a/README.md b/README.md index 49f0635..6e4e06e 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,8 @@ company/chairman scalar candidates, including fixed-world chairman slot / role-g explicit company dword candidate windows, richer chairman qword cache views, and derived holdings-at-share-price / cached purchasing-power comparisons. The same fixed `0x32c8` world block is now probed for the grounded issue-`0x37` pair at `[world+0x29/+0x2d]`, one broader -fixed-dword finance neighborhood around the absolute-calendar and issue lanes, and the separate +fixed-dword finance neighborhood rooted at `[world+0x0d]` that now carries the saved calendar +tuple and absolute-counter owner lanes directly, and the separate six-float economic tuning band, but current atlas evidence still keeps that editor-facing tuning family distinct from the governance issue lanes behind investor confidence and prime-rate math. The next shared company-side slice is now rehosted too: save-native company direct records @@ -66,9 +67,11 @@ dividend / stock-capital logic can extend one owned market reader instead of ano 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 fixed-world finance neighborhood itself is now widened to 16 dwords rooted at `[world+0x11]`, +The fixed-world finance neighborhood itself is now widened to 17 dwords rooted at `[world+0x0d]`, so future issue-`0x38/0x39` closure can build on a broader owned restore-state window rather than -another narrow one-off probe. The next company-side seam is now bundled too: a shared company +another narrow one-off probe; that same owner surface now also carries the saved absolute counter +as first-class runtime restore state instead of leaving it on “requires shell context” metadata. +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 the full two-word current/prior issue-calendar tuples from the owned annual-finance state instead of leaving that logic spread across summary helpers. The diff --git a/crates/rrt-fixtures/src/schema.rs b/crates/rrt-fixtures/src/schema.rs index d88e4e7..df70eb6 100644 --- a/crates/rrt-fixtures/src/schema.rs +++ b/crates/rrt-fixtures/src/schema.rs @@ -35,6 +35,14 @@ pub struct ExpectedRuntimeSummary { #[serde(default)] pub world_restore_absolute_counter_reconstructible_from_save: Option, #[serde(default)] + pub world_restore_current_calendar_tuple_word_raw_u32: Option, + #[serde(default)] + pub world_restore_current_calendar_tuple_word_2_raw_u32: Option, + #[serde(default)] + pub world_restore_absolute_counter_raw_u32: Option, + #[serde(default)] + pub world_restore_absolute_counter_mirror_raw_u32: Option, + #[serde(default)] pub world_restore_disable_cargo_economy_special_condition_slot: Option, #[serde(default)] pub world_restore_disable_cargo_economy_special_condition_reconstructible_from_save: @@ -369,6 +377,38 @@ impl ExpectedRuntimeSummary { )); } } + if let Some(value) = self.world_restore_current_calendar_tuple_word_raw_u32 { + if actual.world_restore_current_calendar_tuple_word_raw_u32 != Some(value) { + mismatches.push(format!( + "world_restore_current_calendar_tuple_word_raw_u32 mismatch: expected {value}, got {:?}", + actual.world_restore_current_calendar_tuple_word_raw_u32 + )); + } + } + if let Some(value) = self.world_restore_current_calendar_tuple_word_2_raw_u32 { + if actual.world_restore_current_calendar_tuple_word_2_raw_u32 != Some(value) { + mismatches.push(format!( + "world_restore_current_calendar_tuple_word_2_raw_u32 mismatch: expected {value}, got {:?}", + actual.world_restore_current_calendar_tuple_word_2_raw_u32 + )); + } + } + if let Some(value) = self.world_restore_absolute_counter_raw_u32 { + if actual.world_restore_absolute_counter_raw_u32 != Some(value) { + mismatches.push(format!( + "world_restore_absolute_counter_raw_u32 mismatch: expected {value}, got {:?}", + actual.world_restore_absolute_counter_raw_u32 + )); + } + } + if let Some(value) = self.world_restore_absolute_counter_mirror_raw_u32 { + if actual.world_restore_absolute_counter_mirror_raw_u32 != Some(value) { + mismatches.push(format!( + "world_restore_absolute_counter_mirror_raw_u32 mismatch: expected {value}, got {:?}", + actual.world_restore_absolute_counter_mirror_raw_u32 + )); + } + } if let Some(slot) = self.world_restore_disable_cargo_economy_special_condition_slot { if actual.world_restore_disable_cargo_economy_special_condition_slot != Some(slot) { mismatches.push(format!( diff --git a/crates/rrt-runtime/src/import.rs b/crates/rrt-runtime/src/import.rs index 5aac836..3c6c3a0 100644 --- a/crates/rrt-runtime/src/import.rs +++ b/crates/rrt-runtime/src/import.rs @@ -527,11 +527,18 @@ fn project_save_slice_components( ); metadata.insert( "save_slice.selected_year_absolute_counter_source".to_string(), - "mode-adjusted-lane-via-0x51d390-0x409e80".to_string(), + if save_slice.world_finance_neighborhood_state.is_some() { + "save-direct-world-block-absolute-counter".to_string() + } else { + "mode-adjusted-lane-via-0x51d390-0x409e80".to_string() + }, ); metadata.insert( "save_slice.selected_year_absolute_counter_reconstructible_from_save".to_string(), - "false".to_string(), + save_slice + .world_finance_neighborhood_state + .is_some() + .to_string(), ); metadata.insert( "save_slice.disable_cargo_economy_special_condition_slot".to_string(), @@ -547,8 +554,12 @@ fn project_save_slice_components( ); metadata.insert( "save_slice.selected_year_absolute_counter_adjustment_context".to_string(), - "editor-map-mode,shell-selected-year-adjust-policy-0x9d26-0x9d28,save-special-condition-disable-cargo-economy-slot-30" - .to_string(), + if save_slice.world_finance_neighborhood_state.is_some() { + "save-direct-world-block-0x32c8".to_string() + } else { + "editor-map-mode,shell-selected-year-adjust-policy-0x9d26-0x9d28,save-special-condition-disable-cargo-economy-slot-30" + .to_string() + }, ); metadata.insert( "save_slice.mechanism_family".to_string(), @@ -726,8 +737,28 @@ fn project_save_slice_components( campaign_scenario_enabled: Some(profile.profile_byte_0xc5 != 0), sandbox_enabled: Some(profile.profile_byte_0x82 != 0), seed_tuple_written_from_raw_lane: Some(true), - absolute_counter_requires_shell_context: Some(true), - absolute_counter_reconstructible_from_save: Some(false), + absolute_counter_requires_shell_context: Some( + save_slice.world_finance_neighborhood_state.is_none(), + ), + absolute_counter_reconstructible_from_save: Some( + save_slice.world_finance_neighborhood_state.is_some(), + ), + current_calendar_tuple_word_raw_u32: save_slice + .world_finance_neighborhood_state + .as_ref() + .map(|state| state.current_calendar_tuple_word_raw_u32), + current_calendar_tuple_word_2_raw_u32: save_slice + .world_finance_neighborhood_state + .as_ref() + .map(|state| state.current_calendar_tuple_word_2_raw_u32), + absolute_counter_raw_u32: save_slice + .world_finance_neighborhood_state + .as_ref() + .map(|state| state.absolute_counter_raw_u32), + absolute_counter_mirror_raw_u32: save_slice + .world_finance_neighborhood_state + .as_ref() + .map(|state| state.absolute_counter_mirror_raw_u32), disable_cargo_economy_special_condition_slot: Some(30), disable_cargo_economy_special_condition_reconstructible_from_save: Some(true), disable_cargo_economy_special_condition_write_side_grounded: Some(true), @@ -791,11 +822,19 @@ fn project_save_slice_components( .map(|state| state.lane_value_f32_text.clone()) .unwrap_or_default(), absolute_counter_restore_kind: Some( - "mode-adjusted-selected-year-lane".to_string(), + if save_slice.world_finance_neighborhood_state.is_some() { + "save-direct-world-absolute-counter".to_string() + } else { + "mode-adjusted-selected-year-lane".to_string() + }, ), absolute_counter_adjustment_context: Some( - "editor-map-mode,shell-selected-year-adjust-policy-0x9d26-0x9d28,save-special-condition-disable-cargo-economy-slot-30" - .to_string(), + if save_slice.world_finance_neighborhood_state.is_some() { + "save-direct-world-block-0x32c8".to_string() + } else { + "editor-map-mode,shell-selected-year-adjust-policy-0x9d26-0x9d28,save-special-condition-disable-cargo-economy-slot-30" + .to_string() + }, ), } } else { @@ -5800,7 +5839,45 @@ mod tests { lane_raw_hex: vec!["0x3f400000".to_string(), "0x3be56042".to_string()], lane_value_f32_text: vec!["0.750000".to_string(), "0.007000".to_string()], }), - world_finance_neighborhood_state: None, + world_finance_neighborhood_state: Some(crate::SmpLoadedWorldFinanceNeighborhoodState { + source_kind: "save-fixed-world-block".to_string(), + semantic_family: "world-finance-neighborhood".to_string(), + current_calendar_tuple_word_raw_u32: 1, + current_calendar_tuple_word_raw_hex: "0x00000001".to_string(), + current_calendar_tuple_word_2_raw_u32: 2, + current_calendar_tuple_word_2_raw_hex: "0x00000002".to_string(), + absolute_counter_raw_u32: 3, + absolute_counter_raw_hex: "0x00000003".to_string(), + absolute_counter_mirror_raw_u32: 4, + absolute_counter_mirror_raw_hex: "0x00000004".to_string(), + labels: vec![ + "current_calendar_tuple_word".to_string(), + "current_calendar_tuple_word_2".to_string(), + "absolute_calendar_counter".to_string(), + "absolute_calendar_counter_mirror".to_string(), + ], + relative_offsets: vec![0x0d, 0x11, 0x15, 0x19], + relative_offset_hex: vec![ + "0xd".to_string(), + "0x11".to_string(), + "0x15".to_string(), + "0x19".to_string(), + ], + raw_u32: vec![1, 2, 3, 4], + raw_hex: vec![ + "0x00000001".to_string(), + "0x00000002".to_string(), + "0x00000003".to_string(), + "0x00000004".to_string(), + ], + value_i32: vec![1, 2, 3, 4], + value_f32_text: vec![ + "0.000000".to_string(), + "0.000000".to_string(), + "0.000000".to_string(), + "0.000000".to_string(), + ], + }), company_roster: None, chairman_profile_table: None, special_conditions_table: Some(crate::SmpLoadedSpecialConditionsTable { @@ -5959,14 +6036,39 @@ mod tests { .state .world_restore .absolute_counter_requires_shell_context, - Some(true) + Some(false) ); assert_eq!( import .state .world_restore .absolute_counter_reconstructible_from_save, - Some(false) + Some(true) + ); + assert_eq!( + import + .state + .world_restore + .current_calendar_tuple_word_raw_u32, + Some(1) + ); + assert_eq!( + import + .state + .world_restore + .current_calendar_tuple_word_2_raw_u32, + Some(2) + ); + assert_eq!( + import.state.world_restore.absolute_counter_raw_u32, + Some(3) + ); + assert_eq!( + import + .state + .world_restore + .absolute_counter_mirror_raw_u32, + Some(4) ); assert_eq!( import @@ -6064,7 +6166,7 @@ mod tests { .world_restore .absolute_counter_restore_kind .as_deref(), - Some("mode-adjusted-selected-year-lane") + Some("save-direct-world-absolute-counter") ); assert_eq!( import @@ -6072,9 +6174,7 @@ mod tests { .world_restore .absolute_counter_adjustment_context .as_deref(), - Some( - "editor-map-mode,shell-selected-year-adjust-policy-0x9d26-0x9d28,save-special-condition-disable-cargo-economy-slot-30" - ) + Some("save-direct-world-block-0x32c8") ); assert_eq!( import.state.save_profile.map_path.as_deref(), diff --git a/crates/rrt-runtime/src/runtime.rs b/crates/rrt-runtime/src/runtime.rs index aba6367..502e631 100644 --- a/crates/rrt-runtime/src/runtime.rs +++ b/crates/rrt-runtime/src/runtime.rs @@ -987,6 +987,14 @@ pub struct RuntimeWorldRestoreState { #[serde(default)] pub absolute_counter_reconstructible_from_save: Option, #[serde(default)] + pub current_calendar_tuple_word_raw_u32: Option, + #[serde(default)] + pub current_calendar_tuple_word_2_raw_u32: Option, + #[serde(default)] + pub absolute_counter_raw_u32: Option, + #[serde(default)] + pub absolute_counter_mirror_raw_u32: Option, + #[serde(default)] pub disable_cargo_economy_special_condition_slot: Option, #[serde(default)] pub disable_cargo_economy_special_condition_reconstructible_from_save: Option, @@ -1909,6 +1917,10 @@ pub fn runtime_world_issue_state( } } +pub fn runtime_world_absolute_counter(state: &RuntimeState) -> Option { + state.world_restore.absolute_counter_raw_u32 +} + pub fn runtime_company_unassigned_share_pool(state: &RuntimeState, company_id: u32) -> Option { let outstanding_shares = state .service_state @@ -2570,6 +2582,10 @@ mod tests { seed_tuple_written_from_raw_lane: Some(true), absolute_counter_requires_shell_context: Some(true), absolute_counter_reconstructible_from_save: Some(false), + current_calendar_tuple_word_raw_u32: None, + current_calendar_tuple_word_2_raw_u32: None, + absolute_counter_raw_u32: None, + absolute_counter_mirror_raw_u32: None, disable_cargo_economy_special_condition_slot: Some(30), disable_cargo_economy_special_condition_reconstructible_from_save: Some(true), disable_cargo_economy_special_condition_write_side_grounded: Some(true), @@ -4078,6 +4094,62 @@ mod tests { 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); + assert_eq!(runtime_world_absolute_counter(&state), None); + } + + #[test] + fn reads_grounded_world_absolute_counter_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 { + absolute_counter_raw_u32: Some(5), + absolute_counter_mirror_raw_u32: Some(5), + current_calendar_tuple_word_raw_u32: Some(0x0108_0210), + current_calendar_tuple_word_2_raw_u32: Some(0x35e6_3160), + ..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(), + }; + + assert_eq!(runtime_world_absolute_counter(&state), Some(5)); } #[test] diff --git a/crates/rrt-runtime/src/smp.rs b/crates/rrt-runtime/src/smp.rs index 530c9ea..6e056bb 100644 --- a/crates/rrt-runtime/src/smp.rs +++ b/crates/rrt-runtime/src/smp.rs @@ -97,10 +97,27 @@ const RT3_SAVE_WORLD_BLOCK_SELECTED_COMPANY_ID_RELATIVE_OFFSET: usize = 0x1d; const RT3_SAVE_WORLD_BLOCK_SELECTED_CHAIRMAN_PROFILE_ID_RELATIVE_OFFSET: usize = 0x21; const RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_MULTIPLIER_RELATIVE_OFFSET: usize = 0x25; const RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_VALUE_RELATIVE_OFFSET: usize = 0x29; -const RT3_SAVE_WORLD_BLOCK_FINANCE_NEIGHBORHOOD_CANDIDATE_FIELDS: [(&str, usize); 10] = [ - ("absolute_calendar_counter_candidate_0", 0x11), - ("absolute_calendar_counter_candidate_1", 0x15), - ("absolute_calendar_counter_candidate_2", 0x19), +const RT3_SAVE_WORLD_BLOCK_CURRENT_CALENDAR_TUPLE_WORD_RELATIVE_OFFSET: usize = 0x0d; +const RT3_SAVE_WORLD_BLOCK_CURRENT_CALENDAR_TUPLE_WORD_2_RELATIVE_OFFSET: usize = 0x11; +const RT3_SAVE_WORLD_BLOCK_ABSOLUTE_COUNTER_RELATIVE_OFFSET: usize = 0x15; +const RT3_SAVE_WORLD_BLOCK_ABSOLUTE_COUNTER_MIRROR_RELATIVE_OFFSET: usize = 0x19; +const RT3_SAVE_WORLD_BLOCK_FINANCE_NEIGHBORHOOD_CANDIDATE_FIELDS: [(&str, usize); 11] = [ + ( + "current_calendar_tuple_word", + RT3_SAVE_WORLD_BLOCK_CURRENT_CALENDAR_TUPLE_WORD_RELATIVE_OFFSET, + ), + ( + "current_calendar_tuple_word_2", + RT3_SAVE_WORLD_BLOCK_CURRENT_CALENDAR_TUPLE_WORD_2_RELATIVE_OFFSET, + ), + ( + "absolute_calendar_counter", + RT3_SAVE_WORLD_BLOCK_ABSOLUTE_COUNTER_RELATIVE_OFFSET, + ), + ( + "absolute_calendar_counter_mirror", + RT3_SAVE_WORLD_BLOCK_ABSOLUTE_COUNTER_MIRROR_RELATIVE_OFFSET, + ), ("selection_context_candidate_0", 0x1d), ("selection_context_candidate_1", 0x21), ("issue_0x37_multiplier", 0x25), @@ -109,8 +126,8 @@ const RT3_SAVE_WORLD_BLOCK_FINANCE_NEIGHBORHOOD_CANDIDATE_FIELDS: [(&str, usize) ("issue_neighbor_candidate_1", 0x31), ("issue_neighbor_candidate_2", 0x35), ]; -const RT3_SAVE_WORLD_BLOCK_FINANCE_NEIGHBORHOOD_ROOT_RELATIVE_OFFSET: usize = 0x11; -const RT3_SAVE_WORLD_BLOCK_FINANCE_NEIGHBORHOOD_WINDOW_LEN_DWORDS: usize = 16; +const RT3_SAVE_WORLD_BLOCK_FINANCE_NEIGHBORHOOD_ROOT_RELATIVE_OFFSET: usize = 0x0d; +const RT3_SAVE_WORLD_BLOCK_FINANCE_NEIGHBORHOOD_WINDOW_LEN_DWORDS: usize = 17; const RT3_SAVE_WORLD_BLOCK_CHAIRMAN_SLOT_SELECTOR_RELATIVE_OFFSET: usize = 0x83; const RT3_SAVE_WORLD_BLOCK_CAMPAIGN_OVERRIDE_FLAG_RELATIVE_OFFSET: usize = 0xc1; const RT3_SAVE_WORLD_BLOCK_CHAIRMAN_ROLE_GATE_RELATIVE_OFFSET: usize = 0x0bbf; @@ -1535,6 +1552,10 @@ pub struct SmpSaveWorldFinanceNeighborhoodProbe { pub payload_offset: usize, pub payload_len: usize, pub payload_len_hex: String, + pub current_calendar_tuple_word_lane: SmpSaveDwordCandidate, + pub current_calendar_tuple_word_2_lane: SmpSaveDwordCandidate, + pub absolute_counter_lane: SmpSaveDwordCandidate, + pub absolute_counter_mirror_lane: SmpSaveDwordCandidate, pub dword_candidates: Vec, pub evidence: Vec, } @@ -2199,6 +2220,14 @@ pub struct SmpLoadedWorldEconomicTuningState { pub struct SmpLoadedWorldFinanceNeighborhoodState { pub source_kind: String, pub semantic_family: String, + pub current_calendar_tuple_word_raw_u32: u32, + pub current_calendar_tuple_word_raw_hex: String, + pub current_calendar_tuple_word_2_raw_u32: u32, + pub current_calendar_tuple_word_2_raw_hex: String, + pub absolute_counter_raw_u32: u32, + pub absolute_counter_raw_hex: String, + pub absolute_counter_mirror_raw_u32: u32, + pub absolute_counter_mirror_raw_hex: String, pub labels: Vec, pub relative_offsets: Vec, pub relative_offset_hex: Vec, @@ -3412,6 +3441,20 @@ fn derive_loaded_world_finance_neighborhood_state_from_probe( SmpLoadedWorldFinanceNeighborhoodState { source_kind: probe.source_kind.clone(), semantic_family: probe.semantic_family.clone(), + current_calendar_tuple_word_raw_u32: probe.current_calendar_tuple_word_lane.raw_u32, + current_calendar_tuple_word_raw_hex: probe + .current_calendar_tuple_word_lane + .raw_u32_hex + .clone(), + current_calendar_tuple_word_2_raw_u32: probe.current_calendar_tuple_word_2_lane.raw_u32, + current_calendar_tuple_word_2_raw_hex: probe + .current_calendar_tuple_word_2_lane + .raw_u32_hex + .clone(), + absolute_counter_raw_u32: probe.absolute_counter_lane.raw_u32, + absolute_counter_raw_hex: probe.absolute_counter_lane.raw_u32_hex.clone(), + absolute_counter_mirror_raw_u32: probe.absolute_counter_mirror_lane.raw_u32, + absolute_counter_mirror_raw_hex: probe.absolute_counter_mirror_lane.raw_u32_hex.clone(), labels: probe .dword_candidates .iter() @@ -8808,6 +8851,30 @@ fn parse_save_world_finance_neighborhood_probe( continue; } + let current_calendar_tuple_word_lane = build_save_dword_candidate( + bytes, + payload_offset, + "current_calendar_tuple_word", + RT3_SAVE_WORLD_BLOCK_CURRENT_CALENDAR_TUPLE_WORD_RELATIVE_OFFSET, + )?; + let current_calendar_tuple_word_2_lane = build_save_dword_candidate( + bytes, + payload_offset, + "current_calendar_tuple_word_2", + RT3_SAVE_WORLD_BLOCK_CURRENT_CALENDAR_TUPLE_WORD_2_RELATIVE_OFFSET, + )?; + let absolute_counter_lane = build_save_dword_candidate( + bytes, + payload_offset, + "absolute_calendar_counter", + RT3_SAVE_WORLD_BLOCK_ABSOLUTE_COUNTER_RELATIVE_OFFSET, + )?; + let absolute_counter_mirror_lane = build_save_dword_candidate( + bytes, + payload_offset, + "absolute_calendar_counter_mirror", + RT3_SAVE_WORLD_BLOCK_ABSOLUTE_COUNTER_MIRROR_RELATIVE_OFFSET, + )?; let dword_candidates = build_save_world_finance_neighborhood_candidates(bytes, payload_offset)?; @@ -8819,6 +8886,10 @@ fn parse_save_world_finance_neighborhood_probe( payload_offset, payload_len: RT3_SAVE_WORLD_BLOCK_LEN, payload_len_hex: format!("0x{:x}", RT3_SAVE_WORLD_BLOCK_LEN), + current_calendar_tuple_word_lane, + current_calendar_tuple_word_2_lane, + absolute_counter_lane, + absolute_counter_mirror_lane, dword_candidates, evidence: vec![ format!( @@ -8827,7 +8898,13 @@ fn parse_save_world_finance_neighborhood_probe( format!( "next chunk tag 0x32c9 appears at 0x{next_chunk_tag_offset:x}, matching the documented 0x4f2c payload span" ), - "finance-neighborhood candidates cover the fixed dword strip around the grounded world absolute-calendar, selection-context, and issue-0x37 lanes so broader issue-state closure can build on one rehosted owner surface.".to_string(), + format!( + "payload +0x{:x}/+0x{:x}/+0x{:x} carry the saved world calendar tuple and absolute counter lanes that later company stock-issue cooldown readers compare against", + RT3_SAVE_WORLD_BLOCK_CURRENT_CALENDAR_TUPLE_WORD_RELATIVE_OFFSET, + RT3_SAVE_WORLD_BLOCK_CURRENT_CALENDAR_TUPLE_WORD_2_RELATIVE_OFFSET, + RT3_SAVE_WORLD_BLOCK_ABSOLUTE_COUNTER_RELATIVE_OFFSET + ), + "finance-neighborhood candidates cover the fixed dword strip around the grounded world calendar tuple, absolute-counter, selection-context, and issue-0x37 lanes so broader finance reader closure can build on one rehosted owner surface.".to_string(), ], }); } @@ -15575,32 +15652,31 @@ mod tests { probe.dword_candidates.len(), RT3_SAVE_WORLD_BLOCK_FINANCE_NEIGHBORHOOD_WINDOW_LEN_DWORDS ); - assert_eq!( - probe.dword_candidates[0].label, - "absolute_calendar_counter_candidate_0" - ); - assert_eq!(probe.dword_candidates[0].relative_offset_hex, "0x11"); + assert_eq!(probe.current_calendar_tuple_word_lane.relative_offset_hex, "0xd"); + assert_eq!(probe.current_calendar_tuple_word_lane.value_i32, 1); + assert_eq!(probe.current_calendar_tuple_word_2_lane.relative_offset_hex, "0x11"); + assert_eq!(probe.current_calendar_tuple_word_2_lane.value_i32, 2); + assert_eq!(probe.absolute_counter_lane.relative_offset_hex, "0x15"); + assert_eq!(probe.absolute_counter_lane.value_i32, 3); + assert_eq!(probe.absolute_counter_mirror_lane.relative_offset_hex, "0x19"); + assert_eq!(probe.absolute_counter_mirror_lane.value_i32, 4); + assert_eq!(probe.dword_candidates[0].label, "current_calendar_tuple_word"); + assert_eq!(probe.dword_candidates[0].relative_offset_hex, "0xd"); assert_eq!(probe.dword_candidates[0].value_i32, 1); - assert_eq!(probe.dword_candidates[5].label, "issue_0x37_multiplier"); - assert_eq!(probe.dword_candidates[5].relative_offset_hex, "0x25"); - assert_eq!( - probe.dword_candidates[9].label, - "issue_neighbor_candidate_2" - ); - assert_eq!(probe.dword_candidates[9].relative_offset_hex, "0x35"); - assert_eq!(probe.dword_candidates[9].value_i32, 10); - assert_eq!( - probe.dword_candidates[10].label, - "finance_neighborhood_word_11" - ); - assert_eq!(probe.dword_candidates[10].relative_offset_hex, "0x39"); + assert_eq!(probe.dword_candidates[6].label, "issue_0x37_multiplier"); + assert_eq!(probe.dword_candidates[6].relative_offset_hex, "0x25"); + assert_eq!(probe.dword_candidates[10].label, "issue_neighbor_candidate_2"); + assert_eq!(probe.dword_candidates[10].relative_offset_hex, "0x35"); assert_eq!(probe.dword_candidates[10].value_i32, 11); + assert_eq!(probe.dword_candidates[11].label, "finance_neighborhood_word_12"); + assert_eq!(probe.dword_candidates[11].relative_offset_hex, "0x39"); + assert_eq!(probe.dword_candidates[11].value_i32, 12); assert_eq!( - probe.dword_candidates[15].label, - "finance_neighborhood_word_16" + probe.dword_candidates[16].label, + "finance_neighborhood_word_17" ); - assert_eq!(probe.dword_candidates[15].relative_offset_hex, "0x4d"); - assert_eq!(probe.dword_candidates[15].value_i32, 16); + assert_eq!(probe.dword_candidates[16].relative_offset_hex, "0x4d"); + assert_eq!(probe.dword_candidates[16].value_i32, 17); } #[test] diff --git a/crates/rrt-runtime/src/summary.rs b/crates/rrt-runtime/src/summary.rs index 5a8d78d..d0ef968 100644 --- a/crates/rrt-runtime/src/summary.rs +++ b/crates/rrt-runtime/src/summary.rs @@ -21,6 +21,10 @@ pub struct RuntimeSummary { pub world_restore_seed_tuple_written_from_raw_lane: Option, pub world_restore_absolute_counter_requires_shell_context: Option, pub world_restore_absolute_counter_reconstructible_from_save: Option, + pub world_restore_current_calendar_tuple_word_raw_u32: Option, + pub world_restore_current_calendar_tuple_word_2_raw_u32: Option, + pub world_restore_absolute_counter_raw_u32: Option, + pub world_restore_absolute_counter_mirror_raw_u32: Option, pub world_restore_disable_cargo_economy_special_condition_slot: Option, pub world_restore_disable_cargo_economy_special_condition_reconstructible_from_save: Option, @@ -178,6 +182,18 @@ impl RuntimeSummary { world_restore_absolute_counter_reconstructible_from_save: state .world_restore .absolute_counter_reconstructible_from_save, + world_restore_current_calendar_tuple_word_raw_u32: state + .world_restore + .current_calendar_tuple_word_raw_u32, + world_restore_current_calendar_tuple_word_2_raw_u32: state + .world_restore + .current_calendar_tuple_word_2_raw_u32, + world_restore_absolute_counter_raw_u32: state + .world_restore + .absolute_counter_raw_u32, + world_restore_absolute_counter_mirror_raw_u32: state + .world_restore + .absolute_counter_mirror_raw_u32, world_restore_disable_cargo_economy_special_condition_slot: state .world_restore .disable_cargo_economy_special_condition_slot, @@ -1142,6 +1158,10 @@ mod tests { world_flags: BTreeMap::new(), save_profile: RuntimeSaveProfileState::default(), world_restore: RuntimeWorldRestoreState { + current_calendar_tuple_word_raw_u32: Some(0x0108_0210), + current_calendar_tuple_word_2_raw_u32: Some(0x35e6_3160), + absolute_counter_raw_u32: Some(5), + absolute_counter_mirror_raw_u32: Some(5), issue_37_value: Some(3), issue_37_multiplier_raw_u32: Some(0x3d75c28f), issue_37_multiplier_value_f32_text: Some("0.06".to_string()), @@ -1206,6 +1226,19 @@ mod tests { let summary = RuntimeSummary::from_state(&state); + assert_eq!( + summary.world_restore_current_calendar_tuple_word_raw_u32, + Some(0x0108_0210) + ); + assert_eq!( + summary.world_restore_current_calendar_tuple_word_2_raw_u32, + Some(0x35e6_3160) + ); + assert_eq!(summary.world_restore_absolute_counter_raw_u32, Some(5)); + assert_eq!( + summary.world_restore_absolute_counter_mirror_raw_u32, + Some(5) + ); assert_eq!(summary.world_restore_issue_37_value, Some(3)); assert_eq!( summary.world_restore_issue_37_multiplier_raw_u32, diff --git a/docs/README.md b/docs/README.md index b391893..1b20b29 100644 --- a/docs/README.md +++ b/docs/README.md @@ -108,7 +108,7 @@ The highest-value next passes are now: `runtime inspect-save-company-chairman ` now dumps those remaining raw record candidates directly from the rehosted parser, including fixed-world chairman slot / role-gate context, the grounded fixed-world issue-`0x37` pair, the fixed-dword world finance - neighborhood around the absolute-calendar and issue lanes, the separate six-float economic + neighborhood rooted at `[world+0x0d]` with the saved calendar tuple and absolute counter, the separate six-float economic tuning band, derived holdings-at-share-price and cached purchasing-power totals, context, company dword candidate windows, and richer chairman qword cache views; the current rehosted company-side owner state now also includes a typed market/cache map carrying saved @@ -122,8 +122,9 @@ The highest-value next passes are now: unassigned share pool derived from outstanding shares minus chairman-held shares for later annual 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 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 + finance neighborhood is now widened to 17 dwords rooted at `[world+0x0d]` so later issue-family + closure can target a broader owned restore-state window, and the saved absolute counter now + flows through normal runtime restore state instead of staying on shell-context metadata; the same annual-finance state now also feeds a shared company market reader for stock-capital, salary, bonus, and the full two-word current/prior issue-calendar tuples, and now derives elapsed years since founding, last dividend, and last bankruptcy for later annual finance-policy rehosting; live bond-slot count now travels through that same owned annual-finance diff --git a/docs/runtime-rehost-plan.md b/docs/runtime-rehost-plan.md index c53bfc7..aa7e355 100644 --- a/docs/runtime-rehost-plan.md +++ b/docs/runtime-rehost-plan.md @@ -209,9 +209,11 @@ chairman-held shares, so later dividend / stock-capital work can extend a shared 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. The fixed-world finance neighborhood is now widened to 16 dwords -rooted at `[world+0x11]`, so later issue-`0x38/0x39` closure can build on a broader owned -restore-state window instead of another narrow probe. The same owned company annual-finance state +scattered single-field helpers. The fixed-world finance neighborhood is now widened to 17 dwords +rooted at `[world+0x0d]`, so later issue-`0x38/0x39` closure can build on a broader owned +restore-state window instead of another narrow probe; that same owner surface now also carries +the saved world absolute counter as first-class runtime restore state instead of shell-context +metadata. The same owned company annual-finance state now also drives a shared company market reader seam for stock-capital, salary, bonus, and the full two-word current/prior issue-calendar tuples, which is a better base for shellless finance simulation than summary-only helpers. That same owned annual-finance state now also derives elapsed