diff --git a/README.md b/README.md index 6e4e06e..de08f31 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,9 @@ The fixed-world finance neighborhood itself is now widened to 17 dwords rooted a so future issue-`0x38/0x39` closure can build on a broader owned restore-state window rather than 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 same save-world owner surface now also carries the packed year word and partial-year progress +lane behind the annual-finance recent-history weighting path, so later finance readers can attach +to real world-calendar state instead of candidate bytes. 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 diff --git a/crates/rrt-fixtures/src/schema.rs b/crates/rrt-fixtures/src/schema.rs index df70eb6..7174fa1 100644 --- a/crates/rrt-fixtures/src/schema.rs +++ b/crates/rrt-fixtures/src/schema.rs @@ -35,6 +35,10 @@ pub struct ExpectedRuntimeSummary { #[serde(default)] pub world_restore_absolute_counter_reconstructible_from_save: Option, #[serde(default)] + pub world_restore_packed_year_word_raw_u16: Option, + #[serde(default)] + pub world_restore_partial_year_progress_raw_u8: 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, @@ -130,6 +134,8 @@ pub struct ExpectedRuntimeSummary { #[serde(default)] pub selected_company_years_since_last_dividend: Option, #[serde(default)] + pub selected_company_current_partial_year_weight_numerator: Option, + #[serde(default)] pub selected_company_chairman_bonus_year: Option, #[serde(default)] pub selected_company_chairman_bonus_amount: Option, @@ -377,6 +383,22 @@ impl ExpectedRuntimeSummary { )); } } + if let Some(value) = self.world_restore_packed_year_word_raw_u16 { + if actual.world_restore_packed_year_word_raw_u16 != Some(value) { + mismatches.push(format!( + "world_restore_packed_year_word_raw_u16 mismatch: expected {value}, got {:?}", + actual.world_restore_packed_year_word_raw_u16 + )); + } + } + if let Some(value) = self.world_restore_partial_year_progress_raw_u8 { + if actual.world_restore_partial_year_progress_raw_u8 != Some(value) { + mismatches.push(format!( + "world_restore_partial_year_progress_raw_u8 mismatch: expected {value}, got {:?}", + actual.world_restore_partial_year_progress_raw_u8 + )); + } + } 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!( @@ -783,6 +805,14 @@ impl ExpectedRuntimeSummary { )); } } + if let Some(value) = self.selected_company_current_partial_year_weight_numerator { + if actual.selected_company_current_partial_year_weight_numerator != Some(value) { + mismatches.push(format!( + "selected_company_current_partial_year_weight_numerator mismatch: expected {value}, got {:?}", + actual.selected_company_current_partial_year_weight_numerator + )); + } + } if let Some(value) = self.selected_company_chairman_bonus_year { if actual.selected_company_chairman_bonus_year != Some(value) { mismatches.push(format!( diff --git a/crates/rrt-runtime/src/import.rs b/crates/rrt-runtime/src/import.rs index 3c6c3a0..c7a2174 100644 --- a/crates/rrt-runtime/src/import.rs +++ b/crates/rrt-runtime/src/import.rs @@ -743,6 +743,14 @@ fn project_save_slice_components( absolute_counter_reconstructible_from_save: Some( save_slice.world_finance_neighborhood_state.is_some(), ), + packed_year_word_raw_u16: save_slice + .world_finance_neighborhood_state + .as_ref() + .map(|state| state.packed_year_word_raw_u16), + partial_year_progress_raw_u8: save_slice + .world_finance_neighborhood_state + .as_ref() + .map(|state| state.partial_year_progress_raw_u8), current_calendar_tuple_word_raw_u32: save_slice .world_finance_neighborhood_state .as_ref() @@ -5842,6 +5850,10 @@ mod tests { world_finance_neighborhood_state: Some(crate::SmpLoadedWorldFinanceNeighborhoodState { source_kind: "save-fixed-world-block".to_string(), semantic_family: "world-finance-neighborhood".to_string(), + packed_year_word_raw_u16: 0x0201, + packed_year_word_raw_hex: "0x0201".to_string(), + partial_year_progress_raw_u8: 3, + partial_year_progress_raw_hex: "0x03".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, @@ -6045,6 +6057,14 @@ mod tests { .absolute_counter_reconstructible_from_save, Some(true) ); + assert_eq!( + import.state.world_restore.packed_year_word_raw_u16, + Some(0x0201) + ); + assert_eq!( + import.state.world_restore.partial_year_progress_raw_u8, + Some(3) + ); assert_eq!( import .state diff --git a/crates/rrt-runtime/src/runtime.rs b/crates/rrt-runtime/src/runtime.rs index 502e631..a51c925 100644 --- a/crates/rrt-runtime/src/runtime.rs +++ b/crates/rrt-runtime/src/runtime.rs @@ -128,6 +128,8 @@ pub struct RuntimeCompanyAnnualFinanceState { pub years_since_last_bankruptcy: Option, #[serde(default)] pub years_since_last_dividend: Option, + #[serde(default)] + pub current_partial_year_weight_numerator: Option, pub current_issue_calendar_word: u32, #[serde(default)] pub current_issue_calendar_word_2: u32, @@ -989,6 +991,10 @@ pub struct RuntimeWorldRestoreState { #[serde(default)] pub current_calendar_tuple_word_raw_u32: Option, #[serde(default)] + pub packed_year_word_raw_u16: Option, + #[serde(default)] + pub partial_year_progress_raw_u8: Option, + #[serde(default)] pub current_calendar_tuple_word_2_raw_u32: Option, #[serde(default)] pub absolute_counter_raw_u32: Option, @@ -1921,6 +1927,10 @@ pub fn runtime_world_absolute_counter(state: &RuntimeState) -> Option { state.world_restore.absolute_counter_raw_u32 } +pub fn runtime_world_partial_year_weight_numerator(state: &RuntimeState) -> Option { + Some(i64::from(state.world_restore.partial_year_progress_raw_u8?) * 5 - 5) +} + pub fn runtime_company_unassigned_share_pool(state: &RuntimeState, company_id: u32) -> Option { let outstanding_shares = state .service_state @@ -1976,6 +1986,7 @@ pub fn runtime_company_annual_finance_state( years_since_founding, years_since_last_bankruptcy, years_since_last_dividend, + current_partial_year_weight_numerator: runtime_world_partial_year_weight_numerator(state), current_issue_calendar_word: market_state.current_issue_calendar_word, current_issue_calendar_word_2: market_state.current_issue_calendar_word_2, prior_issue_calendar_word: market_state.prior_issue_calendar_word, @@ -2582,6 +2593,8 @@ mod tests { seed_tuple_written_from_raw_lane: Some(true), absolute_counter_requires_shell_context: Some(true), absolute_counter_reconstructible_from_save: Some(false), + packed_year_word_raw_u16: None, + partial_year_progress_raw_u8: None, current_calendar_tuple_word_raw_u32: None, current_calendar_tuple_word_2_raw_u32: None, absolute_counter_raw_u32: None, @@ -4111,6 +4124,8 @@ mod tests { world_restore: RuntimeWorldRestoreState { absolute_counter_raw_u32: Some(5), absolute_counter_mirror_raw_u32: Some(5), + packed_year_word_raw_u16: Some(0x0210), + partial_year_progress_raw_u8: Some(8), current_calendar_tuple_word_raw_u32: Some(0x0108_0210), current_calendar_tuple_word_2_raw_u32: Some(0x35e6_3160), ..RuntimeWorldRestoreState::default() @@ -4150,6 +4165,7 @@ mod tests { }; assert_eq!(runtime_world_absolute_counter(&state), Some(5)); + assert_eq!(runtime_world_partial_year_weight_numerator(&state), Some(35)); } #[test] @@ -4386,6 +4402,7 @@ mod tests { years_since_founding: None, years_since_last_bankruptcy: None, years_since_last_dividend: None, + current_partial_year_weight_numerator: None, current_issue_calendar_word: 5, current_issue_calendar_word_2: 6, prior_issue_calendar_word: 4, diff --git a/crates/rrt-runtime/src/smp.rs b/crates/rrt-runtime/src/smp.rs index 6e056bb..eca8336 100644 --- a/crates/rrt-runtime/src/smp.rs +++ b/crates/rrt-runtime/src/smp.rs @@ -1552,6 +1552,10 @@ pub struct SmpSaveWorldFinanceNeighborhoodProbe { pub payload_offset: usize, pub payload_len: usize, pub payload_len_hex: String, + pub packed_year_word_raw_u16: u16, + pub packed_year_word_raw_hex: String, + pub partial_year_progress_raw_u8: u8, + pub partial_year_progress_raw_hex: String, pub current_calendar_tuple_word_lane: SmpSaveDwordCandidate, pub current_calendar_tuple_word_2_lane: SmpSaveDwordCandidate, pub absolute_counter_lane: SmpSaveDwordCandidate, @@ -2220,6 +2224,10 @@ pub struct SmpLoadedWorldEconomicTuningState { pub struct SmpLoadedWorldFinanceNeighborhoodState { pub source_kind: String, pub semantic_family: String, + pub packed_year_word_raw_u16: u16, + pub packed_year_word_raw_hex: String, + pub partial_year_progress_raw_u8: u8, + pub partial_year_progress_raw_hex: 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, @@ -3441,6 +3449,10 @@ fn derive_loaded_world_finance_neighborhood_state_from_probe( SmpLoadedWorldFinanceNeighborhoodState { source_kind: probe.source_kind.clone(), semantic_family: probe.semantic_family.clone(), + packed_year_word_raw_u16: probe.packed_year_word_raw_u16, + packed_year_word_raw_hex: probe.packed_year_word_raw_hex.clone(), + partial_year_progress_raw_u8: probe.partial_year_progress_raw_u8, + partial_year_progress_raw_hex: probe.partial_year_progress_raw_hex.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 @@ -8857,6 +8869,14 @@ fn parse_save_world_finance_neighborhood_probe( "current_calendar_tuple_word", RT3_SAVE_WORLD_BLOCK_CURRENT_CALENDAR_TUPLE_WORD_RELATIVE_OFFSET, )?; + let packed_year_word_raw_u16 = read_u16_at( + bytes, + payload_offset + RT3_SAVE_WORLD_BLOCK_CURRENT_CALENDAR_TUPLE_WORD_RELATIVE_OFFSET, + )?; + let partial_year_progress_raw_u8 = read_u8_at( + bytes, + payload_offset + RT3_SAVE_WORLD_BLOCK_CURRENT_CALENDAR_TUPLE_WORD_RELATIVE_OFFSET + 2, + )?; let current_calendar_tuple_word_2_lane = build_save_dword_candidate( bytes, payload_offset, @@ -8886,6 +8906,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), + packed_year_word_raw_u16, + packed_year_word_raw_hex: format!("0x{packed_year_word_raw_u16:04x}"), + partial_year_progress_raw_u8, + partial_year_progress_raw_hex: format!("0x{partial_year_progress_raw_u8:02x}"), current_calendar_tuple_word_lane, current_calendar_tuple_word_2_lane, absolute_counter_lane, @@ -15653,6 +15677,10 @@ mod tests { RT3_SAVE_WORLD_BLOCK_FINANCE_NEIGHBORHOOD_WINDOW_LEN_DWORDS ); assert_eq!(probe.current_calendar_tuple_word_lane.relative_offset_hex, "0xd"); + assert_eq!(probe.packed_year_word_raw_u16, 1); + assert_eq!(probe.packed_year_word_raw_hex, "0x0001"); + assert_eq!(probe.partial_year_progress_raw_u8, 0); + assert_eq!(probe.partial_year_progress_raw_hex, "0x00"); 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); diff --git a/crates/rrt-runtime/src/summary.rs b/crates/rrt-runtime/src/summary.rs index d0ef968..8c663f0 100644 --- a/crates/rrt-runtime/src/summary.rs +++ b/crates/rrt-runtime/src/summary.rs @@ -21,6 +21,8 @@ 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_packed_year_word_raw_u16: Option, + pub world_restore_partial_year_progress_raw_u8: 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, @@ -69,6 +71,7 @@ pub struct RuntimeSummary { pub selected_company_years_since_founding: Option, pub selected_company_years_since_last_bankruptcy: Option, pub selected_company_years_since_last_dividend: Option, + pub selected_company_current_partial_year_weight_numerator: Option, pub selected_company_chairman_bonus_year: Option, pub selected_company_chairman_bonus_amount: Option, pub player_count: usize, @@ -182,6 +185,12 @@ impl RuntimeSummary { world_restore_absolute_counter_reconstructible_from_save: state .world_restore .absolute_counter_reconstructible_from_save, + world_restore_packed_year_word_raw_u16: state + .world_restore + .packed_year_word_raw_u16, + world_restore_partial_year_progress_raw_u8: state + .world_restore + .partial_year_progress_raw_u8, world_restore_current_calendar_tuple_word_raw_u32: state .world_restore .current_calendar_tuple_word_raw_u32, @@ -319,6 +328,9 @@ impl RuntimeSummary { 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_current_partial_year_weight_numerator: selected_company_annual_finance_state + .as_ref() + .and_then(|finance_state| finance_state.current_partial_year_weight_numerator), selected_company_chairman_bonus_year: selected_company_market_state .map(|market_state| market_state.chairman_bonus_year) .filter(|year| *year != 0), @@ -1158,6 +1170,8 @@ mod tests { world_flags: BTreeMap::new(), save_profile: RuntimeSaveProfileState::default(), world_restore: RuntimeWorldRestoreState { + packed_year_word_raw_u16: Some(0x0201), + partial_year_progress_raw_u8: Some(3), 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), @@ -1226,6 +1240,8 @@ mod tests { let summary = RuntimeSummary::from_state(&state); + assert_eq!(summary.world_restore_packed_year_word_raw_u16, Some(0x0201)); + assert_eq!(summary.world_restore_partial_year_progress_raw_u8, Some(3)); assert_eq!( summary.world_restore_current_calendar_tuple_word_raw_u32, Some(0x0108_0210) diff --git a/docs/README.md b/docs/README.md index 1b20b29..d4a44e1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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 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 + flows through normal runtime restore state instead of staying on shell-context metadata; that + same world owner surface now also carries the packed year word and partial-year progress lane + behind annual-finance recent-history weighting; 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 aa7e355..058999f 100644 --- a/docs/runtime-rehost-plan.md +++ b/docs/runtime-rehost-plan.md @@ -213,7 +213,8 @@ scattered single-field helpers. The fixed-world finance neighborhood is now wide 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 +metadata, plus the packed year word and partial-year progress lane that feed the annual-finance +recent-history weighting path. 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