diff --git a/crates/rrt-runtime/src/building.rs b/crates/rrt-runtime/src/building.rs index f369735..321f17e 100644 --- a/crates/rrt-runtime/src/building.rs +++ b/crates/rrt-runtime/src/building.rs @@ -63,6 +63,15 @@ pub struct BuildingTypeNamedBindingComparison { pub source_only_canonical_stems: Vec, } +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct BuildingTypeRecoveredTableSummary { + pub recovered_style_themes: Vec, + pub recovered_source_kinds: Vec, + pub present_style_station_entries: Vec, + pub present_standalone_entries: Vec, + pub bare_port_warehouse_files: Vec, +} + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct BuildingTypeSourceReport { pub directory_path: String, @@ -72,6 +81,7 @@ pub struct BuildingTypeSourceReport { pub bca_selector_pattern_count: usize, #[serde(default)] pub named_binding_comparison: Option, + pub recovered_table_summary: BuildingTypeRecoveredTableSummary, pub notes: Vec, pub bca_selector_patterns: Vec, pub files: Vec, @@ -215,6 +225,7 @@ pub fn inspect_building_types_dir_with_bindings( "BuildingTypes sources are grouped by a canonical stem that lowercases and strips spaces, underscores, and hyphens so paired .bca/.bty variants collapse onto one asset token.".to_string(), "This report is an offline asset-pool view only; it does not by itself assign live candidate ids or prove scenario candidate-table availability.".to_string(), "For .bca files, the report also exposes the narrow selector-byte window at offsets 0xb8..0xbb used by the grounded aux-candidate and live-candidate stream decoders.".to_string(), + "The recovered stock table above the Tier-2 building seam combines one style/theme subset with one source-kind table; this report now surfaces the matching on-disk filename families directly.".to_string(), ]; let named_binding_comparison = if let Some(bindings_path) = bindings_path { @@ -222,6 +233,7 @@ pub fn inspect_building_types_dir_with_bindings( } else { None }; + let recovered_table_summary = summarize_recovered_table_families(&entries, &files); Ok(BuildingTypeSourceReport { directory_path: path.display().to_string(), @@ -230,6 +242,7 @@ pub fn inspect_building_types_dir_with_bindings( unique_canonical_stem_count: entries.len(), bca_selector_pattern_count: bca_selector_patterns.len(), named_binding_comparison, + recovered_table_summary, notes, bca_selector_patterns, files, @@ -293,6 +306,85 @@ fn canonicalize_building_stem(stem: &str) -> String { .collect() } +fn summarize_recovered_table_families( + entries: &[BuildingTypeSourceEntry], + files: &[BuildingTypeSourceFile], +) -> BuildingTypeRecoveredTableSummary { + const RECOVERED_STYLE_THEMES: [&str; 6] = [ + "Victorian", + "Tudor", + "SoWest", + "Persian", + "Kyoto", + "ClpBrd", + ]; + const RECOVERED_SOURCE_KINDS: [&str; 5] = [ + "StationSml", + "StationMed", + "StationLrg", + "ServiceTower", + "Maintenance", + ]; + + let entry_by_canonical = entries + .iter() + .map(|entry| (entry.canonical_stem.clone(), entry)) + .collect::>(); + + let mut present_style_station_entries = Vec::new(); + for style in RECOVERED_STYLE_THEMES { + for source_kind in ["StationSml", "StationMed", "StationLrg"] { + let canonical = canonicalize_building_stem(&format!("{style}{source_kind}")); + if let Some(entry) = entry_by_canonical.get(&canonical) { + if let Some(raw_stem) = entry.raw_stems.first() { + present_style_station_entries.push(raw_stem.clone()); + } + } + } + } + present_style_station_entries.sort(); + present_style_station_entries.dedup(); + + let mut present_standalone_entries = Vec::new(); + for raw_name in ["ServiceTower", "Maintenance"] { + let canonical = canonicalize_building_stem(raw_name); + if let Some(entry) = entry_by_canonical.get(&canonical) { + if let Some(raw_stem) = entry.raw_stems.first() { + present_standalone_entries.push(raw_stem.clone()); + } + } + } + present_standalone_entries.sort(); + present_standalone_entries.dedup(); + + let mut bare_port_warehouse_files = files + .iter() + .filter(|file| { + matches!( + file.canonical_stem.as_str(), + "port" | "warehouse" + ) + }) + .map(|file| file.file_name.clone()) + .collect::>(); + bare_port_warehouse_files.sort(); + bare_port_warehouse_files.dedup(); + + BuildingTypeRecoveredTableSummary { + recovered_style_themes: RECOVERED_STYLE_THEMES + .into_iter() + .map(str::to_string) + .collect(), + recovered_source_kinds: RECOVERED_SOURCE_KINDS + .into_iter() + .map(str::to_string) + .collect(), + present_style_station_entries, + present_standalone_entries, + bare_port_warehouse_files, + } +} + #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct BuildingBindingArtifact { bindings: Vec, @@ -323,4 +415,68 @@ mod tests { assert_eq!(probe.byte_0xb8_hex, "0x12"); assert_eq!(probe.byte_0xbb_hex, "0x78"); } + + #[test] + fn summarizes_recovered_table_families_from_entries_and_files() { + let entries = vec![ + BuildingTypeSourceEntry { + canonical_stem: canonicalize_building_stem("VictorianStationSml"), + raw_stems: vec!["VictorianStationSml".to_string()], + source_kinds: vec![BuildingTypeSourceKind::Bty], + file_names: vec!["VictorianStationSml.bty".to_string()], + }, + BuildingTypeSourceEntry { + canonical_stem: canonicalize_building_stem("ClpBrdStationLrg"), + raw_stems: vec!["ClpbrdStationLrg".to_string()], + source_kinds: vec![BuildingTypeSourceKind::Bty], + file_names: vec!["ClpbrdStationLrg.bty".to_string()], + }, + BuildingTypeSourceEntry { + canonical_stem: canonicalize_building_stem("Maintenance"), + raw_stems: vec!["Maintenance".to_string()], + source_kinds: vec![BuildingTypeSourceKind::Bty], + file_names: vec!["Maintenance.bty".to_string()], + }, + BuildingTypeSourceEntry { + canonical_stem: canonicalize_building_stem("ServiceTower"), + raw_stems: vec!["ServiceTower".to_string()], + source_kinds: vec![BuildingTypeSourceKind::Bty], + file_names: vec!["ServiceTower.bty".to_string()], + }, + ]; + let files = vec![ + BuildingTypeSourceFile { + file_name: "Port.bty".to_string(), + raw_stem: "Port".to_string(), + canonical_stem: canonicalize_building_stem("Port"), + source_kind: BuildingTypeSourceKind::Bty, + byte_len: None, + bca_selector_probe: None, + }, + BuildingTypeSourceFile { + file_name: "Warehouse.bca".to_string(), + raw_stem: "Warehouse".to_string(), + canonical_stem: canonicalize_building_stem("Warehouse"), + source_kind: BuildingTypeSourceKind::Bca, + byte_len: None, + bca_selector_probe: None, + }, + ]; + + let summary = summarize_recovered_table_families(&entries, &files); + assert!(summary + .present_style_station_entries + .contains(&"VictorianStationSml".to_string())); + assert!(summary + .present_style_station_entries + .contains(&"ClpbrdStationLrg".to_string())); + assert_eq!( + summary.present_standalone_entries, + vec!["Maintenance".to_string(), "ServiceTower".to_string()] + ); + assert_eq!( + summary.bare_port_warehouse_files, + vec!["Port.bty".to_string(), "Warehouse.bca".to_string()] + ); + } }