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")
);
}

View file

@ -446,6 +446,67 @@ const KNOWN_CARGO_SLOT_DEFINITIONS: [KnownCargoSlotDefinition; 11] = [
},
];
const GROUNDED_LOCOMOTIVE_PREFIX: [&str; 58] = [
"2-D-2",
"E-88",
"Adler 2-2-2",
"USA 103",
"American 4-4-0",
"Atlantic 4-4-2",
"Baldwin 0-6-0",
"Be 5/7",
"Beuth 2-2-2",
"Big Boy 4-8-8-4",
"C55 Deltic",
"Camelback 0-6-0",
"Challenger 4-6-6-4",
"Class 01 4-6-2",
"Class 103",
"Class 132",
"Class 500 4-6-0",
"Class 9100",
"Class EF 66",
"Class 6E",
"Consolidation 2-8-0",
"Crampton 4-2-0",
"DD 080-X",
"DD40AX",
"Duke Class 4-4-0",
"E18",
"E428",
"Brenner E412",
"E60CP",
"Eight Wheeler 4-4-0",
"EP-2 Bipolar",
"ET22",
"F3",
"Fairlie 0-6-6-0",
"Firefly 2-2-2",
"FP45",
"Ge 6/6 Crocodile",
"GG1",
"GP7",
"H10 2-8-2",
"HST 125",
"Kriegslok 2-10-0",
"Mallard 4-6-2",
"Norris 4-2-0",
"Northern 4-8-4",
"Orca NX462",
"Pacific 4-6-2",
"Planet 2-2-0",
"Re 6/6",
"Red Devil 4-8-4",
"S3 4-4-0",
"NA-90D",
"Shay (2-Truck)",
"Shinkansen Series 0",
"Stirling 4-2-2",
"Trans-Euro",
"V200",
"VL80T",
];
const REAL_CANDIDATE_AVAILABILITY_CONDITION_TEMPLATE_ID: i32 = 435;
const REAL_CHAIRMAN_CASH_CONDITION_ID: i32 = 2218;
const REAL_CHAIRMAN_HOLDINGS_TOTAL_CONDITION_ID: i32 = 2239;
@ -3752,42 +3813,90 @@ fn recovered_cargo_production_slot(descriptor_id: u32) -> Option<u32> {
fn recovered_locomotive_availability_descriptor_metadata(
descriptor_id: u32,
) -> Option<RealGroupedEffectDescriptorMetadata> {
(241..=351)
.contains(&descriptor_id)
.then_some(RealGroupedEffectDescriptorMetadata {
if let Some(loco_id) = recovered_locomotive_availability_loco_id(descriptor_id) {
let label = recovered_locomotive_availability_label(loco_id);
let executable_in_runtime = (loco_id as usize) <= GROUNDED_LOCOMOTIVE_PREFIX.len();
return Some(RealGroupedEffectDescriptorMetadata {
descriptor_id,
label: "Unknown Loco Available",
label,
target_mask_bits: 0x08,
parameter_family: "locomotive_availability_scalar",
runtime_key: None,
runtime_status: if executable_in_runtime {
RealGroupedEffectRuntimeStatus::Executable
} else {
RealGroupedEffectRuntimeStatus::EvidenceBlocked
},
executable_in_runtime,
});
}
(457..=474)
.contains(&descriptor_id)
.then(|| RealGroupedEffectDescriptorMetadata {
descriptor_id,
label: upper_band_locomotive_availability_label(descriptor_id),
target_mask_bits: 0x08,
parameter_family: "locomotive_availability_scalar",
runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::EvidenceBlocked,
executable_in_runtime: false,
})
.or_else(|| {
(457..=474)
.contains(&descriptor_id)
.then_some(RealGroupedEffectDescriptorMetadata {
descriptor_id,
label: "Unknown Loco Available",
target_mask_bits: 0x08,
parameter_family: "locomotive_availability_scalar",
runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::EvidenceBlocked,
executable_in_runtime: false,
})
})
}
fn recovered_locomotive_availability_loco_id(descriptor_id: u32) -> Option<u32> {
if (241..=351).contains(&descriptor_id) {
return Some(descriptor_id - 240);
}
if (457..=474).contains(&descriptor_id) {
return Some(descriptor_id - 345);
}
None
}
fn grounded_locomotive_name(loco_id: u32) -> Option<&'static str> {
let index = loco_id.checked_sub(1)? as usize;
GROUNDED_LOCOMOTIVE_PREFIX.get(index).copied()
}
fn recovered_locomotive_availability_label(loco_id: u32) -> &'static str {
static LABELS: OnceLock<BTreeMap<u32, &'static str>> = OnceLock::new();
LABELS
.get_or_init(|| {
(1..=111)
.map(|loco_id| {
let label = grounded_locomotive_name(loco_id)
.map(|name| format!("{name} Availability"))
.unwrap_or_else(|| {
format!("Lower-Band Locomotive Availability Slot {loco_id}")
});
(loco_id, Box::leak(label.into_boxed_str()) as &'static str)
})
.collect()
})
.get(&loco_id)
.copied()
.expect("lower-band locomotive availability label should exist")
}
fn upper_band_locomotive_availability_label(descriptor_id: u32) -> &'static str {
static LABELS: OnceLock<BTreeMap<u32, &'static str>> = OnceLock::new();
LABELS
.get_or_init(|| {
(457..=474)
.map(|descriptor_id| {
let label = format!(
"Upper-Band Locomotive Availability Slot {}",
descriptor_id - 456
);
(
descriptor_id,
Box::leak(label.into_boxed_str()) as &'static str,
)
})
.collect()
})
.get(&descriptor_id)
.copied()
.expect("upper-band locomotive availability label should exist")
}
fn recovered_cargo_production_label(descriptor_id: u32) -> Option<&'static str> {
known_cargo_slot_definition_for_descriptor_id(descriptor_id).map(|definition| definition.label)
}
@ -3803,9 +3912,6 @@ fn recovered_locomotive_cost_loco_id(descriptor_id: u32) -> Option<u32> {
if (352..=451).contains(&descriptor_id) {
return Some(descriptor_id - 351);
}
if (475..=500).contains(&descriptor_id) {
return Some(descriptor_id - 374);
}
None
}
@ -3814,11 +3920,14 @@ fn recovered_locomotive_cost_label(descriptor_id: u32) -> Option<&'static str> {
LABELS
.get_or_init(|| {
(352..=451)
.chain(475..=500)
.filter_map(|descriptor_id| {
recovered_locomotive_cost_loco_id(descriptor_id).map(|loco_id| {
let label = Box::leak(format!("Locomotive {loco_id} Cost").into_boxed_str())
as &'static str;
let label = grounded_locomotive_name(loco_id)
.map(|name| format!("{name} Cost"))
.unwrap_or_else(|| {
format!("Lower-Band Locomotive Cost Slot {loco_id}")
});
let label = Box::leak(label.into_boxed_str()) as &'static str;
(descriptor_id, label)
})
})
@ -3826,20 +3935,45 @@ fn recovered_locomotive_cost_label(descriptor_id: u32) -> Option<&'static str> {
})
.get(&descriptor_id)
.copied()
.or_else(|| upper_band_locomotive_cost_label(descriptor_id))
}
fn upper_band_locomotive_cost_label(descriptor_id: u32) -> Option<&'static str> {
static LABELS: OnceLock<BTreeMap<u32, &'static str>> = OnceLock::new();
LABELS
.get_or_init(|| {
(475..=502)
.map(|descriptor_id| {
let label = format!("Upper-Band Locomotive Cost Slot {}", descriptor_id - 474);
(
descriptor_id,
Box::leak(label.into_boxed_str()) as &'static str,
)
})
.collect()
})
.get(&descriptor_id)
.copied()
}
fn recovered_locomotive_cost_descriptor_metadata(
descriptor_id: u32,
) -> Option<RealGroupedEffectDescriptorMetadata> {
recovered_locomotive_cost_label(descriptor_id).map(|label| {
let executable_in_runtime = recovered_locomotive_cost_loco_id(descriptor_id)
.is_some_and(|loco_id| (loco_id as usize) <= GROUNDED_LOCOMOTIVE_PREFIX.len());
RealGroupedEffectDescriptorMetadata {
descriptor_id,
label,
target_mask_bits: 0x08,
parameter_family: "locomotive_cost_scalar",
runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::EvidenceBlocked,
executable_in_runtime: false,
runtime_status: if executable_in_runtime {
RealGroupedEffectRuntimeStatus::Executable
} else {
RealGroupedEffectRuntimeStatus::EvidenceBlocked
},
executable_in_runtime,
}
})
}
@ -11275,11 +11409,11 @@ mod tests {
let metadata =
real_grouped_effect_descriptor_metadata(250).expect("descriptor metadata should exist");
assert_eq!(metadata.label, "Unknown Loco Available");
assert_eq!(metadata.label, "Big Boy 4-8-8-4 Availability");
assert_eq!(metadata.target_mask_bits, 0x08);
assert_eq!(metadata.parameter_family, "locomotive_availability_scalar");
assert_eq!(metadata.runtime_key, None);
assert!(!metadata.executable_in_runtime);
assert!(metadata.executable_in_runtime);
}
#[test]
@ -11287,10 +11421,10 @@ mod tests {
let metadata =
real_grouped_effect_descriptor_metadata(457).expect("descriptor metadata should exist");
assert_eq!(metadata.label, "Unknown Loco Available");
assert_eq!(metadata.label, "Upper-Band Locomotive Availability Slot 1");
assert_eq!(metadata.target_mask_bits, 0x08);
assert_eq!(metadata.parameter_family, "locomotive_availability_scalar");
assert_eq!(recovered_locomotive_availability_loco_id(457), Some(112));
assert_eq!(recovered_locomotive_availability_loco_id(457), None);
}
#[test]
@ -11312,6 +11446,10 @@ mod tests {
.expect("row should parse");
assert_eq!(row.descriptor_id, 250);
assert_eq!(
row.descriptor_label.as_deref(),
Some("Big Boy 4-8-8-4 Availability")
);
assert_eq!(row.recovered_locomotive_id, Some(10));
assert_eq!(
row.parameter_family.as_deref(),
@ -11360,12 +11498,12 @@ mod tests {
let metadata =
real_grouped_effect_descriptor_metadata(352).expect("descriptor metadata should exist");
assert_eq!(metadata.label, "Locomotive 1 Cost");
assert_eq!(metadata.label, "2-D-2 Cost");
assert_eq!(metadata.target_mask_bits, 0x08);
assert_eq!(metadata.parameter_family, "locomotive_cost_scalar");
assert_eq!(metadata.runtime_key, None);
assert_eq!(recovered_locomotive_cost_loco_id(352), Some(1));
assert!(!metadata.executable_in_runtime);
assert!(metadata.executable_in_runtime);
}
#[test]
@ -11373,9 +11511,9 @@ mod tests {
let metadata =
real_grouped_effect_descriptor_metadata(475).expect("descriptor metadata should exist");
assert_eq!(metadata.label, "Locomotive 101 Cost");
assert_eq!(metadata.label, "Upper-Band Locomotive Cost Slot 1");
assert_eq!(metadata.parameter_family, "locomotive_cost_scalar");
assert_eq!(recovered_locomotive_cost_loco_id(475), Some(101));
assert_eq!(recovered_locomotive_cost_loco_id(475), None);
assert!(!metadata.executable_in_runtime);
}
@ -11410,7 +11548,7 @@ mod tests {
.expect("row should parse");
assert_eq!(row.descriptor_id, 352);
assert_eq!(row.descriptor_label.as_deref(), Some("Locomotive 1 Cost"));
assert_eq!(row.descriptor_label.as_deref(), Some("2-D-2 Cost"));
assert_eq!(row.recovered_locomotive_id, Some(1));
assert_eq!(
row.parameter_family.as_deref(),

View file

@ -777,11 +777,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(),
@ -1041,11 +1041,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(),
@ -1097,11 +1097,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(),