Make cargo catalog save-native

This commit is contained in:
Jan Petykiewicz 2026-04-16 14:47:38 -07:00
commit c17b9f55f7
24 changed files with 575 additions and 52 deletions

View file

@ -4483,6 +4483,8 @@ mod tests {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
"../../fixtures/runtime/packed-event-world-scalar-condition-parity-save-slice-fixture.json",
);
let cargo_catalog_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../../fixtures/runtime/packed-event-cargo-catalog-save-slice-fixture.json");
run_runtime_summarize_fixture(&parity_fixture)
.expect("save-slice-backed parity fixture should summarize");
@ -4522,6 +4524,8 @@ mod tests {
.expect("save-slice-backed executable world-scalar condition fixture should summarize");
run_runtime_summarize_fixture(&world_scalar_condition_parity_fixture)
.expect("save-slice-backed parity world-scalar condition fixture should summarize");
run_runtime_summarize_fixture(&cargo_catalog_fixture)
.expect("save-slice-backed cargo catalog fixture should summarize");
}
#[test]
@ -4548,6 +4552,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: None,
notes: vec!["exported for test".to_string()],

View file

@ -178,6 +178,7 @@ mod tests {
selected_player_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(),
@ -265,6 +266,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: None,
notes: vec![],
@ -353,6 +355,7 @@ mod tests {
selected_player_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(),
@ -383,6 +386,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(
rrt_runtime::SmpLoadedEventRuntimeCollectionSummary {

View file

@ -80,6 +80,8 @@ pub struct ExpectedRuntimeSummary {
#[serde(default)]
pub locomotive_catalog_count: Option<usize>,
#[serde(default)]
pub cargo_catalog_count: Option<usize>,
#[serde(default)]
pub territory_count: Option<usize>,
#[serde(default)]
pub company_territory_track_count: Option<usize>,
@ -469,6 +471,14 @@ impl ExpectedRuntimeSummary {
));
}
}
if let Some(count) = self.cargo_catalog_count {
if actual.cargo_catalog_count != count {
mismatches.push(format!(
"cargo_catalog_count mismatch: expected {count}, got {}",
actual.cargo_catalog_count
));
}
}
if let Some(count) = self.territory_count {
if actual.territory_count != count {
mismatches.push(format!(

View file

@ -5,17 +5,17 @@ use serde::{Deserialize, Serialize};
use crate::persistence::{load_runtime_snapshot_document, validate_runtime_snapshot_document};
use crate::{
CalendarPoint, RuntimeCompanyConditionTestScope, RuntimeCompanyControllerKind,
RuntimeCompanyTarget, RuntimeCondition, RuntimeEffect, RuntimeEventRecord,
RuntimeEventRecordTemplate, RuntimeLocomotiveCatalogEntry, RuntimePackedEventCollectionSummary,
RuntimePackedEventCompactControlSummary, RuntimePackedEventConditionRowSummary,
RuntimePackedEventGroupedEffectRowSummary, RuntimePackedEventNegativeSentinelScopeSummary,
RuntimePackedEventRecordSummary, RuntimePackedEventTextBandSummary,
RuntimePlayerConditionTestScope, RuntimePlayerTarget, RuntimeSaveProfileState,
RuntimeServiceState, RuntimeState, RuntimeTerritoryTarget, RuntimeWorldRestoreState,
SmpLoadedPackedEventConditionRowSummary, SmpLoadedPackedEventGroupedEffectRowSummary,
SmpLoadedPackedEventNegativeSentinelScopeSummary, SmpLoadedPackedEventRecordSummary,
SmpLoadedPackedEventTextBandSummary, SmpLoadedSaveSlice,
CalendarPoint, RuntimeCargoCatalogEntry, RuntimeCompanyConditionTestScope,
RuntimeCompanyControllerKind, RuntimeCompanyTarget, RuntimeCondition, RuntimeEffect,
RuntimeEventRecord, RuntimeEventRecordTemplate, RuntimeLocomotiveCatalogEntry,
RuntimePackedEventCollectionSummary, RuntimePackedEventCompactControlSummary,
RuntimePackedEventConditionRowSummary, RuntimePackedEventGroupedEffectRowSummary,
RuntimePackedEventNegativeSentinelScopeSummary, RuntimePackedEventRecordSummary,
RuntimePackedEventTextBandSummary, RuntimePlayerConditionTestScope, RuntimePlayerTarget,
RuntimeSaveProfileState, RuntimeServiceState, RuntimeState, RuntimeTerritoryTarget,
RuntimeWorldRestoreState, SmpLoadedPackedEventConditionRowSummary,
SmpLoadedPackedEventGroupedEffectRowSummary, SmpLoadedPackedEventNegativeSentinelScopeSummary,
SmpLoadedPackedEventRecordSummary, SmpLoadedPackedEventTextBandSummary, SmpLoadedSaveSlice,
};
pub const STATE_DUMP_FORMAT_VERSION: u32 = 1;
@ -96,6 +96,7 @@ struct SaveSliceProjection {
candidate_availability: BTreeMap<String, u32>,
named_locomotive_availability: BTreeMap<String, u32>,
locomotive_catalog: Option<Vec<RuntimeLocomotiveCatalogEntry>>,
cargo_catalog: Option<Vec<RuntimeCargoCatalogEntry>>,
named_locomotive_cost: BTreeMap<String, u32>,
cargo_production_overrides: BTreeMap<u32, u32>,
special_conditions: BTreeMap<String, u32>,
@ -245,6 +246,7 @@ pub fn project_save_slice_to_runtime_state_import(
selected_player_id: None,
trains: Vec::new(),
locomotive_catalog: projection.locomotive_catalog.unwrap_or_default(),
cargo_catalog: projection.cargo_catalog.unwrap_or_default(),
territories: Vec::new(),
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
@ -309,6 +311,9 @@ pub fn project_save_slice_overlay_to_runtime_state_import(
locomotive_catalog: projection
.locomotive_catalog
.unwrap_or_else(|| base_state.locomotive_catalog.clone()),
cargo_catalog: projection
.cargo_catalog
.unwrap_or_else(|| base_state.cargo_catalog.clone()),
territories: base_state.territories.clone(),
company_territory_track_piece_counts: base_state
.company_territory_track_piece_counts
@ -359,6 +364,10 @@ fn project_save_slice_components(
save_slice.locomotive_catalog.is_some()
|| save_slice.named_locomotive_availability_table.is_some(),
);
world_flags.insert(
"save_slice.cargo_catalog_present".to_string(),
save_slice.cargo_catalog.is_some(),
);
world_flags.insert(
"save_slice.event_runtime_collection_present".to_string(),
save_slice.event_runtime_collection.is_some(),
@ -690,6 +699,44 @@ fn project_save_slice_components(
} else {
None
};
let cargo_catalog = if let Some(catalog) = &save_slice.cargo_catalog {
metadata.insert(
"save_slice.cargo_catalog_source_kind".to_string(),
catalog.source_kind.clone(),
);
metadata.insert(
"save_slice.cargo_catalog_semantic_family".to_string(),
catalog.semantic_family.clone(),
);
metadata.insert(
"save_slice.cargo_catalog_entry_count".to_string(),
catalog.observed_entry_count.to_string(),
);
if let Some(root_offset) = catalog.root_offset {
metadata.insert(
"save_slice.cargo_catalog_root_offset".to_string(),
root_offset.to_string(),
);
}
Some(
catalog
.entries
.iter()
.map(|entry| RuntimeCargoCatalogEntry {
slot_id: entry.slot_id,
label: entry.label.clone(),
supplied_token_stem: entry
.supplied_cargo_token_probable_high16_ascii_stem
.clone(),
demanded_token_stem: entry
.demanded_cargo_token_probable_high16_ascii_stem
.clone(),
})
.collect::<Vec<_>>(),
)
} else {
None
};
let named_locomotive_cost = BTreeMap::new();
let cargo_production_overrides = BTreeMap::new();
@ -702,8 +749,11 @@ fn project_save_slice_components(
.collect();
}
let (packed_event_collection, event_runtime_records) =
project_packed_event_collection(save_slice, &packed_event_context)?;
let (packed_event_collection, event_runtime_records) = project_packed_event_collection(
save_slice,
&packed_event_context,
cargo_catalog.as_deref().unwrap_or(&[]),
)?;
if let Some(summary) = &save_slice.event_runtime_collection {
metadata.insert(
"save_slice.event_runtime_collection_source_kind".to_string(),
@ -741,6 +791,7 @@ fn project_save_slice_components(
candidate_availability,
named_locomotive_availability,
locomotive_catalog,
cargo_catalog,
named_locomotive_cost,
cargo_production_overrides,
special_conditions,
@ -750,6 +801,7 @@ fn project_save_slice_components(
fn project_packed_event_collection(
save_slice: &SmpLoadedSaveSlice,
company_context: &ImportRuntimeContext,
cargo_catalog: &[RuntimeCargoCatalogEntry],
) -> Result<
(
Option<RuntimePackedEventCollectionSummary>,
@ -780,6 +832,7 @@ fn project_packed_event_collection(
runtime_packed_event_record_summary_from_smp(
record,
company_context,
cargo_catalog,
imported_record_ids.contains(&record.live_entry_id),
)
})
@ -810,6 +863,7 @@ fn project_packed_event_collection(
fn runtime_packed_event_record_summary_from_smp(
record: &SmpLoadedPackedEventRecordSummary,
company_context: &ImportRuntimeContext,
cargo_catalog: &[RuntimeCargoCatalogEntry],
imported: bool,
) -> RuntimePackedEventRecordSummary {
let lowered_decoded_conditions = lowered_record_decoded_conditions(record, company_context)
@ -840,7 +894,7 @@ fn runtime_packed_event_record_summary_from_smp(
standalone_condition_rows: record
.standalone_condition_rows
.iter()
.map(runtime_packed_event_condition_row_summary_from_smp)
.map(|row| runtime_packed_event_condition_row_summary_from_smp(row, cargo_catalog))
.collect(),
negative_sentinel_scope: record
.negative_sentinel_scope
@ -850,7 +904,7 @@ fn runtime_packed_event_record_summary_from_smp(
grouped_effect_rows: record
.grouped_effect_rows
.iter()
.map(runtime_packed_event_grouped_effect_row_summary_from_smp)
.map(|row| runtime_packed_event_grouped_effect_row_summary_from_smp(row, cargo_catalog))
.collect(),
grouped_company_targets: classify_real_grouped_company_targets(record),
decoded_conditions: lowered_decoded_conditions,
@ -906,7 +960,11 @@ fn runtime_packed_event_text_band_summary_from_smp(
fn runtime_packed_event_condition_row_summary_from_smp(
row: &crate::SmpLoadedPackedEventConditionRowSummary,
cargo_catalog: &[RuntimeCargoCatalogEntry],
) -> RuntimePackedEventConditionRowSummary {
let cargo_entry = row
.recovered_cargo_slot
.and_then(|slot| cargo_catalog.iter().find(|entry| entry.slot_id == slot));
RuntimePackedEventConditionRowSummary {
row_index: row.row_index,
raw_condition_id: row.raw_condition_id,
@ -918,13 +976,26 @@ fn runtime_packed_event_condition_row_summary_from_smp(
semantic_family: row.semantic_family.clone(),
semantic_preview: row.semantic_preview.clone(),
requires_candidate_name_binding: row.requires_candidate_name_binding,
recovered_cargo_slot: row.recovered_cargo_slot,
recovered_cargo_class: row.recovered_cargo_class.clone(),
recovered_cargo_label: cargo_entry
.map(|entry| entry.label.clone())
.or_else(|| row.recovered_cargo_slot.map(default_cargo_slot_label)),
recovered_cargo_supplied_token_stem: cargo_entry
.and_then(|entry| entry.supplied_token_stem.clone()),
recovered_cargo_demanded_token_stem: cargo_entry
.and_then(|entry| entry.demanded_token_stem.clone()),
notes: row.notes.clone(),
}
}
fn runtime_packed_event_grouped_effect_row_summary_from_smp(
row: &crate::SmpLoadedPackedEventGroupedEffectRowSummary,
cargo_catalog: &[RuntimeCargoCatalogEntry],
) -> RuntimePackedEventGroupedEffectRowSummary {
let cargo_entry = row
.recovered_cargo_slot
.and_then(|slot| cargo_catalog.iter().find(|entry| entry.slot_id == slot));
RuntimePackedEventGroupedEffectRowSummary {
group_index: row.group_index,
row_index: row.row_index,
@ -943,12 +1014,25 @@ fn runtime_packed_event_grouped_effect_row_summary_from_smp(
row_shape: row.row_shape.clone(),
semantic_family: row.semantic_family.clone(),
semantic_preview: row.semantic_preview.clone(),
recovered_cargo_slot: row.recovered_cargo_slot,
recovered_cargo_class: row.recovered_cargo_class.clone(),
recovered_cargo_label: cargo_entry
.map(|entry| entry.label.clone())
.or_else(|| row.recovered_cargo_slot.map(default_cargo_slot_label)),
recovered_cargo_supplied_token_stem: cargo_entry
.and_then(|entry| entry.supplied_token_stem.clone()),
recovered_cargo_demanded_token_stem: cargo_entry
.and_then(|entry| entry.demanded_token_stem.clone()),
recovered_locomotive_id: row.recovered_locomotive_id,
locomotive_name: row.locomotive_name.clone(),
notes: row.notes.clone(),
}
}
fn default_cargo_slot_label(slot: u32) -> String {
format!("Cargo Production Slot {slot}")
}
fn smp_packed_record_to_runtime_event_record(
record: &SmpLoadedPackedEventRecordSummary,
company_context: &ImportRuntimeContext,
@ -2251,6 +2335,17 @@ fn determine_packed_event_import_outcome(
{
return "blocked_retire_train_variant".to_string();
}
if record
.standalone_condition_rows
.iter()
.any(|row| row.raw_condition_id >= 0)
{
if record_has_world_state_condition_rows(record) {
return "blocked_unmapped_world_condition".to_string();
} else {
return "blocked_unmapped_ordinary_condition".to_string();
}
}
if record
.grouped_effect_rows
.iter()
@ -2258,19 +2353,7 @@ fn determine_packed_event_import_outcome(
{
return "blocked_unmapped_world_descriptor".to_string();
}
return if record
.standalone_condition_rows
.iter()
.any(|row| row.raw_condition_id >= 0)
{
if record_has_world_state_condition_rows(record) {
"blocked_unmapped_world_condition".to_string()
} else {
"blocked_unmapped_ordinary_condition".to_string()
}
} else {
"blocked_unmapped_real_descriptor".to_string()
};
return "blocked_unmapped_real_descriptor".to_string();
}
if let Some(blocker) = packed_record_condition_scope_import_blocker(record, company_context)
{
@ -2952,6 +3035,7 @@ mod tests {
selected_player_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(),
@ -3717,6 +3801,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: None,
notes: vec![],
@ -3757,6 +3842,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: None,
notes: vec![],
@ -3871,6 +3957,7 @@ mod tests {
},
),
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: Some(crate::SmpLoadedSpecialConditionsTable {
source_kind: "save-fixed-special-conditions-range".to_string(),
table_offset: 0x0d64,
@ -4196,6 +4283,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -4311,6 +4399,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -4404,6 +4493,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -4515,6 +4605,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -4599,6 +4690,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -4733,6 +4825,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -4975,6 +5068,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -5052,6 +5146,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -5153,6 +5248,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -5227,6 +5323,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -5328,6 +5425,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -5405,6 +5503,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: Some(save_named_locomotive_table(112)),
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -5522,6 +5621,7 @@ mod tests {
name: "Locomotive 112".to_string(),
},
],
cargo_catalog: Vec::new(),
territories: Vec::new(),
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
@ -5548,6 +5648,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -5650,6 +5751,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -5726,6 +5828,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -5802,6 +5905,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: Some(save_named_locomotive_table(112)),
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -5912,6 +6016,7 @@ mod tests {
name: "Locomotive 101".to_string(),
},
],
cargo_catalog: Vec::new(),
territories: Vec::new(),
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
@ -5938,6 +6043,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -6033,6 +6139,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -6107,6 +6214,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -6191,6 +6299,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -6294,6 +6403,7 @@ mod tests {
selected_player_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(),
@ -6317,6 +6427,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -6464,6 +6575,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -6568,6 +6680,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -6653,6 +6766,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -6759,6 +6873,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -6865,6 +6980,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -6961,6 +7077,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -7053,6 +7170,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -7153,6 +7271,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -7244,6 +7363,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -7317,6 +7437,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -7395,6 +7516,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -7478,6 +7600,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -7561,6 +7684,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -7660,6 +7784,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -7750,6 +7875,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -7866,6 +7992,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -7995,6 +8122,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -8292,6 +8420,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -8402,6 +8531,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -8575,6 +8705,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -8668,6 +8799,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -8757,6 +8889,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -8911,6 +9044,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -9054,6 +9188,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -9144,6 +9279,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -9261,6 +9397,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -9366,6 +9503,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -9474,6 +9612,7 @@ mod tests {
selected_player_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(),
@ -9512,6 +9651,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -9656,6 +9796,7 @@ mod tests {
selected_player_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(),
@ -9687,6 +9828,7 @@ mod tests {
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),

View file

@ -35,29 +35,30 @@ pub use pk4::{
extract_pk4_entry_bytes, extract_pk4_entry_file, inspect_pk4_bytes, inspect_pk4_file,
};
pub use runtime::{
RuntimeCompany, RuntimeCompanyConditionTestScope, RuntimeCompanyControllerKind,
RuntimeCompanyMetric, 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, RuntimeWorldRestoreState,
RuntimeCargoCatalogEntry, RuntimeCompany, RuntimeCompanyConditionTestScope,
RuntimeCompanyControllerKind, RuntimeCompanyMetric, 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,
RuntimeWorldRestoreState,
};
pub use smp::{
SMP_FOUR_SIDECAR_BYTE_PLANES_MIN_BUNDLE_VERSION, SmpAlignedRuntimeRuleBandLane,
SmpAlignedRuntimeRuleBandProbe, SmpAsciiPreview, SmpClassicPackedProfileBlock,
SmpClassicRehydrateProfileProbe, SmpContainerProfile, SmpEarlyContentProbe,
SmpHeaderVariantProbe, SmpInspectionReport, SmpKnownTagHit,
SmpLoadedCandidateAvailabilityTable, SmpLoadedEventRuntimeCollectionSummary,
SmpLoadedNamedLocomotiveAvailabilityTable, SmpLoadedPackedEventCompactControlSummary,
SmpLoadedPackedEventConditionRowSummary, SmpLoadedPackedEventGroupedEffectRowSummary,
SmpLoadedPackedEventNegativeSentinelScopeSummary, SmpLoadedPackedEventRecordSummary,
SmpLoadedPackedEventTextBandSummary, SmpLoadedProfile, SmpLoadedSaveSlice,
SmpLoadedSpecialConditionsTable, SmpLocomotivePolicyFieldObservation,
SmpLoadedCandidateAvailabilityTable, SmpLoadedCargoCatalog, SmpLoadedCargoCatalogEntry,
SmpLoadedEventRuntimeCollectionSummary, SmpLoadedNamedLocomotiveAvailabilityTable,
SmpLoadedPackedEventCompactControlSummary, SmpLoadedPackedEventConditionRowSummary,
SmpLoadedPackedEventGroupedEffectRowSummary, SmpLoadedPackedEventNegativeSentinelScopeSummary,
SmpLoadedPackedEventRecordSummary, SmpLoadedPackedEventTextBandSummary, SmpLoadedProfile,
SmpLoadedSaveSlice, SmpLoadedSpecialConditionsTable, SmpLocomotivePolicyFieldObservation,
SmpLocomotivePolicyFloatAlignmentCandidate, SmpLocomotivePolicyNeighborhoodProbe,
SmpPackedProfileWordLane, SmpPostSpecialConditionsScalarLane,
SmpPostSpecialConditionsScalarProbe, SmpPostTextFieldNeighborhoodProbe,

View file

@ -98,6 +98,7 @@ mod tests {
selected_player_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(),

View file

@ -113,6 +113,16 @@ pub struct RuntimeLocomotiveCatalogEntry {
pub name: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RuntimeCargoCatalogEntry {
pub slot_id: u32,
pub label: String,
#[serde(default)]
pub supplied_token_stem: Option<String>,
#[serde(default)]
pub demanded_token_stem: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum RuntimeCompanyTarget {
@ -533,6 +543,16 @@ pub struct RuntimePackedEventConditionRowSummary {
#[serde(default)]
pub requires_candidate_name_binding: bool,
#[serde(default)]
pub recovered_cargo_slot: Option<u32>,
#[serde(default)]
pub recovered_cargo_class: Option<String>,
#[serde(default)]
pub recovered_cargo_label: Option<String>,
#[serde(default)]
pub recovered_cargo_supplied_token_stem: Option<String>,
#[serde(default)]
pub recovered_cargo_demanded_token_stem: Option<String>,
#[serde(default)]
pub notes: Vec<String>,
}
@ -561,6 +581,16 @@ pub struct RuntimePackedEventGroupedEffectRowSummary {
#[serde(default)]
pub semantic_preview: Option<String>,
#[serde(default)]
pub recovered_cargo_slot: Option<u32>,
#[serde(default)]
pub recovered_cargo_class: Option<String>,
#[serde(default)]
pub recovered_cargo_label: Option<String>,
#[serde(default)]
pub recovered_cargo_supplied_token_stem: Option<String>,
#[serde(default)]
pub recovered_cargo_demanded_token_stem: Option<String>,
#[serde(default)]
pub recovered_locomotive_id: Option<u32>,
#[serde(default)]
pub locomotive_name: Option<String>,
@ -684,6 +714,8 @@ pub struct RuntimeState {
#[serde(default)]
pub locomotive_catalog: Vec<RuntimeLocomotiveCatalogEntry>,
#[serde(default)]
pub cargo_catalog: Vec<RuntimeCargoCatalogEntry>,
#[serde(default)]
pub territories: Vec<RuntimeTerritory>,
#[serde(default)]
pub company_territory_track_piece_counts: Vec<RuntimeCompanyTerritoryTrackPieceCount>,
@ -837,6 +869,28 @@ impl RuntimeState {
));
}
}
let mut seen_cargo_slots = BTreeSet::new();
let mut seen_cargo_labels = BTreeSet::new();
for entry in &self.cargo_catalog {
if !(1..=11).contains(&entry.slot_id) {
return Err(format!(
"cargo_catalog entry has out-of-range slot_id {}",
entry.slot_id
));
}
if !seen_cargo_slots.insert(entry.slot_id) {
return Err(format!("duplicate cargo_catalog.slot_id {}", entry.slot_id));
}
if entry.label.trim().is_empty() {
return Err(format!(
"cargo_catalog entry {} has an empty label",
entry.slot_id
));
}
if !seen_cargo_labels.insert(entry.label.clone()) {
return Err(format!("duplicate cargo_catalog.label {:?}", entry.label));
}
}
for entry in &self.company_territory_track_piece_counts {
if !seen_company_ids.contains(&entry.company_id) {
return Err(format!(
@ -1550,6 +1604,7 @@ mod tests {
selected_player_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(),
@ -1610,6 +1665,7 @@ mod tests {
selected_player_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(),
@ -1655,6 +1711,7 @@ mod tests {
selected_player_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(),
@ -1713,6 +1770,7 @@ mod tests {
selected_player_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(),
@ -1771,6 +1829,7 @@ mod tests {
selected_player_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(),
@ -1880,6 +1939,7 @@ mod tests {
selected_player_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(),
@ -1925,6 +1985,7 @@ mod tests {
selected_player_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(),
@ -1987,6 +2048,7 @@ mod tests {
},
],
locomotive_catalog: Vec::new(),
cargo_catalog: Vec::new(),
territories: Vec::new(),
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
@ -2039,6 +2101,7 @@ mod tests {
retired: false,
}],
locomotive_catalog: Vec::new(),
cargo_catalog: Vec::new(),
territories: Vec::new(),
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
@ -2091,6 +2154,7 @@ mod tests {
retired: false,
}],
locomotive_catalog: Vec::new(),
cargo_catalog: Vec::new(),
territories: vec![RuntimeTerritory {
territory_id: 1,
name: Some("Appalachia".to_string()),
@ -2147,6 +2211,7 @@ mod tests {
retired: true,
}],
locomotive_catalog: Vec::new(),
cargo_catalog: Vec::new(),
territories: Vec::new(),
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
@ -2192,6 +2257,7 @@ mod tests {
selected_player_id: None,
trains: Vec::new(),
locomotive_catalog: Vec::new(),
cargo_catalog: Vec::new(),
territories: vec![RuntimeTerritory {
territory_id: 7,
name: Some("Appalachia".to_string()),
@ -2250,6 +2316,7 @@ mod tests {
selected_player_id: None,
trains: Vec::new(),
locomotive_catalog: Vec::new(),
cargo_catalog: Vec::new(),
territories: vec![RuntimeTerritory {
territory_id: 7,
name: Some("Appalachia".to_string()),
@ -2302,6 +2369,7 @@ mod tests {
selected_player_id: None,
trains: Vec::new(),
locomotive_catalog: Vec::new(),
cargo_catalog: Vec::new(),
territories: vec![RuntimeTerritory {
territory_id: 7,
name: Some("Appalachia".to_string()),

View file

@ -1625,6 +1625,33 @@ pub struct SmpLoadedLocomotiveCatalog {
pub entries: Vec<SmpLoadedLocomotiveCatalogEntry>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpLoadedCargoCatalogEntry {
pub slot_id: u32,
pub label: String,
pub book_index: usize,
pub max_annual_production_word: u32,
pub mode_word: u32,
pub runtime_import_branch_kind: String,
pub annual_amount_word: u32,
pub supplied_cargo_token_word: u32,
#[serde(default)]
pub supplied_cargo_token_probable_high16_ascii_stem: Option<String>,
pub demanded_cargo_token_word: u32,
#[serde(default)]
pub demanded_cargo_token_probable_high16_ascii_stem: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpLoadedCargoCatalog {
pub source_kind: String,
pub semantic_family: String,
#[serde(default)]
pub root_offset: Option<usize>,
pub observed_entry_count: usize,
pub entries: Vec<SmpLoadedCargoCatalogEntry>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpLoadedSpecialConditionsTable {
pub source_kind: String,
@ -1808,6 +1835,8 @@ pub struct SmpLoadedSaveSlice {
pub named_locomotive_availability_table: Option<SmpLoadedNamedLocomotiveAvailabilityTable>,
#[serde(default)]
pub locomotive_catalog: Option<SmpLoadedLocomotiveCatalog>,
#[serde(default)]
pub cargo_catalog: Option<SmpLoadedCargoCatalog>,
pub special_conditions_table: Option<SmpLoadedSpecialConditionsTable>,
pub event_runtime_collection: Option<SmpLoadedEventRuntimeCollectionSummary>,
pub notes: Vec<String>,
@ -1972,6 +2001,10 @@ pub fn load_save_slice_from_report(
let locomotive_catalog = named_locomotive_availability_table
.as_ref()
.and_then(derive_locomotive_catalog_from_named_availability_table);
let cargo_catalog = report
.recipe_book_summary_probe
.as_ref()
.and_then(derive_cargo_catalog_from_recipe_book_probe);
let special_conditions_table =
report
.special_conditions_probe
@ -1996,6 +2029,7 @@ pub fn load_save_slice_from_report(
candidate_availability_table,
named_locomotive_availability_table,
locomotive_catalog,
cargo_catalog,
special_conditions_table,
event_runtime_collection: report.event_runtime_collection_summary.clone(),
notes: summary.notes.clone(),
@ -2028,6 +2062,57 @@ fn derive_locomotive_catalog_from_named_availability_table(
})
}
fn derive_cargo_catalog_from_recipe_book_probe(
probe: &SmpRecipeBookSummaryProbe,
) -> Option<SmpLoadedCargoCatalog> {
if probe.books.is_empty() {
return None;
}
let entries = probe
.books
.iter()
.filter(|book| book.book_index < 11)
.filter_map(|book| {
let line = book
.lines
.iter()
.find(|line| line.imports_to_runtime_descriptor)
.or_else(|| book.lines.first())?;
let slot_id = (book.book_index + 1) as u32;
Some(SmpLoadedCargoCatalogEntry {
slot_id,
label: format!("Cargo Production Slot {slot_id}"),
book_index: book.book_index,
max_annual_production_word: book.max_annual_production_word,
mode_word: line.mode_word,
runtime_import_branch_kind: line.runtime_import_branch_kind.clone(),
annual_amount_word: line.annual_amount_word,
supplied_cargo_token_word: line.supplied_cargo_token_word,
supplied_cargo_token_probable_high16_ascii_stem: line
.supplied_cargo_token_probable_high16_ascii_stem
.clone(),
demanded_cargo_token_word: line.demanded_cargo_token_word,
demanded_cargo_token_probable_high16_ascii_stem: line
.demanded_cargo_token_probable_high16_ascii_stem
.clone(),
})
})
.collect::<Vec<_>>();
if entries.is_empty() {
return None;
}
Some(SmpLoadedCargoCatalog {
source_kind: format!("{}-slot-catalog", probe.source_kind),
semantic_family: "scenario-save-derived-cargo-catalog".to_string(),
root_offset: Some(probe.root_offset),
observed_entry_count: entries.len(),
entries,
})
}
fn parse_event_runtime_collection_summary(
bytes: &[u8],
container_profile: Option<&SmpContainerProfile>,

View file

@ -1249,6 +1249,7 @@ mod tests {
selected_player_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(),

View file

@ -37,6 +37,7 @@ pub struct RuntimeSummary {
pub active_train_count: usize,
pub retired_train_count: usize,
pub locomotive_catalog_count: usize,
pub cargo_catalog_count: usize,
pub territory_count: usize,
pub company_territory_track_count: usize,
pub packed_event_collection_present: bool,
@ -172,6 +173,7 @@ impl RuntimeSummary {
active_train_count: state.trains.iter().filter(|train| train.active).count(),
retired_train_count: state.trains.iter().filter(|train| train.retired).count(),
locomotive_catalog_count: state.locomotive_catalog.len(),
cargo_catalog_count: state.cargo_catalog.len(),
territory_count: state.territories.len(),
company_territory_track_count: state.company_territory_track_piece_counts.len(),
packed_event_collection_present: state.packed_event_collection.is_some(),
@ -675,6 +677,7 @@ mod tests {
name: "Locomotive 112".to_string(),
},
],
cargo_catalog: Vec::new(),
territories: Vec::new(),
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
@ -917,6 +920,7 @@ mod tests {
name: "Locomotive 112".to_string(),
},
],
cargo_catalog: Vec::new(),
territories: Vec::new(),
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
@ -963,6 +967,7 @@ mod tests {
name: "Locomotive 112".to_string(),
},
],
cargo_catalog: Vec::new(),
territories: Vec::new(),
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
@ -1006,6 +1011,7 @@ mod tests {
selected_player_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(),
@ -1049,6 +1055,7 @@ mod tests {
selected_player_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(),
@ -1087,6 +1094,7 @@ mod tests {
selected_player_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(),
@ -1194,6 +1202,7 @@ mod tests {
selected_player_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(),