From f7fde709f7b5c85b63f19527331049c838c451ee Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sat, 18 Apr 2026 07:42:59 -0700 Subject: [PATCH] Rehost periodic route preference service seam --- README.md | 3 + crates/rrt-runtime/src/import.rs | 82 +++++++++ crates/rrt-runtime/src/lib.rs | 42 ++--- crates/rrt-runtime/src/runtime.rs | 278 +++++++++++++++++++++++++++++- crates/rrt-runtime/src/smp.rs | 77 +++++++-- crates/rrt-runtime/src/summary.rs | 120 ++++++++++++- docs/README.md | 3 + docs/rehost-queue.md | 19 +- docs/runtime-rehost-plan.md | 3 + 9 files changed, 578 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 460e5fd..32e915a 100644 --- a/README.md +++ b/README.md @@ -141,6 +141,9 @@ shellless repayment and bond-burden simulation instead of another round of raw-s The same 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 that sits beside the city-connection and linked-transit finance gates. +That same seam now also resolves the base world route-preference byte at `[world+0x4c74]`, the +effective electric-only override fed by `0x0d17`, and the matching `1.4x` versus `1.8x` +route-quality multiplier as a normal runtime reader instead of leaving that bridge in atlas notes. That same seam now also derives the current live coupon burden directly from owned bond slots, so later finance service work can consume a runtime reader instead of recomputing from scattered raw fields. diff --git a/crates/rrt-runtime/src/import.rs b/crates/rrt-runtime/src/import.rs index 611b885..8fe1c7d 100644 --- a/crates/rrt-runtime/src/import.rs +++ b/crates/rrt-runtime/src/import.rs @@ -855,6 +855,34 @@ fn project_save_slice_components( .as_ref() .and_then(|state| state.linked_site_removal_follow_on_gate_raw_u8) .map(|raw| raw != 0), + auto_show_grade_during_track_lay_raw_u8: save_slice + .world_locomotive_policy_state + .as_ref() + .and_then(|state| state.auto_show_grade_during_track_lay_raw_u8), + starting_building_density_level_raw_u8: save_slice + .world_locomotive_policy_state + .as_ref() + .and_then(|state| state.starting_building_density_level_raw_u8), + post_text_building_density_growth_raw_u8: save_slice + .world_locomotive_policy_state + .as_ref() + .and_then(|state| state.building_density_growth_raw_u8), + leftover_simulation_time_accumulator_raw_u32: save_slice + .world_locomotive_policy_state + .as_ref() + .and_then(|state| state.leftover_simulation_time_accumulator_raw_u32), + leftover_simulation_time_accumulator_value_f32_text: save_slice + .world_locomotive_policy_state + .as_ref() + .and_then(|state| { + state + .leftover_simulation_time_accumulator_value_f32_text + .clone() + }), + selected_year_lane_snapshot_raw_u8: save_slice + .world_locomotive_policy_state + .as_ref() + .and_then(|state| state.selected_year_lane_snapshot_raw_u8), all_steam_locomotives_available_raw_u8: save_slice .world_locomotive_policy_state .as_ref() @@ -6191,6 +6219,17 @@ mod tests { selected_year_gap_scalar_value_f32_text: Some("0.333333".to_string()), linked_site_removal_follow_on_gate_raw_u8: Some(1), linked_site_removal_follow_on_gate_raw_hex: Some("0x01".to_string()), + auto_show_grade_during_track_lay_raw_u8: Some(2), + auto_show_grade_during_track_lay_raw_hex: Some("0x02".to_string()), + starting_building_density_level_raw_u8: Some(3), + starting_building_density_level_raw_hex: Some("0x03".to_string()), + building_density_growth_raw_u8: Some(1), + building_density_growth_raw_hex: Some("0x01".to_string()), + leftover_simulation_time_accumulator_raw_u32: Some(0x3f000000), + leftover_simulation_time_accumulator_raw_hex: Some("0x3f000000".to_string()), + leftover_simulation_time_accumulator_value_f32_text: Some("0.500000".to_string()), + selected_year_lane_snapshot_raw_u8: Some(7), + selected_year_lane_snapshot_raw_hex: Some("0x07".to_string()), all_steam_locomotives_available_raw_u8: Some(1), all_steam_locomotives_available_raw_hex: Some("0x01".to_string()), all_diesel_locomotives_available_raw_u8: Some(0), @@ -6368,6 +6407,49 @@ mod tests { .absolute_counter_reconstructible_from_save, Some(true) ); + assert_eq!( + import + .state + .world_restore + .auto_show_grade_during_track_lay_raw_u8, + Some(2) + ); + assert_eq!( + import + .state + .world_restore + .starting_building_density_level_raw_u8, + Some(3) + ); + assert_eq!( + import + .state + .world_restore + .post_text_building_density_growth_raw_u8, + Some(1) + ); + assert_eq!( + import + .state + .world_restore + .leftover_simulation_time_accumulator_raw_u32, + Some(0x3f000000) + ); + assert_eq!( + import + .state + .world_restore + .leftover_simulation_time_accumulator_value_f32_text + .as_deref(), + Some("0.500000") + ); + assert_eq!( + import + .state + .world_restore + .selected_year_lane_snapshot_raw_u8, + Some(7) + ); assert_eq!( import.state.world_restore.packed_year_word_raw_u16, Some(0x0201) diff --git a/crates/rrt-runtime/src/lib.rs b/crates/rrt-runtime/src/lib.rs index 7d7e438..c5a196b 100644 --- a/crates/rrt-runtime/src/lib.rs +++ b/crates/rrt-runtime/src/lib.rs @@ -58,18 +58,19 @@ pub use runtime::{ RuntimeCompanyAnnualFinanceState, RuntimeCompanyAnnualStockIssueState, RuntimeCompanyAnnualStockRepurchaseState, RuntimeCompanyBondSlot, RuntimeCompanyConditionTestScope, RuntimeCompanyControllerKind, RuntimeCompanyMarketMetric, - 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, + RuntimeCompanyMarketState, RuntimeCompanyMetric, RuntimeCompanyPeriodicServiceState, + 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, @@ -80,14 +81,15 @@ pub use runtime::{ runtime_company_assigned_share_pool, runtime_company_average_live_bond_coupon, runtime_company_book_value_per_share, runtime_company_credit_rating, runtime_company_investor_confidence, runtime_company_management_attitude, - runtime_company_market_value, runtime_company_prime_rate, - runtime_company_recent_per_share_subscore, runtime_company_stat_value, - runtime_company_stat_value_f64, runtime_company_unassigned_share_pool, - runtime_world_annual_finance_mode_active, runtime_world_bankruptcy_allowed, - runtime_world_bond_issue_and_repayment_allowed, runtime_world_building_density_growth_setting, - runtime_world_dividend_adjustment_allowed, runtime_world_issue_opinion_multiplier, - runtime_world_issue_opinion_term_sum_raw, runtime_world_issue_state, - runtime_world_prime_rate_baseline, runtime_world_stock_issue_and_buyback_allowed, + runtime_company_market_value, runtime_company_periodic_service_state, + runtime_company_prime_rate, runtime_company_recent_per_share_subscore, + runtime_company_stat_value, runtime_company_stat_value_f64, + runtime_company_unassigned_share_pool, runtime_world_annual_finance_mode_active, + runtime_world_bankruptcy_allowed, runtime_world_bond_issue_and_repayment_allowed, + runtime_world_building_density_growth_setting, runtime_world_dividend_adjustment_allowed, + runtime_world_issue_opinion_multiplier, runtime_world_issue_opinion_term_sum_raw, + runtime_world_issue_state, runtime_world_prime_rate_baseline, + runtime_world_stock_issue_and_buyback_allowed, }; 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 29ac8c4..1012727 100644 --- a/crates/rrt-runtime/src/runtime.rs +++ b/crates/rrt-runtime/src/runtime.rs @@ -194,6 +194,21 @@ pub struct RuntimeCompanyPeriodicSideLatchState { pub linked_transit_latch: bool, } +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct RuntimeCompanyPeriodicServiceState { + pub company_id: u32, + #[serde(default)] + pub preferred_locomotive_engine_type_raw_u8: Option, + pub city_connection_latch: bool, + pub linked_transit_latch: bool, + #[serde(default)] + pub base_route_preference_raw_u8: Option, + #[serde(default)] + pub effective_route_preference_raw_u8: Option, + pub electric_route_preference_override_active: bool, + pub effective_route_quality_multiplier_basis_points: i64, +} + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RuntimeCompanyAnnualCreditorPressureState { pub company_id: u32, @@ -1377,6 +1392,18 @@ pub struct RuntimeWorldRestoreState { #[serde(default)] pub linked_site_removal_follow_on_gate_enabled: Option, #[serde(default)] + pub auto_show_grade_during_track_lay_raw_u8: Option, + #[serde(default)] + pub starting_building_density_level_raw_u8: Option, + #[serde(default)] + pub post_text_building_density_growth_raw_u8: Option, + #[serde(default)] + pub leftover_simulation_time_accumulator_raw_u32: Option, + #[serde(default)] + pub leftover_simulation_time_accumulator_value_f32_text: Option, + #[serde(default)] + pub selected_year_lane_snapshot_raw_u8: Option, + #[serde(default)] pub all_steam_locomotives_available_raw_u8: Option, #[serde(default)] pub all_steam_locomotives_available_enabled: Option, @@ -4523,10 +4550,7 @@ 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 periodic_service_state = runtime_company_periodic_service_state(state, 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 = @@ -4601,14 +4625,61 @@ 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, - preferred_locomotive_engine_type_raw_u8: periodic_side_latch_state - .and_then(|latch_state| latch_state.preferred_locomotive_engine_type_raw_u8), + preferred_locomotive_engine_type_raw_u8: periodic_service_state + .as_ref() + .and_then(|service_state| service_state.preferred_locomotive_engine_type_raw_u8), + city_connection_latch: periodic_service_state + .as_ref() + .map(|service_state| service_state.city_connection_latch) + .unwrap_or(market_state.city_connection_latch), + linked_transit_latch: periodic_service_state + .as_ref() + .map(|service_state| service_state.linked_transit_latch) + .unwrap_or(market_state.linked_transit_latch), + }) +} + +pub fn runtime_company_periodic_service_state( + state: &RuntimeState, + company_id: u32, +) -> Option { + const DEFAULT_ROUTE_QUALITY_MULTIPLIER_BASIS_POINTS: i64 = 140; + const ELECTRIC_ROUTE_QUALITY_MULTIPLIER_BASIS_POINTS: i64 = 180; + const ELECTRIC_ENGINE_TYPE_RAW_U8: u8 = 2; + + 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 preferred_locomotive_engine_type_raw_u8 = periodic_side_latch_state + .and_then(|latch_state| latch_state.preferred_locomotive_engine_type_raw_u8); + let base_route_preference_raw_u8 = state.world_restore.auto_show_grade_during_track_lay_raw_u8; + let electric_route_preference_override_active = + preferred_locomotive_engine_type_raw_u8 == Some(ELECTRIC_ENGINE_TYPE_RAW_U8); + let effective_route_preference_raw_u8 = if electric_route_preference_override_active { + Some(ELECTRIC_ENGINE_TYPE_RAW_U8) + } else { + base_route_preference_raw_u8 + }; + Some(RuntimeCompanyPeriodicServiceState { + company_id, + 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), + base_route_preference_raw_u8, + effective_route_preference_raw_u8, + electric_route_preference_override_active, + effective_route_quality_multiplier_basis_points: + if electric_route_preference_override_active { + ELECTRIC_ROUTE_QUALITY_MULTIPLIER_BASIS_POINTS + } else { + DEFAULT_ROUTE_QUALITY_MULTIPLIER_BASIS_POINTS + }, }) } @@ -5259,6 +5330,12 @@ mod tests { territory_access_cost: None, linked_site_removal_follow_on_gate_raw_u8: None, linked_site_removal_follow_on_gate_enabled: None, + auto_show_grade_during_track_lay_raw_u8: None, + starting_building_density_level_raw_u8: None, + post_text_building_density_growth_raw_u8: None, + leftover_simulation_time_accumulator_raw_u32: None, + leftover_simulation_time_accumulator_value_f32_text: None, + selected_year_lane_snapshot_raw_u8: None, all_steam_locomotives_available_raw_u8: None, all_steam_locomotives_available_enabled: None, all_diesel_locomotives_available_raw_u8: None, @@ -8865,6 +8942,195 @@ mod tests { assert_eq!(runtime_company_annual_finance_state(&state, 99), None); } + #[test] + fn derives_company_periodic_service_state_from_side_latches_and_world_route_preference() { + let state = RuntimeState { + calendar: CalendarPoint { + year: 1845, + month_slot: 0, + phase_slot: 0, + tick_slot: 0, + }, + world_flags: BTreeMap::new(), + save_profile: RuntimeSaveProfileState::default(), + world_restore: RuntimeWorldRestoreState { + auto_show_grade_during_track_lay_raw_u8: Some(1), + ..RuntimeWorldRestoreState::default() + }, + metadata: BTreeMap::new(), + companies: vec![RuntimeCompany { + company_id: 4, + current_cash: 0, + debt: 0, + credit_rating_score: None, + prime_rate: None, + active: true, + available_track_laying_capacity: None, + controller_kind: RuntimeCompanyControllerKind::Unknown, + linked_chairman_profile_id: None, + book_value_per_share: 0, + investor_confidence: 0, + management_attitude: 0, + takeover_cooldown_year: None, + merger_cooldown_year: None, + track_piece_counts: RuntimeTrackPieceCounts::default(), + }], + selected_company_id: Some(4), + 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([( + 4, + RuntimeCompanyMarketState { + city_connection_latch: true, + linked_transit_latch: false, + ..RuntimeCompanyMarketState::default() + }, + )]), + company_periodic_side_latch_state: BTreeMap::from([( + 4, + RuntimeCompanyPeriodicSideLatchState { + preferred_locomotive_engine_type_raw_u8: Some(2), + city_connection_latch: false, + linked_transit_latch: true, + }, + )]), + ..RuntimeServiceState::default() + }, + }; + + assert_eq!( + runtime_company_periodic_service_state(&state, 4), + Some(RuntimeCompanyPeriodicServiceState { + company_id: 4, + preferred_locomotive_engine_type_raw_u8: Some(2), + city_connection_latch: false, + linked_transit_latch: true, + base_route_preference_raw_u8: Some(1), + effective_route_preference_raw_u8: Some(2), + electric_route_preference_override_active: true, + effective_route_quality_multiplier_basis_points: 180, + }) + ); + assert_eq!(runtime_company_periodic_service_state(&state, 99), None); + } + + #[test] + fn periodic_service_state_falls_back_to_market_latches_and_world_base_route_preference() { + let state = RuntimeState { + calendar: CalendarPoint { + year: 1845, + month_slot: 0, + phase_slot: 0, + tick_slot: 0, + }, + world_flags: BTreeMap::new(), + save_profile: RuntimeSaveProfileState::default(), + world_restore: RuntimeWorldRestoreState { + auto_show_grade_during_track_lay_raw_u8: Some(3), + ..RuntimeWorldRestoreState::default() + }, + metadata: BTreeMap::new(), + companies: vec![RuntimeCompany { + company_id: 8, + current_cash: 0, + debt: 0, + credit_rating_score: None, + prime_rate: None, + active: true, + available_track_laying_capacity: None, + controller_kind: RuntimeCompanyControllerKind::Unknown, + linked_chairman_profile_id: None, + book_value_per_share: 0, + investor_confidence: 0, + management_attitude: 0, + takeover_cooldown_year: None, + merger_cooldown_year: None, + track_piece_counts: RuntimeTrackPieceCounts::default(), + }], + selected_company_id: Some(8), + 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([( + 8, + RuntimeCompanyMarketState { + city_connection_latch: true, + linked_transit_latch: false, + ..RuntimeCompanyMarketState::default() + }, + )]), + ..RuntimeServiceState::default() + }, + }; + + assert_eq!( + runtime_company_periodic_service_state(&state, 8), + Some(RuntimeCompanyPeriodicServiceState { + company_id: 8, + preferred_locomotive_engine_type_raw_u8: None, + city_connection_latch: true, + linked_transit_latch: false, + base_route_preference_raw_u8: Some(3), + effective_route_preference_raw_u8: Some(3), + electric_route_preference_override_active: false, + effective_route_quality_multiplier_basis_points: 140, + }) + ); + } + #[test] fn derives_annual_creditor_pressure_from_rehosted_finance_owner_state() { let mut year_stat_family_qword_bits = vec![ diff --git a/crates/rrt-runtime/src/smp.rs b/crates/rrt-runtime/src/smp.rs index 28f416a..88c9ed6 100644 --- a/crates/rrt-runtime/src/smp.rs +++ b/crates/rrt-runtime/src/smp.rs @@ -2304,6 +2304,28 @@ pub struct SmpLoadedWorldLocomotivePolicyState { #[serde(default)] pub linked_site_removal_follow_on_gate_raw_hex: Option, #[serde(default)] + pub auto_show_grade_during_track_lay_raw_u8: Option, + #[serde(default)] + pub auto_show_grade_during_track_lay_raw_hex: Option, + #[serde(default)] + pub starting_building_density_level_raw_u8: Option, + #[serde(default)] + pub starting_building_density_level_raw_hex: Option, + #[serde(default)] + pub building_density_growth_raw_u8: Option, + #[serde(default)] + pub building_density_growth_raw_hex: Option, + #[serde(default)] + pub leftover_simulation_time_accumulator_raw_u32: Option, + #[serde(default)] + pub leftover_simulation_time_accumulator_raw_hex: Option, + #[serde(default)] + pub leftover_simulation_time_accumulator_value_f32_text: Option, + #[serde(default)] + pub selected_year_lane_snapshot_raw_u8: Option, + #[serde(default)] + pub selected_year_lane_snapshot_raw_hex: Option, + #[serde(default)] pub all_steam_locomotives_available_raw_u8: Option, #[serde(default)] pub all_steam_locomotives_available_raw_hex: Option, @@ -2932,10 +2954,10 @@ pub fn load_save_slice_from_report( .save_world_finance_neighborhood_probe .as_ref() .map(derive_loaded_world_finance_neighborhood_state_from_probe); - let world_locomotive_policy_state = report - .locomotive_policy_neighborhood_probe - .as_ref() - .map(derive_loaded_world_locomotive_policy_state_from_probe); + let world_locomotive_policy_state = derive_loaded_world_locomotive_policy_state_from_probes( + report.post_text_field_neighborhood_probe.as_ref(), + report.locomotive_policy_neighborhood_probe.as_ref(), + ); let company_roster = report.save_company_roster_probe.clone().or_else(|| { report .save_world_selection_context_probe @@ -3638,23 +3660,37 @@ fn derive_loaded_world_finance_neighborhood_state_from_probe( } } -fn derive_loaded_world_locomotive_policy_state_from_probe( - probe: &SmpLocomotivePolicyNeighborhoodProbe, -) -> SmpLoadedWorldLocomotivePolicyState { +fn derive_loaded_world_locomotive_policy_state_from_probes( + post_text_probe: Option<&SmpPostTextFieldNeighborhoodProbe>, + locomotive_policy_probe: Option<&SmpLocomotivePolicyNeighborhoodProbe>, +) -> Option { let field_by_name = |name: &str| { - probe + locomotive_policy_probe? + .grounded_field_observations + .iter() + .find(|field| field.field_name == name) + }; + let post_text_field_by_name = |name: &str| { + post_text_probe? .grounded_field_observations .iter() .find(|field| field.field_name == name) }; let selected_year_gap_scalar = field_by_name("selected-year bucket companion scalar"); let linked_site_gate = field_by_name("linked-site removal follow-on gate"); + let auto_show_grade = post_text_field_by_name("Auto-Show Grade During Track Lay"); + let starting_building_density = post_text_field_by_name("Starting Building Density Level"); + let building_density_growth = post_text_field_by_name("Building Density Growth"); + let leftover_simulation_time = post_text_field_by_name("leftover simulation time accumulator"); + let selected_year_snapshot = post_text_field_by_name("selected-year lane snapshot"); let all_steam = field_by_name("All Steam Locos Avail."); let all_diesel = field_by_name("All Diesel Locos Avail."); let all_electric = field_by_name("All Electric Locos Avail."); let cached_available_rating = field_by_name("cached available-locomotive rating"); - SmpLoadedWorldLocomotivePolicyState { - source_kind: probe.source_kind.clone(), + Some(SmpLoadedWorldLocomotivePolicyState { + source_kind: locomotive_policy_probe + .map(|probe| probe.source_kind.clone()) + .or_else(|| post_text_probe.map(|probe| probe.source_kind.clone()))?, semantic_family: "world-locomotive-policy".to_string(), selected_year_gap_scalar_raw_u32: selected_year_gap_scalar .and_then(|field| field.value_u32), @@ -3666,6 +3702,25 @@ fn derive_loaded_world_locomotive_policy_state_from_probe( .and_then(|field| field.value_u8), linked_site_removal_follow_on_gate_raw_hex: linked_site_gate .and_then(|field| field.value_u8_hex.clone()), + auto_show_grade_during_track_lay_raw_u8: auto_show_grade.and_then(|field| field.value_u8), + auto_show_grade_during_track_lay_raw_hex: auto_show_grade + .and_then(|field| field.value_u8_hex.clone()), + starting_building_density_level_raw_u8: starting_building_density + .and_then(|field| field.value_u8), + starting_building_density_level_raw_hex: starting_building_density + .and_then(|field| field.value_u8_hex.clone()), + building_density_growth_raw_u8: building_density_growth.and_then(|field| field.value_u8), + building_density_growth_raw_hex: building_density_growth + .and_then(|field| field.value_u8_hex.clone()), + leftover_simulation_time_accumulator_raw_u32: leftover_simulation_time + .and_then(|field| field.value_u32), + leftover_simulation_time_accumulator_raw_hex: leftover_simulation_time + .and_then(|field| field.value_u32_hex.clone()), + leftover_simulation_time_accumulator_value_f32_text: leftover_simulation_time + .and_then(|field| field.probable_f32_le.clone()), + selected_year_lane_snapshot_raw_u8: selected_year_snapshot.and_then(|field| field.value_u8), + selected_year_lane_snapshot_raw_hex: selected_year_snapshot + .and_then(|field| field.value_u8_hex.clone()), all_steam_locomotives_available_raw_u8: all_steam.and_then(|field| field.value_u8), all_steam_locomotives_available_raw_hex: all_steam .and_then(|field| field.value_u8_hex.clone()), @@ -3681,7 +3736,7 @@ fn derive_loaded_world_locomotive_policy_state_from_probe( .and_then(|field| field.value_u32_hex.clone()), cached_available_locomotive_rating_value_f32_text: cached_available_rating .and_then(|field| field.probable_f32_le.clone()), - } + }) } fn derive_selection_only_company_roster_from_save_world_probe( diff --git a/crates/rrt-runtime/src/summary.rs b/crates/rrt-runtime/src/summary.rs index c6339f7..ca41bcc 100644 --- a/crates/rrt-runtime/src/summary.rs +++ b/crates/rrt-runtime/src/summary.rs @@ -7,7 +7,7 @@ use crate::{ runtime_company_annual_finance_policy_action_label, runtime_company_annual_finance_policy_state, runtime_company_annual_finance_state, runtime_company_annual_stock_issue_state, runtime_company_annual_stock_repurchase_state, - runtime_company_unassigned_share_pool, + runtime_company_periodic_service_state, runtime_company_unassigned_share_pool, }; fn raw_u32_to_f32_text(raw: u32) -> String { @@ -51,6 +51,12 @@ pub struct RuntimeSummary { pub world_restore_territory_access_cost: Option, pub world_restore_linked_site_removal_follow_on_gate_raw_u8: Option, pub world_restore_linked_site_removal_follow_on_gate_enabled: Option, + pub world_restore_auto_show_grade_during_track_lay_raw_u8: Option, + pub world_restore_starting_building_density_level_raw_u8: Option, + pub world_restore_post_text_building_density_growth_raw_u8: Option, + pub world_restore_leftover_simulation_time_accumulator_raw_u32: Option, + pub world_restore_leftover_simulation_time_accumulator_value_f32_text: Option, + pub world_restore_selected_year_lane_snapshot_raw_u8: Option, pub world_restore_all_steam_locomotives_available_raw_u8: Option, pub world_restore_all_steam_locomotives_available_enabled: Option, pub world_restore_all_diesel_locomotives_available_raw_u8: Option, @@ -122,6 +128,10 @@ pub struct RuntimeSummary { 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_periodic_service_base_route_preference_raw_u8: Option, + pub selected_company_periodic_service_effective_route_preference_raw_u8: Option, + pub selected_company_periodic_service_electric_route_preference_override_active: Option, + pub selected_company_periodic_service_route_quality_multiplier_basis_points: 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, @@ -307,6 +317,9 @@ impl RuntimeSummary { .company_periodic_side_latch_state .get(&company_id) }); + let selected_company_periodic_service_state = state + .selected_company_id + .and_then(|company_id| runtime_company_periodic_service_state(state, company_id)); let selected_company_annual_finance_state = state .selected_company_id .and_then(|company_id| runtime_company_annual_finance_state(state, company_id)); @@ -464,6 +477,25 @@ impl RuntimeSummary { world_restore_linked_site_removal_follow_on_gate_enabled: state .world_restore .linked_site_removal_follow_on_gate_enabled, + world_restore_auto_show_grade_during_track_lay_raw_u8: state + .world_restore + .auto_show_grade_during_track_lay_raw_u8, + world_restore_starting_building_density_level_raw_u8: state + .world_restore + .starting_building_density_level_raw_u8, + world_restore_post_text_building_density_growth_raw_u8: state + .world_restore + .post_text_building_density_growth_raw_u8, + world_restore_leftover_simulation_time_accumulator_raw_u32: state + .world_restore + .leftover_simulation_time_accumulator_raw_u32, + world_restore_leftover_simulation_time_accumulator_value_f32_text: state + .world_restore + .leftover_simulation_time_accumulator_value_f32_text + .clone(), + world_restore_selected_year_lane_snapshot_raw_u8: state + .world_restore + .selected_year_lane_snapshot_raw_u8, world_restore_all_steam_locomotives_available_raw_u8: state .world_restore .all_steam_locomotives_available_raw_u8, @@ -622,6 +654,24 @@ impl RuntimeSummary { selected_company_periodic_side_latch_linked_transit_latch: selected_company_periodic_side_latch_state .map(|latch_state| latch_state.linked_transit_latch), + selected_company_periodic_service_base_route_preference_raw_u8: + selected_company_periodic_service_state + .as_ref() + .and_then(|service_state| service_state.base_route_preference_raw_u8), + selected_company_periodic_service_effective_route_preference_raw_u8: + selected_company_periodic_service_state + .as_ref() + .and_then(|service_state| service_state.effective_route_preference_raw_u8), + selected_company_periodic_service_electric_route_preference_override_active: + selected_company_periodic_service_state + .as_ref() + .map(|service_state| service_state.electric_route_preference_override_active), + selected_company_periodic_service_route_quality_multiplier_basis_points: + selected_company_periodic_service_state + .as_ref() + .map(|service_state| { + service_state.effective_route_quality_multiplier_basis_points + }), selected_company_chairman_bonus_year: selected_company_market_state .map(|market_state| market_state.chairman_bonus_year) .filter(|year| *year != 0), @@ -1578,7 +1628,10 @@ mod tests { }, world_flags: BTreeMap::new(), save_profile: RuntimeSaveProfileState::default(), - world_restore: RuntimeWorldRestoreState::default(), + world_restore: RuntimeWorldRestoreState { + auto_show_grade_during_track_lay_raw_u8: Some(2), + ..RuntimeWorldRestoreState::default() + }, metadata: BTreeMap::new(), companies: Vec::new(), selected_company_id: None, @@ -1857,6 +1910,12 @@ mod tests { ], linked_site_removal_follow_on_gate_raw_u8: Some(1), linked_site_removal_follow_on_gate_enabled: Some(true), + auto_show_grade_during_track_lay_raw_u8: Some(2), + starting_building_density_level_raw_u8: Some(3), + post_text_building_density_growth_raw_u8: Some(1), + leftover_simulation_time_accumulator_raw_u32: Some(0x3f000000), + leftover_simulation_time_accumulator_value_f32_text: Some("0.500000".to_string()), + selected_year_lane_snapshot_raw_u8: Some(7), all_steam_locomotives_available_raw_u8: Some(1), all_steam_locomotives_available_enabled: Some(true), all_diesel_locomotives_available_raw_u8: Some(0), @@ -2017,6 +2076,32 @@ mod tests { summary.world_restore_linked_site_removal_follow_on_gate_enabled, Some(true) ); + assert_eq!( + summary.world_restore_auto_show_grade_during_track_lay_raw_u8, + Some(2) + ); + assert_eq!( + summary.world_restore_starting_building_density_level_raw_u8, + Some(3) + ); + assert_eq!( + summary.world_restore_post_text_building_density_growth_raw_u8, + Some(1) + ); + assert_eq!( + summary.world_restore_leftover_simulation_time_accumulator_raw_u32, + Some(0x3f000000) + ); + assert_eq!( + summary + .world_restore_leftover_simulation_time_accumulator_value_f32_text + .as_deref(), + Some("0.500000") + ); + assert_eq!( + summary.world_restore_selected_year_lane_snapshot_raw_u8, + Some(7) + ); assert_eq!( summary.world_restore_all_steam_locomotives_available_raw_u8, Some(1) @@ -2113,7 +2198,10 @@ mod tests { }, world_flags: BTreeMap::new(), save_profile: RuntimeSaveProfileState::default(), - world_restore: RuntimeWorldRestoreState::default(), + world_restore: RuntimeWorldRestoreState { + auto_show_grade_during_track_lay_raw_u8: Some(2), + ..RuntimeWorldRestoreState::default() + }, metadata: BTreeMap::new(), companies: vec![ RuntimeCompany { @@ -2218,7 +2306,10 @@ mod tests { }, world_flags: BTreeMap::new(), save_profile: RuntimeSaveProfileState::default(), - world_restore: RuntimeWorldRestoreState::default(), + world_restore: RuntimeWorldRestoreState { + auto_show_grade_during_track_lay_raw_u8: Some(2), + ..RuntimeWorldRestoreState::default() + }, metadata: BTreeMap::new(), companies: Vec::new(), selected_company_id: None, @@ -2770,7 +2861,10 @@ mod tests { }, world_flags: BTreeMap::new(), save_profile: RuntimeSaveProfileState::default(), - world_restore: RuntimeWorldRestoreState::default(), + world_restore: RuntimeWorldRestoreState { + auto_show_grade_during_track_lay_raw_u8: Some(2), + ..RuntimeWorldRestoreState::default() + }, metadata: BTreeMap::new(), companies: vec![RuntimeCompany { company_id: 1, @@ -2972,6 +3066,22 @@ mod tests { summary.selected_company_periodic_side_latch_linked_transit_latch, Some(false) ); + assert_eq!( + summary.selected_company_periodic_service_base_route_preference_raw_u8, + Some(2) + ); + assert_eq!( + summary.selected_company_periodic_service_effective_route_preference_raw_u8, + Some(2) + ); + assert_eq!( + summary.selected_company_periodic_service_electric_route_preference_override_active, + Some(true) + ); + assert_eq!( + summary.selected_company_periodic_service_route_quality_multiplier_basis_points, + Some(180) + ); assert_eq!(summary.selected_company_chairman_bonus_year, Some(1842)); assert_eq!(summary.selected_company_chairman_bonus_amount, Some(750)); } diff --git a/docs/README.md b/docs/README.md index a270fc2..fc587c5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -184,6 +184,9 @@ The highest-value next passes are now: - the same 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 seam now also resolves the base world route-preference byte at `[world+0x4c74]`, the + effective electric-only override fed by `0x0d17`, and the matching `1.4x` versus `1.8x` + route-quality multiplier as a normal runtime reader instead of a pure atlas note - 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/rehost-queue.md b/docs/rehost-queue.md index ba7ee47..bcc6823 100644 --- a/docs/rehost-queue.md +++ b/docs/rehost-queue.md @@ -9,13 +9,13 @@ Working rule: ## Next -- 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 runtime state instead of leaving - that pass split across annual-finance readers and atlas notes. -- Rehost the world-side `[world+0x4c74]` post-text owner lane so the temporary route-preference - override fed by company-side `0x0d17` can eventually move through a real runtime seam instead - of staying atlas-only. +- Rehost the next live branch inside + `company_service_periodic_city_connection_finance_and_linked_transit_lanes`, especially the + city-connection announcement / linked-transit roster-maintenance side that now sits on top of + the owned periodic-service seam instead of loose atlas notes. +- Keep widening the temporary world route-preference owner around `[world+0x4c74]` from a pure + reader seam into a real service mutation seam when the linked-transit route search / balancing + branch is grounded strongly enough. - 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. @@ -66,6 +66,11 @@ Working rule: - The periodic-boundary owner now also clears the transient preferred-locomotive side latch every cycle and reseeds the finance latches from market state where present, while preserving side-latch-only company context when no market projection exists. +- The outer periodic-company seam now also has a first-class runtime reader: + selected-company summaries and finance readers can resolve the base `[world+0x4c74]` + route-preference byte, the effective electric-only override fed by `0x0d17`, and the matching + `1.4x` versus `1.8x` route-quality multiplier through owned periodic-service state instead of + leaving that bridge in atlas notes. - 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 diff --git a/docs/runtime-rehost-plan.md b/docs/runtime-rehost-plan.md index bee6353..7075893 100644 --- a/docs/runtime-rehost-plan.md +++ b/docs/runtime-rehost-plan.md @@ -261,6 +261,9 @@ policy instead of treating dividend changes as shell-dialog-only logic. The same 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 seam now also resolves the base world route-preference byte at `[world+0x4c74]`, the +effective electric-only override fed by `0x0d17`, and the matching `1.4x` versus `1.8x` +route-quality multiplier as a first-class runtime reader rather than a loose atlas-only bridge. That same seam now also carries the fixed-world building-density growth setting plus the linked chairman personality byte, which is enough to rehost the annual stock-repurchase gate on owned save/runtime state instead of another threshold-only note. The stock-capital issue branch now