Probe raw save selection context

This commit is contained in:
Jan Petykiewicz 2026-04-17 09:41:16 -07:00
commit 40c0e94ad5
9 changed files with 581 additions and 137 deletions

View file

@ -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

View file

@ -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,

View file

@ -97,9 +97,11 @@ struct SaveSliceProjection {
event_runtime_records: Vec<RuntimeEventRecord>,
companies: Vec<RuntimeCompany>,
has_company_projection: bool,
has_company_selection_override: bool,
selected_company_id: Option<u32>,
chairman_profiles: Vec<RuntimeChairmanProfile>,
has_chairman_projection: bool,
has_chairman_selection_override: bool,
selected_chairman_profile_id: Option<u32>,
candidate_availability: BTreeMap<String, u32>,
named_locomotive_availability: BTreeMap<String, u32>,
@ -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::<Vec<_>>(),
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::<Vec<_>>(),
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::<Vec<_>>(),
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 {

View file

@ -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<String>,
}
#[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<String>,
}
#[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<SmpRuntimePostSpanProbe>,
pub rt3_105_post_span_bridge_probe: Option<SmpRt3105PostSpanBridgeProbe>,
pub rt3_105_save_bridge_payload_probe: Option<SmpRt3105SaveBridgePayloadProbe>,
pub save_world_selection_context_probe: Option<SmpSaveWorldSelectionContextProbe>,
pub rt3_105_save_name_table_probe: Option<SmpRt3105SaveNameTableProbe>,
pub rt3_105_save_named_locomotive_availability_probe:
Option<SmpRt3105SaveNamedLocomotiveAvailabilityProbe>,
@ -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<SmpLoadedCompanyRoster> {
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<SmpLoadedChairmanProfileTable> {
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<KnownCargoSlotDefinition> {
KNOWN_CARGO_SLOT_DEFINITIONS
.iter()
@ -5264,6 +5335,11 @@ fn inspect_bundle_bytes(bytes: &[u8], file_extension_hint: Option<String>) -> 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<String>) -> 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<SmpSaveWorldSelectionContextProbe> {
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<usize> {
.collect()
}
fn find_u32_le_offsets(bytes: &[u8], needle: u32) -> Vec<usize> {
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<usize> {
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 {

View file

@ -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,

View file

@ -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

View file

@ -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"
}

View file

@ -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,

View file

@ -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"