Add named locomotive availability runtime surface

This commit is contained in:
Jan Petykiewicz 2026-04-16 10:23:29 -07:00
commit 8c7ff335cb
16 changed files with 542 additions and 13 deletions

View file

@ -94,6 +94,7 @@ struct SaveSliceProjection {
packed_event_collection: Option<RuntimePackedEventCollectionSummary>,
event_runtime_records: Vec<RuntimeEventRecord>,
candidate_availability: BTreeMap<String, u32>,
named_locomotive_availability: BTreeMap<String, u32>,
special_conditions: BTreeMap<String, u32>,
}
@ -238,6 +239,7 @@ pub fn project_save_slice_to_runtime_state_import(
packed_event_collection: projection.packed_event_collection,
event_runtime_records: projection.event_runtime_records,
candidate_availability: projection.candidate_availability,
named_locomotive_availability: projection.named_locomotive_availability,
special_conditions: projection.special_conditions,
service_state: RuntimeServiceState::default(),
};
@ -295,6 +297,7 @@ pub fn project_save_slice_overlay_to_runtime_state_import(
packed_event_collection: projection.packed_event_collection,
event_runtime_records: projection.event_runtime_records,
candidate_availability: projection.candidate_availability,
named_locomotive_availability: projection.named_locomotive_availability,
special_conditions: projection.special_conditions,
service_state: base_state.service_state.clone(),
};
@ -325,6 +328,10 @@ fn project_save_slice_components(
"save_slice.special_conditions_present".to_string(),
save_slice.special_conditions_table.is_some(),
);
world_flags.insert(
"save_slice.named_locomotive_availability_present".to_string(),
save_slice.named_locomotive_availability_table.is_some(),
);
world_flags.insert(
"save_slice.event_runtime_collection_present".to_string(),
save_slice.event_runtime_collection.is_some(),
@ -590,6 +597,35 @@ fn project_save_slice_components(
}
}
let mut named_locomotive_availability = BTreeMap::new();
if let Some(table) = &save_slice.named_locomotive_availability_table {
metadata.insert(
"save_slice.named_locomotive_availability_source_kind".to_string(),
table.source_kind.clone(),
);
metadata.insert(
"save_slice.named_locomotive_availability_semantic_family".to_string(),
table.semantic_family.clone(),
);
metadata.insert(
"save_slice.named_locomotive_availability_entry_count".to_string(),
table.observed_entry_count.to_string(),
);
metadata.insert(
"save_slice.named_locomotive_availability_zero_count".to_string(),
table.zero_availability_count.to_string(),
);
if let Some(header_offset) = table.header_offset {
metadata.insert(
"save_slice.named_locomotive_availability_header_offset".to_string(),
header_offset.to_string(),
);
}
for entry in &table.entries {
named_locomotive_availability.insert(entry.text.clone(), entry.availability_dword);
}
}
for (index, note) in save_slice.notes.iter().enumerate() {
metadata.insert(format!("save_slice.note.{index}"), note.clone());
}
@ -602,6 +638,7 @@ fn project_save_slice_components(
packed_event_collection,
event_runtime_records,
candidate_availability,
named_locomotive_availability,
special_conditions,
})
}
@ -1133,6 +1170,12 @@ fn lower_condition_targets_in_effect(
value: *value,
}
}
RuntimeEffect::SetNamedLocomotiveAvailability { name, value } => {
RuntimeEffect::SetNamedLocomotiveAvailability {
name: name.clone(),
value: *value,
}
}
RuntimeEffect::SetSpecialCondition { label, value } => RuntimeEffect::SetSpecialCondition {
label: label.clone(),
value: *value,
@ -1562,6 +1605,12 @@ fn smp_runtime_effect_to_runtime_effect(
value: *value,
})
}
RuntimeEffect::SetNamedLocomotiveAvailability { name, value } => {
Ok(RuntimeEffect::SetNamedLocomotiveAvailability {
name: name.clone(),
value: *value,
})
}
RuntimeEffect::SetSpecialCondition { label, value } => {
Ok(RuntimeEffect::SetSpecialCondition {
label: label.clone(),
@ -2111,6 +2160,7 @@ fn runtime_effect_uses_condition_true_company(effect: &RuntimeEffect) -> bool {
| RuntimeEffect::SetPlayerCash { .. }
| RuntimeEffect::DeactivatePlayer { .. }
| RuntimeEffect::SetCandidateAvailability { .. }
| RuntimeEffect::SetNamedLocomotiveAvailability { .. }
| RuntimeEffect::SetSpecialCondition { .. }
| RuntimeEffect::ActivateEventRecord { .. }
| RuntimeEffect::DeactivateEventRecord { .. }
@ -2188,6 +2238,7 @@ fn runtime_effect_company_target_import_blocker(
| RuntimeEffect::SetLimitedTrackBuildingAmount { .. }
| RuntimeEffect::SetEconomicStatusCode { .. }
| RuntimeEffect::SetCandidateAvailability { .. }
| RuntimeEffect::SetNamedLocomotiveAvailability { .. }
| RuntimeEffect::SetSpecialCondition { .. }
| RuntimeEffect::ActivateEventRecord { .. }
| RuntimeEffect::DeactivateEventRecord { .. }
@ -2526,6 +2577,7 @@ mod tests {
packed_event_collection: None,
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
}
@ -3080,6 +3132,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
special_conditions_table: None,
event_runtime_collection: None,
notes: vec![],
@ -3118,6 +3171,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
special_conditions_table: None,
event_runtime_collection: None,
notes: vec![],
@ -3199,6 +3253,38 @@ mod tests {
},
],
}),
named_locomotive_availability_table: Some(
crate::SmpLoadedNamedLocomotiveAvailabilityTable {
source_kind: "runtime-save-direct-serializer".to_string(),
semantic_family: "scenario-named-locomotive-availability-table".to_string(),
header_offset: None,
entries_offset: None,
entries_end_offset: None,
observed_entry_count: 2,
zero_availability_count: 1,
zero_availability_names: vec!["Big Boy".to_string()],
entries: vec![
crate::SmpRt3105SaveNameTableEntry {
index: 0,
offset: 0,
text: "Big Boy".to_string(),
availability_dword: 0,
availability_dword_hex: "0x00000000".to_string(),
trailer_word: 0,
trailer_word_hex: "0x00000000".to_string(),
},
crate::SmpRt3105SaveNameTableEntry {
index: 1,
offset: 0x41,
text: "GP7".to_string(),
availability_dword: 1,
availability_dword_hex: "0x00000001".to_string(),
trailer_word: 1,
trailer_word_hex: "0x00000001".to_string(),
},
],
},
),
special_conditions_table: Some(crate::SmpLoadedSpecialConditionsTable {
source_kind: "save-fixed-special-conditions-range".to_string(),
table_offset: 0x0d64,
@ -3444,10 +3530,34 @@ mod tests {
import.state.candidate_availability.get("Uranium Mine"),
Some(&0)
);
assert_eq!(
import.state.named_locomotive_availability.get("Big Boy"),
Some(&0)
);
assert_eq!(
import.state.named_locomotive_availability.get("GP7"),
Some(&1)
);
assert_eq!(
import.state.special_conditions.get("Disable Cargo Economy"),
Some(&0)
);
assert_eq!(
import
.state
.metadata
.get("save_slice.named_locomotive_availability_source_kind")
.map(String::as_str),
Some("runtime-save-direct-serializer")
);
assert_eq!(
import
.state
.metadata
.get("save_slice.named_locomotive_availability_entry_count")
.map(String::as_str),
Some("2")
);
assert_eq!(
import
.state
@ -3485,6 +3595,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -3598,6 +3709,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -3689,6 +3801,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -3798,6 +3911,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -3880,6 +3994,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -4012,6 +4127,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -4252,6 +4368,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -4327,6 +4444,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -4424,6 +4542,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -4496,6 +4615,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -4615,6 +4735,7 @@ mod tests {
packed_event_collection: None,
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
};
@ -4627,6 +4748,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -4769,6 +4891,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -4871,6 +4994,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -4954,6 +5078,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -5058,6 +5183,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -5162,6 +5288,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -5256,6 +5383,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -5346,6 +5474,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -5444,6 +5573,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -5533,6 +5663,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -5604,6 +5735,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -5680,6 +5812,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -5761,6 +5894,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -5842,6 +5976,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -5939,6 +6074,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -6027,6 +6163,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -6141,6 +6278,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -6264,6 +6402,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -6372,6 +6511,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -6543,6 +6683,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -6634,6 +6775,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -6721,6 +6863,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -6873,6 +7016,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -7014,6 +7158,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -7102,6 +7247,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -7217,6 +7363,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -7320,6 +7467,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -7443,6 +7591,7 @@ mod tests {
effects: vec![],
}],
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState {
periodic_boundary_calls: 9,
@ -7460,6 +7609,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
@ -7609,6 +7759,7 @@ mod tests {
packed_event_collection: None,
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
},
@ -7629,6 +7780,7 @@ mod tests {
bridge_family: None,
profile: None,
candidate_availability_table: None,
named_locomotive_availability_table: None,
special_conditions_table: None,
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),