From 9169d307a6d97c8298f5447c659193775a1ef3c2 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Fri, 17 Apr 2026 22:20:54 -0700 Subject: [PATCH] Rehost company issue calendar packer --- crates/rrt-fixtures/src/schema.rs | 30 ++++++++ crates/rrt-runtime/src/runtime.rs | 119 ++++++++++++++++++++++++++++++ crates/rrt-runtime/src/summary.rs | 13 ++++ 3 files changed, 162 insertions(+) diff --git a/crates/rrt-fixtures/src/schema.rs b/crates/rrt-fixtures/src/schema.rs index bc622f0..964ab95 100644 --- a/crates/rrt-fixtures/src/schema.rs +++ b/crates/rrt-fixtures/src/schema.rs @@ -142,6 +142,12 @@ pub struct ExpectedRuntimeSummary { #[serde(default)] pub selected_company_current_partial_year_weight_numerator: Option, #[serde(default)] + pub selected_company_current_issue_absolute_counter: Option, + #[serde(default)] + pub selected_company_prior_issue_absolute_counter: Option, + #[serde(default)] + pub selected_company_current_issue_age_absolute_counter_delta: Option, + #[serde(default)] pub selected_company_chairman_bonus_year: Option, #[serde(default)] pub selected_company_chairman_bonus_amount: Option, @@ -843,6 +849,30 @@ impl ExpectedRuntimeSummary { )); } } + if let Some(value) = self.selected_company_current_issue_absolute_counter { + if actual.selected_company_current_issue_absolute_counter != Some(value) { + mismatches.push(format!( + "selected_company_current_issue_absolute_counter mismatch: expected {value}, got {:?}", + actual.selected_company_current_issue_absolute_counter + )); + } + } + if let Some(value) = self.selected_company_prior_issue_absolute_counter { + if actual.selected_company_prior_issue_absolute_counter != Some(value) { + mismatches.push(format!( + "selected_company_prior_issue_absolute_counter mismatch: expected {value}, got {:?}", + actual.selected_company_prior_issue_absolute_counter + )); + } + } + if let Some(value) = self.selected_company_current_issue_age_absolute_counter_delta { + if actual.selected_company_current_issue_age_absolute_counter_delta != Some(value) { + mismatches.push(format!( + "selected_company_current_issue_age_absolute_counter_delta mismatch: expected {value}, got {:?}", + actual.selected_company_current_issue_age_absolute_counter_delta + )); + } + } 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/runtime.rs b/crates/rrt-runtime/src/runtime.rs index 31480a8..b539471 100644 --- a/crates/rrt-runtime/src/runtime.rs +++ b/crates/rrt-runtime/src/runtime.rs @@ -130,6 +130,12 @@ pub struct RuntimeCompanyAnnualFinanceState { pub years_since_last_dividend: Option, #[serde(default)] pub current_partial_year_weight_numerator: Option, + #[serde(default)] + pub current_issue_absolute_counter: Option, + #[serde(default)] + pub prior_issue_absolute_counter: Option, + #[serde(default)] + pub current_issue_age_absolute_counter_delta: Option, pub current_issue_calendar_word: u32, #[serde(default)] pub current_issue_calendar_word_2: u32, @@ -421,6 +427,17 @@ pub struct RuntimeWorldIssueState { pub multiplier_value_f32_text: Option, } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct RuntimePackedCalendarTuple { + pub year_word: u16, + pub month_1_based: u8, + pub week_1_based: u8, + pub day_1_based: u8, + pub hour_0_based: u8, + pub quarter_day_1_based: u8, + pub minute_0_based: u8, +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum RuntimeTerritoryMetric { @@ -1960,6 +1977,49 @@ pub fn runtime_world_partial_year_weight_numerator(state: &RuntimeState) -> Opti Some(i64::from(state.world_restore.partial_year_progress_raw_u8?) * 5 - 5) } +pub fn runtime_decode_packed_calendar_tuple( + word_0: u32, + word_1: u32, +) -> RuntimePackedCalendarTuple { + let bytes_0 = word_0.to_le_bytes(); + let bytes_1 = word_1.to_le_bytes(); + RuntimePackedCalendarTuple { + year_word: u16::from_le_bytes([bytes_0[0], bytes_0[1]]), + month_1_based: bytes_0[2], + week_1_based: bytes_0[3], + day_1_based: bytes_1[0], + hour_0_based: bytes_1[1], + quarter_day_1_based: bytes_1[2], + minute_0_based: bytes_1[3], + } +} + +pub fn runtime_pack_packed_calendar_tuple_to_absolute_counter( + tuple: RuntimePackedCalendarTuple, +) -> Option { + if !(1..=12).contains(&tuple.month_1_based) { + return None; + } + if !(1..=28).contains(&tuple.day_1_based) { + return None; + } + if tuple.hour_0_based >= 24 { + return None; + } + if tuple.minute_0_based >= 60 { + return None; + } + + let year = u64::from(tuple.year_word); + let month = u64::from(tuple.month_1_based); + let day = u64::from(tuple.day_1_based); + let hour = u64::from(tuple.hour_0_based); + let minute = u64::from(tuple.minute_0_based); + let absolute_counter = + ((((year * 12 + month) * 28 + day).checked_sub(29)? * 24 + hour) * 60) + minute; + u32::try_from(absolute_counter).ok() +} + pub fn runtime_company_unassigned_share_pool(state: &RuntimeState, company_id: u32) -> Option { let outstanding_shares = state .service_state @@ -1996,6 +2056,25 @@ pub fn runtime_company_annual_finance_state( ); let years_since_last_dividend = derive_runtime_company_elapsed_years(state.calendar.year, market_state.last_dividend_year); + let current_issue_absolute_counter = runtime_pack_packed_calendar_tuple_to_absolute_counter( + runtime_decode_packed_calendar_tuple( + market_state.current_issue_calendar_word, + market_state.current_issue_calendar_word_2, + ), + ); + let prior_issue_absolute_counter = runtime_pack_packed_calendar_tuple_to_absolute_counter( + runtime_decode_packed_calendar_tuple( + market_state.prior_issue_calendar_word, + market_state.prior_issue_calendar_word_2, + ), + ); + let current_issue_age_absolute_counter_delta = + match (runtime_world_absolute_counter(state), current_issue_absolute_counter) { + (Some(world_counter), Some(issue_counter)) if world_counter >= issue_counter => { + Some(i64::from(world_counter - issue_counter)) + } + _ => None, + }; Some(RuntimeCompanyAnnualFinanceState { company_id, outstanding_shares: market_state.outstanding_shares, @@ -2016,6 +2095,9 @@ pub fn runtime_company_annual_finance_state( years_since_last_bankruptcy, years_since_last_dividend, current_partial_year_weight_numerator: runtime_world_partial_year_weight_numerator(state), + current_issue_absolute_counter, + prior_issue_absolute_counter, + current_issue_age_absolute_counter_delta, 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, @@ -4221,6 +4303,40 @@ mod tests { assert_eq!(runtime_world_partial_year_weight_numerator(&state), Some(35)); } + #[test] + fn decodes_and_packs_company_issue_calendar_tuple() { + let tuple = + runtime_decode_packed_calendar_tuple(0x0101_0726, 0x0001_0001); + assert_eq!( + tuple, + RuntimePackedCalendarTuple { + year_word: 0x0726, + month_1_based: 1, + week_1_based: 1, + day_1_based: 1, + hour_0_based: 0, + quarter_day_1_based: 1, + minute_0_based: 0, + } + ); + assert_eq!( + runtime_pack_packed_calendar_tuple_to_absolute_counter(tuple), + Some(885_427_200) + ); + assert_eq!( + runtime_pack_packed_calendar_tuple_to_absolute_counter(RuntimePackedCalendarTuple { + year_word: 1830, + month_1_based: 13, + week_1_based: 1, + day_1_based: 1, + hour_0_based: 0, + quarter_day_1_based: 1, + minute_0_based: 0, + }), + None + ); + } + #[test] fn derives_company_unassigned_share_pool_from_market_state_and_holdings() { let state = RuntimeState { @@ -4456,6 +4572,9 @@ mod tests { years_since_last_bankruptcy: None, years_since_last_dividend: None, current_partial_year_weight_numerator: None, + current_issue_absolute_counter: None, + prior_issue_absolute_counter: None, + current_issue_age_absolute_counter_delta: None, current_issue_calendar_word: 5, current_issue_calendar_word_2: 6, prior_issue_calendar_word: 4, diff --git a/crates/rrt-runtime/src/summary.rs b/crates/rrt-runtime/src/summary.rs index 65951f7..3c713ad 100644 --- a/crates/rrt-runtime/src/summary.rs +++ b/crates/rrt-runtime/src/summary.rs @@ -75,6 +75,9 @@ pub struct RuntimeSummary { 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_current_issue_absolute_counter: Option, + pub selected_company_prior_issue_absolute_counter: Option, + pub selected_company_current_issue_age_absolute_counter_delta: Option, pub selected_company_chairman_bonus_year: Option, pub selected_company_chairman_bonus_amount: Option, pub player_count: usize, @@ -337,6 +340,16 @@ impl RuntimeSummary { 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_current_issue_absolute_counter: selected_company_annual_finance_state + .as_ref() + .and_then(|finance_state| finance_state.current_issue_absolute_counter), + selected_company_prior_issue_absolute_counter: selected_company_annual_finance_state + .as_ref() + .and_then(|finance_state| finance_state.prior_issue_absolute_counter), + selected_company_current_issue_age_absolute_counter_delta: + selected_company_annual_finance_state + .as_ref() + .and_then(|finance_state| finance_state.current_issue_age_absolute_counter_delta), selected_company_chairman_bonus_year: selected_company_market_state .map(|market_state| market_state.chairman_bonus_year) .filter(|year| *year != 0),