Load placed-structure triplets into save slices

This commit is contained in:
Jan Petykiewicz 2026-04-19 03:12:53 -07:00
commit 7abd582aea
5 changed files with 433 additions and 1 deletions

View file

@ -5783,6 +5783,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: None,
notes: vec!["exported for test".to_string()],

View file

@ -1439,6 +1439,39 @@ fn project_save_slice_components(
)
};
if let Some(collection) = &save_slice.placed_structure_collection {
metadata.insert(
"save_slice.placed_structure_collection_source_kind".to_string(),
collection.source_kind.clone(),
);
metadata.insert(
"save_slice.placed_structure_collection_semantic_family".to_string(),
collection.semantic_family.clone(),
);
metadata.insert(
"save_slice.placed_structure_collection_entry_count".to_string(),
collection.observed_entry_count.to_string(),
);
metadata.insert(
"save_slice.placed_structure_collection_farm_growth_stage_count".to_string(),
collection
.entries
.iter()
.filter(|entry| entry.farm_growth_stage_index.is_some())
.count()
.to_string(),
);
metadata.insert(
"save_slice.placed_structure_collection_nondefault_status_count".to_string(),
collection
.entries
.iter()
.filter(|entry| entry.profile_status_kind != "unset")
.count()
.to_string(),
);
}
let named_locomotive_cost = BTreeMap::new();
let all_cargo_price_override = None;
let named_cargo_price_overrides = BTreeMap::new();
@ -5471,6 +5504,42 @@ mod tests {
}
}
fn save_placed_structure_collection() -> crate::SmpLoadedPlacedStructureCollection {
crate::SmpLoadedPlacedStructureCollection {
source_kind: "save-placed-structure-record-triplets".to_string(),
semantic_family: "scenario-save-placed-structure-triplet-collection".to_string(),
observed_entry_count: 2,
entries: vec![
crate::SmpLoadedPlacedStructureEntry {
record_index: 0,
primary_name: "FarmCorn".to_string(),
secondary_name: "FarmSet".to_string(),
policy_trailing_word: 1,
policy_trailing_word_hex: "0x0001".to_string(),
profile_payload_dword: 0,
profile_payload_dword_hex: "0x00000000".to_string(),
profile_status_kind: "farm_growth_stage_bucket".to_string(),
farm_growth_stage_index: Some(4),
profile_companion_byte_u8: Some(0),
profile_companion_byte_hex: Some("0x00".to_string()),
},
crate::SmpLoadedPlacedStructureEntry {
record_index: 1,
primary_name: "StationA".to_string(),
secondary_name: "StationSetA".to_string(),
policy_trailing_word: 1,
policy_trailing_word_hex: "0x0001".to_string(),
profile_payload_dword: 0x00005dc1,
profile_payload_dword_hex: "0x00005dc1".to_string(),
profile_status_kind: "opaque_nondefault".to_string(),
farm_growth_stage_index: None,
profile_companion_byte_u8: Some(7),
profile_companion_byte_hex: Some("0x07".to_string()),
},
],
}
}
fn save_chairman_profile_table() -> crate::SmpLoadedChairmanProfileTable {
crate::SmpLoadedChairmanProfileTable {
source_kind: "tracked-save-slice-chairman-profile-table".to_string(),
@ -5970,6 +6039,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: None,
notes: vec![],
@ -6017,6 +6087,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: None,
notes: vec![],
@ -6246,6 +6317,7 @@ mod tests {
}),
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: Some(crate::SmpLoadedSpecialConditionsTable {
source_kind: "save-fixed-special-conditions-range".to_string(),
table_offset: 0x0d64,
@ -6793,6 +6865,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: Some(save_company_roster()),
chairman_profile_table: Some(save_chairman_profile_table()),
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: None,
notes: vec![],
@ -6823,6 +6896,73 @@ mod tests {
);
}
#[test]
fn projects_placed_structure_collection_metadata_from_save_slice() {
let save_slice = SmpLoadedSaveSlice {
file_extension_hint: Some("gms".to_string()),
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
mechanism_family: "classic-save-rehydrate-v1".to_string(),
mechanism_confidence: "grounded".to_string(),
trailer_family: None,
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
locomotive_catalog: None,
cargo_catalog: None,
world_issue_37_state: None,
world_economic_tuning_state: None,
world_finance_neighborhood_state: None,
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: Some(save_placed_structure_collection()),
special_conditions_table: None,
event_runtime_collection: None,
notes: vec![],
};
let import = project_save_slice_to_runtime_state_import(
&save_slice,
"save-native-placed-structures",
None,
)
.expect("save slice should project");
assert_eq!(
import
.state
.metadata
.get("save_slice.placed_structure_collection_source_kind")
.map(String::as_str),
Some("save-placed-structure-record-triplets")
);
assert_eq!(
import
.state
.metadata
.get("save_slice.placed_structure_collection_entry_count")
.map(String::as_str),
Some("2")
);
assert_eq!(
import
.state
.metadata
.get("save_slice.placed_structure_collection_farm_growth_stage_count")
.map(String::as_str),
Some("1")
);
assert_eq!(
import
.state
.metadata
.get("save_slice.placed_structure_collection_nondefault_status_count")
.map(String::as_str),
Some("2")
);
}
#[test]
fn overlay_replaces_base_company_and_chairman_context_from_save_slice() {
let base_state = RuntimeState {
@ -6881,6 +7021,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: Some(save_company_roster()),
chairman_profile_table: Some(save_chairman_profile_table()),
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: None,
notes: vec![],
@ -7052,6 +7193,7 @@ mod tests {
selected_chairman_profile_id: Some(1),
entries: Vec::new(),
}),
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: None,
notes: vec![],
@ -7101,6 +7243,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -7233,6 +7376,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -7343,6 +7487,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -7479,6 +7624,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -7576,6 +7722,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -7741,6 +7888,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -7996,6 +8144,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -8086,6 +8235,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -8200,6 +8350,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -8287,6 +8438,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: Some(save_company_roster()),
chairman_profile_table: Some(save_chairman_profile_table()),
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -8377,6 +8529,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: Some(save_company_roster()),
chairman_profile_table: Some(save_chairman_profile_table()),
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -8480,6 +8633,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -8599,6 +8753,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -8689,6 +8844,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -8856,6 +9012,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -8968,6 +9125,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -9057,6 +9215,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -9146,6 +9305,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -9309,6 +9469,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -9417,6 +9578,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -9504,6 +9666,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -9601,6 +9764,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -9707,6 +9871,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -9821,6 +9986,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -9924,6 +10090,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -10013,6 +10180,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -10172,6 +10340,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -10341,6 +10510,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -10458,6 +10628,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -10556,6 +10727,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -10681,6 +10853,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -10800,6 +10973,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -10909,6 +11083,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -11014,6 +11189,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -11133,6 +11309,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -11237,6 +11414,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -11323,6 +11501,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -11414,6 +11593,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -11510,6 +11690,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -11606,6 +11787,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -11718,6 +11900,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -11821,6 +12004,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -11971,6 +12155,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -12120,6 +12305,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -12660,6 +12846,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -12854,6 +13041,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -12993,6 +13181,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: Some(save_company_roster()),
chairman_profile_table: Some(save_chairman_profile_table()),
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -13131,6 +13320,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: Some(save_company_roster()),
chairman_profile_table: Some(save_chairman_profile_table()),
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -13270,6 +13460,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -13393,6 +13584,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -13591,6 +13783,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -13697,6 +13890,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -13805,6 +13999,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -13984,6 +14179,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -14146,6 +14342,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -14249,6 +14446,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -14385,6 +14583,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -14509,6 +14708,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -14707,6 +14907,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -14915,6 +15116,7 @@ mod tests {
world_locomotive_policy_state: None,
company_roster: None,
chairman_profile_table: None,
placed_structure_collection: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),

View file

@ -109,7 +109,8 @@ pub use smp::{
SmpLoadedNamedLocomotiveAvailabilityTable, SmpLoadedPackedEventCompactControlSummary,
SmpLoadedPackedEventConditionRowSummary, SmpLoadedPackedEventGroupedEffectRowSummary,
SmpLoadedPackedEventNegativeSentinelScopeSummary, SmpLoadedPackedEventRecordSummary,
SmpLoadedPackedEventTextBandSummary, SmpLoadedProfile, SmpLoadedSaveSlice,
SmpLoadedPackedEventTextBandSummary, SmpLoadedPlacedStructureCollection,
SmpLoadedPlacedStructureEntry, SmpLoadedProfile, SmpLoadedSaveSlice,
SmpLoadedSpecialConditionsTable, SmpLoadedWorldEconomicTuningState,
SmpLoadedWorldFinanceNeighborhoodState, SmpLoadedWorldIssue37State,
SmpLocomotivePolicyFieldObservation, SmpLocomotivePolicyFloatAlignmentCandidate,

View file

@ -1928,6 +1928,33 @@ pub struct SmpSavePlacedStructureRecordTripletProbe {
pub evidence: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpLoadedPlacedStructureEntry {
pub record_index: usize,
pub primary_name: String,
pub secondary_name: String,
pub policy_trailing_word: u16,
pub policy_trailing_word_hex: String,
pub profile_payload_dword: u32,
pub profile_payload_dword_hex: String,
pub profile_status_kind: String,
#[serde(default)]
pub farm_growth_stage_index: Option<u8>,
#[serde(default)]
pub profile_companion_byte_u8: Option<u8>,
#[serde(default)]
pub profile_companion_byte_hex: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpLoadedPlacedStructureCollection {
pub source_kind: String,
pub semantic_family: String,
pub observed_entry_count: usize,
#[serde(default)]
pub entries: Vec<SmpLoadedPlacedStructureEntry>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpSavePlacedStructureDynamicSideBufferProbe {
pub profile_family: String,
@ -4038,6 +4065,8 @@ pub struct SmpLoadedSaveSlice {
pub company_roster: Option<SmpLoadedCompanyRoster>,
#[serde(default)]
pub chairman_profile_table: Option<SmpLoadedChairmanProfileTable>,
#[serde(default)]
pub placed_structure_collection: Option<SmpLoadedPlacedStructureCollection>,
pub special_conditions_table: Option<SmpLoadedSpecialConditionsTable>,
pub event_runtime_collection: Option<SmpLoadedEventRuntimeCollectionSummary>,
pub notes: Vec<String>,
@ -6830,6 +6859,33 @@ pub fn inspect_smp_bytes(bytes: &[u8]) -> SmpInspectionReport {
inspect_bundle_bytes(bytes, None)
}
fn derive_loaded_placed_structure_collection_from_probe(
probe: &SmpSavePlacedStructureRecordTripletProbe,
) -> SmpLoadedPlacedStructureCollection {
SmpLoadedPlacedStructureCollection {
source_kind: probe.source_kind.clone(),
semantic_family: "scenario-save-placed-structure-triplet-collection".to_string(),
observed_entry_count: probe.record_count,
entries: probe
.entries
.iter()
.map(|entry| SmpLoadedPlacedStructureEntry {
record_index: entry.record_index,
primary_name: entry.primary_name.clone(),
secondary_name: entry.secondary_name.clone(),
policy_trailing_word: entry.policy_trailing_word,
policy_trailing_word_hex: entry.policy_trailing_word_hex.clone(),
profile_payload_dword: entry.profile_payload_dword,
profile_payload_dword_hex: entry.profile_payload_dword_hex.clone(),
profile_status_kind: entry.profile_status_kind.clone(),
farm_growth_stage_index: entry.farm_growth_stage_index,
profile_companion_byte_u8: entry.profile_companion_byte_u8,
profile_companion_byte_hex: entry.profile_companion_byte_hex.clone(),
})
.collect(),
}
}
pub fn load_save_slice_file(path: &Path) -> Result<SmpLoadedSaveSlice, Box<dyn std::error::Error>> {
let inspection = inspect_smp_file(path)?;
load_save_slice_from_report(&inspection)
@ -6974,6 +7030,10 @@ pub fn load_save_slice_from_report(
)
})
});
let placed_structure_collection = report
.save_placed_structure_record_triplet_probe
.as_ref()
.map(derive_loaded_placed_structure_collection_from_probe);
let special_conditions_table =
report
.special_conditions_probe
@ -7144,6 +7204,22 @@ pub fn load_save_slice_from_report(
probe.entries.first().map(|entry| entry.profile_status_kind.as_str())
));
}
if let Some(collection) = &placed_structure_collection {
let farm_growth_stage_count = collection
.entries
.iter()
.filter(|entry| entry.farm_growth_stage_index.is_some())
.count();
let opaque_status_count = collection
.entries
.iter()
.filter(|entry| entry.profile_status_kind != "unset")
.count();
notes.push(format!(
"Save-slice projection now carries {} loaded placed-structure triplet rows as first-class context, with {} farm growth-stage rows and {} non-default footer-status rows.",
collection.observed_entry_count, farm_growth_stage_count, opaque_status_count
));
}
if let Some(probe) = &placed_structure_dynamic_side_buffer_probe {
let dominant_pattern = probe.compact_prefix_pattern_summaries.first();
let payload_envelope_summary = probe.payload_envelope_summary.as_ref();
@ -7240,6 +7316,7 @@ pub fn load_save_slice_from_report(
world_locomotive_policy_state,
company_roster,
chairman_profile_table,
placed_structure_collection,
special_conditions_table,
event_runtime_collection: report.event_runtime_collection_summary.clone(),
notes,
@ -25240,6 +25317,154 @@ mod tests {
);
}
#[test]
fn loads_placed_structure_collection_from_report() {
let mut report = inspect_smp_bytes(&[]);
let classic_probe = SmpClassicRehydrateProfileProbe {
profile_family: "rt3-classic-save-container-v1".to_string(),
progress_32dc_offset: 0x76e8,
progress_3714_offset: 0x76ec,
progress_3715_offset: 0x77f8,
packed_profile_offset: 0x76f0,
packed_profile_len: 0x108,
packed_profile_len_hex: "0x108".to_string(),
packed_profile_block: SmpClassicPackedProfileBlock {
relative_len: 0x108,
relative_len_hex: "0x108".to_string(),
leading_word_0: 3,
leading_word_0_hex: "0x00000003".to_string(),
trailing_zero_word_count_after_leading_word: 3,
map_path_offset: 0x13,
map_path: Some("British Isles.gmp".to_string()),
display_name_offset: 0x46,
display_name: Some("British Isles".to_string()),
profile_byte_0x77: 0,
profile_byte_0x77_hex: "0x00".to_string(),
profile_byte_0x82: 0,
profile_byte_0x82_hex: "0x00".to_string(),
profile_byte_0x97: 0,
profile_byte_0x97_hex: "0x00".to_string(),
profile_byte_0xc5: 0,
profile_byte_0xc5_hex: "0x00".to_string(),
stable_nonzero_words: vec![],
},
ascii_runs: vec![],
};
report.classic_rehydrate_profile_probe = Some(classic_probe.clone());
report.save_load_summary = build_save_load_summary(
Some("gms"),
Some(&SmpContainerProfile {
profile_family: "rt3-classic-save-container-v1".to_string(),
profile_evidence: vec![],
is_known_profile: true,
}),
None,
None,
Some(&classic_probe),
None,
None,
);
report.save_placed_structure_record_triplet_probe =
Some(SmpSavePlacedStructureRecordTripletProbe {
profile_family: "rt3-classic-save-container-v1".to_string(),
source_kind: "save-placed-structure-record-triplets".to_string(),
semantic_family: "scenario-save-placed-structure-record-triplets".to_string(),
records_tag_offset: 0x3600,
close_tag_offset: 0x3800,
record_count: 2,
entries: vec![
SmpSavePlacedStructureRecordTripletEntryProbe {
record_index: 0,
primary_name: "FarmCorn".to_string(),
secondary_name: "FarmSet".to_string(),
name_tag_relative_offset: 0,
policy_tag_relative_offset: 0x10,
profile_tag_relative_offset: 0x2e,
policy_chunk_len: 0x1a,
profile_chunk_len: 0x10,
policy_f32_lane_0: 1.0,
policy_f32_lane_1: 2.0,
policy_f32_lane_2: 3.0,
policy_f32_lane_3: 4.0,
policy_f32_lane_4: 5.0,
policy_reserved_dword: 0,
policy_trailing_word: 1,
policy_trailing_word_hex: "0x0001".to_string(),
profile_open_marker: 0x00005dc1,
profile_open_marker_hex: "0x00005dc1".to_string(),
profile_repeated_primary_name: "FarmCorn".to_string(),
profile_repeated_secondary_name: "FarmSet".to_string(),
profile_footer_relative_offset: 0x08,
profile_footer_relative_offset_hex: "0x8".to_string(),
profile_pre_footer_padding_len: 1,
profile_pre_footer_padding_hex_bytes: vec!["0x00".to_string()],
profile_companion_byte_u8: Some(0),
profile_companion_byte_hex: Some("0x00".to_string()),
profile_payload_dword: 0,
profile_payload_dword_hex: "0x00000000".to_string(),
profile_sentinel_i32: 4,
profile_status_kind: "farm_growth_stage_bucket".to_string(),
farm_growth_stage_index: Some(4),
profile_close_marker: 0x00005dc2,
profile_close_marker_hex: "0x00005dc2".to_string(),
},
SmpSavePlacedStructureRecordTripletEntryProbe {
record_index: 1,
primary_name: "StationA".to_string(),
secondary_name: "StationSetA".to_string(),
name_tag_relative_offset: 0x40,
policy_tag_relative_offset: 0x50,
profile_tag_relative_offset: 0x6e,
policy_chunk_len: 0x1a,
profile_chunk_len: 0x10,
policy_f32_lane_0: 0.0,
policy_f32_lane_1: 0.0,
policy_f32_lane_2: 0.0,
policy_f32_lane_3: 0.0,
policy_f32_lane_4: 0.0,
policy_reserved_dword: 0,
policy_trailing_word: 1,
policy_trailing_word_hex: "0x0001".to_string(),
profile_open_marker: 0x00005dc1,
profile_open_marker_hex: "0x00005dc1".to_string(),
profile_repeated_primary_name: "StationA".to_string(),
profile_repeated_secondary_name: "StationSetA".to_string(),
profile_footer_relative_offset: 0x08,
profile_footer_relative_offset_hex: "0x8".to_string(),
profile_pre_footer_padding_len: 1,
profile_pre_footer_padding_hex_bytes: vec!["0x07".to_string()],
profile_companion_byte_u8: Some(7),
profile_companion_byte_hex: Some("0x07".to_string()),
profile_payload_dword: 0x00005dc1,
profile_payload_dword_hex: "0x00005dc1".to_string(),
profile_sentinel_i32: 0,
profile_status_kind: "opaque_nondefault".to_string(),
farm_growth_stage_index: None,
profile_close_marker: 0x00005dc2,
profile_close_marker_hex: "0x00005dc2".to_string(),
},
],
evidence: vec![],
});
let slice = load_save_slice_from_report(&report).expect("classic save slice");
let collection = slice
.placed_structure_collection
.expect("placed structure collection should project");
assert_eq!(
collection.source_kind,
"save-placed-structure-record-triplets"
);
assert_eq!(collection.observed_entry_count, 2);
assert_eq!(collection.entries[0].primary_name, "FarmCorn");
assert_eq!(collection.entries[0].farm_growth_stage_index, Some(4));
assert_eq!(collection.entries[1].profile_companion_byte_u8, Some(7));
assert!(slice.notes.iter().any(|line| {
line.contains("placed-structure triplet rows as first-class context")
&& line.contains("2")
}));
}
#[test]
fn loads_rt3_105_save_slice_from_report() {
let mut report = inspect_smp_bytes(&[]);