From b1c6dad7231889f7a39a5357d56a925e299144e2 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sat, 18 Apr 2026 07:23:07 -0700 Subject: [PATCH] Rehost company periodic side-latch state --- crates/rrt-runtime/src/import.rs | 39 ++++++++ crates/rrt-runtime/src/lib.rs | 24 ++--- crates/rrt-runtime/src/runtime.rs | 142 +++++++++++++++++++++++++++++- crates/rrt-runtime/src/summary.rs | 39 ++++++++ docs/rehost-queue.md | 10 ++- 5 files changed, 238 insertions(+), 16 deletions(-) diff --git a/crates/rrt-runtime/src/import.rs b/crates/rrt-runtime/src/import.rs index 8800b58..611b885 100644 --- a/crates/rrt-runtime/src/import.rs +++ b/crates/rrt-runtime/src/import.rs @@ -101,6 +101,7 @@ struct SaveSliceProjection { has_company_selection_override: bool, selected_company_id: Option, company_market_state: BTreeMap, + company_periodic_side_latch_state: BTreeMap, has_company_market_projection: bool, world_issue_opinion_base_terms_raw_i32: Vec, chairman_profiles: Vec, @@ -318,6 +319,7 @@ pub fn project_save_slice_to_runtime_state_import( world_issue_opinion_base_terms_raw_i32: projection .world_issue_opinion_base_terms_raw_i32, company_market_state: projection.company_market_state, + company_periodic_side_latch_state: projection.company_periodic_side_latch_state, chairman_issue_opinion_terms_raw_i32: projection.chairman_issue_opinion_terms_raw_i32, chairman_personality_raw_u8: projection.chairman_personality_raw_u8, ..RuntimeServiceState::default() @@ -443,6 +445,14 @@ pub fn project_save_slice_overlay_to_runtime_state_import( } else { base_state.service_state.company_market_state.clone() }, + company_periodic_side_latch_state: if projection.has_company_market_projection { + projection.company_periodic_side_latch_state + } else { + base_state + .service_state + .company_periodic_side_latch_state + .clone() + }, chairman_issue_opinion_terms_raw_i32: if projection.has_chairman_projection { projection.chairman_issue_opinion_terms_raw_i32 } else { @@ -1192,6 +1202,7 @@ fn project_save_slice_components( has_company_selection_override, selected_company_id, company_market_state, + company_periodic_side_latch_state, world_issue_opinion_base_terms_raw_i32, has_company_market_projection, ) = if let Some(roster) = &save_slice.company_roster { @@ -1217,6 +1228,29 @@ fn project_save_slice_components( .map(|state| (entry.company_id, state.clone())) }) .collect::>(); + let periodic_side_latch_state = roster + .entries + .iter() + .map(|entry| { + ( + entry.company_id, + crate::RuntimeCompanyPeriodicSideLatchState { + preferred_locomotive_engine_type_raw_u8: entry + .preferred_locomotive_engine_type_raw_u8, + city_connection_latch: entry + .market_state + .as_ref() + .map(|state| state.city_connection_latch) + .unwrap_or(false), + linked_transit_latch: entry + .market_state + .as_ref() + .map(|state| state.linked_transit_latch) + .unwrap_or(false), + }, + ) + }) + .collect::>(); metadata.insert( "save_slice.company_market_state_owner_count".to_string(), market_state.len().to_string(), @@ -1234,6 +1268,7 @@ fn project_save_slice_components( roster.selected_company_id.is_some(), roster.selected_company_id, BTreeMap::new(), + periodic_side_latch_state, save_slice .world_issue_37_state .as_ref() @@ -1268,6 +1303,7 @@ fn project_save_slice_components( roster.selected_company_id.is_some(), roster.selected_company_id, market_state, + periodic_side_latch_state, save_slice .world_issue_37_state .as_ref() @@ -1283,6 +1319,7 @@ fn project_save_slice_components( false, None, BTreeMap::new(), + BTreeMap::new(), save_slice .world_issue_37_state .as_ref() @@ -1456,6 +1493,7 @@ fn project_save_slice_components( has_company_selection_override, selected_company_id, company_market_state, + company_periodic_side_latch_state, has_company_market_projection, world_issue_opinion_base_terms_raw_i32, chairman_profiles, @@ -14191,6 +14229,7 @@ mod tests { dirty_rerun_count: 2, world_issue_opinion_base_terms_raw_i32: Vec::new(), company_market_state: BTreeMap::new(), + company_periodic_side_latch_state: BTreeMap::new(), annual_finance_last_actions: BTreeMap::new(), annual_finance_action_counts: BTreeMap::new(), annual_dividend_adjustment_commit_count: 0, diff --git a/crates/rrt-runtime/src/lib.rs b/crates/rrt-runtime/src/lib.rs index 772178e..7d7e438 100644 --- a/crates/rrt-runtime/src/lib.rs +++ b/crates/rrt-runtime/src/lib.rs @@ -58,18 +58,18 @@ pub use runtime::{ RuntimeCompanyAnnualFinanceState, RuntimeCompanyAnnualStockIssueState, RuntimeCompanyAnnualStockRepurchaseState, RuntimeCompanyBondSlot, RuntimeCompanyConditionTestScope, RuntimeCompanyControllerKind, RuntimeCompanyMarketMetric, - RuntimeCompanyMarketState, RuntimeCompanyMetric, RuntimeCompanyStatBandCandidate, - RuntimeCompanyStatSelector, RuntimeCompanyTarget, RuntimeCompanyTerritoryAccess, - RuntimeCompanyTerritoryTrackPieceCount, RuntimeCondition, RuntimeConditionComparator, - RuntimeEffect, RuntimeEventRecord, RuntimeEventRecordTemplate, RuntimeLocomotiveCatalogEntry, - RuntimePackedEventCollectionSummary, RuntimePackedEventCompactControlSummary, - RuntimePackedEventConditionRowSummary, RuntimePackedEventGroupedEffectRowSummary, - RuntimePackedEventNegativeSentinelScopeSummary, RuntimePackedEventRecordSummary, - RuntimePackedEventTextBandSummary, RuntimePlayer, RuntimePlayerConditionTestScope, - RuntimePlayerTarget, RuntimeSaveProfileState, RuntimeServiceState, RuntimeState, - RuntimeTerritory, RuntimeTerritoryMetric, RuntimeTerritoryTarget, RuntimeTrackMetric, - RuntimeTrackPieceCounts, RuntimeTrain, RuntimeWorldFinanceNeighborhoodCandidate, - RuntimeWorldIssueState, RuntimeWorldRestoreState, + RuntimeCompanyMarketState, RuntimeCompanyMetric, RuntimeCompanyPeriodicSideLatchState, + RuntimeCompanyStatBandCandidate, RuntimeCompanyStatSelector, RuntimeCompanyTarget, + RuntimeCompanyTerritoryAccess, RuntimeCompanyTerritoryTrackPieceCount, RuntimeCondition, + RuntimeConditionComparator, RuntimeEffect, RuntimeEventRecord, RuntimeEventRecordTemplate, + RuntimeLocomotiveCatalogEntry, RuntimePackedEventCollectionSummary, + RuntimePackedEventCompactControlSummary, RuntimePackedEventConditionRowSummary, + RuntimePackedEventGroupedEffectRowSummary, RuntimePackedEventNegativeSentinelScopeSummary, + RuntimePackedEventRecordSummary, RuntimePackedEventTextBandSummary, RuntimePlayer, + RuntimePlayerConditionTestScope, RuntimePlayerTarget, RuntimeSaveProfileState, + RuntimeServiceState, RuntimeState, RuntimeTerritory, RuntimeTerritoryMetric, + RuntimeTerritoryTarget, RuntimeTrackMetric, RuntimeTrackPieceCounts, RuntimeTrain, + RuntimeWorldFinanceNeighborhoodCandidate, RuntimeWorldIssueState, RuntimeWorldRestoreState, runtime_annual_bond_principal_flow_relation_label, runtime_annual_finance_news_family_candidate_label, runtime_company_annual_bond_policy_state, runtime_company_annual_creditor_pressure_state, runtime_company_annual_deep_distress_state, diff --git a/crates/rrt-runtime/src/runtime.rs b/crates/rrt-runtime/src/runtime.rs index 97499e8..38988c3 100644 --- a/crates/rrt-runtime/src/runtime.rs +++ b/crates/rrt-runtime/src/runtime.rs @@ -178,10 +178,22 @@ pub struct RuntimeCompanyAnnualFinanceState { pub prior_issue_calendar_word: u32, #[serde(default)] pub prior_issue_calendar_word_2: u32, + #[serde(default)] + pub preferred_locomotive_engine_type_raw_u8: Option, pub city_connection_latch: bool, pub linked_transit_latch: bool, } +#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] +pub struct RuntimeCompanyPeriodicSideLatchState { + #[serde(default)] + pub preferred_locomotive_engine_type_raw_u8: Option, + #[serde(default)] + pub city_connection_latch: bool, + #[serde(default)] + pub linked_transit_latch: bool, +} + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RuntimeCompanyAnnualCreditorPressureState { pub company_id: u32, @@ -1243,6 +1255,8 @@ pub struct RuntimeServiceState { #[serde(default)] pub company_market_state: BTreeMap, #[serde(default)] + pub company_periodic_side_latch_state: BTreeMap, + #[serde(default)] pub annual_finance_last_actions: BTreeMap, #[serde(default)] pub annual_finance_action_counts: BTreeMap, @@ -2203,6 +2217,14 @@ impl RuntimeState { )); } } + for company_id in self.service_state.company_periodic_side_latch_state.keys() { + if !seen_company_ids.contains(company_id) { + return Err(format!( + "service_state.company_periodic_side_latch_state references unknown company_id {}", + company_id + )); + } + } for company_id in self.service_state.annual_finance_last_actions.keys() { if !seen_company_ids.contains(company_id) { return Err(format!( @@ -2373,6 +2395,24 @@ impl RuntimeState { } } + self.service_state + .company_periodic_side_latch_state + .retain(|company_id, _| { + self.service_state + .company_market_state + .contains_key(company_id) + }); + for (company_id, market_state) in &self.service_state.company_market_state { + self.service_state + .company_periodic_side_latch_state + .entry(*company_id) + .or_insert_with(|| RuntimeCompanyPeriodicSideLatchState { + preferred_locomotive_engine_type_raw_u8: None, + city_connection_latch: market_state.city_connection_latch, + linked_transit_latch: market_state.linked_transit_latch, + }); + } + for profile in &mut self.chairman_profiles { let preserved_threshold_adjusted_holdings_component = profile .purchasing_power_total @@ -4482,6 +4522,10 @@ pub fn runtime_company_annual_finance_state( company_id: u32, ) -> Option { let market_state = state.service_state.company_market_state.get(&company_id)?; + let periodic_side_latch_state = state + .service_state + .company_periodic_side_latch_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)?; let years_since_founding = @@ -4556,8 +4600,14 @@ pub fn runtime_company_annual_finance_state( current_issue_calendar_word_2: market_state.current_issue_calendar_word_2, prior_issue_calendar_word: market_state.prior_issue_calendar_word, prior_issue_calendar_word_2: market_state.prior_issue_calendar_word_2, - city_connection_latch: market_state.city_connection_latch, - linked_transit_latch: market_state.linked_transit_latch, + preferred_locomotive_engine_type_raw_u8: periodic_side_latch_state + .and_then(|latch_state| latch_state.preferred_locomotive_engine_type_raw_u8), + city_connection_latch: periodic_side_latch_state + .map(|latch_state| latch_state.city_connection_latch) + .unwrap_or(market_state.city_connection_latch), + linked_transit_latch: periodic_side_latch_state + .map(|latch_state| latch_state.linked_transit_latch) + .unwrap_or(market_state.linked_transit_latch), }) } @@ -6681,6 +6731,93 @@ mod tests { assert_eq!(state.companies[0].management_attitude, 58); } + #[test] + fn seeds_company_periodic_side_latch_state_from_company_market_state() { + let mut 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: 1, + current_cash: 0, + debt: 0, + credit_rating_score: None, + prime_rate: None, + track_piece_counts: RuntimeTrackPieceCounts::default(), + active: true, + available_track_laying_capacity: None, + linked_chairman_profile_id: None, + book_value_per_share: 0, + investor_confidence: 0, + management_attitude: 0, + takeover_cooldown_year: None, + merger_cooldown_year: None, + controller_kind: RuntimeCompanyControllerKind::Unknown, + }], + selected_company_id: Some(1), + 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 { + company_market_state: BTreeMap::from([( + 1, + RuntimeCompanyMarketState { + city_connection_latch: true, + linked_transit_latch: false, + ..RuntimeCompanyMarketState::default() + }, + )]), + ..RuntimeServiceState::default() + }, + }; + + state.refresh_derived_market_state(); + + assert_eq!( + state + .service_state + .company_periodic_side_latch_state + .get(&1), + Some(&RuntimeCompanyPeriodicSideLatchState { + preferred_locomotive_engine_type_raw_u8: None, + city_connection_latch: true, + linked_transit_latch: false, + }) + ); + } + #[test] fn reads_grounded_company_stat_family_slots_from_runtime_state() { let mut year_stat_family_qword_bits = vec![ @@ -8631,6 +8768,7 @@ mod tests { current_issue_calendar_word_2: 6, prior_issue_calendar_word: 4, prior_issue_calendar_word_2: 5, + preferred_locomotive_engine_type_raw_u8: None, city_connection_latch: true, linked_transit_latch: false, }) diff --git a/crates/rrt-runtime/src/summary.rs b/crates/rrt-runtime/src/summary.rs index 34bd15e..c6339f7 100644 --- a/crates/rrt-runtime/src/summary.rs +++ b/crates/rrt-runtime/src/summary.rs @@ -119,6 +119,9 @@ pub struct RuntimeSummary { 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_periodic_side_latch_preferred_locomotive_engine_type_raw_u8: Option, + pub selected_company_periodic_side_latch_city_connection_latch: Option, + pub selected_company_periodic_side_latch_linked_transit_latch: Option, pub selected_company_chairman_bonus_year: Option, pub selected_company_chairman_bonus_amount: Option, pub selected_company_creditor_pressure_recent_bad_net_profit_year_count: Option, @@ -297,6 +300,13 @@ 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_periodic_side_latch_state = + state.selected_company_id.and_then(|company_id| { + state + .service_state + .company_periodic_side_latch_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)); @@ -603,6 +613,15 @@ impl RuntimeSummary { .and_then(|finance_state| { finance_state.current_issue_age_absolute_counter_delta }), + selected_company_periodic_side_latch_preferred_locomotive_engine_type_raw_u8: + selected_company_periodic_side_latch_state + .and_then(|latch_state| latch_state.preferred_locomotive_engine_type_raw_u8), + selected_company_periodic_side_latch_city_connection_latch: + selected_company_periodic_side_latch_state + .map(|latch_state| latch_state.city_connection_latch), + selected_company_periodic_side_latch_linked_transit_latch: + selected_company_periodic_side_latch_state + .map(|latch_state| latch_state.linked_transit_latch), selected_company_chairman_bonus_year: selected_company_market_state .map(|market_state| market_state.chairman_bonus_year) .filter(|year| *year != 0), @@ -2887,6 +2906,14 @@ mod tests { direct_control_transfer_int_fields_raw_u32: BTreeMap::new(), }, )]), + company_periodic_side_latch_state: BTreeMap::from([( + 1, + crate::RuntimeCompanyPeriodicSideLatchState { + preferred_locomotive_engine_type_raw_u8: Some(2), + city_connection_latch: true, + linked_transit_latch: false, + }, + )]), ..RuntimeServiceState::default() }, }; @@ -2933,6 +2960,18 @@ mod tests { assert_eq!(summary.selected_company_years_since_founding, None); assert_eq!(summary.selected_company_years_since_last_bankruptcy, None); assert_eq!(summary.selected_company_years_since_last_dividend, None); + assert_eq!( + summary.selected_company_periodic_side_latch_preferred_locomotive_engine_type_raw_u8, + Some(2) + ); + assert_eq!( + summary.selected_company_periodic_side_latch_city_connection_latch, + Some(true) + ); + assert_eq!( + summary.selected_company_periodic_side_latch_linked_transit_latch, + Some(false) + ); assert_eq!(summary.selected_company_chairman_bonus_year, Some(1842)); assert_eq!(summary.selected_company_chairman_bonus_amount, Some(750)); } diff --git a/docs/rehost-queue.md b/docs/rehost-queue.md index 28773ee..8eff935 100644 --- a/docs/rehost-queue.md +++ b/docs/rehost-queue.md @@ -11,8 +11,11 @@ Working rule: - Rehost the outer periodic company-service seam around `company_service_periodic_city_connection_finance_and_linked_transit_lanes`, using the now - save-native side-latch trio `0x0d17/0x0d18/0x0d56` as owned state instead of leaving that pass - split across annual-finance readers and atlas notes. + save-native side-latch trio `0x0d17/0x0d18/0x0d56` as owned runtime state instead of leaving + that pass split across annual-finance readers and atlas notes. +- Move the periodic-boundary owner from passive imported side-latch state toward same-cycle + service-owned refresh or reset behavior, so earlier periodic branches can eventually set those + lanes before annual finance consumes them. - Keep widening selected-year world-owner state only when a full owning reader/rebuild family is grounded strongly enough to avoid one-off leaf guesses. @@ -57,6 +60,9 @@ Working rule: - The save-native company direct-record seam now also carries the full outer periodic-company side-latch trio rooted at `0x0d17/0x0d18/0x0d56`, including the preferred-locomotive engine-type chooser byte beside the city-connection and linked-transit finance gates. +- That same side-latch trio now also has a runtime-owned service-state map and summary surface, + so later periodic company-service work can stop reading those lanes directly from imported + market/cache residue. - Company cash, confiscation, and major governance effects now write through owner state instead of drifting from market/cache readers. - Company credit rating, prime rate, book value per share, investor confidence, and management