Box in Tier-2 source family rows

This commit is contained in:
Jan Petykiewicz 2026-04-19 16:18:10 -07:00
commit 07ae8b693a
5 changed files with 485 additions and 10 deletions

View file

@ -91,6 +91,7 @@ pub struct BuildingTypeRecoveredTableSummary {
pub recovered_source_kinds: Vec<String>,
pub present_style_station_entries: Vec<String>,
pub present_standalone_entries: Vec<String>,
pub recovered_source_family_summaries: Vec<BuildingTypeRecoveredSourceFamilySummary>,
pub bare_port_warehouse_files: Vec<String>,
pub nonzero_bty_header_dword_summaries: Vec<BuildingTypeBtyHeaderDwordSummary>,
pub nonzero_bty_header_name_0x40_summaries: Vec<BuildingTypeBtyHeaderNameSummary>,
@ -109,6 +110,22 @@ pub struct BuildingTypeBtyHeaderDwordSummary {
pub sample_file_names: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BuildingTypeRecoveredSourceFamilySummary {
pub canonical_stem: String,
pub sample_raw_stem: String,
pub has_bca_pair: bool,
pub type_id_hex: String,
pub name_0x40: String,
pub name_0x5e: String,
pub name_0x7c: String,
pub dword_0xbb_hex: String,
#[serde(default)]
pub byte_0xba_hex: Option<String>,
#[serde(default)]
pub byte_0xbb_hex: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BuildingTypeBtyHeaderNameSummary {
pub header_offset_hex: String,
@ -482,6 +499,13 @@ fn summarize_recovered_table_families(
present_standalone_entries.sort();
present_standalone_entries.dedup();
let recovered_source_family_summaries = summarize_recovered_source_family_rows(
entries,
files,
&present_style_station_entries,
&present_standalone_entries,
);
let mut bare_port_warehouse_files = files
.iter()
.filter(|file| matches!(file.canonical_stem.as_str(), "port" | "warehouse"))
@ -541,6 +565,7 @@ fn summarize_recovered_table_families(
.collect(),
present_style_station_entries,
present_standalone_entries,
recovered_source_family_summaries,
bare_port_warehouse_files,
nonzero_bty_header_dword_summaries,
nonzero_bty_header_name_0x40_summaries,
@ -598,6 +623,75 @@ fn summarize_nonzero_bty_header_name_lane(
summaries
}
fn summarize_recovered_source_family_rows(
entries: &[BuildingTypeSourceEntry],
files: &[BuildingTypeSourceFile],
present_style_station_entries: &[String],
present_standalone_entries: &[String],
) -> Vec<BuildingTypeRecoveredSourceFamilySummary> {
let file_by_name = files
.iter()
.map(|file| (file.file_name.as_str(), file))
.collect::<BTreeMap<_, _>>();
let mut canonical_stems = present_style_station_entries
.iter()
.chain(present_standalone_entries.iter())
.map(|raw_stem| canonicalize_building_stem(raw_stem))
.collect::<Vec<_>>();
canonical_stems.sort();
canonical_stems.dedup();
let mut summaries = Vec::new();
for canonical_stem in canonical_stems {
let Some(entry) = entries
.iter()
.find(|entry| entry.canonical_stem == canonical_stem)
else {
continue;
};
let bty_file = entry
.file_names
.iter()
.filter_map(|name| file_by_name.get(name.as_str()))
.find(|file| matches!(file.source_kind, BuildingTypeSourceKind::Bty));
let Some(bty_file) = bty_file else {
continue;
};
let Some(bty_probe) = &bty_file.bty_header_probe else {
continue;
};
let bca_file = entry
.file_names
.iter()
.filter_map(|name| file_by_name.get(name.as_str()))
.find(|file| matches!(file.source_kind, BuildingTypeSourceKind::Bca));
let bca_probe = bca_file.and_then(|file| file.bca_selector_probe.as_ref());
summaries.push(BuildingTypeRecoveredSourceFamilySummary {
canonical_stem: canonical_stem.clone(),
sample_raw_stem: entry
.raw_stems
.first()
.cloned()
.unwrap_or_else(|| canonical_stem.clone()),
has_bca_pair: bca_file.is_some(),
type_id_hex: bty_probe.type_id_hex.clone(),
name_0x40: bty_probe.name_0x40.clone(),
name_0x5e: bty_probe.name_0x5e.clone(),
name_0x7c: bty_probe.name_0x7c.clone(),
dword_0xbb_hex: bty_probe.dword_0xbb_hex.clone(),
byte_0xba_hex: bca_probe.map(|probe| probe.byte_0xba_hex.clone()),
byte_0xbb_hex: bca_probe.map(|probe| probe.byte_0xbb_hex.clone()),
});
}
summaries.sort_by(|left, right| {
left.canonical_stem
.cmp(&right.canonical_stem)
.then_with(|| left.sample_raw_stem.cmp(&right.sample_raw_stem))
});
summaries
}
fn summarize_bty_header_name_lane_by_dword(
files: &[BuildingTypeSourceFile],
offset: u32,
@ -921,6 +1015,110 @@ mod tests {
},
];
let files = vec![
BuildingTypeSourceFile {
file_name: "VictorianStationSml.bty".to_string(),
raw_stem: "VictorianStationSml".to_string(),
canonical_stem: canonicalize_building_stem("VictorianStationSml"),
source_kind: BuildingTypeSourceKind::Bty,
byte_len: None,
bca_selector_probe: None,
bty_header_probe: Some(BuildingTypeBtyHeaderProbe {
type_id: 0x03ec,
type_id_hex: "0x000003ec".to_string(),
name_0x04: "VictorianStationSml".to_string(),
name_0x22: "VictorianStationSml".to_string(),
name_0x40: "VictorianStations".to_string(),
name_0x5e: "SmallTudorHouse".to_string(),
name_0x7c: "VictorianStations".to_string(),
name_0x9a: "VictorianStationSml".to_string(),
byte_0xb8: 0x06,
byte_0xb8_hex: "0x06".to_string(),
byte_0xb9: 0x06,
byte_0xb9_hex: "0x06".to_string(),
byte_0xba: 0x30,
byte_0xba_hex: "0x30".to_string(),
dword_0xbb: 0,
dword_0xbb_hex: "0x00000000".to_string(),
}),
},
BuildingTypeSourceFile {
file_name: "ClpbrdStationLrg.bty".to_string(),
raw_stem: "ClpbrdStationLrg".to_string(),
canonical_stem: canonicalize_building_stem("ClpbrdStationLrg"),
source_kind: BuildingTypeSourceKind::Bty,
byte_len: None,
bca_selector_probe: None,
bty_header_probe: Some(BuildingTypeBtyHeaderProbe {
type_id: 0x03ec,
type_id_hex: "0x000003ec".to_string(),
name_0x04: "ClpbrdStationLrg".to_string(),
name_0x22: "ClpbrdStationLrg".to_string(),
name_0x40: "ClpBrdStations".to_string(),
name_0x5e: "SmallTudorHouse".to_string(),
name_0x7c: "ClpBrdStations".to_string(),
name_0x9a: "ClpbrdStationLrg".to_string(),
byte_0xb8: 0x06,
byte_0xb8_hex: "0x06".to_string(),
byte_0xb9: 0x06,
byte_0xb9_hex: "0x06".to_string(),
byte_0xba: 0x30,
byte_0xba_hex: "0x30".to_string(),
dword_0xbb: 0,
dword_0xbb_hex: "0x00000000".to_string(),
}),
},
BuildingTypeSourceFile {
file_name: "Maintenance.bty".to_string(),
raw_stem: "Maintenance".to_string(),
canonical_stem: canonicalize_building_stem("Maintenance"),
source_kind: BuildingTypeSourceKind::Bty,
byte_len: None,
bca_selector_probe: None,
bty_header_probe: Some(BuildingTypeBtyHeaderProbe {
type_id: 0x03ec,
type_id_hex: "0x000003ec".to_string(),
name_0x04: "Maintenance".to_string(),
name_0x22: "Maintenance".to_string(),
name_0x40: "Maintenance Facility".to_string(),
name_0x5e: "200FtRulerCross".to_string(),
name_0x7c: "Maintenance Facility".to_string(),
name_0x9a: "Maintenance".to_string(),
byte_0xb8: 0x06,
byte_0xb8_hex: "0x06".to_string(),
byte_0xb9: 0x06,
byte_0xb9_hex: "0x06".to_string(),
byte_0xba: 0x30,
byte_0xba_hex: "0x30".to_string(),
dword_0xbb: 0,
dword_0xbb_hex: "0x00000000".to_string(),
}),
},
BuildingTypeSourceFile {
file_name: "ServiceTower.bty".to_string(),
raw_stem: "ServiceTower".to_string(),
canonical_stem: canonicalize_building_stem("ServiceTower"),
source_kind: BuildingTypeSourceKind::Bty,
byte_len: None,
bca_selector_probe: None,
bty_header_probe: Some(BuildingTypeBtyHeaderProbe {
type_id: 0x03ec,
type_id_hex: "0x000003ec".to_string(),
name_0x04: "ServiceTower".to_string(),
name_0x22: "ServiceTower".to_string(),
name_0x40: "Service Tower".to_string(),
name_0x5e: "200FtRulerCross".to_string(),
name_0x7c: "Service Tower".to_string(),
name_0x9a: "ServiceTower".to_string(),
byte_0xb8: 0x06,
byte_0xb8_hex: "0x06".to_string(),
byte_0xb9: 0x06,
byte_0xb9_hex: "0x06".to_string(),
byte_0xba: 0x30,
byte_0xba_hex: "0x30".to_string(),
dword_0xbb: 0,
dword_0xbb_hex: "0x00000000".to_string(),
}),
},
BuildingTypeSourceFile {
file_name: "Port.bty".to_string(),
raw_stem: "Port".to_string(),
@ -982,6 +1180,18 @@ mod tests {
summary.present_standalone_entries,
vec!["Maintenance".to_string(), "ServiceTower".to_string()]
);
assert_eq!(summary.recovered_source_family_summaries.len(), 4);
assert!(summary.recovered_source_family_summaries.iter().any(|row| {
row.canonical_stem == canonicalize_building_stem("Maintenance")
&& row.name_0x40 == "Maintenance Facility"
&& row.dword_0xbb_hex == "0x00000000"
&& row.byte_0xba_hex.is_none()
}));
assert!(summary.recovered_source_family_summaries.iter().any(|row| {
row.canonical_stem == canonicalize_building_stem("VictorianStationSml")
&& row.name_0x40 == "VictorianStations"
&& row.dword_0xbb_hex == "0x00000000"
}));
assert_eq!(
summary.bare_port_warehouse_files,
vec!["Port.bca".to_string(), "Port.bty".to_string()]
@ -1022,16 +1232,14 @@ mod tests {
sample_file_names: vec!["Port.bty".to_string()],
}]
);
assert_eq!(
summary.bty_header_name_0x5e_dword_summaries,
vec![BuildingTypeBtyHeaderNameDwordSummary {
header_offset_hex: "0x5e".to_string(),
header_value: "TextileMill".to_string(),
dword_0xbb: 0x01f4,
dword_0xbb_hex: "0x000001f4".to_string(),
file_count: 1,
sample_file_names: vec!["Port.bty".to_string()],
}]
assert!(
summary.bty_header_name_0x5e_dword_summaries.iter().any(|row| {
row.header_offset_hex == "0x5e"
&& row.header_value == "TextileMill"
&& row.dword_0xbb == 0x01f4
&& row.file_count == 1
&& row.sample_file_names == vec!["Port.bty".to_string()]
})
);
assert_eq!(
summary.nonzero_bty_header_alias_selector_summaries,