Commit runtime loader and atlas updates

This commit is contained in:
Jan Petykiewicz 2026-04-11 18:12:25 -07:00
commit b173c50c1a
19 changed files with 8425 additions and 698 deletions

File diff suppressed because it is too large Load diff

View file

@ -81,8 +81,9 @@ mod tests {
use super::*;
use crate::FixtureStateOrigin;
use rrt_runtime::{
CalendarPoint, RuntimeServiceState, RuntimeSnapshotDocument, RuntimeSnapshotSource,
RuntimeState, SNAPSHOT_FORMAT_VERSION, save_runtime_snapshot_document,
CalendarPoint, RuntimeSaveProfileState, RuntimeServiceState, RuntimeSnapshotDocument,
RuntimeSnapshotSource, RuntimeState, RuntimeWorldRestoreState, SNAPSHOT_FORMAT_VERSION,
save_runtime_snapshot_document,
};
use std::collections::BTreeMap;
@ -111,8 +112,13 @@ mod tests {
tick_slot: 5,
},
world_flags: BTreeMap::new(),
save_profile: RuntimeSaveProfileState::default(),
world_restore: RuntimeWorldRestoreState::default(),
metadata: BTreeMap::new(),
companies: Vec::new(),
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
},
};

View file

@ -16,12 +16,77 @@ pub struct ExpectedRuntimeSummary {
#[serde(default)]
pub calendar: Option<rrt_runtime::CalendarPoint>,
#[serde(default)]
pub calendar_projection_source: Option<String>,
#[serde(default)]
pub calendar_projection_is_placeholder: Option<bool>,
#[serde(default)]
pub world_flag_count: Option<usize>,
#[serde(default)]
pub world_restore_selected_year_profile_lane: Option<u8>,
#[serde(default)]
pub world_restore_campaign_scenario_enabled: Option<bool>,
#[serde(default)]
pub world_restore_sandbox_enabled: Option<bool>,
#[serde(default)]
pub world_restore_seed_tuple_written_from_raw_lane: Option<bool>,
#[serde(default)]
pub world_restore_absolute_counter_requires_shell_context: Option<bool>,
#[serde(default)]
pub world_restore_absolute_counter_reconstructible_from_save: Option<bool>,
#[serde(default)]
pub world_restore_disable_cargo_economy_special_condition_slot: Option<u8>,
#[serde(default)]
pub world_restore_disable_cargo_economy_special_condition_reconstructible_from_save:
Option<bool>,
#[serde(default)]
pub world_restore_disable_cargo_economy_special_condition_write_side_grounded: Option<bool>,
#[serde(default)]
pub world_restore_disable_cargo_economy_special_condition_enabled: Option<bool>,
#[serde(default)]
pub world_restore_use_bio_accelerator_cars_enabled: Option<bool>,
#[serde(default)]
pub world_restore_use_wartime_cargos_enabled: Option<bool>,
#[serde(default)]
pub world_restore_disable_train_crashes_enabled: Option<bool>,
#[serde(default)]
pub world_restore_disable_train_crashes_and_breakdowns_enabled: Option<bool>,
#[serde(default)]
pub world_restore_ai_ignore_territories_at_startup_enabled: Option<bool>,
#[serde(default)]
pub world_restore_absolute_counter_restore_kind: Option<String>,
#[serde(default)]
pub world_restore_absolute_counter_adjustment_context: Option<String>,
#[serde(default)]
pub metadata_count: Option<usize>,
#[serde(default)]
pub company_count: Option<usize>,
#[serde(default)]
pub event_runtime_record_count: Option<usize>,
#[serde(default)]
pub candidate_availability_count: Option<usize>,
#[serde(default)]
pub zero_candidate_availability_count: Option<usize>,
#[serde(default)]
pub special_condition_count: Option<usize>,
#[serde(default)]
pub enabled_special_condition_count: Option<usize>,
#[serde(default)]
pub save_profile_kind: Option<String>,
#[serde(default)]
pub save_profile_family: Option<String>,
#[serde(default)]
pub save_profile_map_path: Option<String>,
#[serde(default)]
pub save_profile_display_name: Option<String>,
#[serde(default)]
pub save_profile_selected_year_profile_lane: Option<u8>,
#[serde(default)]
pub save_profile_sandbox_enabled: Option<bool>,
#[serde(default)]
pub save_profile_campaign_scenario_enabled: Option<bool>,
#[serde(default)]
pub save_profile_staged_profile_copy_on_restore: Option<bool>,
#[serde(default)]
pub total_event_record_service_count: Option<u64>,
#[serde(default)]
pub periodic_boundary_call_count: Option<u64>,
@ -45,6 +110,22 @@ impl ExpectedRuntimeSummary {
));
}
}
if let Some(source) = &self.calendar_projection_source {
if actual.calendar_projection_source.as_ref() != Some(source) {
mismatches.push(format!(
"calendar_projection_source mismatch: expected {source:?}, got {:?}",
actual.calendar_projection_source
));
}
}
if let Some(is_placeholder) = self.calendar_projection_is_placeholder {
if actual.calendar_projection_is_placeholder != is_placeholder {
mismatches.push(format!(
"calendar_projection_is_placeholder mismatch: expected {is_placeholder}, got {}",
actual.calendar_projection_is_placeholder
));
}
}
if let Some(count) = self.world_flag_count {
if actual.world_flag_count != count {
mismatches.push(format!(
@ -53,6 +134,164 @@ impl ExpectedRuntimeSummary {
));
}
}
if let Some(lane) = self.world_restore_selected_year_profile_lane {
if actual.world_restore_selected_year_profile_lane != Some(lane) {
mismatches.push(format!(
"world_restore_selected_year_profile_lane mismatch: expected {lane}, got {:?}",
actual.world_restore_selected_year_profile_lane
));
}
}
if let Some(enabled) = self.world_restore_campaign_scenario_enabled {
if actual.world_restore_campaign_scenario_enabled != Some(enabled) {
mismatches.push(format!(
"world_restore_campaign_scenario_enabled mismatch: expected {enabled}, got {:?}",
actual.world_restore_campaign_scenario_enabled
));
}
}
if let Some(enabled) = self.world_restore_sandbox_enabled {
if actual.world_restore_sandbox_enabled != Some(enabled) {
mismatches.push(format!(
"world_restore_sandbox_enabled mismatch: expected {enabled}, got {:?}",
actual.world_restore_sandbox_enabled
));
}
}
if let Some(enabled) = self.world_restore_seed_tuple_written_from_raw_lane {
if actual.world_restore_seed_tuple_written_from_raw_lane != Some(enabled) {
mismatches.push(format!(
"world_restore_seed_tuple_written_from_raw_lane mismatch: expected {enabled}, got {:?}",
actual.world_restore_seed_tuple_written_from_raw_lane
));
}
}
if let Some(enabled) = self.world_restore_absolute_counter_requires_shell_context {
if actual.world_restore_absolute_counter_requires_shell_context != Some(enabled) {
mismatches.push(format!(
"world_restore_absolute_counter_requires_shell_context mismatch: expected {enabled}, got {:?}",
actual.world_restore_absolute_counter_requires_shell_context
));
}
}
if let Some(enabled) = self.world_restore_absolute_counter_reconstructible_from_save {
if actual.world_restore_absolute_counter_reconstructible_from_save != Some(enabled) {
mismatches.push(format!(
"world_restore_absolute_counter_reconstructible_from_save mismatch: expected {enabled}, got {:?}",
actual.world_restore_absolute_counter_reconstructible_from_save
));
}
}
if let Some(slot) = self.world_restore_disable_cargo_economy_special_condition_slot {
if actual.world_restore_disable_cargo_economy_special_condition_slot != Some(slot) {
mismatches.push(format!(
"world_restore_disable_cargo_economy_special_condition_slot mismatch: expected {slot}, got {:?}",
actual.world_restore_disable_cargo_economy_special_condition_slot
));
}
}
if let Some(enabled) =
self.world_restore_disable_cargo_economy_special_condition_reconstructible_from_save
{
if actual
.world_restore_disable_cargo_economy_special_condition_reconstructible_from_save
!= Some(enabled)
{
mismatches.push(format!(
"world_restore_disable_cargo_economy_special_condition_reconstructible_from_save mismatch: expected {enabled}, got {:?}",
actual.world_restore_disable_cargo_economy_special_condition_reconstructible_from_save
));
}
}
if let Some(enabled) =
self.world_restore_disable_cargo_economy_special_condition_write_side_grounded
{
if actual.world_restore_disable_cargo_economy_special_condition_write_side_grounded
!= Some(enabled)
{
mismatches.push(format!(
"world_restore_disable_cargo_economy_special_condition_write_side_grounded mismatch: expected {enabled}, got {:?}",
actual.world_restore_disable_cargo_economy_special_condition_write_side_grounded
));
}
}
if let Some(enabled) = self.world_restore_disable_cargo_economy_special_condition_enabled {
if actual.world_restore_disable_cargo_economy_special_condition_enabled != Some(enabled)
{
mismatches.push(format!(
"world_restore_disable_cargo_economy_special_condition_enabled mismatch: expected {enabled}, got {:?}",
actual.world_restore_disable_cargo_economy_special_condition_enabled
));
}
}
if let Some(enabled) = self.world_restore_use_bio_accelerator_cars_enabled {
if actual.world_restore_use_bio_accelerator_cars_enabled != Some(enabled) {
mismatches.push(format!(
"world_restore_use_bio_accelerator_cars_enabled mismatch: expected {enabled}, got {:?}",
actual.world_restore_use_bio_accelerator_cars_enabled
));
}
}
if let Some(enabled) = self.world_restore_use_wartime_cargos_enabled {
if actual.world_restore_use_wartime_cargos_enabled != Some(enabled) {
mismatches.push(format!(
"world_restore_use_wartime_cargos_enabled mismatch: expected {enabled}, got {:?}",
actual.world_restore_use_wartime_cargos_enabled
));
}
}
if let Some(enabled) = self.world_restore_disable_train_crashes_enabled {
if actual.world_restore_disable_train_crashes_enabled != Some(enabled) {
mismatches.push(format!(
"world_restore_disable_train_crashes_enabled mismatch: expected {enabled}, got {:?}",
actual.world_restore_disable_train_crashes_enabled
));
}
}
if let Some(enabled) = self.world_restore_disable_train_crashes_and_breakdowns_enabled {
if actual.world_restore_disable_train_crashes_and_breakdowns_enabled != Some(enabled) {
mismatches.push(format!(
"world_restore_disable_train_crashes_and_breakdowns_enabled mismatch: expected {enabled}, got {:?}",
actual.world_restore_disable_train_crashes_and_breakdowns_enabled
));
}
}
if let Some(enabled) = self.world_restore_ai_ignore_territories_at_startup_enabled {
if actual.world_restore_ai_ignore_territories_at_startup_enabled != Some(enabled) {
mismatches.push(format!(
"world_restore_ai_ignore_territories_at_startup_enabled mismatch: expected {enabled}, got {:?}",
actual.world_restore_ai_ignore_territories_at_startup_enabled
));
}
}
if let Some(kind) = &self.world_restore_absolute_counter_restore_kind {
if actual.world_restore_absolute_counter_restore_kind.as_ref() != Some(kind) {
mismatches.push(format!(
"world_restore_absolute_counter_restore_kind mismatch: expected {kind:?}, got {:?}",
actual.world_restore_absolute_counter_restore_kind
));
}
}
if let Some(context) = &self.world_restore_absolute_counter_adjustment_context {
if actual
.world_restore_absolute_counter_adjustment_context
.as_ref()
!= Some(context)
{
mismatches.push(format!(
"world_restore_absolute_counter_adjustment_context mismatch: expected {context:?}, got {:?}",
actual.world_restore_absolute_counter_adjustment_context
));
}
}
if let Some(count) = self.metadata_count {
if actual.metadata_count != count {
mismatches.push(format!(
"metadata_count mismatch: expected {count}, got {}",
actual.metadata_count
));
}
}
if let Some(count) = self.company_count {
if actual.company_count != count {
mismatches.push(format!(
@ -69,6 +308,102 @@ impl ExpectedRuntimeSummary {
));
}
}
if let Some(count) = self.candidate_availability_count {
if actual.candidate_availability_count != count {
mismatches.push(format!(
"candidate_availability_count mismatch: expected {count}, got {}",
actual.candidate_availability_count
));
}
}
if let Some(count) = self.zero_candidate_availability_count {
if actual.zero_candidate_availability_count != count {
mismatches.push(format!(
"zero_candidate_availability_count mismatch: expected {count}, got {}",
actual.zero_candidate_availability_count
));
}
}
if let Some(count) = self.special_condition_count {
if actual.special_condition_count != count {
mismatches.push(format!(
"special_condition_count mismatch: expected {count}, got {}",
actual.special_condition_count
));
}
}
if let Some(count) = self.enabled_special_condition_count {
if actual.enabled_special_condition_count != count {
mismatches.push(format!(
"enabled_special_condition_count mismatch: expected {count}, got {}",
actual.enabled_special_condition_count
));
}
}
if let Some(kind) = &self.save_profile_kind {
if actual.save_profile_kind.as_ref() != Some(kind) {
mismatches.push(format!(
"save_profile_kind mismatch: expected {kind:?}, got {:?}",
actual.save_profile_kind
));
}
}
if let Some(family) = &self.save_profile_family {
if actual.save_profile_family.as_ref() != Some(family) {
mismatches.push(format!(
"save_profile_family mismatch: expected {family:?}, got {:?}",
actual.save_profile_family
));
}
}
if let Some(map_path) = &self.save_profile_map_path {
if actual.save_profile_map_path.as_ref() != Some(map_path) {
mismatches.push(format!(
"save_profile_map_path mismatch: expected {map_path:?}, got {:?}",
actual.save_profile_map_path
));
}
}
if let Some(display_name) = &self.save_profile_display_name {
if actual.save_profile_display_name.as_ref() != Some(display_name) {
mismatches.push(format!(
"save_profile_display_name mismatch: expected {display_name:?}, got {:?}",
actual.save_profile_display_name
));
}
}
if let Some(lane) = self.save_profile_selected_year_profile_lane {
if actual.save_profile_selected_year_profile_lane != Some(lane) {
mismatches.push(format!(
"save_profile_selected_year_profile_lane mismatch: expected {lane}, got {:?}",
actual.save_profile_selected_year_profile_lane
));
}
}
if let Some(enabled) = self.save_profile_sandbox_enabled {
if actual.save_profile_sandbox_enabled != Some(enabled) {
mismatches.push(format!(
"save_profile_sandbox_enabled mismatch: expected {enabled}, got {:?}",
actual.save_profile_sandbox_enabled
));
}
}
if let Some(enabled) = self.save_profile_campaign_scenario_enabled {
if actual.save_profile_campaign_scenario_enabled != Some(enabled) {
mismatches.push(format!(
"save_profile_campaign_scenario_enabled mismatch: expected {enabled}, got {:?}",
actual.save_profile_campaign_scenario_enabled
));
}
}
if let Some(enabled) = self.save_profile_staged_profile_copy_on_restore {
if actual.save_profile_staged_profile_copy_on_restore != Some(enabled) {
mismatches.push(format!(
"save_profile_staged_profile_copy_on_restore mismatch: expected {enabled}, got {:?}",
actual.save_profile_staged_profile_copy_on_restore
));
}
}
if let Some(count) = self.total_event_record_service_count {
if actual.total_event_record_service_count != count {
mismatches.push(format!(

View file

@ -1,8 +1,12 @@
use std::collections::BTreeMap;
use std::path::Path;
use serde::{Deserialize, Serialize};
use crate::RuntimeState;
use crate::{
CalendarPoint, RuntimeSaveProfileState, RuntimeServiceState, RuntimeState,
RuntimeWorldRestoreState, SmpLoadedSaveSlice,
};
pub const STATE_DUMP_FORMAT_VERSION: u32 = 1;
@ -30,6 +34,282 @@ pub struct RuntimeStateImport {
pub state: RuntimeState,
}
pub fn project_save_slice_to_runtime_state_import(
save_slice: &SmpLoadedSaveSlice,
import_id: &str,
description: Option<String>,
) -> Result<RuntimeStateImport, String> {
if import_id.trim().is_empty() {
return Err("import_id must not be empty".to_string());
}
let mut world_flags = BTreeMap::new();
world_flags.insert(
"save_slice.profile_present".to_string(),
save_slice.profile.is_some(),
);
world_flags.insert(
"save_slice.candidate_availability_present".to_string(),
save_slice.candidate_availability_table.is_some(),
);
world_flags.insert(
"save_slice.special_conditions_present".to_string(),
save_slice.special_conditions_table.is_some(),
);
world_flags.insert(
"save_slice.mechanism_confidence_grounded".to_string(),
save_slice.mechanism_confidence == "grounded",
);
if let Some(profile) = &save_slice.profile {
world_flags.insert(
"save_slice.profile_byte_0x82_nonzero".to_string(),
profile.profile_byte_0x82 != 0,
);
world_flags.insert(
"save_slice.profile_byte_0x97_nonzero".to_string(),
profile.profile_byte_0x97 != 0,
);
world_flags.insert(
"save_slice.profile_byte_0xc5_nonzero".to_string(),
profile.profile_byte_0xc5 != 0,
);
}
let mut metadata = BTreeMap::new();
metadata.insert(
"save_slice.import_projection".to_string(),
"partial-runtime-restore-v1".to_string(),
);
metadata.insert(
"save_slice.calendar_source".to_string(),
"default-1830-placeholder".to_string(),
);
metadata.insert(
"save_slice.selected_year_seed_tuple_source".to_string(),
"raw-lane-via-0x51d3f0".to_string(),
);
metadata.insert(
"save_slice.selected_year_absolute_counter_source".to_string(),
"mode-adjusted-lane-via-0x51d390-0x409e80".to_string(),
);
metadata.insert(
"save_slice.selected_year_absolute_counter_reconstructible_from_save".to_string(),
"false".to_string(),
);
metadata.insert(
"save_slice.disable_cargo_economy_special_condition_slot".to_string(),
"30".to_string(),
);
metadata.insert(
"save_slice.disable_cargo_economy_special_condition_reconstructible_from_save".to_string(),
"true".to_string(),
);
metadata.insert(
"save_slice.disable_cargo_economy_special_condition_write_side_grounded".to_string(),
"true".to_string(),
);
metadata.insert(
"save_slice.selected_year_absolute_counter_adjustment_context".to_string(),
"editor-map-mode,shell-selected-year-adjust-policy-0x9d26-0x9d28,save-special-condition-disable-cargo-economy-slot-30"
.to_string(),
);
metadata.insert(
"save_slice.mechanism_family".to_string(),
save_slice.mechanism_family.clone(),
);
metadata.insert(
"save_slice.mechanism_confidence".to_string(),
save_slice.mechanism_confidence.clone(),
);
if let Some(family) = &save_slice.container_profile_family {
metadata.insert(
"save_slice.container_profile_family".to_string(),
family.clone(),
);
}
if let Some(family) = &save_slice.trailer_family {
metadata.insert("save_slice.trailer_family".to_string(), family.clone());
}
if let Some(family) = &save_slice.bridge_family {
metadata.insert("save_slice.bridge_family".to_string(), family.clone());
}
let save_profile = if let Some(profile) = &save_slice.profile {
metadata.insert(
"save_slice.profile_kind".to_string(),
profile.profile_kind.clone(),
);
metadata.insert(
"save_slice.profile_family".to_string(),
profile.profile_family.clone(),
);
metadata.insert(
"save_slice.packed_profile_offset".to_string(),
profile.packed_profile_offset.to_string(),
);
metadata.insert(
"save_slice.packed_profile_len".to_string(),
profile.packed_profile_len.to_string(),
);
metadata.insert(
"save_slice.leading_word_0_hex".to_string(),
profile.leading_word_0_hex.clone(),
);
metadata.insert(
"save_slice.profile_byte_0x77_hex".to_string(),
profile.profile_byte_0x77_hex.clone(),
);
metadata.insert(
"save_slice.profile_byte_0x82_hex".to_string(),
profile.profile_byte_0x82_hex.clone(),
);
metadata.insert(
"save_slice.profile_byte_0x97_hex".to_string(),
profile.profile_byte_0x97_hex.clone(),
);
metadata.insert(
"save_slice.profile_byte_0xc5_hex".to_string(),
profile.profile_byte_0xc5_hex.clone(),
);
if let Some(header_flag_word_3_hex) = &profile.header_flag_word_3_hex {
metadata.insert(
"save_slice.header_flag_word_3_hex".to_string(),
header_flag_word_3_hex.clone(),
);
}
if let Some(map_path) = &profile.map_path {
metadata.insert("save_slice.map_path".to_string(), map_path.clone());
}
if let Some(display_name) = &profile.display_name {
metadata.insert("save_slice.display_name".to_string(), display_name.clone());
}
RuntimeSaveProfileState {
profile_kind: Some(profile.profile_kind.clone()),
profile_family: Some(profile.profile_family.clone()),
map_path: profile.map_path.clone(),
display_name: profile.display_name.clone(),
selected_year_profile_lane: Some(profile.profile_byte_0x77),
sandbox_enabled: Some(profile.profile_byte_0x82 != 0),
campaign_scenario_enabled: Some(profile.profile_byte_0xc5 != 0),
staged_profile_copy_on_restore: Some(profile.profile_byte_0x97 != 0),
}
} else {
RuntimeSaveProfileState::default()
};
let special_condition_enabled = |slot_index: u8| {
save_slice.special_conditions_table.as_ref().map(|table| {
table
.entries
.iter()
.find(|entry| entry.slot_index == slot_index)
.map(|entry| entry.value != 0)
.unwrap_or(false)
})
};
let world_restore = if let Some(profile) = &save_slice.profile {
let disable_cargo_economy_special_condition_enabled = special_condition_enabled(30);
RuntimeWorldRestoreState {
selected_year_profile_lane: Some(profile.profile_byte_0x77),
campaign_scenario_enabled: Some(profile.profile_byte_0xc5 != 0),
sandbox_enabled: Some(profile.profile_byte_0x82 != 0),
seed_tuple_written_from_raw_lane: Some(true),
absolute_counter_requires_shell_context: Some(true),
absolute_counter_reconstructible_from_save: Some(false),
disable_cargo_economy_special_condition_slot: Some(30),
disable_cargo_economy_special_condition_reconstructible_from_save: Some(true),
disable_cargo_economy_special_condition_write_side_grounded: Some(true),
disable_cargo_economy_special_condition_enabled,
use_bio_accelerator_cars_enabled: special_condition_enabled(29),
use_wartime_cargos_enabled: special_condition_enabled(31),
disable_train_crashes_enabled: special_condition_enabled(32),
disable_train_crashes_and_breakdowns_enabled: special_condition_enabled(33),
ai_ignore_territories_at_startup_enabled: special_condition_enabled(34),
absolute_counter_restore_kind: Some(
"mode-adjusted-selected-year-lane".to_string(),
),
absolute_counter_adjustment_context: Some(
"editor-map-mode,shell-selected-year-adjust-policy-0x9d26-0x9d28,save-special-condition-disable-cargo-economy-slot-30"
.to_string(),
),
}
} else {
RuntimeWorldRestoreState::default()
};
let mut candidate_availability = BTreeMap::new();
if let Some(table) = &save_slice.candidate_availability_table {
metadata.insert(
"save_slice.candidate_table_source_kind".to_string(),
table.source_kind.clone(),
);
metadata.insert(
"save_slice.candidate_table_semantic_family".to_string(),
table.semantic_family.clone(),
);
metadata.insert(
"save_slice.candidate_table_entry_count".to_string(),
table.observed_entry_count.to_string(),
);
metadata.insert(
"save_slice.candidate_table_zero_count".to_string(),
table.zero_availability_count.to_string(),
);
for entry in &table.entries {
candidate_availability.insert(entry.text.clone(), entry.availability_dword);
}
}
let mut special_conditions = BTreeMap::new();
if let Some(table) = &save_slice.special_conditions_table {
metadata.insert(
"save_slice.special_conditions_source_kind".to_string(),
table.source_kind.clone(),
);
metadata.insert(
"save_slice.special_conditions_table_offset".to_string(),
table.table_offset.to_string(),
);
metadata.insert(
"save_slice.special_conditions_enabled_visible_count".to_string(),
table.enabled_visible_count.to_string(),
);
for entry in &table.entries {
if !entry.hidden {
special_conditions.insert(entry.label.clone(), entry.value);
}
}
}
for (index, note) in save_slice.notes.iter().enumerate() {
metadata.insert(format!("save_slice.note.{index}"), note.clone());
}
let state = RuntimeState {
calendar: CalendarPoint {
year: 1830,
month_slot: 0,
phase_slot: 0,
tick_slot: 0,
},
world_flags,
save_profile,
world_restore,
metadata,
companies: Vec::new(),
event_runtime_records: Vec::new(),
candidate_availability,
special_conditions,
service_state: RuntimeServiceState::default(),
};
state.validate()?;
Ok(RuntimeStateImport {
import_id: import_id.to_string(),
description,
state,
})
}
pub fn validate_runtime_state_dump_document(
document: &RuntimeStateDumpDocument,
) -> Result<(), String> {
@ -85,8 +365,6 @@ pub fn load_runtime_state_import_from_str(
#[cfg(test)]
mod tests {
use super::*;
use crate::{CalendarPoint, RuntimeServiceState};
use std::collections::BTreeMap;
fn state() -> RuntimeState {
RuntimeState {
@ -97,8 +375,13 @@ mod tests {
tick_slot: 0,
},
world_flags: BTreeMap::new(),
save_profile: RuntimeSaveProfileState::default(),
world_restore: RuntimeWorldRestoreState::default(),
metadata: BTreeMap::new(),
companies: Vec::new(),
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
}
}
@ -130,4 +413,236 @@ mod tests {
assert_eq!(import.import_id, "fallback");
assert!(import.description.is_none());
}
#[test]
fn projects_save_slice_into_runtime_state_import() {
let save_slice = SmpLoadedSaveSlice {
file_extension_hint: Some("gms".to_string()),
container_profile_family: Some("rt3-105-save-container-v1".to_string()),
mechanism_family: "rt3-105-save-post-span-bridge-v1".to_string(),
mechanism_confidence: "mixed".to_string(),
trailer_family: Some("rt3-105-save-trailer-v1".to_string()),
bridge_family: Some("rt3-105-save-post-span-bridge-v1".to_string()),
profile: Some(crate::SmpLoadedProfile {
profile_kind: "rt3-105-packed-profile".to_string(),
profile_family: "rt3-105-save-container-v1".to_string(),
packed_profile_offset: 0x73c0,
packed_profile_len: 0x108,
packed_profile_len_hex: "0x108".to_string(),
leading_word_0: 3,
leading_word_0_hex: "0x00000003".to_string(),
header_flag_word_3: Some(0x01000000),
header_flag_word_3_hex: Some("0x01000000".to_string()),
map_path: Some("Alternate USA.gmp".to_string()),
display_name: Some("Alternate USA".to_string()),
profile_byte_0x77: 0x07,
profile_byte_0x77_hex: "0x07".to_string(),
profile_byte_0x82: 0x4d,
profile_byte_0x82_hex: "0x4d".to_string(),
profile_byte_0x97: 0x00,
profile_byte_0x97_hex: "0x00".to_string(),
profile_byte_0xc5: 0x00,
profile_byte_0xc5_hex: "0x00".to_string(),
}),
candidate_availability_table: Some(crate::SmpLoadedCandidateAvailabilityTable {
source_kind: "save-bridge-secondary-block".to_string(),
semantic_family: "scenario-named-candidate-availability-table".to_string(),
header_offset: 0x6a70,
entries_offset: 0x6ad1,
entries_end_offset: 0x73b7,
observed_entry_count: 2,
zero_availability_count: 1,
zero_availability_names: vec!["Uranium Mine".to_string()],
footer_progress_hex_words: vec!["0x000032dc".to_string(), "0x00003714".to_string()],
entries: vec![
crate::SmpRt3105SaveNameTableEntry {
index: 0,
offset: 0x6ad1,
text: "AutoPlant".to_string(),
availability_dword: 1,
availability_dword_hex: "0x00000001".to_string(),
trailer_word: 1,
trailer_word_hex: "0x00000001".to_string(),
},
crate::SmpRt3105SaveNameTableEntry {
index: 1,
offset: 0x6af3,
text: "Uranium Mine".to_string(),
availability_dword: 0,
availability_dword_hex: "0x00000000".to_string(),
trailer_word: 0,
trailer_word_hex: "0x00000000".to_string(),
},
],
}),
special_conditions_table: Some(crate::SmpLoadedSpecialConditionsTable {
source_kind: "save-fixed-special-conditions-range".to_string(),
table_offset: 0x0d64,
table_len: 36 * 4,
enabled_visible_count: 0,
enabled_visible_labels: vec![],
entries: vec![
crate::SmpSpecialConditionEntry {
slot_index: 30,
hidden: false,
label_id: 3722,
help_id: 3723,
label: "Disable Cargo Economy".to_string(),
value: 0,
value_hex: "0x00000000".to_string(),
},
crate::SmpSpecialConditionEntry {
slot_index: 35,
hidden: true,
label_id: 3,
help_id: 3,
label: "Hidden sentinel".to_string(),
value: 1,
value_hex: "0x00000001".to_string(),
},
],
}),
notes: vec!["packed profile recovered".to_string()],
};
let import = project_save_slice_to_runtime_state_import(
&save_slice,
"save-import-smoke",
Some("test save import".to_string()),
)
.expect("save slice should project");
assert_eq!(import.import_id, "save-import-smoke");
assert_eq!(
import
.state
.metadata
.get("save_slice.map_path")
.map(String::as_str),
Some("Alternate USA.gmp")
);
assert_eq!(
import.state.save_profile.selected_year_profile_lane,
Some(0x07)
);
assert_eq!(import.state.save_profile.sandbox_enabled, Some(true));
assert_eq!(
import.state.world_restore.selected_year_profile_lane,
Some(0x07)
);
assert_eq!(import.state.world_restore.sandbox_enabled, Some(true));
assert_eq!(
import.state.world_restore.campaign_scenario_enabled,
Some(false)
);
assert_eq!(
import.state.world_restore.seed_tuple_written_from_raw_lane,
Some(true)
);
assert_eq!(
import
.state
.world_restore
.absolute_counter_requires_shell_context,
Some(true)
);
assert_eq!(
import
.state
.world_restore
.absolute_counter_reconstructible_from_save,
Some(false)
);
assert_eq!(
import
.state
.world_restore
.disable_cargo_economy_special_condition_slot,
Some(30)
);
assert_eq!(
import
.state
.world_restore
.disable_cargo_economy_special_condition_reconstructible_from_save,
Some(true)
);
assert_eq!(
import
.state
.world_restore
.disable_cargo_economy_special_condition_write_side_grounded,
Some(true)
);
assert_eq!(
import
.state
.world_restore
.disable_cargo_economy_special_condition_enabled,
Some(false)
);
assert_eq!(
import.state.world_restore.use_bio_accelerator_cars_enabled,
Some(false)
);
assert_eq!(
import.state.world_restore.use_wartime_cargos_enabled,
Some(false)
);
assert_eq!(
import.state.world_restore.disable_train_crashes_enabled,
Some(false)
);
assert_eq!(
import
.state
.world_restore
.disable_train_crashes_and_breakdowns_enabled,
Some(false)
);
assert_eq!(
import
.state
.world_restore
.ai_ignore_territories_at_startup_enabled,
Some(false)
);
assert_eq!(
import
.state
.world_restore
.absolute_counter_restore_kind
.as_deref(),
Some("mode-adjusted-selected-year-lane")
);
assert_eq!(
import
.state
.world_restore
.absolute_counter_adjustment_context
.as_deref(),
Some(
"editor-map-mode,shell-selected-year-adjust-policy-0x9d26-0x9d28,save-special-condition-disable-cargo-economy-slot-30"
)
);
assert_eq!(
import.state.save_profile.map_path.as_deref(),
Some("Alternate USA.gmp")
);
assert_eq!(
import.state.candidate_availability.get("Uranium Mine"),
Some(&0)
);
assert_eq!(
import.state.special_conditions.get("Disable Cargo Economy"),
Some(&0)
);
assert_eq!(
import
.state
.world_flags
.get("save_slice.profile_byte_0x82_nonzero"),
Some(&true)
);
}
}

View file

@ -16,7 +16,8 @@ pub use campaign_exe::{
};
pub use import::{
RuntimeStateDumpDocument, RuntimeStateDumpSource, RuntimeStateImport,
STATE_DUMP_FORMAT_VERSION, load_runtime_state_import, validate_runtime_state_dump_document,
STATE_DUMP_FORMAT_VERSION, load_runtime_state_import,
project_save_slice_to_runtime_state_import, validate_runtime_state_dump_document,
};
pub use persistence::{
RuntimeSnapshotDocument, RuntimeSnapshotSource, SNAPSHOT_FORMAT_VERSION,
@ -27,16 +28,30 @@ pub use pk4::{
PK4_DIRECTORY_ENTRY_STRIDE, PK4_MAGIC, Pk4Entry, Pk4ExtractionReport, Pk4InspectionReport,
extract_pk4_entry_bytes, extract_pk4_entry_file, inspect_pk4_bytes, inspect_pk4_file,
};
pub use runtime::{RuntimeCompany, RuntimeEventRecord, RuntimeServiceState, RuntimeState};
pub use runtime::{
RuntimeCompany, RuntimeEventRecord, RuntimeSaveProfileState, RuntimeServiceState, RuntimeState,
RuntimeWorldRestoreState,
};
pub use smp::{
SMP_FOUR_SIDECAR_BYTE_PLANES_MIN_BUNDLE_VERSION, SmpAsciiPreview, SmpClassicPackedProfileBlock,
SMP_FOUR_SIDECAR_BYTE_PLANES_MIN_BUNDLE_VERSION, SmpAlignedRuntimeRuleBandLane,
SmpAlignedRuntimeRuleBandProbe, SmpAsciiPreview, SmpClassicPackedProfileBlock,
SmpClassicRehydrateProfileProbe, SmpContainerProfile, SmpEarlyContentProbe,
SmpHeaderVariantProbe, SmpInspectionReport, SmpKnownTagHit, SmpPackedProfileWordLane,
SmpPreamble, SmpPreambleWord, SmpRt3105PackedProfileBlock, SmpRt3105PackedProfileProbe,
SmpRt3105PostSpanBridgeProbe, SmpRt3105SaveBridgePayloadProbe, SmpRt3105SaveNameTableEntry,
SmpRt3105SaveNameTableProbe, SmpRuntimeAnchorCycleBlock, SmpRuntimePostSpanHeaderCandidate,
SmpRuntimePostSpanProbe, SmpRuntimeTrailerBlock, SmpSaveAnchorRunBlock, SmpSaveBootstrapBlock,
SmpSecondaryVariantProbe, SmpSharedHeader, inspect_smp_bytes, inspect_smp_file,
SmpHeaderVariantProbe, SmpInspectionReport, SmpKnownTagHit,
SmpLoadedCandidateAvailabilityTable, SmpLoadedProfile, SmpLoadedSaveSlice,
SmpLoadedSpecialConditionsTable, SmpLocomotivePolicyFieldObservation,
SmpLocomotivePolicyFloatAlignmentCandidate, SmpLocomotivePolicyNeighborhoodProbe,
SmpPackedProfileWordLane, SmpPostSpecialConditionsScalarLane,
SmpPostSpecialConditionsScalarProbe, SmpPostTextFieldNeighborhoodProbe,
SmpPostTextFloatAlignmentCandidate, SmpPostTextGroundedFieldObservation,
SmpPreRecipeScalarPlateauLane, SmpPreRecipeScalarPlateauProbe, SmpPreamble, SmpPreambleWord,
SmpRecipeBookLineSummary, SmpRecipeBookSummaryBook, SmpRecipeBookSummaryProbe,
SmpRt3105PackedProfileBlock, SmpRt3105PackedProfileProbe, SmpRt3105PostSpanBridgeProbe,
SmpRt3105SaveBridgePayloadProbe, SmpRt3105SaveNameTableEntry, SmpRt3105SaveNameTableProbe,
SmpRuntimeAnchorCycleBlock, SmpRuntimePostSpanHeaderCandidate, SmpRuntimePostSpanProbe,
SmpRuntimeTrailerBlock, SmpSaveAnchorRunBlock, SmpSaveBootstrapBlock,
SmpSaveLoadCandidateTableSummary, SmpSaveLoadSummary, SmpSecondaryVariantProbe,
SmpSharedHeader, SmpSpecialConditionEntry, SmpSpecialConditionsProbe, inspect_smp_bytes,
inspect_smp_file, load_save_slice_file, load_save_slice_from_report,
};
pub use step::{BoundaryEvent, ServiceEvent, StepCommand, StepResult, execute_step_command};
pub use summary::RuntimeSummary;

View file

@ -68,7 +68,9 @@ pub fn save_runtime_snapshot_document(
#[cfg(test)]
mod tests {
use super::*;
use crate::{CalendarPoint, RuntimeServiceState};
use crate::{
CalendarPoint, RuntimeSaveProfileState, RuntimeServiceState, RuntimeWorldRestoreState,
};
use std::collections::BTreeMap;
fn snapshot() -> RuntimeSnapshotDocument {
@ -87,8 +89,13 @@ mod tests {
tick_slot: 0,
},
world_flags: BTreeMap::new(),
save_profile: RuntimeSaveProfileState::default(),
world_restore: RuntimeWorldRestoreState::default(),
metadata: BTreeMap::new(),
companies: Vec::new(),
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
},
}

View file

@ -34,16 +34,84 @@ pub struct RuntimeServiceState {
pub dirty_rerun_count: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct RuntimeSaveProfileState {
#[serde(default)]
pub profile_kind: Option<String>,
#[serde(default)]
pub profile_family: Option<String>,
#[serde(default)]
pub map_path: Option<String>,
#[serde(default)]
pub display_name: Option<String>,
#[serde(default)]
pub selected_year_profile_lane: Option<u8>,
#[serde(default)]
pub sandbox_enabled: Option<bool>,
#[serde(default)]
pub campaign_scenario_enabled: Option<bool>,
#[serde(default)]
pub staged_profile_copy_on_restore: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct RuntimeWorldRestoreState {
#[serde(default)]
pub selected_year_profile_lane: Option<u8>,
#[serde(default)]
pub campaign_scenario_enabled: Option<bool>,
#[serde(default)]
pub sandbox_enabled: Option<bool>,
#[serde(default)]
pub seed_tuple_written_from_raw_lane: Option<bool>,
#[serde(default)]
pub absolute_counter_requires_shell_context: Option<bool>,
#[serde(default)]
pub absolute_counter_reconstructible_from_save: Option<bool>,
#[serde(default)]
pub disable_cargo_economy_special_condition_slot: Option<u8>,
#[serde(default)]
pub disable_cargo_economy_special_condition_reconstructible_from_save: Option<bool>,
#[serde(default)]
pub disable_cargo_economy_special_condition_write_side_grounded: Option<bool>,
#[serde(default)]
pub disable_cargo_economy_special_condition_enabled: Option<bool>,
#[serde(default)]
pub use_bio_accelerator_cars_enabled: Option<bool>,
#[serde(default)]
pub use_wartime_cargos_enabled: Option<bool>,
#[serde(default)]
pub disable_train_crashes_enabled: Option<bool>,
#[serde(default)]
pub disable_train_crashes_and_breakdowns_enabled: Option<bool>,
#[serde(default)]
pub ai_ignore_territories_at_startup_enabled: Option<bool>,
#[serde(default)]
pub absolute_counter_restore_kind: Option<String>,
#[serde(default)]
pub absolute_counter_adjustment_context: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RuntimeState {
pub calendar: CalendarPoint,
#[serde(default)]
pub world_flags: BTreeMap<String, bool>,
#[serde(default)]
pub save_profile: RuntimeSaveProfileState,
#[serde(default)]
pub world_restore: RuntimeWorldRestoreState,
#[serde(default)]
pub metadata: BTreeMap<String, String>,
#[serde(default)]
pub companies: Vec<RuntimeCompany>,
#[serde(default)]
pub event_runtime_records: Vec<RuntimeEventRecord>,
#[serde(default)]
pub candidate_availability: BTreeMap<String, u32>,
#[serde(default)]
pub special_conditions: BTreeMap<String, u32>,
#[serde(default)]
pub service_state: RuntimeServiceState,
}
@ -71,6 +139,79 @@ impl RuntimeState {
}
}
for (label, value) in [
(
"save_profile.profile_kind",
self.save_profile.profile_kind.as_deref(),
),
(
"save_profile.profile_family",
self.save_profile.profile_family.as_deref(),
),
(
"save_profile.map_path",
self.save_profile.map_path.as_deref(),
),
(
"save_profile.display_name",
self.save_profile.display_name.as_deref(),
),
] {
if value.is_some_and(|text| text.trim().is_empty()) {
return Err(format!("{label} must not be empty"));
}
}
if self.world_restore.selected_year_profile_lane.is_none()
&& (self.world_restore.campaign_scenario_enabled.is_some()
|| self.world_restore.sandbox_enabled.is_some())
{
return Err(
"world_restore.selected_year_profile_lane must be present when world restore flags are populated"
.to_string(),
);
}
if self
.world_restore
.absolute_counter_restore_kind
.as_deref()
.is_some_and(|text| text.trim().is_empty())
{
return Err(
"world_restore.absolute_counter_restore_kind must not be empty".to_string(),
);
}
if self
.world_restore
.absolute_counter_adjustment_context
.as_deref()
.is_some_and(|text| text.trim().is_empty())
{
return Err(
"world_restore.absolute_counter_adjustment_context must not be empty".to_string(),
);
}
for (key, value) in &self.metadata {
if key.trim().is_empty() {
return Err("metadata contains an empty key".to_string());
}
if value.trim().is_empty() {
return Err(format!("metadata[{key}] must not be empty"));
}
}
for key in self.candidate_availability.keys() {
if key.trim().is_empty() {
return Err("candidate_availability contains an empty key".to_string());
}
}
for key in self.special_conditions.keys() {
if key.trim().is_empty() {
return Err("special_conditions contains an empty key".to_string());
}
}
Ok(())
}
}
@ -89,6 +230,9 @@ mod tests {
tick_slot: 0,
},
world_flags: BTreeMap::new(),
save_profile: RuntimeSaveProfileState::default(),
world_restore: RuntimeWorldRestoreState::default(),
metadata: BTreeMap::new(),
companies: vec![
RuntimeCompany {
company_id: 1,
@ -102,6 +246,53 @@ mod tests {
},
],
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
};
assert!(state.validate().is_err());
}
#[test]
fn rejects_partial_world_restore_without_year_lane() {
let 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 {
selected_year_profile_lane: None,
campaign_scenario_enabled: Some(false),
sandbox_enabled: Some(true),
seed_tuple_written_from_raw_lane: Some(true),
absolute_counter_requires_shell_context: Some(true),
absolute_counter_reconstructible_from_save: Some(false),
disable_cargo_economy_special_condition_slot: Some(30),
disable_cargo_economy_special_condition_reconstructible_from_save: Some(true),
disable_cargo_economy_special_condition_write_side_grounded: Some(true),
disable_cargo_economy_special_condition_enabled: Some(false),
use_bio_accelerator_cars_enabled: Some(false),
use_wartime_cargos_enabled: Some(false),
disable_train_crashes_enabled: Some(false),
disable_train_crashes_and_breakdowns_enabled: Some(false),
ai_ignore_territories_at_startup_enabled: Some(false),
absolute_counter_restore_kind: Some(
"mode-adjusted-selected-year-lane".to_string(),
),
absolute_counter_adjustment_context: Some(
"editor-map-mode,shell-selected-year-adjust-policy-0x9d26-0x9d28,save-special-condition-disable-cargo-economy-slot-30".to_string(),
),
},
metadata: BTreeMap::new(),
companies: Vec::new(),
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
};

File diff suppressed because it is too large Load diff

View file

@ -188,7 +188,10 @@ mod tests {
use std::collections::BTreeMap;
use super::*;
use crate::{CalendarPoint, RuntimeCompany, RuntimeEventRecord, RuntimeServiceState};
use crate::{
CalendarPoint, RuntimeCompany, RuntimeEventRecord, RuntimeSaveProfileState,
RuntimeServiceState, RuntimeWorldRestoreState,
};
fn state() -> RuntimeState {
RuntimeState {
@ -199,12 +202,17 @@ mod tests {
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: 10,
debt: 0,
}],
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
}
}

View file

@ -5,9 +5,42 @@ use crate::{CalendarPoint, RuntimeState};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RuntimeSummary {
pub calendar: CalendarPoint,
pub calendar_projection_source: Option<String>,
pub calendar_projection_is_placeholder: bool,
pub world_flag_count: usize,
pub world_restore_selected_year_profile_lane: Option<u8>,
pub world_restore_campaign_scenario_enabled: Option<bool>,
pub world_restore_sandbox_enabled: Option<bool>,
pub world_restore_seed_tuple_written_from_raw_lane: Option<bool>,
pub world_restore_absolute_counter_requires_shell_context: Option<bool>,
pub world_restore_absolute_counter_reconstructible_from_save: Option<bool>,
pub world_restore_disable_cargo_economy_special_condition_slot: Option<u8>,
pub world_restore_disable_cargo_economy_special_condition_reconstructible_from_save:
Option<bool>,
pub world_restore_disable_cargo_economy_special_condition_write_side_grounded: Option<bool>,
pub world_restore_disable_cargo_economy_special_condition_enabled: Option<bool>,
pub world_restore_use_bio_accelerator_cars_enabled: Option<bool>,
pub world_restore_use_wartime_cargos_enabled: Option<bool>,
pub world_restore_disable_train_crashes_enabled: Option<bool>,
pub world_restore_disable_train_crashes_and_breakdowns_enabled: Option<bool>,
pub world_restore_ai_ignore_territories_at_startup_enabled: Option<bool>,
pub world_restore_absolute_counter_restore_kind: Option<String>,
pub world_restore_absolute_counter_adjustment_context: Option<String>,
pub metadata_count: usize,
pub company_count: usize,
pub event_runtime_record_count: usize,
pub candidate_availability_count: usize,
pub zero_candidate_availability_count: usize,
pub special_condition_count: usize,
pub enabled_special_condition_count: usize,
pub save_profile_kind: Option<String>,
pub save_profile_family: Option<String>,
pub save_profile_map_path: Option<String>,
pub save_profile_display_name: Option<String>,
pub save_profile_selected_year_profile_lane: Option<u8>,
pub save_profile_sandbox_enabled: Option<bool>,
pub save_profile_campaign_scenario_enabled: Option<bool>,
pub save_profile_staged_profile_copy_on_restore: Option<bool>,
pub total_event_record_service_count: u64,
pub periodic_boundary_call_count: u64,
pub total_trigger_dispatch_count: u64,
@ -19,9 +52,86 @@ impl RuntimeSummary {
pub fn from_state(state: &RuntimeState) -> Self {
Self {
calendar: state.calendar,
calendar_projection_source: state.metadata.get("save_slice.calendar_source").cloned(),
calendar_projection_is_placeholder: state
.metadata
.get("save_slice.calendar_source")
.is_some_and(|value| value == "default-1830-placeholder"),
world_flag_count: state.world_flags.len(),
world_restore_selected_year_profile_lane: state
.world_restore
.selected_year_profile_lane,
world_restore_campaign_scenario_enabled: state.world_restore.campaign_scenario_enabled,
world_restore_sandbox_enabled: state.world_restore.sandbox_enabled,
world_restore_seed_tuple_written_from_raw_lane: state
.world_restore
.seed_tuple_written_from_raw_lane,
world_restore_absolute_counter_requires_shell_context: state
.world_restore
.absolute_counter_requires_shell_context,
world_restore_absolute_counter_reconstructible_from_save: state
.world_restore
.absolute_counter_reconstructible_from_save,
world_restore_disable_cargo_economy_special_condition_slot: state
.world_restore
.disable_cargo_economy_special_condition_slot,
world_restore_disable_cargo_economy_special_condition_reconstructible_from_save: state
.world_restore
.disable_cargo_economy_special_condition_reconstructible_from_save,
world_restore_disable_cargo_economy_special_condition_write_side_grounded: state
.world_restore
.disable_cargo_economy_special_condition_write_side_grounded,
world_restore_disable_cargo_economy_special_condition_enabled: state
.world_restore
.disable_cargo_economy_special_condition_enabled,
world_restore_use_bio_accelerator_cars_enabled: state
.world_restore
.use_bio_accelerator_cars_enabled,
world_restore_use_wartime_cargos_enabled: state
.world_restore
.use_wartime_cargos_enabled,
world_restore_disable_train_crashes_enabled: state
.world_restore
.disable_train_crashes_enabled,
world_restore_disable_train_crashes_and_breakdowns_enabled: state
.world_restore
.disable_train_crashes_and_breakdowns_enabled,
world_restore_ai_ignore_territories_at_startup_enabled: state
.world_restore
.ai_ignore_territories_at_startup_enabled,
world_restore_absolute_counter_restore_kind: state
.world_restore
.absolute_counter_restore_kind
.clone(),
world_restore_absolute_counter_adjustment_context: state
.world_restore
.absolute_counter_adjustment_context
.clone(),
metadata_count: state.metadata.len(),
company_count: state.companies.len(),
event_runtime_record_count: state.event_runtime_records.len(),
candidate_availability_count: state.candidate_availability.len(),
zero_candidate_availability_count: state
.candidate_availability
.values()
.filter(|value| **value == 0)
.count(),
special_condition_count: state.special_conditions.len(),
enabled_special_condition_count: state
.special_conditions
.values()
.filter(|value| **value != 0)
.count(),
save_profile_kind: state.save_profile.profile_kind.clone(),
save_profile_family: state.save_profile.profile_family.clone(),
save_profile_map_path: state.save_profile.map_path.clone(),
save_profile_display_name: state.save_profile.display_name.clone(),
save_profile_selected_year_profile_lane: state.save_profile.selected_year_profile_lane,
save_profile_sandbox_enabled: state.save_profile.sandbox_enabled,
save_profile_campaign_scenario_enabled: state.save_profile.campaign_scenario_enabled,
save_profile_staged_profile_copy_on_restore: state
.save_profile
.staged_profile_copy_on_restore,
total_event_record_service_count: state.service_state.total_event_record_services,
periodic_boundary_call_count: state.service_state.periodic_boundary_calls,
total_trigger_dispatch_count: state

View file

@ -382,20 +382,24 @@ fn collect_anonymous_selector_records(
let mut records = Vec::new();
let mut start = 0usize;
while let Some(relative) = bytes
.get(start..)
.and_then(|slice| slice.windows(PRELUDE.len()).position(|window| window == PRELUDE))
{
while let Some(relative) = bytes.get(start..).and_then(|slice| {
slice
.windows(PRELUDE.len())
.position(|window| window == PRELUDE)
}) {
let record_offset = start + relative;
let name_len = read_u32_le(bytes, record_offset + PRELUDE.len()).unwrap_or(0);
if name_len == 0 {
let selector_word_0 = read_u32_le(bytes, record_offset + 0x10).unwrap_or(0);
let selector_word_0_low_u16 = (selector_word_0 & 0xffff) as u16;
if (0xc352..=0xc39b).contains(&selector_word_0_low_u16) {
let preceding_named_record =
references.iter().rev().find(|reference| reference.offset < record_offset);
let following_named_record =
references.iter().find(|reference| reference.offset > record_offset);
let preceding_named_record = references
.iter()
.rev()
.find(|reference| reference.offset < record_offset);
let following_named_record = references
.iter()
.find(|reference| reference.offset > record_offset);
let selector_word_1 = read_u32_le(bytes, record_offset + 0x14).unwrap_or(0);
let selector_word_0_high_u16 = ((selector_word_0 >> 16) & 0xffff) as u16;
let selector_word_1_middle_u16 = ((selector_word_1 >> 8) & 0xffff) as u16;