Ground locomotives-page descriptor semantics

This commit is contained in:
Jan Petykiewicz 2026-04-16 22:26:23 -07:00
commit 358d4cdec3
26 changed files with 1157 additions and 710 deletions

View file

@ -2984,6 +2984,12 @@ fn real_grouped_row_is_unsupported_executable_descriptor_variant(
56 | 57 => row.row_shape != "scalar_assignment",
_ => match row.parameter_family.as_deref() {
Some("world_scalar_override") => row.row_shape != "scalar_assignment",
Some("locomotive_availability_scalar") => {
!(row.row_shape == "scalar_assignment" && row.raw_scalar_value >= 0)
}
Some("locomotive_cost_scalar") => {
!(row.row_shape == "scalar_assignment" && row.raw_scalar_value >= 0)
}
Some("cargo_price_scalar") => {
row.descriptor_id == 105
&& !(row.row_shape == "scalar_assignment" && row.raw_scalar_value >= 0)
@ -4153,11 +4159,94 @@ mod tests {
descriptor_id: u32,
value: i32,
) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary {
fn grounded_locomotive_name(locomotive_id: u32) -> Option<&'static str> {
match locomotive_id {
1 => Some("2-D-2"),
2 => Some("E-88"),
3 => Some("Adler 2-2-2"),
4 => Some("USA 103"),
5 => Some("American 4-4-0"),
6 => Some("Atlantic 4-4-2"),
7 => Some("Baldwin 0-6-0"),
8 => Some("Be 5/7"),
9 => Some("Beuth 2-2-2"),
10 => Some("Big Boy 4-8-8-4"),
11 => Some("C55 Deltic"),
12 => Some("Camelback 0-6-0"),
13 => Some("Challenger 4-6-6-4"),
14 => Some("Class 01 4-6-2"),
15 => Some("Class 103"),
16 => Some("Class 132"),
17 => Some("Class 500 4-6-0"),
18 => Some("Class 9100"),
19 => Some("Class EF 66"),
20 => Some("Class 6E"),
21 => Some("Consolidation 2-8-0"),
22 => Some("Crampton 4-2-0"),
23 => Some("DD 080-X"),
24 => Some("DD40AX"),
25 => Some("Duke Class 4-4-0"),
26 => Some("E18"),
27 => Some("E428"),
28 => Some("Brenner E412"),
29 => Some("E60CP"),
30 => Some("Eight Wheeler 4-4-0"),
31 => Some("EP-2 Bipolar"),
32 => Some("ET22"),
33 => Some("F3"),
34 => Some("Fairlie 0-6-6-0"),
35 => Some("Firefly 2-2-2"),
36 => Some("FP45"),
37 => Some("Ge 6/6 Crocodile"),
38 => Some("GG1"),
39 => Some("GP7"),
40 => Some("H10 2-8-2"),
41 => Some("HST 125"),
42 => Some("Kriegslok 2-10-0"),
43 => Some("Mallard 4-6-2"),
44 => Some("Norris 4-2-0"),
45 => Some("Northern 4-8-4"),
46 => Some("Orca NX462"),
47 => Some("Pacific 4-6-2"),
48 => Some("Planet 2-2-0"),
49 => Some("Re 6/6"),
50 => Some("Red Devil 4-8-4"),
51 => Some("S3 4-4-0"),
52 => Some("NA-90D"),
53 => Some("Shay (2-Truck)"),
54 => Some("Shinkansen Series 0"),
55 => Some("Stirling 4-2-2"),
56 => Some("Trans-Euro"),
57 => Some("V200"),
58 => Some("VL80T"),
_ => None,
}
}
let recovered_locomotive_id = match descriptor_id {
241..=351 => Some(descriptor_id - 240),
_ => None,
};
let descriptor_label = match descriptor_id {
457..=474 => {
format!(
"Upper-Band Locomotive Availability Slot {}",
descriptor_id - 456
)
}
_ => recovered_locomotive_id
.map(|loco_id| {
grounded_locomotive_name(loco_id)
.map(|name| format!("{name} Availability"))
.unwrap_or_else(|| format!("Locomotive {loco_id} Availability"))
})
.unwrap_or_else(|| "Locomotive Availability".to_string()),
};
crate::SmpLoadedPackedEventGroupedEffectRowSummary {
group_index: 0,
row_index: 0,
descriptor_id,
descriptor_label: Some("Unknown Loco Available".to_string()),
descriptor_label: Some(descriptor_label.clone()),
target_mask_bits: Some(0x08),
parameter_family: Some("locomotive_availability_scalar".to_string()),
grouped_target_subject: None,
@ -4172,14 +4261,10 @@ 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 Loco Available to {value}")),
semantic_preview: Some(format!("Set {descriptor_label} to {value}")),
recovered_cargo_slot: None,
recovered_cargo_class: None,
recovered_locomotive_id: match descriptor_id {
241..=351 => Some(descriptor_id - 240),
457..=474 => Some(descriptor_id - 345),
_ => None,
},
recovered_locomotive_id,
locomotive_name: None,
notes: vec![],
}
@ -4189,14 +4274,84 @@ mod tests {
descriptor_id: u32,
value: i32,
) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary {
fn grounded_locomotive_name(locomotive_id: u32) -> Option<&'static str> {
match locomotive_id {
1 => Some("2-D-2"),
2 => Some("E-88"),
3 => Some("Adler 2-2-2"),
4 => Some("USA 103"),
5 => Some("American 4-4-0"),
6 => Some("Atlantic 4-4-2"),
7 => Some("Baldwin 0-6-0"),
8 => Some("Be 5/7"),
9 => Some("Beuth 2-2-2"),
10 => Some("Big Boy 4-8-8-4"),
11 => Some("C55 Deltic"),
12 => Some("Camelback 0-6-0"),
13 => Some("Challenger 4-6-6-4"),
14 => Some("Class 01 4-6-2"),
15 => Some("Class 103"),
16 => Some("Class 132"),
17 => Some("Class 500 4-6-0"),
18 => Some("Class 9100"),
19 => Some("Class EF 66"),
20 => Some("Class 6E"),
21 => Some("Consolidation 2-8-0"),
22 => Some("Crampton 4-2-0"),
23 => Some("DD 080-X"),
24 => Some("DD40AX"),
25 => Some("Duke Class 4-4-0"),
26 => Some("E18"),
27 => Some("E428"),
28 => Some("Brenner E412"),
29 => Some("E60CP"),
30 => Some("Eight Wheeler 4-4-0"),
31 => Some("EP-2 Bipolar"),
32 => Some("ET22"),
33 => Some("F3"),
34 => Some("Fairlie 0-6-6-0"),
35 => Some("Firefly 2-2-2"),
36 => Some("FP45"),
37 => Some("Ge 6/6 Crocodile"),
38 => Some("GG1"),
39 => Some("GP7"),
40 => Some("H10 2-8-2"),
41 => Some("HST 125"),
42 => Some("Kriegslok 2-10-0"),
43 => Some("Mallard 4-6-2"),
44 => Some("Norris 4-2-0"),
45 => Some("Northern 4-8-4"),
46 => Some("Orca NX462"),
47 => Some("Pacific 4-6-2"),
48 => Some("Planet 2-2-0"),
49 => Some("Re 6/6"),
50 => Some("Red Devil 4-8-4"),
51 => Some("S3 4-4-0"),
52 => Some("NA-90D"),
53 => Some("Shay (2-Truck)"),
54 => Some("Shinkansen Series 0"),
55 => Some("Stirling 4-2-2"),
56 => Some("Trans-Euro"),
57 => Some("V200"),
58 => Some("VL80T"),
_ => None,
}
}
let recovered_locomotive_id = match descriptor_id {
352..=451 => Some(descriptor_id - 351),
475..=500 => Some(descriptor_id - 374),
_ => None,
};
let descriptor_label = recovered_locomotive_id
.map(|loco_id| format!("Locomotive {loco_id} Cost"))
.unwrap_or_else(|| "Locomotive Cost".to_string());
let descriptor_label = match descriptor_id {
475..=502 => format!("Upper-Band Locomotive Cost Slot {}", descriptor_id - 474),
_ => recovered_locomotive_id
.map(|loco_id| {
grounded_locomotive_name(loco_id)
.map(|name| format!("{name} Cost"))
.unwrap_or_else(|| format!("Locomotive {loco_id} Cost"))
})
.unwrap_or_else(|| "Locomotive Cost".to_string()),
};
crate::SmpLoadedPackedEventGroupedEffectRowSummary {
group_index: 0,
row_index: 0,
@ -4228,6 +4383,71 @@ mod tests {
fn save_named_locomotive_table(
count: usize,
) -> crate::SmpLoadedNamedLocomotiveAvailabilityTable {
fn grounded_locomotive_name(index: usize) -> String {
match index {
0 => "2-D-2",
1 => "E-88",
2 => "Adler 2-2-2",
3 => "USA 103",
4 => "American 4-4-0",
5 => "Atlantic 4-4-2",
6 => "Baldwin 0-6-0",
7 => "Be 5/7",
8 => "Beuth 2-2-2",
9 => "Big Boy 4-8-8-4",
10 => "C55 Deltic",
11 => "Camelback 0-6-0",
12 => "Challenger 4-6-6-4",
13 => "Class 01 4-6-2",
14 => "Class 103",
15 => "Class 132",
16 => "Class 500 4-6-0",
17 => "Class 9100",
18 => "Class EF 66",
19 => "Class 6E",
20 => "Consolidation 2-8-0",
21 => "Crampton 4-2-0",
22 => "DD 080-X",
23 => "DD40AX",
24 => "Duke Class 4-4-0",
25 => "E18",
26 => "E428",
27 => "Brenner E412",
28 => "E60CP",
29 => "Eight Wheeler 4-4-0",
30 => "EP-2 Bipolar",
31 => "ET22",
32 => "F3",
33 => "Fairlie 0-6-6-0",
34 => "Firefly 2-2-2",
35 => "FP45",
36 => "Ge 6/6 Crocodile",
37 => "GG1",
38 => "GP7",
39 => "H10 2-8-2",
40 => "HST 125",
41 => "Kriegslok 2-10-0",
42 => "Mallard 4-6-2",
43 => "Norris 4-2-0",
44 => "Northern 4-8-4",
45 => "Orca NX462",
46 => "Pacific 4-6-2",
47 => "Planet 2-2-0",
48 => "Re 6/6",
49 => "Red Devil 4-8-4",
50 => "S3 4-4-0",
51 => "NA-90D",
52 => "Shay (2-Truck)",
53 => "Shinkansen Series 0",
54 => "Stirling 4-2-2",
55 => "Trans-Euro",
56 => "V200",
57 => "VL80T",
_ => return format!("Locomotive {}", index + 1),
}
.to_string()
}
crate::SmpLoadedNamedLocomotiveAvailabilityTable {
source_kind: "runtime-save-direct-serializer".to_string(),
semantic_family: "scenario-named-locomotive-availability-table".to_string(),
@ -4241,7 +4461,7 @@ mod tests {
.map(|index| crate::SmpRt3105SaveNameTableEntry {
index,
offset: 0x7c78 + index * 0x41,
text: format!("Locomotive {}", index + 1),
text: grounded_locomotive_name(index),
availability_dword: 1,
availability_dword_hex: "0x00000001".to_string(),
trailer_word: 1,
@ -6712,7 +6932,7 @@ mod tests {
group_index: 0,
row_index: 0,
descriptor_id: 250,
descriptor_label: Some("Unknown Loco Available".to_string()),
descriptor_label: Some("Big Boy 4-8-8-4 Availability".to_string()),
target_mask_bits: Some(0x08),
parameter_family: Some("locomotive_availability_scalar".to_string()),
grouped_target_subject: None,
@ -6727,7 +6947,9 @@ mod tests {
value_word_0x16: 0,
row_shape: "scalar_assignment".to_string(),
semantic_family: Some("scalar_assignment".to_string()),
semantic_preview: Some("Set Unknown Loco Available to 42".to_string()),
semantic_preview: Some(
"Set Big Boy 4-8-8-4 Availability to 42".to_string(),
),
recovered_cargo_slot: None,
recovered_cargo_class: None,
recovered_locomotive_id: Some(10),
@ -6856,7 +7078,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: Some(save_named_locomotive_table(112)),
named_locomotive_availability_table: Some(save_named_locomotive_table(58)),
locomotive_catalog: None,
cargo_catalog: None,
company_roster: None,
@ -6896,7 +7118,7 @@ mod tests {
grouped_effect_row_counts: vec![2, 0, 0, 0],
grouped_effect_rows: vec![
real_locomotive_availability_row(250, 42),
real_locomotive_availability_row(457, 7),
real_locomotive_availability_row(298, 7),
],
decoded_conditions: Vec::new(),
decoded_actions: vec![],
@ -6917,7 +7139,7 @@ mod tests {
)
.expect("save slice should project");
assert_eq!(import.state.locomotive_catalog.len(), 112);
assert_eq!(import.state.locomotive_catalog.len(), 58);
assert_eq!(import.state.event_runtime_records.len(), 1);
assert_eq!(
import
@ -6938,14 +7160,11 @@ mod tests {
import
.state
.named_locomotive_availability
.get("Locomotive 10"),
.get("Big Boy 4-8-8-4"),
Some(&42)
);
assert_eq!(
import
.state
.named_locomotive_availability
.get("Locomotive 112"),
import.state.named_locomotive_availability.get("VL80T"),
Some(&7)
);
}
@ -6973,11 +7192,11 @@ mod tests {
locomotive_catalog: vec![
crate::RuntimeLocomotiveCatalogEntry {
locomotive_id: 10,
name: "Locomotive 10".to_string(),
name: "Big Boy 4-8-8-4".to_string(),
},
crate::RuntimeLocomotiveCatalogEntry {
locomotive_id: 112,
name: "Locomotive 112".to_string(),
locomotive_id: 58,
name: "VL80T".to_string(),
},
],
cargo_catalog: Vec::new(),
@ -6988,8 +7207,8 @@ mod tests {
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::from([
("Locomotive 10".to_string(), 0),
("Locomotive 112".to_string(), 1),
("Big Boy 4-8-8-4".to_string(), 0),
("VL80T".to_string(), 1),
]),
named_locomotive_cost: BTreeMap::new(),
all_cargo_price_override: None,
@ -7052,7 +7271,7 @@ mod tests {
grouped_effect_row_counts: vec![2, 0, 0, 0],
grouped_effect_rows: vec![
real_locomotive_availability_row(250, 42),
real_locomotive_availability_row(457, 7),
real_locomotive_availability_row(298, 7),
],
decoded_conditions: Vec::new(),
decoded_actions: vec![],
@ -7094,14 +7313,11 @@ mod tests {
import
.state
.named_locomotive_availability
.get("Locomotive 10"),
.get("Big Boy 4-8-8-4"),
Some(&42)
);
assert_eq!(
import
.state
.named_locomotive_availability
.get("Locomotive 112"),
import.state.named_locomotive_availability.get("VL80T"),
Some(&7)
);
}
@ -7275,7 +7491,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: Some(save_named_locomotive_table(112)),
named_locomotive_availability_table: Some(save_named_locomotive_table(58)),
locomotive_catalog: None,
cargo_catalog: None,
company_roster: None,
@ -7315,7 +7531,7 @@ mod tests {
grouped_effect_row_counts: vec![2, 0, 0, 0],
grouped_effect_rows: vec![
real_locomotive_cost_row(352, 250000),
real_locomotive_cost_row(475, 325000),
real_locomotive_cost_row(409, 325000),
],
decoded_conditions: Vec::new(),
decoded_actions: vec![],
@ -7335,7 +7551,7 @@ mod tests {
)
.expect("save slice should project");
assert_eq!(import.state.locomotive_catalog.len(), 112);
assert_eq!(import.state.locomotive_catalog.len(), 58);
assert_eq!(import.state.event_runtime_records.len(), 1);
assert_eq!(
import
@ -7353,11 +7569,11 @@ mod tests {
.expect("save-derived locomotive cost record should run");
assert_eq!(
import.state.named_locomotive_cost.get("Locomotive 1"),
import.state.named_locomotive_cost.get("2-D-2"),
Some(&250000)
);
assert_eq!(
import.state.named_locomotive_cost.get("Locomotive 101"),
import.state.named_locomotive_cost.get("VL80T"),
Some(&325000)
);
}
@ -7385,11 +7601,11 @@ mod tests {
locomotive_catalog: vec![
crate::RuntimeLocomotiveCatalogEntry {
locomotive_id: 1,
name: "Locomotive 1".to_string(),
name: "2-D-2".to_string(),
},
crate::RuntimeLocomotiveCatalogEntry {
locomotive_id: 101,
name: "Locomotive 101".to_string(),
locomotive_id: 58,
name: "VL80T".to_string(),
},
],
cargo_catalog: Vec::new(),
@ -7401,8 +7617,8 @@ mod tests {
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::from([
("Locomotive 1".to_string(), 100000),
("Locomotive 101".to_string(), 200000),
("2-D-2".to_string(), 100000),
("VL80T".to_string(), 200000),
]),
all_cargo_price_override: None,
named_cargo_price_overrides: BTreeMap::new(),
@ -7464,7 +7680,7 @@ mod tests {
grouped_effect_row_counts: vec![2, 0, 0, 0],
grouped_effect_rows: vec![
real_locomotive_cost_row(352, 250000),
real_locomotive_cost_row(475, 325000),
real_locomotive_cost_row(409, 325000),
],
decoded_conditions: Vec::new(),
decoded_actions: vec![],
@ -7502,11 +7718,11 @@ mod tests {
.expect("overlay-imported locomotive cost record should run");
assert_eq!(
import.state.named_locomotive_cost.get("Locomotive 1"),
import.state.named_locomotive_cost.get("2-D-2"),
Some(&250000)
);
assert_eq!(
import.state.named_locomotive_cost.get("Locomotive 101"),
import.state.named_locomotive_cost.get("VL80T"),
Some(&325000)
);
}
@ -7584,7 +7800,7 @@ mod tests {
.packed_event_collection
.as_ref()
.and_then(|summary| summary.records[0].import_outcome.as_deref()),
Some("blocked_evidence_blocked_descriptor")
Some("blocked_variant_or_scope_blocked_descriptor")
);
}