Load placed-structure side-buffer summaries into save slices
This commit is contained in:
parent
7abd582aea
commit
777e11e230
5 changed files with 337 additions and 15 deletions
|
|
@ -2019,6 +2019,26 @@ pub struct SmpSavePlacedStructureDynamicSideBufferProbe {
|
|||
pub evidence: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SmpLoadedPlacedStructureDynamicSideBufferSummary {
|
||||
pub source_kind: String,
|
||||
pub semantic_family: String,
|
||||
pub observed_entry_count: u32,
|
||||
pub owner_shared_dword_hex: String,
|
||||
pub unique_embedded_name_pair_count: usize,
|
||||
pub decoded_embedded_name_row_count: usize,
|
||||
pub first_prefix_leading_dword_hex: String,
|
||||
pub first_prefix_trailing_word_hex: String,
|
||||
pub first_prefix_separator_byte_hex: String,
|
||||
pub triplet_alignment_overlap_count: usize,
|
||||
pub triplet_alignment_side_buffer_only_name_pair_count: usize,
|
||||
#[serde(default)]
|
||||
pub compact_prefix_pattern_summaries:
|
||||
Vec<SmpSavePlacedStructureDynamicSideBufferPrefixPatternSummary>,
|
||||
#[serde(default)]
|
||||
pub name_pair_summaries: Vec<SmpSavePlacedStructureDynamicSideBufferNamePairSummary>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SmpSavePlacedStructureDynamicSideBufferSampleEntry {
|
||||
pub sample_index: usize,
|
||||
|
|
@ -4067,6 +4087,9 @@ pub struct SmpLoadedSaveSlice {
|
|||
pub chairman_profile_table: Option<SmpLoadedChairmanProfileTable>,
|
||||
#[serde(default)]
|
||||
pub placed_structure_collection: Option<SmpLoadedPlacedStructureCollection>,
|
||||
#[serde(default)]
|
||||
pub placed_structure_dynamic_side_buffer_summary:
|
||||
Option<SmpLoadedPlacedStructureDynamicSideBufferSummary>,
|
||||
pub special_conditions_table: Option<SmpLoadedSpecialConditionsTable>,
|
||||
pub event_runtime_collection: Option<SmpLoadedEventRuntimeCollectionSummary>,
|
||||
pub notes: Vec<String>,
|
||||
|
|
@ -6886,6 +6909,35 @@ fn derive_loaded_placed_structure_collection_from_probe(
|
|||
}
|
||||
}
|
||||
|
||||
fn derive_loaded_placed_structure_dynamic_side_buffer_summary(
|
||||
probe: &SmpSavePlacedStructureDynamicSideBufferProbe,
|
||||
alignment: Option<&SmpSavePlacedStructureDynamicSideBufferAlignmentProbe>,
|
||||
) -> SmpLoadedPlacedStructureDynamicSideBufferSummary {
|
||||
SmpLoadedPlacedStructureDynamicSideBufferSummary {
|
||||
source_kind: probe.source_kind.clone(),
|
||||
semantic_family: "scenario-save-placed-structure-dynamic-side-buffer-summary".to_string(),
|
||||
observed_entry_count: probe.live_record_count,
|
||||
owner_shared_dword_hex: probe.owner_shared_dword_hex.clone(),
|
||||
unique_embedded_name_pair_count: probe.unique_embedded_name_pair_count,
|
||||
decoded_embedded_name_row_count: probe.decoded_embedded_name_row_count,
|
||||
first_prefix_leading_dword_hex: probe.prefix_leading_dword_hex.clone(),
|
||||
first_prefix_trailing_word_hex: probe.prefix_trailing_word_hex.clone(),
|
||||
first_prefix_separator_byte_hex: probe.prefix_separator_byte_hex.clone(),
|
||||
triplet_alignment_overlap_count: alignment
|
||||
.map(|alignment| alignment.overlapping_name_pair_count)
|
||||
.unwrap_or_default(),
|
||||
triplet_alignment_side_buffer_only_name_pair_count: alignment
|
||||
.map(|alignment| {
|
||||
alignment
|
||||
.unique_side_buffer_name_pair_count
|
||||
.saturating_sub(alignment.overlapping_name_pair_count)
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
compact_prefix_pattern_summaries: probe.compact_prefix_pattern_summaries.clone(),
|
||||
name_pair_summaries: probe.name_pair_summaries.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
|
@ -7049,6 +7101,22 @@ pub fn load_save_slice_from_report(
|
|||
let placed_structure_dynamic_side_buffer_probe = report
|
||||
.save_placed_structure_dynamic_side_buffer_probe
|
||||
.clone();
|
||||
let placed_structure_dynamic_side_buffer_alignment = report
|
||||
.save_placed_structure_dynamic_side_buffer_probe
|
||||
.as_ref()
|
||||
.zip(report.save_placed_structure_record_triplet_probe.as_ref())
|
||||
.map(|(side_buffer, triplets)| {
|
||||
summarize_placed_structure_dynamic_side_buffer_alignment(side_buffer, triplets)
|
||||
});
|
||||
let placed_structure_dynamic_side_buffer_summary = report
|
||||
.save_placed_structure_dynamic_side_buffer_probe
|
||||
.as_ref()
|
||||
.map(|probe| {
|
||||
derive_loaded_placed_structure_dynamic_side_buffer_summary(
|
||||
probe,
|
||||
placed_structure_dynamic_side_buffer_alignment.as_ref(),
|
||||
)
|
||||
});
|
||||
let mut notes = summary.notes.clone();
|
||||
if let Some(probe) = &report.save_world_selection_context_probe {
|
||||
notes.push(format!(
|
||||
|
|
@ -7285,6 +7353,14 @@ pub fn load_save_slice_from_report(
|
|||
);
|
||||
}
|
||||
}
|
||||
if let Some(summary) = &placed_structure_dynamic_side_buffer_summary {
|
||||
notes.push(format!(
|
||||
"Save-slice projection now also carries the placed-structure dynamic side-buffer summary with {} decoded name rows, {} unique name pairs, and {} overlapping triplet name pairs.",
|
||||
summary.decoded_embedded_name_row_count,
|
||||
summary.unique_embedded_name_pair_count,
|
||||
summary.triplet_alignment_overlap_count
|
||||
));
|
||||
}
|
||||
if let Some(roster) = &report.save_company_roster_probe {
|
||||
notes.push(format!(
|
||||
"Raw save inspection reconstructed {} company direct records from the tagged company collection.",
|
||||
|
|
@ -7317,6 +7393,7 @@ pub fn load_save_slice_from_report(
|
|||
company_roster,
|
||||
chairman_profile_table,
|
||||
placed_structure_collection,
|
||||
placed_structure_dynamic_side_buffer_summary,
|
||||
special_conditions_table,
|
||||
event_runtime_collection: report.event_runtime_collection_summary.clone(),
|
||||
notes,
|
||||
|
|
@ -25446,7 +25523,86 @@ mod tests {
|
|||
],
|
||||
evidence: vec![],
|
||||
});
|
||||
|
||||
report.save_placed_structure_dynamic_side_buffer_probe =
|
||||
Some(SmpSavePlacedStructureDynamicSideBufferProbe {
|
||||
profile_family: "rt3-classic-save-container-v1".to_string(),
|
||||
source_kind: "save-placed-structure-dynamic-side-buffer-records".to_string(),
|
||||
semantic_family: "scenario-save-placed-structure-dynamic-side-buffer-records"
|
||||
.to_string(),
|
||||
metadata_tag_offset: 0x3800,
|
||||
records_tag_offset: 0x3900,
|
||||
close_tag_offset: 0x3d00,
|
||||
records_span_len: 0x400,
|
||||
direct_record_stride: 6,
|
||||
direct_record_stride_hex: "0x6".to_string(),
|
||||
live_id_bound: 0x80,
|
||||
live_id_bound_hex: "0x00000080".to_string(),
|
||||
live_record_count: 118,
|
||||
live_record_count_hex: "0x00000076".to_string(),
|
||||
owner_shared_dword: 0xff0000ff,
|
||||
owner_shared_dword_hex: "0xff0000ff".to_string(),
|
||||
owner_shared_dword_relative_offset: 0,
|
||||
owner_shared_dword_matches_first_compact_prefix_leading_dword: true,
|
||||
first_record_child_count_after_owner_shared: Some(1),
|
||||
first_record_child_count_after_owner_shared_hex: Some("0x0001".to_string()),
|
||||
first_record_saved_primary_child_byte_after_owner_shared: Some(0xff),
|
||||
first_record_saved_primary_child_byte_after_owner_shared_hex: Some(
|
||||
"0xff".to_string(),
|
||||
),
|
||||
first_record_first_name_tag_relative_offset_after_owner_shared: Some(3),
|
||||
prefix_leading_dword: 0xff0000ff,
|
||||
prefix_leading_dword_hex: "0xff0000ff".to_string(),
|
||||
prefix_trailing_word: 1,
|
||||
prefix_trailing_word_hex: "0x0001".to_string(),
|
||||
prefix_separator_byte: 0xff,
|
||||
prefix_separator_byte_hex: "0xff".to_string(),
|
||||
first_embedded_name_tag_relative_offset: 3,
|
||||
embedded_name_tag_count: 118,
|
||||
decoded_embedded_name_row_count: 118,
|
||||
decoded_embedded_name_row_with_tertiary_name_count: 0,
|
||||
unique_compact_prefix_pattern_count: 4,
|
||||
prefix_leading_dword_matching_embedded_profile_tag_count: 0,
|
||||
unique_embedded_name_pair_count: 9,
|
||||
first_embedded_primary_name: Some("StationA".to_string()),
|
||||
first_embedded_secondary_name: Some("StationSetA".to_string()),
|
||||
first_embedded_tertiary_name: None,
|
||||
embedded_name_row_samples: vec![],
|
||||
compact_prefix_pattern_summaries: vec![
|
||||
SmpSavePlacedStructureDynamicSideBufferPrefixPatternSummary {
|
||||
prefix_leading_dword: 0xff0000ff,
|
||||
prefix_leading_dword_hex: "0xff0000ff".to_string(),
|
||||
prefix_trailing_word: 1,
|
||||
prefix_trailing_word_hex: "0x0001".to_string(),
|
||||
prefix_separator_byte: 0xff,
|
||||
prefix_separator_byte_hex: "0xff".to_string(),
|
||||
count: 62,
|
||||
first_name_tag_relative_offset: 3,
|
||||
prefix_leading_dword_matches_embedded_profile_tag: false,
|
||||
section_like_primary_name_count: 12,
|
||||
cap_like_primary_name_count: 21,
|
||||
other_primary_name_count: 29,
|
||||
first_primary_name: Some("StationA".to_string()),
|
||||
first_secondary_name: Some("StationSetA".to_string()),
|
||||
},
|
||||
],
|
||||
name_pair_summaries: vec![SmpSavePlacedStructureDynamicSideBufferNamePairSummary {
|
||||
primary_name: "StationA".to_string(),
|
||||
secondary_name: "StationSetA".to_string(),
|
||||
count: 14,
|
||||
first_name_tag_relative_offset: 3,
|
||||
unique_compact_prefix_pattern_count: 1,
|
||||
dominant_prefix_leading_dword: 0xff0000ff,
|
||||
dominant_prefix_leading_dword_hex: "0xff0000ff".to_string(),
|
||||
dominant_prefix_trailing_word: 1,
|
||||
dominant_prefix_trailing_word_hex: "0x0001".to_string(),
|
||||
dominant_prefix_separator_byte: 0xff,
|
||||
dominant_prefix_separator_byte_hex: "0xff".to_string(),
|
||||
dominant_prefix_count: 14,
|
||||
}],
|
||||
payload_envelope_summary: None,
|
||||
live_entry_prelude_summary: None,
|
||||
evidence: vec![],
|
||||
});
|
||||
let slice = load_save_slice_from_report(&report).expect("classic save slice");
|
||||
let collection = slice
|
||||
.placed_structure_collection
|
||||
|
|
@ -25459,10 +25615,28 @@ mod tests {
|
|||
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));
|
||||
let side_buffer_summary = slice
|
||||
.placed_structure_dynamic_side_buffer_summary
|
||||
.expect("side-buffer summary should project");
|
||||
assert_eq!(
|
||||
side_buffer_summary.source_kind,
|
||||
"save-placed-structure-dynamic-side-buffer-records"
|
||||
);
|
||||
assert_eq!(side_buffer_summary.observed_entry_count, 118);
|
||||
assert_eq!(side_buffer_summary.unique_embedded_name_pair_count, 9);
|
||||
assert_eq!(side_buffer_summary.triplet_alignment_overlap_count, 1);
|
||||
assert_eq!(
|
||||
side_buffer_summary.triplet_alignment_side_buffer_only_name_pair_count,
|
||||
0
|
||||
);
|
||||
assert!(slice.notes.iter().any(|line| {
|
||||
line.contains("placed-structure triplet rows as first-class context")
|
||||
&& line.contains("2")
|
||||
}));
|
||||
assert!(slice.notes.iter().any(|line| {
|
||||
line.contains("placed-structure dynamic side-buffer summary")
|
||||
&& line.contains("118 decoded name rows")
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue