Rehost save-native company market cache state

This commit is contained in:
Jan Petykiewicz 2026-04-17 18:28:53 -07:00
commit 5198f80cd9
9 changed files with 630 additions and 79 deletions

View file

@ -7,9 +7,9 @@ use crate::persistence::{load_runtime_snapshot_document, validate_runtime_snapsh
use crate::{
CalendarPoint, RuntimeCargoCatalogEntry, RuntimeCargoPriceTarget, RuntimeCargoProductionTarget,
RuntimeChairmanProfile, RuntimeChairmanTarget, RuntimeCompany,
RuntimeCompanyConditionTestScope, RuntimeCompanyControllerKind, RuntimeCompanyTarget,
RuntimeCondition, RuntimeEffect, RuntimeEventRecord, RuntimeEventRecordTemplate,
RuntimeLocomotiveCatalogEntry, RuntimePackedEventCollectionSummary,
RuntimeCompanyConditionTestScope, RuntimeCompanyControllerKind, RuntimeCompanyMarketState,
RuntimeCompanyTarget, RuntimeCondition, RuntimeEffect, RuntimeEventRecord,
RuntimeEventRecordTemplate, RuntimeLocomotiveCatalogEntry, RuntimePackedEventCollectionSummary,
RuntimePackedEventCompactControlSummary, RuntimePackedEventConditionRowSummary,
RuntimePackedEventGroupedEffectRowSummary, RuntimePackedEventNegativeSentinelScopeSummary,
RuntimePackedEventRecordSummary, RuntimePackedEventTextBandSummary,
@ -99,6 +99,8 @@ struct SaveSliceProjection {
has_company_projection: bool,
has_company_selection_override: bool,
selected_company_id: Option<u32>,
company_market_state: BTreeMap<u32, RuntimeCompanyMarketState>,
has_company_market_projection: bool,
chairman_profiles: Vec<RuntimeChairmanProfile>,
has_chairman_projection: bool,
has_chairman_selection_override: bool,
@ -308,7 +310,10 @@ pub fn project_save_slice_to_runtime_state_import(
territory_runtime_variables: BTreeMap::new(),
world_scalar_overrides: projection.world_scalar_overrides,
special_conditions: projection.special_conditions,
service_state: RuntimeServiceState::default(),
service_state: RuntimeServiceState {
company_market_state: projection.company_market_state,
..RuntimeServiceState::default()
},
};
state.validate()?;
@ -410,7 +415,14 @@ pub fn project_save_slice_overlay_to_runtime_state_import(
territory_runtime_variables: base_state.territory_runtime_variables.clone(),
world_scalar_overrides: base_state.world_scalar_overrides.clone(),
special_conditions: projection.special_conditions,
service_state: base_state.service_state.clone(),
service_state: RuntimeServiceState {
company_market_state: if projection.has_company_market_projection {
projection.company_market_state
} else {
base_state.service_state.company_market_state.clone()
},
..base_state.service_state.clone()
},
};
state.validate()?;
@ -915,64 +927,88 @@ fn project_save_slice_components(
None
};
let (companies, has_company_projection, has_company_selection_override, selected_company_id) =
if let Some(roster) = &save_slice.company_roster {
let (
companies,
has_company_projection,
has_company_selection_override,
selected_company_id,
company_market_state,
has_company_market_projection,
) = if let Some(roster) = &save_slice.company_roster {
metadata.insert(
"save_slice.company_roster_source_kind".to_string(),
roster.source_kind.clone(),
);
metadata.insert(
"save_slice.company_roster_semantic_family".to_string(),
roster.semantic_family.clone(),
);
metadata.insert(
"save_slice.company_roster_entry_count".to_string(),
roster.observed_entry_count.to_string(),
);
let market_state = roster
.entries
.iter()
.filter_map(|entry| {
entry
.market_state
.as_ref()
.map(|state| (entry.company_id, state.clone()))
})
.collect::<BTreeMap<_, _>>();
metadata.insert(
"save_slice.company_market_state_owner_count".to_string(),
market_state.len().to_string(),
);
if let Some(selected_company_id) = roster.selected_company_id {
metadata.insert(
"save_slice.company_roster_source_kind".to_string(),
roster.source_kind.clone(),
"save_slice.selected_company_id".to_string(),
selected_company_id.to_string(),
);
metadata.insert(
"save_slice.company_roster_semantic_family".to_string(),
roster.semantic_family.clone(),
);
metadata.insert(
"save_slice.company_roster_entry_count".to_string(),
roster.observed_entry_count.to_string(),
);
if let Some(selected_company_id) = roster.selected_company_id {
metadata.insert(
"save_slice.selected_company_id".to_string(),
selected_company_id.to_string(),
);
}
if roster.entries.is_empty() {
(
Vec::new(),
false,
roster.selected_company_id.is_some(),
roster.selected_company_id,
)
} else {
(
roster
.entries
.iter()
.map(|entry| RuntimeCompany {
company_id: entry.company_id,
current_cash: entry.current_cash,
debt: entry.debt,
credit_rating_score: entry.credit_rating_score,
prime_rate: entry.prime_rate,
active: entry.active,
available_track_laying_capacity: entry.available_track_laying_capacity,
controller_kind: entry.controller_kind,
linked_chairman_profile_id: entry.linked_chairman_profile_id,
book_value_per_share: entry.book_value_per_share,
investor_confidence: entry.investor_confidence,
management_attitude: entry.management_attitude,
takeover_cooldown_year: entry.takeover_cooldown_year,
merger_cooldown_year: entry.merger_cooldown_year,
track_piece_counts: entry.track_piece_counts,
})
.collect::<Vec<_>>(),
true,
roster.selected_company_id.is_some(),
roster.selected_company_id,
)
}
}
if roster.entries.is_empty() {
(
Vec::new(),
false,
roster.selected_company_id.is_some(),
roster.selected_company_id,
BTreeMap::new(),
false,
)
} else {
(Vec::new(), false, false, None)
};
(
roster
.entries
.iter()
.map(|entry| RuntimeCompany {
company_id: entry.company_id,
current_cash: entry.current_cash,
debt: entry.debt,
credit_rating_score: entry.credit_rating_score,
prime_rate: entry.prime_rate,
active: entry.active,
available_track_laying_capacity: entry.available_track_laying_capacity,
controller_kind: entry.controller_kind,
linked_chairman_profile_id: entry.linked_chairman_profile_id,
book_value_per_share: entry.book_value_per_share,
investor_confidence: entry.investor_confidence,
management_attitude: entry.management_attitude,
takeover_cooldown_year: entry.takeover_cooldown_year,
merger_cooldown_year: entry.merger_cooldown_year,
track_piece_counts: entry.track_piece_counts,
})
.collect::<Vec<_>>(),
true,
roster.selected_company_id.is_some(),
roster.selected_company_id,
market_state,
true,
)
}
} else {
(Vec::new(), false, false, None, BTreeMap::new(), false)
};
let (
chairman_profiles,
@ -1112,6 +1148,8 @@ fn project_save_slice_components(
has_company_projection,
has_company_selection_override,
selected_company_id,
company_market_state,
has_company_market_projection,
chairman_profiles,
has_chairman_projection,
has_chairman_selection_override,
@ -4952,6 +4990,22 @@ mod tests {
management_attitude: 58,
takeover_cooldown_year: Some(1839),
merger_cooldown_year: Some(1838),
market_state: Some(crate::RuntimeCompanyMarketState {
outstanding_shares: 20_000,
mutable_support_scalar_raw_u32: 0x3f99999a,
young_company_support_scalar_raw_u32: 0x42700000,
support_progress_word: 12,
recent_per_share_subscore_raw_u32: 0x420c0000,
cached_share_price_raw_u32: 0x42200000,
chairman_salary_baseline: 24,
chairman_salary_current: 30,
founding_year: 1831,
last_bankruptcy_year: 0,
current_issue_calendar_word: 5,
prior_issue_calendar_word: 4,
city_connection_latch: true,
linked_transit_latch: false,
}),
},
crate::SmpLoadedCompanyRosterEntry {
company_id: 2,
@ -4976,6 +5030,22 @@ mod tests {
management_attitude: 31,
takeover_cooldown_year: None,
merger_cooldown_year: None,
market_state: Some(crate::RuntimeCompanyMarketState {
outstanding_shares: 18_000,
mutable_support_scalar_raw_u32: 0x3f4ccccd,
young_company_support_scalar_raw_u32: 0x42580000,
support_progress_word: 9,
recent_per_share_subscore_raw_u32: 0x41f00000,
cached_share_price_raw_u32: 0x41f80000,
chairman_salary_baseline: 20,
chairman_salary_current: 22,
founding_year: 1833,
last_bankruptcy_year: 0,
current_issue_calendar_word: 3,
prior_issue_calendar_word: 2,
city_connection_latch: false,
linked_transit_latch: true,
}),
},
],
}
@ -6073,6 +6143,16 @@ mod tests {
assert_eq!(import.state.selected_chairman_profile_id, Some(1));
assert_eq!(import.state.companies[0].book_value_per_share, 2620);
assert_eq!(import.state.chairman_profiles[0].current_cash, 500);
assert_eq!(import.state.service_state.company_market_state.len(), 2);
assert_eq!(
import
.state
.service_state
.company_market_state
.get(&1)
.map(|state| state.cached_share_price_raw_u32),
Some(0x42200000)
);
}
#[test]
@ -6149,6 +6229,15 @@ mod tests {
assert_eq!(import.state.chairman_profiles.len(), 2);
assert_eq!(import.state.selected_chairman_profile_id, Some(1));
assert_eq!(import.state.territories, base_state.territories);
assert_eq!(
import
.state
.service_state
.company_market_state
.get(&2)
.map(|state| state.linked_transit_latch),
Some(true)
);
}
#[test]
@ -6216,6 +6305,28 @@ mod tests {
},
],
selected_chairman_profile_id: Some(9),
service_state: RuntimeServiceState {
company_market_state: BTreeMap::from([(
42,
crate::RuntimeCompanyMarketState {
outstanding_shares: 30_000,
mutable_support_scalar_raw_u32: 0x3f19999a,
young_company_support_scalar_raw_u32: 0x42580000,
support_progress_word: 8,
recent_per_share_subscore_raw_u32: 0x42000000,
cached_share_price_raw_u32: 0x42180000,
chairman_salary_baseline: 21,
chairman_salary_current: 24,
founding_year: 1834,
last_bankruptcy_year: 0,
current_issue_calendar_word: 4,
prior_issue_calendar_word: 3,
city_connection_latch: false,
linked_transit_latch: true,
},
)]),
..RuntimeServiceState::default()
},
..state()
};
let save_slice = SmpLoadedSaveSlice {
@ -6263,6 +6374,10 @@ mod tests {
assert_eq!(import.state.selected_company_id, Some(1));
assert_eq!(import.state.chairman_profiles, base_state.chairman_profiles);
assert_eq!(import.state.selected_chairman_profile_id, Some(1));
assert_eq!(
import.state.service_state.company_market_state,
base_state.service_state.company_market_state
);
}
#[test]
@ -13371,6 +13486,7 @@ mod tests {
trigger_dispatch_counts: BTreeMap::new(),
total_event_record_services: 4,
dirty_rerun_count: 2,
company_market_state: BTreeMap::new(),
},
};
let save_slice = SmpLoadedSaveSlice {