diff --git a/README.md b/README.md index ddb9081..2bb2abd 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,10 @@ chairman ordinals remain explicit frontier. Checked-in save-slice documents can now also carry explicit company rosters and chairman-profile tables, so the current company-targeted and chairman-targeted descriptor and condition batches can execute from standalone save-slice fixtures without overlay snapshots when that context is present; raw `.gms` inspection -still does not reconstruct those company/chairman collections automatically. A checked-in +still does not reconstruct those company/chairman collections automatically, but it now does +reconstruct selection-only company/chairman context from the fixed save-side `0x32c8` world block. +Those raw selected ids can flow through save-slice export/import and override overlay-backed base +selection even while the full raw rosters remain absent. A checked-in `EventEffects` export now exists too in `artifacts/exports/rt3-1.06/event-effects-table.json`, and a checked-in semantic closure layer now exists beside it in `artifacts/exports/rt3-1.06/event-effects-semantic-catalog.json`. Recovered @@ -55,7 +58,9 @@ runtime landing surfaces too: descriptor `105` `All Cargo Prices` plus descripto event-owned cargo override state, and the grounded named cargo-production strip `180..229` now imports into named cargo production overrides too. The named cargo-price strip `106..176` remains explicit `blocked_evidence_blocked_descriptor` parity until descriptor ordering is pinned -more strongly. The add-building strip `503..519` is now explicitly classified as recovered +more strongly, but the semantic catalog now gives that band stable `Named Cargo Price Slot N` +labels instead of anonymous `Unknown Cargo Price` residue. The add-building strip `503..519` is +now explicitly classified as recovered shell-owned descriptor parity rather than generic unresolved residue. The first grounded condition-side unlock now exists for negative-sentinel `raw_condition_id = -1` company scopes, and the first ordinary nonnegative condition batch now executes too: numeric-threshold company diff --git a/artifacts/exports/rt3-1.06/event-effects-semantic-catalog.json b/artifacts/exports/rt3-1.06/event-effects-semantic-catalog.json index f94539d..6794893 100644 --- a/artifacts/exports/rt3-1.06/event-effects-semantic-catalog.json +++ b/artifacts/exports/rt3-1.06/event-effects-semantic-catalog.json @@ -959,7 +959,7 @@ }, { "descriptor_id": 106, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 1", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -968,7 +968,7 @@ }, { "descriptor_id": 107, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 2", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -977,7 +977,7 @@ }, { "descriptor_id": 108, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 3", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -986,7 +986,7 @@ }, { "descriptor_id": 109, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 4", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -995,7 +995,7 @@ }, { "descriptor_id": 110, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 5", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1004,7 +1004,7 @@ }, { "descriptor_id": 111, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 6", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1013,7 +1013,7 @@ }, { "descriptor_id": 112, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 7", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1022,7 +1022,7 @@ }, { "descriptor_id": 113, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 8", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1031,7 +1031,7 @@ }, { "descriptor_id": 114, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 9", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1040,7 +1040,7 @@ }, { "descriptor_id": 115, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 10", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1049,7 +1049,7 @@ }, { "descriptor_id": 116, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 11", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1058,7 +1058,7 @@ }, { "descriptor_id": 117, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 12", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1067,7 +1067,7 @@ }, { "descriptor_id": 118, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 13", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1076,7 +1076,7 @@ }, { "descriptor_id": 119, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 14", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1085,7 +1085,7 @@ }, { "descriptor_id": 120, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 15", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1094,7 +1094,7 @@ }, { "descriptor_id": 121, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 16", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1103,7 +1103,7 @@ }, { "descriptor_id": 122, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 17", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1112,7 +1112,7 @@ }, { "descriptor_id": 123, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 18", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1121,7 +1121,7 @@ }, { "descriptor_id": 124, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 19", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1130,7 +1130,7 @@ }, { "descriptor_id": 125, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 20", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1139,7 +1139,7 @@ }, { "descriptor_id": 126, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 21", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1148,7 +1148,7 @@ }, { "descriptor_id": 127, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 22", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1157,7 +1157,7 @@ }, { "descriptor_id": 128, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 23", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1166,7 +1166,7 @@ }, { "descriptor_id": 129, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 24", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1175,7 +1175,7 @@ }, { "descriptor_id": 130, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 25", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1184,7 +1184,7 @@ }, { "descriptor_id": 131, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 26", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1193,7 +1193,7 @@ }, { "descriptor_id": 132, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 27", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1202,7 +1202,7 @@ }, { "descriptor_id": 133, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 28", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1211,7 +1211,7 @@ }, { "descriptor_id": 134, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 29", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1220,7 +1220,7 @@ }, { "descriptor_id": 135, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 30", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1229,7 +1229,7 @@ }, { "descriptor_id": 136, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 31", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1238,7 +1238,7 @@ }, { "descriptor_id": 137, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 32", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1247,7 +1247,7 @@ }, { "descriptor_id": 138, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 33", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1256,7 +1256,7 @@ }, { "descriptor_id": 139, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 34", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1265,7 +1265,7 @@ }, { "descriptor_id": 140, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 35", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1274,7 +1274,7 @@ }, { "descriptor_id": 141, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 36", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1283,7 +1283,7 @@ }, { "descriptor_id": 142, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 37", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1292,7 +1292,7 @@ }, { "descriptor_id": 143, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 38", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1301,7 +1301,7 @@ }, { "descriptor_id": 144, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 39", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1310,7 +1310,7 @@ }, { "descriptor_id": 145, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 40", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1319,7 +1319,7 @@ }, { "descriptor_id": 146, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 41", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1328,7 +1328,7 @@ }, { "descriptor_id": 147, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 42", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1337,7 +1337,7 @@ }, { "descriptor_id": 148, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 43", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1346,7 +1346,7 @@ }, { "descriptor_id": 149, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 44", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1355,7 +1355,7 @@ }, { "descriptor_id": 150, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 45", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1364,7 +1364,7 @@ }, { "descriptor_id": 151, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 46", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1373,7 +1373,7 @@ }, { "descriptor_id": 152, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 47", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1382,7 +1382,7 @@ }, { "descriptor_id": 153, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 48", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1391,7 +1391,7 @@ }, { "descriptor_id": 154, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 49", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1400,7 +1400,7 @@ }, { "descriptor_id": 155, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 50", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1409,7 +1409,7 @@ }, { "descriptor_id": 156, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 51", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1418,7 +1418,7 @@ }, { "descriptor_id": 157, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 52", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1427,7 +1427,7 @@ }, { "descriptor_id": 158, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 53", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1436,7 +1436,7 @@ }, { "descriptor_id": 159, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 54", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1445,7 +1445,7 @@ }, { "descriptor_id": 160, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 55", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1454,7 +1454,7 @@ }, { "descriptor_id": 161, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 56", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1463,7 +1463,7 @@ }, { "descriptor_id": 162, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 57", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1472,7 +1472,7 @@ }, { "descriptor_id": 163, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 58", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1481,7 +1481,7 @@ }, { "descriptor_id": 164, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 59", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1490,7 +1490,7 @@ }, { "descriptor_id": 165, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 60", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1499,7 +1499,7 @@ }, { "descriptor_id": 166, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 61", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1508,7 +1508,7 @@ }, { "descriptor_id": 167, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 62", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1517,7 +1517,7 @@ }, { "descriptor_id": 168, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 63", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1526,7 +1526,7 @@ }, { "descriptor_id": 169, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 64", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1535,7 +1535,7 @@ }, { "descriptor_id": 170, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 65", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1544,7 +1544,7 @@ }, { "descriptor_id": 171, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 66", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1553,7 +1553,7 @@ }, { "descriptor_id": 172, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 67", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1562,7 +1562,7 @@ }, { "descriptor_id": 173, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 68", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1571,7 +1571,7 @@ }, { "descriptor_id": 174, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 69", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1580,7 +1580,7 @@ }, { "descriptor_id": 175, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 70", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, @@ -1589,7 +1589,7 @@ }, { "descriptor_id": 176, - "label": "Unknown Cargo Price", + "label": "Named Cargo Price Slot 71", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, diff --git a/crates/rrt-runtime/src/import.rs b/crates/rrt-runtime/src/import.rs index 58a0f98..62428ec 100644 --- a/crates/rrt-runtime/src/import.rs +++ b/crates/rrt-runtime/src/import.rs @@ -97,9 +97,11 @@ struct SaveSliceProjection { event_runtime_records: Vec, companies: Vec, has_company_projection: bool, + has_company_selection_override: bool, selected_company_id: Option, chairman_profiles: Vec, has_chairman_projection: bool, + has_chairman_selection_override: bool, selected_chairman_profile_id: Option, candidate_availability: BTreeMap, named_locomotive_availability: BTreeMap, @@ -269,11 +271,19 @@ pub fn project_save_slice_to_runtime_state_import( world_restore: projection.world_restore, metadata: projection.metadata, companies: projection.companies, - selected_company_id: projection.selected_company_id, + selected_company_id: if projection.has_company_projection { + projection.selected_company_id + } else { + None + }, players: Vec::new(), selected_player_id: None, chairman_profiles: projection.chairman_profiles, - selected_chairman_profile_id: projection.selected_chairman_profile_id, + selected_chairman_profile_id: if projection.has_chairman_projection { + projection.selected_chairman_profile_id + } else { + None + }, trains: Vec::new(), locomotive_catalog: projection.locomotive_catalog.unwrap_or_default(), cargo_catalog: projection.cargo_catalog.unwrap_or_default(), @@ -349,7 +359,9 @@ pub fn project_save_slice_overlay_to_runtime_state_import( } else { base_state.companies.clone() }, - selected_company_id: if projection.has_company_projection { + selected_company_id: if projection.has_company_projection + || projection.has_company_selection_override + { projection.selected_company_id } else { base_state.selected_company_id @@ -361,7 +373,9 @@ pub fn project_save_slice_overlay_to_runtime_state_import( } else { base_state.chairman_profiles.clone() }, - selected_chairman_profile_id: if projection.has_chairman_projection { + selected_chairman_profile_id: if projection.has_chairman_projection + || projection.has_chairman_selection_override + { projection.selected_chairman_profile_id } else { base_state.selected_chairman_profile_id @@ -809,7 +823,7 @@ fn project_save_slice_components( None }; - let (companies, has_company_projection, selected_company_id) = + let (companies, has_company_projection, has_company_selection_override, selected_company_id) = if let Some(roster) = &save_slice.company_roster { metadata.insert( "save_slice.company_roster_source_kind".to_string(), @@ -829,55 +843,77 @@ fn project_save_slice_components( selected_company_id.to_string(), ); } - ( - roster - .entries - .iter() - .map(|entry| RuntimeCompany { - company_id: entry.company_id, - current_cash: entry.current_cash, - debt: entry.debt, - credit_rating_score: entry.credit_rating_score, - prime_rate: entry.prime_rate, - active: entry.active, - available_track_laying_capacity: entry.available_track_laying_capacity, - controller_kind: entry.controller_kind, - linked_chairman_profile_id: entry.linked_chairman_profile_id, - book_value_per_share: entry.book_value_per_share, - investor_confidence: entry.investor_confidence, - management_attitude: entry.management_attitude, - takeover_cooldown_year: entry.takeover_cooldown_year, - merger_cooldown_year: entry.merger_cooldown_year, - track_piece_counts: entry.track_piece_counts, - }) - .collect::>(), - true, - roster.selected_company_id, - ) + if roster.entries.is_empty() { + ( + Vec::new(), + false, + roster.selected_company_id.is_some(), + roster.selected_company_id, + ) + } else { + ( + roster + .entries + .iter() + .map(|entry| RuntimeCompany { + company_id: entry.company_id, + current_cash: entry.current_cash, + debt: entry.debt, + credit_rating_score: entry.credit_rating_score, + prime_rate: entry.prime_rate, + active: entry.active, + available_track_laying_capacity: entry.available_track_laying_capacity, + controller_kind: entry.controller_kind, + linked_chairman_profile_id: entry.linked_chairman_profile_id, + book_value_per_share: entry.book_value_per_share, + investor_confidence: entry.investor_confidence, + management_attitude: entry.management_attitude, + takeover_cooldown_year: entry.takeover_cooldown_year, + merger_cooldown_year: entry.merger_cooldown_year, + track_piece_counts: entry.track_piece_counts, + }) + .collect::>(), + true, + roster.selected_company_id.is_some(), + roster.selected_company_id, + ) + } } else { - (Vec::new(), false, None) + (Vec::new(), false, false, None) }; - let (chairman_profiles, has_chairman_projection, selected_chairman_profile_id) = - if let Some(table) = &save_slice.chairman_profile_table { + let ( + chairman_profiles, + has_chairman_projection, + has_chairman_selection_override, + selected_chairman_profile_id, + ) = if let Some(table) = &save_slice.chairman_profile_table { + metadata.insert( + "save_slice.chairman_profile_table_source_kind".to_string(), + table.source_kind.clone(), + ); + metadata.insert( + "save_slice.chairman_profile_table_semantic_family".to_string(), + table.semantic_family.clone(), + ); + metadata.insert( + "save_slice.chairman_profile_table_entry_count".to_string(), + table.observed_entry_count.to_string(), + ); + if let Some(selected_chairman_profile_id) = table.selected_chairman_profile_id { metadata.insert( - "save_slice.chairman_profile_table_source_kind".to_string(), - table.source_kind.clone(), + "save_slice.selected_chairman_profile_id".to_string(), + selected_chairman_profile_id.to_string(), ); - metadata.insert( - "save_slice.chairman_profile_table_semantic_family".to_string(), - table.semantic_family.clone(), - ); - metadata.insert( - "save_slice.chairman_profile_table_entry_count".to_string(), - table.observed_entry_count.to_string(), - ); - if let Some(selected_chairman_profile_id) = table.selected_chairman_profile_id { - metadata.insert( - "save_slice.selected_chairman_profile_id".to_string(), - selected_chairman_profile_id.to_string(), - ); - } + } + if table.entries.is_empty() { + ( + Vec::new(), + false, + table.selected_chairman_profile_id.is_some(), + table.selected_chairman_profile_id, + ) + } else { ( table .entries @@ -895,11 +931,13 @@ fn project_save_slice_components( }) .collect::>(), true, + table.selected_chairman_profile_id.is_some(), table.selected_chairman_profile_id, ) - } else { - (Vec::new(), false, None) - }; + } + } else { + (Vec::new(), false, false, None) + }; let named_locomotive_cost = BTreeMap::new(); let all_cargo_price_override = None; @@ -920,6 +958,8 @@ fn project_save_slice_components( && companies .iter() .all(|company| company.controller_kind != RuntimeCompanyControllerKind::Unknown); + } else if has_company_selection_override { + packed_event_context.selected_company_id = selected_company_id; } if has_chairman_projection { packed_event_context.known_chairman_profile_ids = chairman_profiles @@ -927,6 +967,8 @@ fn project_save_slice_components( .map(|profile| profile.profile_id) .collect(); packed_event_context.selected_chairman_profile_id = selected_chairman_profile_id; + } else if has_chairman_selection_override { + packed_event_context.selected_chairman_profile_id = selected_chairman_profile_id; } if let Some(catalog) = &locomotive_catalog { packed_event_context.locomotive_catalog_names_by_id = catalog @@ -976,9 +1018,11 @@ fn project_save_slice_components( event_runtime_records, companies, has_company_projection, + has_company_selection_override, selected_company_id, chairman_profiles, has_chairman_projection, + has_chairman_selection_override, selected_chairman_profile_id, candidate_availability, named_locomotive_availability, @@ -4918,11 +4962,12 @@ mod tests { descriptor_id: u32, value: i32, ) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary { + let slot = descriptor_id.saturating_sub(105); crate::SmpLoadedPackedEventGroupedEffectRowSummary { group_index: 0, row_index: 0, descriptor_id, - descriptor_label: Some("Unknown Cargo Price".to_string()), + descriptor_label: Some(format!("Named Cargo Price Slot {slot}")), target_mask_bits: Some(0x08), parameter_family: Some("cargo_price_scalar".to_string()), grouped_target_subject: None, @@ -4937,7 +4982,7 @@ mod tests { value_word_0x16: 0, row_shape: "scalar_assignment".to_string(), semantic_family: Some("scalar_assignment".to_string()), - semantic_preview: Some(format!("Set Unknown Cargo Price to {value}")), + semantic_preview: Some(format!("Set Named Cargo Price Slot {slot} to {value}")), recovered_cargo_slot: None, recovered_cargo_class: None, recovered_cargo_label: None, @@ -5874,6 +5919,118 @@ mod tests { assert_eq!(import.state.territories, base_state.territories); } + #[test] + fn overlay_applies_selection_only_company_and_chairman_context_from_save_slice() { + let base_state = RuntimeState { + companies: vec![ + crate::RuntimeCompany { + company_id: 1, + current_cash: 100, + debt: 0, + credit_rating_score: None, + prime_rate: None, + active: true, + available_track_laying_capacity: None, + controller_kind: RuntimeCompanyControllerKind::Human, + linked_chairman_profile_id: Some(1), + book_value_per_share: 0, + investor_confidence: 0, + management_attitude: 0, + takeover_cooldown_year: None, + merger_cooldown_year: None, + track_piece_counts: RuntimeTrackPieceCounts::default(), + }, + crate::RuntimeCompany { + company_id: 42, + current_cash: 200, + debt: 0, + credit_rating_score: None, + prime_rate: None, + active: true, + available_track_laying_capacity: None, + controller_kind: RuntimeCompanyControllerKind::Human, + linked_chairman_profile_id: Some(9), + book_value_per_share: 0, + investor_confidence: 0, + management_attitude: 0, + takeover_cooldown_year: None, + merger_cooldown_year: None, + track_piece_counts: RuntimeTrackPieceCounts::default(), + }, + ], + selected_company_id: Some(42), + chairman_profiles: vec![ + crate::RuntimeChairmanProfile { + profile_id: 1, + name: "Selected".to_string(), + active: true, + current_cash: 0, + linked_company_id: Some(1), + company_holdings: BTreeMap::new(), + holdings_value_total: 0, + net_worth_total: 0, + purchasing_power_total: 0, + }, + crate::RuntimeChairmanProfile { + profile_id: 9, + name: "Base".to_string(), + active: true, + current_cash: 0, + linked_company_id: Some(42), + company_holdings: BTreeMap::new(), + holdings_value_total: 0, + net_worth_total: 0, + purchasing_power_total: 0, + }, + ], + selected_chairman_profile_id: Some(9), + ..state() + }; + let save_slice = SmpLoadedSaveSlice { + file_extension_hint: Some("gms".to_string()), + container_profile_family: Some("rt3-105-save-container-v1".to_string()), + mechanism_family: "rt3-105-save-post-span-bridge-v1".to_string(), + mechanism_confidence: "mixed".to_string(), + trailer_family: Some("rt3-105-save-trailer-v1".to_string()), + bridge_family: Some("rt3-105-save-post-span-bridge-v1".to_string()), + profile: None, + candidate_availability_table: None, + named_locomotive_availability_table: None, + locomotive_catalog: None, + cargo_catalog: None, + company_roster: Some(crate::SmpLoadedCompanyRoster { + source_kind: "save-direct-world-block-company-selection-only".to_string(), + semantic_family: "scenario-selected-company-context".to_string(), + observed_entry_count: 0, + selected_company_id: Some(1), + entries: Vec::new(), + }), + chairman_profile_table: Some(crate::SmpLoadedChairmanProfileTable { + source_kind: "save-direct-world-block-chairman-selection-only".to_string(), + semantic_family: "scenario-selected-chairman-context".to_string(), + observed_entry_count: 0, + selected_chairman_profile_id: Some(1), + entries: Vec::new(), + }), + special_conditions_table: None, + event_runtime_collection: None, + notes: vec![], + }; + + let import = project_save_slice_overlay_to_runtime_state_import( + &base_state, + &save_slice, + "overlay-save-selection-only-context", + None, + ) + .expect("overlay import should project"); + + assert_eq!(import.state.companies, base_state.companies); + assert_eq!(import.state.selected_company_id, Some(1)); + assert_eq!(import.state.chairman_profiles, base_state.chairman_profiles); + assert_eq!(import.state.selected_chairman_profile_id, Some(1)); + } + #[test] fn projects_executable_packed_records_into_runtime_and_services_follow_on() { let save_slice = SmpLoadedSaveSlice { diff --git a/crates/rrt-runtime/src/smp.rs b/crates/rrt-runtime/src/smp.rs index 24fbfa1..9d3ab6c 100644 --- a/crates/rrt-runtime/src/smp.rs +++ b/crates/rrt-runtime/src/smp.rs @@ -90,6 +90,11 @@ const RECIPE_BOOK_LINE_STRIDE: usize = 0x30; const RECIPE_BOOK_LINE_AREA_LEN: usize = RECIPE_BOOK_LINE_COUNT * RECIPE_BOOK_LINE_STRIDE; const RECIPE_BOOK_SUMMARY_END_OFFSET: usize = RECIPE_BOOK_ROOT_OFFSET + RECIPE_BOOK_COUNT * RECIPE_BOOK_STRIDE; +const RT3_SAVE_WORLD_BLOCK_CHUNK_TAG: u32 = 0x000032c8; +const RT3_SAVE_WORLD_BLOCK_NEXT_CHUNK_TAG: u32 = 0x000032c9; +const RT3_SAVE_WORLD_BLOCK_LEN: usize = 0x4f2c; +const RT3_SAVE_WORLD_BLOCK_SELECTED_COMPANY_ID_RELATIVE_OFFSET: usize = 0x1d; +const RT3_SAVE_WORLD_BLOCK_SELECTED_CHAIRMAN_PROFILE_ID_RELATIVE_OFFSET: usize = 0x21; const EVENT_RUNTIME_COLLECTION_METADATA_TAG: u16 = 0x4e99; const EVENT_RUNTIME_COLLECTION_RECORDS_TAG: u16 = 0x4e9a; const EVENT_RUNTIME_COLLECTION_CLOSE_TAG: u16 = 0x4e9b; @@ -1444,6 +1449,24 @@ pub struct SmpRt3105SaveBridgePayloadProbe { pub evidence: Vec, } +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct SmpSaveWorldSelectionContextProbe { + pub profile_family: String, + pub source_kind: String, + pub semantic_family: String, + pub chunk_tag_offset: usize, + pub payload_offset: usize, + pub payload_len: usize, + pub payload_len_hex: String, + pub selected_company_id_offset: usize, + pub selected_company_id: u32, + pub selected_company_id_hex: String, + pub selected_chairman_profile_id_offset: usize, + pub selected_chairman_profile_id: u32, + pub selected_chairman_profile_id_hex: String, + pub evidence: Vec, +} + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct SmpRt3105SaveNameTableProbe { pub profile_family: String, @@ -2362,6 +2385,7 @@ pub struct SmpInspectionReport { pub runtime_post_span_probe: Option, pub rt3_105_post_span_bridge_probe: Option, pub rt3_105_save_bridge_payload_probe: Option, + pub save_world_selection_context_probe: Option, pub rt3_105_save_name_table_probe: Option, pub rt3_105_save_named_locomotive_availability_probe: Option, @@ -2497,6 +2521,14 @@ pub fn load_save_slice_from_report( .recipe_book_summary_probe .as_ref() .and_then(derive_cargo_catalog_from_recipe_book_probe); + let company_roster = report + .save_world_selection_context_probe + .as_ref() + .and_then(derive_selection_only_company_roster_from_save_world_probe); + let chairman_profile_table = report + .save_world_selection_context_probe + .as_ref() + .and_then(derive_selection_only_chairman_profile_table_from_save_world_probe); let special_conditions_table = report .special_conditions_probe @@ -2509,6 +2541,21 @@ pub fn load_save_slice_from_report( enabled_visible_labels: probe.enabled_visible_labels.clone(), entries: probe.entries.clone(), }); + let mut notes = summary.notes.clone(); + if let Some(probe) = &report.save_world_selection_context_probe { + notes.push(format!( + "Raw save fixed world block exposes selected_company_id={} at file offset 0x{:x}.", + probe.selected_company_id, probe.selected_company_id_offset + )); + notes.push(format!( + "Raw save fixed world block exposes selected_chairman_profile_id={} at file offset 0x{:x}.", + probe.selected_chairman_profile_id, probe.selected_chairman_profile_id_offset + )); + notes.push( + "Raw save inspection still does not reconstruct full company_roster or chairman_profile_table payloads; the grounded package-save path only proves selection ids and header-level collection state for those families." + .to_string(), + ); + } Ok(SmpLoadedSaveSlice { file_extension_hint: summary.file_extension_hint.clone(), @@ -2522,11 +2569,11 @@ pub fn load_save_slice_from_report( named_locomotive_availability_table, locomotive_catalog, cargo_catalog, - company_roster: None, - chairman_profile_table: None, + company_roster, + chairman_profile_table, special_conditions_table, event_runtime_collection: report.event_runtime_collection_summary.clone(), - notes: summary.notes.clone(), + notes, }) } @@ -2609,6 +2656,30 @@ fn derive_cargo_catalog_from_recipe_book_probe( }) } +fn derive_selection_only_company_roster_from_save_world_probe( + probe: &SmpSaveWorldSelectionContextProbe, +) -> Option { + Some(SmpLoadedCompanyRoster { + source_kind: format!("{}-company-selection-only", probe.source_kind), + semantic_family: "scenario-selected-company-context".to_string(), + observed_entry_count: 0, + selected_company_id: Some(probe.selected_company_id), + entries: Vec::new(), + }) +} + +fn derive_selection_only_chairman_profile_table_from_save_world_probe( + probe: &SmpSaveWorldSelectionContextProbe, +) -> Option { + Some(SmpLoadedChairmanProfileTable { + source_kind: format!("{}-chairman-selection-only", probe.source_kind), + semantic_family: "scenario-selected-chairman-context".to_string(), + observed_entry_count: 0, + selected_chairman_profile_id: Some(probe.selected_chairman_profile_id), + entries: Vec::new(), + }) +} + fn known_cargo_slot_definition(slot_id: u32) -> Option { KNOWN_CARGO_SLOT_DEFINITIONS .iter() @@ -5264,6 +5335,11 @@ fn inspect_bundle_bytes(bytes: &[u8], file_extension_hint: Option) -> Sm ); let rt3_105_save_bridge_payload_probe = parse_rt3_105_save_bridge_payload_probe(bytes, rt3_105_post_span_bridge_probe.as_ref()); + let save_world_selection_context_probe = parse_save_world_selection_context_probe( + bytes, + file_extension_hint.as_deref(), + container_profile.as_ref(), + ); let rt3_105_save_name_table_probe = parse_rt3_105_save_name_table_probe( bytes, file_extension_hint.as_deref(), @@ -5410,6 +5486,7 @@ fn inspect_bundle_bytes(bytes: &[u8], file_extension_hint: Option) -> Sm runtime_post_span_probe, rt3_105_post_span_bridge_probe, rt3_105_save_bridge_payload_probe, + save_world_selection_context_probe, rt3_105_save_name_table_probe, rt3_105_save_named_locomotive_availability_probe, special_conditions_probe, @@ -6826,6 +6903,74 @@ fn parse_rt3_105_save_bridge_payload_probe( }) } +fn parse_save_world_selection_context_probe( + bytes: &[u8], + file_extension_hint: Option<&str>, + container_profile: Option<&SmpContainerProfile>, +) -> Option { + if file_extension_hint != Some("gms") { + return None; + } + let profile = container_profile?; + let supported = matches!( + profile.profile_family.as_str(), + "rt3-classic-save-container-v1" + | "rt3-105-save-container-v1" + | "rt3-105-scenario-save-container-v1" + | "rt3-105-alt-save-container-v1" + ); + if !supported { + return None; + } + + for chunk_tag_offset in find_u32_le_offsets(bytes, RT3_SAVE_WORLD_BLOCK_CHUNK_TAG) { + let payload_offset = chunk_tag_offset + 4; + let next_chunk_tag_offset = payload_offset.checked_add(RT3_SAVE_WORLD_BLOCK_LEN)?; + if read_u32_at(bytes, next_chunk_tag_offset) != Some(RT3_SAVE_WORLD_BLOCK_NEXT_CHUNK_TAG) { + continue; + } + let selected_company_id_offset = + payload_offset + RT3_SAVE_WORLD_BLOCK_SELECTED_COMPANY_ID_RELATIVE_OFFSET; + let selected_chairman_profile_id_offset = + payload_offset + RT3_SAVE_WORLD_BLOCK_SELECTED_CHAIRMAN_PROFILE_ID_RELATIVE_OFFSET; + let selected_company_id = read_u32_at(bytes, selected_company_id_offset)?; + let selected_chairman_profile_id = read_u32_at(bytes, selected_chairman_profile_id_offset)?; + return Some(SmpSaveWorldSelectionContextProbe { + profile_family: profile.profile_family.clone(), + source_kind: "save-direct-world-block".to_string(), + semantic_family: "scenario-selected-company-and-chairman-context".to_string(), + chunk_tag_offset, + payload_offset, + payload_len: RT3_SAVE_WORLD_BLOCK_LEN, + payload_len_hex: format!("0x{:x}", RT3_SAVE_WORLD_BLOCK_LEN), + selected_company_id_offset, + selected_company_id, + selected_company_id_hex: format!("0x{selected_company_id:08x}"), + selected_chairman_profile_id_offset, + selected_chairman_profile_id, + selected_chairman_profile_id_hex: format!("0x{selected_chairman_profile_id:08x}"), + evidence: vec![ + format!( + "chunk tag 0x32c8 at 0x{chunk_tag_offset:x} matches the fixed [world+0x04] save block" + ), + format!( + "next chunk tag 0x32c9 appears at 0x{next_chunk_tag_offset:x}, matching the documented 0x4f2c payload span" + ), + format!( + "selected company id comes from payload +0x{:x} ([world+0x21])", + RT3_SAVE_WORLD_BLOCK_SELECTED_COMPANY_ID_RELATIVE_OFFSET + ), + format!( + "selected chairman profile id comes from payload +0x{:x} ([world+0x25])", + RT3_SAVE_WORLD_BLOCK_SELECTED_CHAIRMAN_PROFILE_ID_RELATIVE_OFFSET + ), + ], + }); + } + + None +} + fn parse_rt3_105_save_name_table_probe( bytes: &[u8], file_extension_hint: Option<&str>, @@ -8619,6 +8764,15 @@ fn find_u16_le_offsets(bytes: &[u8], needle: u16) -> Vec { .collect() } +fn find_u32_le_offsets(bytes: &[u8], needle: u32) -> Vec { + let pattern = needle.to_le_bytes(); + bytes + .windows(pattern.len()) + .enumerate() + .filter_map(|(offset, window)| (window == pattern).then_some(offset)) + .collect() +} + fn find_next_nonzero_offset(bytes: &[u8], start: usize) -> Option { bytes .iter() @@ -13025,6 +13179,114 @@ mod tests { ); } + #[test] + fn parses_save_world_selection_context_probe_from_fixed_world_block() { + let mut bytes = vec![0u8; 0x8000]; + let chunk_tag_offset = 0x3ceusize; + let payload_offset = chunk_tag_offset + 4; + bytes[chunk_tag_offset..chunk_tag_offset + 4] + .copy_from_slice(&RT3_SAVE_WORLD_BLOCK_CHUNK_TAG.to_le_bytes()); + bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_SELECTED_COMPANY_ID_RELATIVE_OFFSET + ..payload_offset + RT3_SAVE_WORLD_BLOCK_SELECTED_COMPANY_ID_RELATIVE_OFFSET + 4] + .copy_from_slice(&7u32.to_le_bytes()); + bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_SELECTED_CHAIRMAN_PROFILE_ID_RELATIVE_OFFSET + ..payload_offset + + RT3_SAVE_WORLD_BLOCK_SELECTED_CHAIRMAN_PROFILE_ID_RELATIVE_OFFSET + + 4] + .copy_from_slice(&9u32.to_le_bytes()); + let next_chunk_offset = payload_offset + RT3_SAVE_WORLD_BLOCK_LEN; + bytes[next_chunk_offset..next_chunk_offset + 4] + .copy_from_slice(&RT3_SAVE_WORLD_BLOCK_NEXT_CHUNK_TAG.to_le_bytes()); + + let probe = parse_save_world_selection_context_probe( + &bytes, + Some("gms"), + Some(&SmpContainerProfile { + profile_family: "rt3-105-save-container-v1".to_string(), + profile_evidence: vec![], + is_known_profile: true, + }), + ) + .expect("selection-context probe should parse"); + + assert_eq!(probe.chunk_tag_offset, chunk_tag_offset); + assert_eq!(probe.payload_offset, payload_offset); + assert_eq!(probe.selected_company_id, 7); + assert_eq!(probe.selected_chairman_profile_id, 9); + } + + #[test] + fn loads_selection_only_company_and_chairman_context_from_save_world_probe() { + let mut report = inspect_smp_bytes(&[]); + report.save_load_summary = Some(SmpSaveLoadSummary { + file_extension_hint: Some("gms".to_string()), + container_profile_family: Some("rt3-105-save-container-v1".to_string()), + mechanism_family: "rt3-105-save-post-span-bridge-v1".to_string(), + mechanism_confidence: "mixed".to_string(), + packed_profile_kind: None, + packed_profile_family: None, + packed_profile_offset: None, + packed_profile_len: None, + map_path: None, + display_name: None, + profile_byte_0x77: None, + profile_byte_0x77_hex: None, + profile_byte_0x82: None, + profile_byte_0x82_hex: None, + profile_byte_0x97: None, + profile_byte_0x97_hex: None, + profile_byte_0xc5: None, + profile_byte_0xc5_hex: None, + trailer_family: Some("rt3-105-save-trailer-v1".to_string()), + bridge_family: Some("rt3-105-save-post-span-bridge-v1".to_string()), + candidate_table: None, + notes: vec![], + }); + report.save_world_selection_context_probe = Some(SmpSaveWorldSelectionContextProbe { + profile_family: "rt3-105-save-container-v1".to_string(), + source_kind: "save-direct-world-block".to_string(), + semantic_family: "scenario-selected-company-and-chairman-context".to_string(), + chunk_tag_offset: 0x3ce, + payload_offset: 0x3d2, + payload_len: RT3_SAVE_WORLD_BLOCK_LEN, + payload_len_hex: format!("0x{:x}", RT3_SAVE_WORLD_BLOCK_LEN), + selected_company_id_offset: 0x3ef, + selected_company_id: 1, + selected_company_id_hex: "0x00000001".to_string(), + selected_chairman_profile_id_offset: 0x3f3, + selected_chairman_profile_id: 9, + selected_chairman_profile_id_hex: "0x00000009".to_string(), + evidence: vec![], + }); + + let slice = load_save_slice_from_report(&report).expect("save slice"); + + let company_roster = slice.company_roster.expect("selection-only company roster"); + assert_eq!(company_roster.observed_entry_count, 0); + assert_eq!(company_roster.selected_company_id, Some(1)); + assert!(company_roster.entries.is_empty()); + + let chairman_table = slice + .chairman_profile_table + .expect("selection-only chairman table"); + assert_eq!(chairman_table.observed_entry_count, 0); + assert_eq!(chairman_table.selected_chairman_profile_id, Some(9)); + assert!(chairman_table.entries.is_empty()); + + assert!( + slice + .notes + .iter() + .any(|note| note.contains("selected_company_id=1")) + ); + assert!( + slice + .notes + .iter() + .any(|note| note.contains("selected_chairman_profile_id=9")) + ); + } + #[test] fn classifies_rt3_105_post_span_bridge_variants() { let base_trailer = SmpRuntimeTrailerBlock { diff --git a/docs/README.md b/docs/README.md index 01faf19..62dc874 100644 --- a/docs/README.md +++ b/docs/README.md @@ -96,7 +96,10 @@ The highest-value next passes are now: - checked-in save-slice documents can now carry explicit company rosters and chairman profile tables too, so the current company-targeted and chairman-targeted descriptor/condition batches can execute from standalone save-slice fixtures without overlay snapshots when that context is - present; raw `.gms` inspection/export still does not reconstruct those company/chairman surfaces + present; raw `.gms` inspection/export still does not reconstruct full company/chairman rosters, + but it now does reconstruct selection-only company/chairman context from the fixed save-side + `0x32c8` world block, so overlay imports can reuse base rosters while honoring raw save-native + selected company/chairman ids - a checked-in `EventEffects` export now exists at `artifacts/exports/rt3-1.06/event-effects-table.json`, and a checked-in semantic closure layer now exists at `artifacts/exports/rt3-1.06/event-effects-semantic-catalog.json` @@ -122,7 +125,9 @@ The highest-value next passes are now: / `All Farm/Mine Production` land on bounded event-owned cargo override state, and the grounded named cargo-production strip `180..229` now lands on named cargo production overrides too - the named cargo-price strip `106..176` remains explicit - `blocked_evidence_blocked_descriptor` parity until descriptor ordering is pinned more strongly + `blocked_evidence_blocked_descriptor` parity until descriptor ordering is pinned more strongly, + but the checked-in semantic catalog now gives that band stable `Named Cargo Price Slot N` + labels instead of anonymous `Unknown Cargo Price` residue - the add-building strip `503..519` is now explicitly classified as recovered shell-owned parity, with tracked fixture coverage, instead of generic unresolved descriptor residue - widen real packed-event executable coverage descriptor by descriptor after identity, target mask, diff --git a/docs/runtime-rehost-plan.md b/docs/runtime-rehost-plan.md index fdac63f..53b55ed 100644 --- a/docs/runtime-rehost-plan.md +++ b/docs/runtime-rehost-plan.md @@ -59,7 +59,9 @@ Implemented today: those save-owned surfaces; that lets the currently supported company-targeted and chairman-targeted descriptor/condition batches execute from standalone save-slice fixtures without overlay snapshots when the checked-in documents include that context, while raw `.gms` - inspection/export still leaves those company/chairman surfaces absent + inspection/export still leaves full company/chairman rosters absent; the grounded raw-save + tranche now covers only selection-only company/chairman context from the fixed `0x32c8` world + block, which overlay import can use to replace selected ids while preserving base rosters - a checked-in `EventEffects` export now exists too at `artifacts/exports/rt3-1.06/event-effects-table.json`, and a checked-in semantic closure layer now exists at `artifacts/exports/rt3-1.06/event-effects-semantic-catalog.json` @@ -84,7 +86,9 @@ Implemented today: / `All Farm/Mine Production` import through bounded cargo override surfaces, and the grounded named cargo-production strip `180..229` now imports through named cargo production overrides too - the named cargo-price strip `106..176` now sits on explicit - `blocked_evidence_blocked_descriptor` parity instead of generic unmapped-descriptor frontier + `blocked_evidence_blocked_descriptor` parity instead of generic unmapped-descriptor frontier; + the checked-in semantic catalog now at least gives that band stable `Named Cargo Price Slot N` + labels instead of anonymous `Unknown Cargo Price` residue - the add-building strip `503..519` is now explicitly classified as recovered shell-owned parity with tracked fixture coverage, not generic unresolved descriptor residue - a minimal event-owned train surface and an opaque economic-status lane now exist in runtime diff --git a/fixtures/runtime/packed-event-cargo-economics-parity-save-slice-fixture.json b/fixtures/runtime/packed-event-cargo-economics-parity-save-slice-fixture.json index 2a9d9cf..faeeba6 100644 --- a/fixtures/runtime/packed-event-cargo-economics-parity-save-slice-fixture.json +++ b/fixtures/runtime/packed-event-cargo-economics-parity-save-slice-fixture.json @@ -31,7 +31,7 @@ "grouped_effect_rows": [ { "descriptor_id": 106, - "descriptor_label": "Unknown Cargo Price", + "descriptor_label": "Named Cargo Price Slot 1", "parameter_family": "cargo_price_scalar", "semantic_family": "scalar_assignment" } diff --git a/fixtures/runtime/packed-event-cargo-economics-parity-save-slice.json b/fixtures/runtime/packed-event-cargo-economics-parity-save-slice.json index dc9a1d1..b641380 100644 --- a/fixtures/runtime/packed-event-cargo-economics-parity-save-slice.json +++ b/fixtures/runtime/packed-event-cargo-economics-parity-save-slice.json @@ -71,7 +71,7 @@ "group_index": 0, "row_index": 0, "descriptor_id": 106, - "descriptor_label": "Unknown Cargo Price", + "descriptor_label": "Named Cargo Price Slot 1", "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "opcode": 3, @@ -84,7 +84,7 @@ "value_word_0x16": 0, "row_shape": "scalar_assignment", "semantic_family": "scalar_assignment", - "semantic_preview": "Set Unknown Cargo Price to 140", + "semantic_preview": "Set Named Cargo Price Slot 1 to 140", "recovered_cargo_slot": null, "recovered_cargo_class": null, "recovered_locomotive_id": null, diff --git a/tools/py/build_event_effect_semantic_catalog.py b/tools/py/build_event_effect_semantic_catalog.py index f9007bb..9135227 100644 --- a/tools/py/build_event_effect_semantic_catalog.py +++ b/tools/py/build_event_effect_semantic_catalog.py @@ -98,6 +98,12 @@ def locomotive_cost_label(locomotive_id: int) -> str: return f"Lower-Band Locomotive Cost Slot {locomotive_id}" +def cargo_price_label(descriptor_id: int, binding: dict[str, object] | None) -> str: + if binding is not None: + return f"{binding['cargo_name']} Price" + return f"Named Cargo Price Slot {descriptor_id - 105}" + + def load_cargo_bindings(raw_table_path: Path) -> dict[int, dict[str, object]]: bindings_path = raw_table_path.parent / "event-effects-cargo-bindings.json" if not bindings_path.exists(): @@ -152,6 +158,11 @@ def classify( executable_in_runtime = True elif 106 <= descriptor_id <= 176: parameter_family = "cargo_price_scalar" + binding = cargo_bindings.get(descriptor_id) + label = cargo_price_label(descriptor_id, binding) + if binding is not None: + runtime_status = "executable" + executable_in_runtime = True elif 177 <= descriptor_id <= 179: parameter_family = "cargo_production_scalar" runtime_status = "executable"