Load region triplets into save slices
This commit is contained in:
parent
777e11e230
commit
9a4dd5d8d4
5 changed files with 478 additions and 16 deletions
|
|
@ -1749,6 +1749,49 @@ pub struct SmpSaveRegionRecordTripletProbe {
|
|||
pub evidence: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct SmpLoadedRegionProfileEntry {
|
||||
pub entry_index: usize,
|
||||
pub name: String,
|
||||
pub trailing_weight_f32: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct SmpLoadedRegionProfileCollection {
|
||||
pub direct_collection_flag: u32,
|
||||
pub entry_stride: u32,
|
||||
pub live_id_bound: u32,
|
||||
pub live_record_count: u32,
|
||||
pub trailing_padding_len: usize,
|
||||
#[serde(default)]
|
||||
pub entries: Vec<SmpLoadedRegionProfileEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct SmpLoadedRegionEntry {
|
||||
pub record_index: usize,
|
||||
pub name: String,
|
||||
pub pre_name_prefix_len: usize,
|
||||
pub policy_leading_f32_0: f32,
|
||||
pub policy_leading_f32_1: f32,
|
||||
pub policy_leading_f32_2: f32,
|
||||
#[serde(default)]
|
||||
pub policy_reserved_dwords: Vec<u32>,
|
||||
pub policy_trailing_word: u16,
|
||||
pub policy_trailing_word_hex: String,
|
||||
#[serde(default)]
|
||||
pub profile_collection: Option<SmpLoadedRegionProfileCollection>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct SmpLoadedRegionCollection {
|
||||
pub source_kind: String,
|
||||
pub semantic_family: String,
|
||||
pub observed_entry_count: usize,
|
||||
#[serde(default)]
|
||||
pub entries: Vec<SmpLoadedRegionEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SmpSaveRegionQueuedNoticeRecordEntryProbe {
|
||||
pub node_base_offset: usize,
|
||||
|
|
@ -4058,7 +4101,7 @@ enum RealGroupedTargetSubject {
|
|||
WholeGame,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct SmpLoadedSaveSlice {
|
||||
pub file_extension_hint: Option<String>,
|
||||
pub container_profile_family: Option<String>,
|
||||
|
|
@ -4086,6 +4129,8 @@ pub struct SmpLoadedSaveSlice {
|
|||
#[serde(default)]
|
||||
pub chairman_profile_table: Option<SmpLoadedChairmanProfileTable>,
|
||||
#[serde(default)]
|
||||
pub region_collection: Option<SmpLoadedRegionCollection>,
|
||||
#[serde(default)]
|
||||
pub placed_structure_collection: Option<SmpLoadedPlacedStructureCollection>,
|
||||
#[serde(default)]
|
||||
pub placed_structure_dynamic_side_buffer_summary:
|
||||
|
|
@ -6909,6 +6954,49 @@ fn derive_loaded_placed_structure_collection_from_probe(
|
|||
}
|
||||
}
|
||||
|
||||
fn derive_loaded_region_collection_from_probe(
|
||||
probe: &SmpSaveRegionRecordTripletProbe,
|
||||
) -> SmpLoadedRegionCollection {
|
||||
SmpLoadedRegionCollection {
|
||||
source_kind: probe.source_kind.clone(),
|
||||
semantic_family: "scenario-save-region-triplet-collection".to_string(),
|
||||
observed_entry_count: probe.record_count,
|
||||
entries: probe
|
||||
.entries
|
||||
.iter()
|
||||
.map(|entry| SmpLoadedRegionEntry {
|
||||
record_index: entry.record_index,
|
||||
name: entry.name.clone(),
|
||||
pre_name_prefix_len: entry.pre_name_prefix_len,
|
||||
policy_leading_f32_0: entry.policy_leading_f32_0,
|
||||
policy_leading_f32_1: entry.policy_leading_f32_1,
|
||||
policy_leading_f32_2: entry.policy_leading_f32_2,
|
||||
policy_reserved_dwords: entry.policy_reserved_dwords.clone(),
|
||||
policy_trailing_word: entry.policy_trailing_word,
|
||||
policy_trailing_word_hex: entry.policy_trailing_word_hex.clone(),
|
||||
profile_collection: entry.profile_collection.as_ref().map(|collection| {
|
||||
SmpLoadedRegionProfileCollection {
|
||||
direct_collection_flag: collection.direct_collection_flag,
|
||||
entry_stride: collection.entry_stride,
|
||||
live_id_bound: collection.live_id_bound,
|
||||
live_record_count: collection.live_record_count,
|
||||
trailing_padding_len: collection.trailing_padding_len,
|
||||
entries: collection
|
||||
.entries
|
||||
.iter()
|
||||
.map(|entry| SmpLoadedRegionProfileEntry {
|
||||
entry_index: entry.entry_index,
|
||||
name: entry.name.clone(),
|
||||
trailing_weight_f32: entry.trailing_weight_f32,
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}),
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn derive_loaded_placed_structure_dynamic_side_buffer_summary(
|
||||
probe: &SmpSavePlacedStructureDynamicSideBufferProbe,
|
||||
alignment: Option<&SmpSavePlacedStructureDynamicSideBufferAlignmentProbe>,
|
||||
|
|
@ -7082,6 +7170,10 @@ pub fn load_save_slice_from_report(
|
|||
)
|
||||
})
|
||||
});
|
||||
let region_collection = report
|
||||
.save_region_record_triplet_probe
|
||||
.as_ref()
|
||||
.map(derive_loaded_region_collection_from_probe);
|
||||
let placed_structure_collection = report
|
||||
.save_placed_structure_record_triplet_probe
|
||||
.as_ref()
|
||||
|
|
@ -7246,6 +7338,36 @@ pub fn load_save_slice_from_report(
|
|||
})
|
||||
));
|
||||
}
|
||||
if let Some(collection) = ®ion_collection {
|
||||
let total_profile_rows = collection
|
||||
.entries
|
||||
.iter()
|
||||
.map(|entry| {
|
||||
entry
|
||||
.profile_collection
|
||||
.as_ref()
|
||||
.map(|collection| collection.entries.len())
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.sum::<usize>();
|
||||
let nonzero_prefix_count = collection
|
||||
.entries
|
||||
.iter()
|
||||
.filter(|entry| entry.pre_name_prefix_len != 0)
|
||||
.count();
|
||||
let nonzero_reserved_count = collection
|
||||
.entries
|
||||
.iter()
|
||||
.filter(|entry| entry.policy_reserved_dwords.iter().any(|raw| *raw != 0))
|
||||
.count();
|
||||
notes.push(format!(
|
||||
"Save-slice projection now carries {} loaded region triplet rows as first-class context, with {} embedded profile rows, {} rows with nonzero pre-name prefixes, and {} rows with nonzero reserved policy dwords.",
|
||||
collection.observed_entry_count,
|
||||
total_profile_rows,
|
||||
nonzero_prefix_count,
|
||||
nonzero_reserved_count
|
||||
));
|
||||
}
|
||||
if let Some(probe) = &report.save_placed_structure_collection_header_probe {
|
||||
notes.push(format!(
|
||||
"Raw save tagged placed-structure header reports live_record_count={} and live_id_bound={} with serialized stride hint 0x{:x} at file offsets 0x{:x}/0x{:x}/0x{:x}.",
|
||||
|
|
@ -7392,6 +7514,7 @@ pub fn load_save_slice_from_report(
|
|||
world_locomotive_policy_state,
|
||||
company_roster,
|
||||
chairman_profile_table,
|
||||
region_collection,
|
||||
placed_structure_collection,
|
||||
placed_structure_dynamic_side_buffer_summary,
|
||||
special_conditions_table,
|
||||
|
|
@ -25441,6 +25564,100 @@ mod tests {
|
|||
None,
|
||||
None,
|
||||
);
|
||||
report.save_region_record_triplet_probe = Some(SmpSaveRegionRecordTripletProbe {
|
||||
profile_family: "rt3-classic-save-container-v1".to_string(),
|
||||
source_kind: "save-region-record-triplets".to_string(),
|
||||
semantic_family: "scenario-save-region-record-triplets".to_string(),
|
||||
records_tag_offset: 0x3400,
|
||||
close_tag_offset: 0x3500,
|
||||
record_count: 2,
|
||||
entries: vec![
|
||||
SmpSaveRegionRecordTripletEntryProbe {
|
||||
record_index: 0,
|
||||
name: "Marker09".to_string(),
|
||||
record_payload_relative_offset: 0,
|
||||
record_payload_relative_offset_hex: "0x0".to_string(),
|
||||
name_tag_relative_offset: 0,
|
||||
policy_tag_relative_offset: 0x10,
|
||||
profile_tag_relative_offset: 0x2e,
|
||||
pre_name_prefix_len: 0,
|
||||
pre_name_prefix_hex_bytes: Vec::new(),
|
||||
pre_name_prefix_dword_candidates: Vec::new(),
|
||||
policy_chunk_len: 0x1a,
|
||||
profile_chunk_len: 0x40,
|
||||
policy_leading_f32_0: 368.0,
|
||||
policy_leading_f32_1: 0.0,
|
||||
policy_leading_f32_2: 92.0,
|
||||
policy_reserved_dwords: vec![0, 0, 0],
|
||||
policy_reserved_dword_candidates: Vec::new(),
|
||||
policy_trailing_word: 1,
|
||||
policy_trailing_word_hex: "0x0001".to_string(),
|
||||
profile_collection: Some(SmpSaveRegionProfileCollectionProbe {
|
||||
direct_collection_flag: 1,
|
||||
entry_stride: 0x22,
|
||||
live_id_bound: 18,
|
||||
live_record_count: 17,
|
||||
entry_start_relative_offset: 0x4d,
|
||||
trailing_padding_len: 2,
|
||||
entries: vec![
|
||||
SmpSaveRegionProfileEntryProbe {
|
||||
entry_index: 0,
|
||||
row_relative_offset: 0x4d,
|
||||
name: "House".to_string(),
|
||||
trailing_weight_f32: 0.2,
|
||||
},
|
||||
SmpSaveRegionProfileEntryProbe {
|
||||
entry_index: 1,
|
||||
row_relative_offset: 0x6f,
|
||||
name: "Farm Corn".to_string(),
|
||||
trailing_weight_f32: 0.2,
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
SmpSaveRegionRecordTripletEntryProbe {
|
||||
record_index: 1,
|
||||
name: "Marker10".to_string(),
|
||||
record_payload_relative_offset: 0x6e,
|
||||
record_payload_relative_offset_hex: "0x6e".to_string(),
|
||||
name_tag_relative_offset: 0x76,
|
||||
policy_tag_relative_offset: 0x86,
|
||||
profile_tag_relative_offset: 0xa4,
|
||||
pre_name_prefix_len: 8,
|
||||
pre_name_prefix_hex_bytes: vec![
|
||||
"0xaa".to_string(),
|
||||
"0xbb".to_string(),
|
||||
"0xcc".to_string(),
|
||||
"0xdd".to_string(),
|
||||
],
|
||||
pre_name_prefix_dword_candidates: Vec::new(),
|
||||
policy_chunk_len: 0x1a,
|
||||
profile_chunk_len: 0x20,
|
||||
policy_leading_f32_0: 552.0,
|
||||
policy_leading_f32_1: 0.0,
|
||||
policy_leading_f32_2: 276.0,
|
||||
policy_reserved_dwords: vec![0, 4, 0],
|
||||
policy_reserved_dword_candidates: Vec::new(),
|
||||
policy_trailing_word: 1,
|
||||
policy_trailing_word_hex: "0x0001".to_string(),
|
||||
profile_collection: Some(SmpSaveRegionProfileCollectionProbe {
|
||||
direct_collection_flag: 1,
|
||||
entry_stride: 0x22,
|
||||
live_id_bound: 26,
|
||||
live_record_count: 24,
|
||||
entry_start_relative_offset: 0x50,
|
||||
trailing_padding_len: 0,
|
||||
entries: vec![SmpSaveRegionProfileEntryProbe {
|
||||
entry_index: 0,
|
||||
row_relative_offset: 0x50,
|
||||
name: "Farm Corn".to_string(),
|
||||
trailing_weight_f32: 0.2,
|
||||
}],
|
||||
}),
|
||||
},
|
||||
],
|
||||
evidence: vec![],
|
||||
});
|
||||
report.save_placed_structure_record_triplet_probe =
|
||||
Some(SmpSavePlacedStructureRecordTripletProbe {
|
||||
profile_family: "rt3-classic-save-container-v1".to_string(),
|
||||
|
|
@ -25604,6 +25821,24 @@ mod tests {
|
|||
evidence: vec![],
|
||||
});
|
||||
let slice = load_save_slice_from_report(&report).expect("classic save slice");
|
||||
let region_collection = slice
|
||||
.region_collection
|
||||
.expect("region collection should project");
|
||||
assert_eq!(region_collection.source_kind, "save-region-record-triplets");
|
||||
assert_eq!(region_collection.observed_entry_count, 2);
|
||||
assert_eq!(region_collection.entries[0].name, "Marker09");
|
||||
assert_eq!(region_collection.entries[1].pre_name_prefix_len, 8);
|
||||
assert_eq!(
|
||||
region_collection.entries[1].policy_reserved_dwords,
|
||||
vec![0, 4, 0]
|
||||
);
|
||||
assert_eq!(
|
||||
region_collection.entries[0]
|
||||
.profile_collection
|
||||
.as_ref()
|
||||
.map(|collection| collection.entries.len()),
|
||||
Some(2)
|
||||
);
|
||||
let collection = slice
|
||||
.placed_structure_collection
|
||||
.expect("placed structure collection should project");
|
||||
|
|
@ -25629,6 +25864,10 @@ mod tests {
|
|||
side_buffer_summary.triplet_alignment_side_buffer_only_name_pair_count,
|
||||
0
|
||||
);
|
||||
assert!(slice.notes.iter().any(|line| {
|
||||
line.contains("loaded region triplet rows as first-class context")
|
||||
&& line.contains("3 embedded profile rows")
|
||||
}));
|
||||
assert!(slice.notes.iter().any(|line| {
|
||||
line.contains("placed-structure triplet rows as first-class context")
|
||||
&& line.contains("2")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue