diff --git a/crates/rrt-runtime/src/inspect/engine_types.rs b/crates/rrt-runtime/src/inspect/engine_types.rs index 4d3156c..9ecd171 100644 --- a/crates/rrt-runtime/src/inspect/engine_types.rs +++ b/crates/rrt-runtime/src/inspect/engine_types.rs @@ -188,6 +188,8 @@ pub struct EngineTypesInspectionReport { pub cgo_scalar_value_counts: BTreeMap, pub cgo_scalar_ladder_counts: BTreeMap, pub cgo_scalar_values_by_content_stem: BTreeMap>, + pub cgo_content_stem_cct_value_map: BTreeMap, + pub cct_identifiers_without_cgo_content_stem: Vec, pub cct_identifier_counts: BTreeMap, pub cct_value_counts: BTreeMap, pub locomotive_display_census: EngineTypeLocomotiveDisplayCensusReport, @@ -530,6 +532,12 @@ pub fn inspect_engine_types_dir( ); let cgo_scalar_values_by_content_stem = build_cgo_scalar_values_by_content_stem(cgo_reports.values()); + let cgo_content_stem_cct_value_map = + build_cgo_content_stem_cct_value_map(&family_entries, &cgo_scalar_values_by_content_stem); + let cct_identifiers_without_cgo_content_stem = build_cct_identifiers_without_cgo_content_stem( + &family_entries, + &cgo_scalar_values_by_content_stem, + ); let cgo_scalar_ladder_counts = build_cgo_scalar_ladder_counts(cgo_scalar_values_by_content_stem.values()); let cct_identifier_counts = count_named_values( @@ -613,6 +621,8 @@ pub fn inspect_engine_types_dir( cgo_scalar_value_counts, cgo_scalar_ladder_counts, cgo_scalar_values_by_content_stem, + cgo_content_stem_cct_value_map, + cct_identifiers_without_cgo_content_stem, cct_identifier_counts, cct_value_counts, locomotive_display_census, @@ -1040,6 +1050,37 @@ fn build_cgo_scalar_ladder_counts<'a>( count_owned_values(ladders.map(|ladder| ladder.join(" -> "))) } +fn build_cgo_content_stem_cct_value_map( + families: &[EngineTypeFamilyEntry], + cgo_scalar_values_by_content_stem: &BTreeMap>, +) -> BTreeMap { + families + .iter() + .filter_map(|family| { + let identifier = family.cct_identifier.as_ref()?; + let value = family.cct_value?; + cgo_scalar_values_by_content_stem + .contains_key(identifier) + .then_some((identifier.clone(), value)) + }) + .collect() +} + +fn build_cct_identifiers_without_cgo_content_stem( + families: &[EngineTypeFamilyEntry], + cgo_scalar_values_by_content_stem: &BTreeMap>, +) -> Vec { + let mut identifiers = families + .iter() + .filter_map(|family| family.cct_identifier.as_ref()) + .filter(|identifier| !cgo_scalar_values_by_content_stem.contains_key(*identifier)) + .cloned() + .collect::>(); + identifiers.sort(); + identifiers.dedup(); + identifiers +} + fn build_lco_low_cardinality_lane_counts<'a>( reports: impl Iterator, ) -> BTreeMap> { @@ -1397,6 +1438,72 @@ mod tests { ); } + #[test] + fn maps_cgo_content_stems_to_matching_cct_values() { + let families = vec![ + EngineTypeFamilyEntry { + canonical_stem: "box".to_string(), + car_file: None, + lco_file: None, + cgo_file: None, + cct_file: Some("Box.cct".to_string()), + primary_display_name: None, + content_name: None, + internal_stem: None, + auxiliary_stem: None, + side_view_resource: None, + side_view_resource_found_in_pk4: None, + companion_stem: None, + body_type_label: None, + internal_ne_profile_name: None, + internal_ne_profile_found_in_pk4: None, + cct_identifier: Some("Box".to_string()), + cct_value: Some(11), + has_matched_locomotive_pair: false, + }, + EngineTypeFamilyEntry { + canonical_stem: "troop".to_string(), + cct_file: Some("Troop.cct".to_string()), + cct_identifier: Some("Troop".to_string()), + cct_value: Some(9), + ..EngineTypeFamilyEntry { + canonical_stem: "box".to_string(), + car_file: None, + lco_file: None, + cgo_file: None, + cct_file: None, + primary_display_name: None, + content_name: None, + internal_stem: None, + auxiliary_stem: None, + side_view_resource: None, + side_view_resource_found_in_pk4: None, + companion_stem: None, + body_type_label: None, + internal_ne_profile_name: None, + internal_ne_profile_found_in_pk4: None, + cct_identifier: None, + cct_value: None, + has_matched_locomotive_pair: false, + } + }, + ]; + let cgo_scalar_values_by_content_stem = BTreeMap::from([( + "Box".to_string(), + vec!["10.000000".to_string(), "20.000000".to_string()], + )]); + + let mapped = + build_cgo_content_stem_cct_value_map(&families, &cgo_scalar_values_by_content_stem); + let missing = build_cct_identifiers_without_cgo_content_stem( + &families, + &cgo_scalar_values_by_content_stem, + ); + + assert_eq!(mapped.get("Box"), Some(&11)); + assert_eq!(missing, vec!["Troop".to_string()]); + } + #[test] fn counts_owned_value_strings() { let counts = diff --git a/docs/rehost-queue.md b/docs/rehost-queue.md index a5c82c8..b457631 100644 --- a/docs/rehost-queue.md +++ b/docs/rehost-queue.md @@ -18,6 +18,7 @@ This file is the short active queue for the current runtime and reverse-engineer The checked PK4 linkage split is now explicit too: `132 / 145` side-view resource names resolve directly, but the remaining `13` are the missing `CarSideView_3.imb` cohort and that hole exists in both checked installs, while `43 / 145` derived `{internal_stem}_NE.imb` names resolve and all of those hits belong to matched locomotive pairs. The packaged profile metadata is stable enough to summarize: `CarSideView_1` is `512x512` at `0.04` VRAM, `CarSideView_2` is `512x256` at `0.02`, and every packaged `_NE` profile is `512x128` with `HorizontalScaleModifier = 0.75` and `MaxPercentOfInterfaceVRAM = 0.09`. The `_NE` split is now aligned with the locomotive display census too: all `43` packaged `_NE` hits live inside the grounded display prefix, and all `5` unmatched display-tail families are still missing packaged `_NE` profiles. + The cargo side is partially linked now as well: the `.cgo` ladder families and `.cct` sidecar identifiers share the same cargo-family keys for ten checked families, with `Troop` left as the only `.cct`-only outlier. The next honest static work is to keep promoting those fixed lanes into stable parser fields, explain the five remaining distinct auxiliary-stem cases, and decide how far the `.cgo` ladders plus the low-cardinality `.lco` lanes can be grounded without overclaiming semantics. Preserved checked parser detail now lives in [EngineTypes parser semantics](rehost-queue/engine-types-parser-semantics-2026-04-21.md). Preserved checked format inventory detail now lives in [RT3 format inventory](rehost-queue/format-inventory-2026-04-21.md). diff --git a/docs/rehost-queue/engine-types-parser-semantics-2026-04-21.md b/docs/rehost-queue/engine-types-parser-semantics-2026-04-21.md index 60818c6..89f1ef2 100644 --- a/docs/rehost-queue/engine-types-parser-semantics-2026-04-21.md +++ b/docs/rehost-queue/engine-types-parser-semantics-2026-04-21.md @@ -49,6 +49,11 @@ first `.car` / `.lco` / `.cgo` / `.cct` inspector pass landed. - `55 -> 85` for `Auto_Carrier` - `6 -> 13 -> 27 -> 53` for `Passenger` - `7 -> 13 -> 27 -> 53` for `Mail` +- The cargo-side sidecars now have one grounded join seam: + - `cgo_scalar_values_by_content_stem` keys line up directly with the `.cct` identifiers for + `Auto_Carrier`, `Box`, `Covered_Hopper`, `Flatbed`, `Mail`, `Open_Hopper`, `Passenger`, + `Reefer`, `Stock_Car`, and `Tanker` + - `Troop` remains the only checked `.cct` identifier with no matching `.cgo` content stem - `.cct` remains the least ambiguous sidecar: current shipped files still look like narrow one-row text metadata. - The side-view resource seam is no longer a loose-file dead end: @@ -100,6 +105,7 @@ first `.car` / `.lco` / `.cgo` / `.cct` inspector pass landed. - leading scalar lane - content stem - scalar ladder counts by shared cargo-car family + - shared-key map from `.cgo` content stem to `.cct` sidecar value - `.cct` - tokenized identifier/value row - `.imb` @@ -130,6 +136,8 @@ first `.car` / `.lco` / `.cgo` / `.cct` inspector pass landed. - `.cgo` - whether the leading scalar ladders are enough to justify a named typed field, or whether they should stay conservative report-only ladders until more binary/code correlation exists + - whether the matching `.cct` sidecar values justify a named field, or whether they should stay + as cargo-family sidecar codes until more binary/code correlation exists ## Next Static Parser Work