Execute locomotive cost packed event descriptors
This commit is contained in:
parent
e2174713a9
commit
09039d24e4
18 changed files with 785 additions and 21 deletions
|
|
@ -95,6 +95,7 @@ struct SaveSliceProjection {
|
|||
event_runtime_records: Vec<RuntimeEventRecord>,
|
||||
candidate_availability: BTreeMap<String, u32>,
|
||||
named_locomotive_availability: BTreeMap<String, u32>,
|
||||
named_locomotive_cost: BTreeMap<String, u32>,
|
||||
special_conditions: BTreeMap<String, u32>,
|
||||
}
|
||||
|
||||
|
|
@ -249,6 +250,7 @@ pub fn project_save_slice_to_runtime_state_import(
|
|||
event_runtime_records: projection.event_runtime_records,
|
||||
candidate_availability: projection.candidate_availability,
|
||||
named_locomotive_availability: projection.named_locomotive_availability,
|
||||
named_locomotive_cost: projection.named_locomotive_cost,
|
||||
special_conditions: projection.special_conditions,
|
||||
service_state: RuntimeServiceState::default(),
|
||||
};
|
||||
|
|
@ -308,6 +310,7 @@ pub fn project_save_slice_overlay_to_runtime_state_import(
|
|||
event_runtime_records: projection.event_runtime_records,
|
||||
candidate_availability: projection.candidate_availability,
|
||||
named_locomotive_availability: projection.named_locomotive_availability,
|
||||
named_locomotive_cost: base_state.named_locomotive_cost.clone(),
|
||||
special_conditions: projection.special_conditions,
|
||||
service_state: base_state.service_state.clone(),
|
||||
};
|
||||
|
|
@ -636,6 +639,8 @@ fn project_save_slice_components(
|
|||
}
|
||||
}
|
||||
|
||||
let named_locomotive_cost = BTreeMap::new();
|
||||
|
||||
for (index, note) in save_slice.notes.iter().enumerate() {
|
||||
metadata.insert(format!("save_slice.note.{index}"), note.clone());
|
||||
}
|
||||
|
|
@ -649,6 +654,7 @@ fn project_save_slice_components(
|
|||
event_runtime_records,
|
||||
candidate_availability,
|
||||
named_locomotive_availability,
|
||||
named_locomotive_cost,
|
||||
special_conditions,
|
||||
})
|
||||
}
|
||||
|
|
@ -972,6 +978,10 @@ fn lower_contextual_real_grouped_effects(
|
|||
|
||||
let mut effects = Vec::with_capacity(record.grouped_effect_rows.len());
|
||||
for row in &record.grouped_effect_rows {
|
||||
if let Some(effect) = lower_contextual_locomotive_cost_effect(row, company_context)? {
|
||||
effects.push(effect);
|
||||
continue;
|
||||
}
|
||||
if let Some(effect) = lower_contextual_locomotive_availability_effect(row, company_context)?
|
||||
{
|
||||
effects.push(effect);
|
||||
|
|
@ -1022,6 +1032,33 @@ fn lower_contextual_locomotive_availability_effect(
|
|||
}))
|
||||
}
|
||||
|
||||
fn lower_contextual_locomotive_cost_effect(
|
||||
row: &SmpLoadedPackedEventGroupedEffectRowSummary,
|
||||
company_context: &ImportRuntimeContext,
|
||||
) -> Result<Option<RuntimeEffect>, ImportBlocker> {
|
||||
if row.parameter_family.as_deref() != Some("locomotive_cost_scalar") {
|
||||
return Ok(None);
|
||||
}
|
||||
if row.row_shape != "scalar_assignment" {
|
||||
return Ok(None);
|
||||
}
|
||||
let value = u32::try_from(row.raw_scalar_value).ok();
|
||||
let Some(value) = value else {
|
||||
return Ok(None);
|
||||
};
|
||||
let Some(locomotive_id) = row.recovered_locomotive_id else {
|
||||
return Ok(None);
|
||||
};
|
||||
let Some(name) = company_context
|
||||
.locomotive_catalog_names_by_id
|
||||
.get(&locomotive_id)
|
||||
.cloned()
|
||||
else {
|
||||
return Err(ImportBlocker::MissingLocomotiveCatalogContext);
|
||||
};
|
||||
Ok(Some(RuntimeEffect::SetNamedLocomotiveCost { name, value }))
|
||||
}
|
||||
|
||||
fn packed_record_condition_scope_import_blocker(
|
||||
record: &SmpLoadedPackedEventRecordSummary,
|
||||
company_context: &ImportRuntimeContext,
|
||||
|
|
@ -1251,6 +1288,12 @@ fn lower_condition_targets_in_effect(
|
|||
value: *value,
|
||||
}
|
||||
}
|
||||
RuntimeEffect::SetNamedLocomotiveCost { name, value } => {
|
||||
RuntimeEffect::SetNamedLocomotiveCost {
|
||||
name: name.clone(),
|
||||
value: *value,
|
||||
}
|
||||
}
|
||||
RuntimeEffect::SetSpecialCondition { label, value } => RuntimeEffect::SetSpecialCondition {
|
||||
label: label.clone(),
|
||||
value: *value,
|
||||
|
|
@ -1686,6 +1729,12 @@ fn smp_runtime_effect_to_runtime_effect(
|
|||
value: *value,
|
||||
})
|
||||
}
|
||||
RuntimeEffect::SetNamedLocomotiveCost { name, value } => {
|
||||
Ok(RuntimeEffect::SetNamedLocomotiveCost {
|
||||
name: name.clone(),
|
||||
value: *value,
|
||||
})
|
||||
}
|
||||
RuntimeEffect::SetSpecialCondition { label, value } => {
|
||||
Ok(RuntimeEffect::SetSpecialCondition {
|
||||
label: label.clone(),
|
||||
|
|
@ -2247,6 +2296,7 @@ fn runtime_effect_uses_condition_true_company(effect: &RuntimeEffect) -> bool {
|
|||
| RuntimeEffect::DeactivatePlayer { .. }
|
||||
| RuntimeEffect::SetCandidateAvailability { .. }
|
||||
| RuntimeEffect::SetNamedLocomotiveAvailability { .. }
|
||||
| RuntimeEffect::SetNamedLocomotiveCost { .. }
|
||||
| RuntimeEffect::SetSpecialCondition { .. }
|
||||
| RuntimeEffect::ActivateEventRecord { .. }
|
||||
| RuntimeEffect::DeactivateEventRecord { .. }
|
||||
|
|
@ -2325,6 +2375,7 @@ fn runtime_effect_company_target_import_blocker(
|
|||
| RuntimeEffect::SetEconomicStatusCode { .. }
|
||||
| RuntimeEffect::SetCandidateAvailability { .. }
|
||||
| RuntimeEffect::SetNamedLocomotiveAvailability { .. }
|
||||
| RuntimeEffect::SetNamedLocomotiveCost { .. }
|
||||
| RuntimeEffect::SetSpecialCondition { .. }
|
||||
| RuntimeEffect::ActivateEventRecord { .. }
|
||||
| RuntimeEffect::DeactivateEventRecord { .. }
|
||||
|
|
@ -2665,6 +2716,7 @@ mod tests {
|
|||
event_runtime_records: Vec::new(),
|
||||
candidate_availability: BTreeMap::new(),
|
||||
named_locomotive_availability: BTreeMap::new(),
|
||||
named_locomotive_cost: BTreeMap::new(),
|
||||
special_conditions: BTreeMap::new(),
|
||||
service_state: RuntimeServiceState::default(),
|
||||
}
|
||||
|
|
@ -5041,6 +5093,7 @@ mod tests {
|
|||
("Locomotive 10".to_string(), 0),
|
||||
("Locomotive 112".to_string(), 1),
|
||||
]),
|
||||
named_locomotive_cost: BTreeMap::new(),
|
||||
special_conditions: BTreeMap::new(),
|
||||
service_state: RuntimeServiceState::default(),
|
||||
};
|
||||
|
|
@ -5144,7 +5197,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn keeps_recovered_locomotive_cost_rows_parity_only() {
|
||||
fn blocks_recovered_locomotive_cost_rows_without_catalog_context_lower_band() {
|
||||
let save_slice = SmpLoadedSaveSlice {
|
||||
file_extension_hint: Some("gms".to_string()),
|
||||
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
|
||||
|
|
@ -5192,7 +5245,9 @@ mod tests {
|
|||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: vec![],
|
||||
executable_import_ready: false,
|
||||
notes: vec!["locomotive cost rows remain metadata-only".to_string()],
|
||||
notes: vec![
|
||||
"scalar locomotive cost row still needs catalog context".to_string(),
|
||||
],
|
||||
}],
|
||||
}),
|
||||
notes: vec![],
|
||||
|
|
@ -5200,7 +5255,289 @@ mod tests {
|
|||
|
||||
let import = project_save_slice_to_runtime_state_import(
|
||||
&save_slice,
|
||||
"packed-events-locomotive-cost-frontier",
|
||||
"packed-events-locomotive-cost-frontier-lower-band",
|
||||
None,
|
||||
)
|
||||
.expect("save slice should project");
|
||||
|
||||
assert!(import.state.event_runtime_records.is_empty());
|
||||
assert_eq!(
|
||||
import
|
||||
.state
|
||||
.packed_event_collection
|
||||
.as_ref()
|
||||
.and_then(|summary| summary.records[0].import_outcome.as_deref()),
|
||||
Some("blocked_missing_locomotive_catalog_context")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blocks_recovered_locomotive_cost_rows_without_catalog_context() {
|
||||
let save_slice = SmpLoadedSaveSlice {
|
||||
file_extension_hint: Some("gms".to_string()),
|
||||
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
|
||||
mechanism_family: "classic-save-rehydrate-v1".to_string(),
|
||||
mechanism_confidence: "grounded".to_string(),
|
||||
trailer_family: None,
|
||||
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(),
|
||||
mechanism_family: "classic-save-rehydrate-v1".to_string(),
|
||||
mechanism_confidence: "grounded".to_string(),
|
||||
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
|
||||
metadata_tag_offset: 0x7100,
|
||||
records_tag_offset: 0x7200,
|
||||
close_tag_offset: 0x7600,
|
||||
packed_state_version: 0x3e9,
|
||||
packed_state_version_hex: "0x000003e9".to_string(),
|
||||
live_id_bound: 35,
|
||||
live_record_count: 1,
|
||||
live_entry_ids: vec![35],
|
||||
decoded_record_count: 1,
|
||||
imported_runtime_record_count: 0,
|
||||
records: vec![crate::SmpLoadedPackedEventRecordSummary {
|
||||
record_index: 0,
|
||||
live_entry_id: 35,
|
||||
payload_offset: Some(0x7202),
|
||||
payload_len: Some(96),
|
||||
decode_status: "parity_only".to_string(),
|
||||
payload_family: "real_packed_v1".to_string(),
|
||||
trigger_kind: Some(7),
|
||||
active: None,
|
||||
marks_collection_dirty: None,
|
||||
one_shot: Some(false),
|
||||
compact_control: Some(real_compact_control()),
|
||||
text_bands: vec![],
|
||||
standalone_condition_row_count: 0,
|
||||
standalone_condition_rows: vec![],
|
||||
negative_sentinel_scope: None,
|
||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||
grouped_effect_rows: vec![real_locomotive_cost_row(352, 250000)],
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: vec![],
|
||||
executable_import_ready: false,
|
||||
notes: vec![
|
||||
"scalar locomotive cost row still needs catalog context".to_string(),
|
||||
],
|
||||
}],
|
||||
}),
|
||||
notes: vec![],
|
||||
};
|
||||
|
||||
let import = project_save_slice_to_runtime_state_import(
|
||||
&save_slice,
|
||||
"packed-events-locomotive-cost-missing-catalog",
|
||||
None,
|
||||
)
|
||||
.expect("save slice should project");
|
||||
|
||||
assert!(import.state.event_runtime_records.is_empty());
|
||||
assert_eq!(
|
||||
import
|
||||
.state
|
||||
.packed_event_collection
|
||||
.as_ref()
|
||||
.and_then(|summary| summary.records[0].import_outcome.as_deref()),
|
||||
Some("blocked_missing_locomotive_catalog_context")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn overlays_scalar_locomotive_cost_rows_into_named_cost_effects() {
|
||||
let base_state = RuntimeState {
|
||||
calendar: CalendarPoint {
|
||||
year: 1845,
|
||||
month_slot: 2,
|
||||
phase_slot: 1,
|
||||
tick_slot: 3,
|
||||
},
|
||||
world_flags: BTreeMap::new(),
|
||||
save_profile: RuntimeSaveProfileState::default(),
|
||||
world_restore: RuntimeWorldRestoreState::default(),
|
||||
metadata: BTreeMap::new(),
|
||||
companies: Vec::new(),
|
||||
selected_company_id: None,
|
||||
players: Vec::new(),
|
||||
selected_player_id: None,
|
||||
trains: Vec::new(),
|
||||
locomotive_catalog: vec![
|
||||
crate::RuntimeLocomotiveCatalogEntry {
|
||||
locomotive_id: 1,
|
||||
name: "Locomotive 1".to_string(),
|
||||
},
|
||||
crate::RuntimeLocomotiveCatalogEntry {
|
||||
locomotive_id: 101,
|
||||
name: "Locomotive 101".to_string(),
|
||||
},
|
||||
],
|
||||
territories: Vec::new(),
|
||||
company_territory_track_piece_counts: Vec::new(),
|
||||
company_territory_access: Vec::new(),
|
||||
packed_event_collection: None,
|
||||
event_runtime_records: Vec::new(),
|
||||
candidate_availability: BTreeMap::new(),
|
||||
named_locomotive_availability: BTreeMap::new(),
|
||||
named_locomotive_cost: BTreeMap::from([
|
||||
("Locomotive 1".to_string(), 100000),
|
||||
("Locomotive 101".to_string(), 200000),
|
||||
]),
|
||||
special_conditions: BTreeMap::new(),
|
||||
service_state: RuntimeServiceState::default(),
|
||||
};
|
||||
let save_slice = SmpLoadedSaveSlice {
|
||||
file_extension_hint: Some("gms".to_string()),
|
||||
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
|
||||
mechanism_family: "classic-save-rehydrate-v1".to_string(),
|
||||
mechanism_confidence: "grounded".to_string(),
|
||||
trailer_family: None,
|
||||
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(),
|
||||
mechanism_family: "classic-save-rehydrate-v1".to_string(),
|
||||
mechanism_confidence: "grounded".to_string(),
|
||||
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
|
||||
metadata_tag_offset: 0x7100,
|
||||
records_tag_offset: 0x7200,
|
||||
close_tag_offset: 0x7600,
|
||||
packed_state_version: 0x3e9,
|
||||
packed_state_version_hex: "0x000003e9".to_string(),
|
||||
live_id_bound: 36,
|
||||
live_record_count: 1,
|
||||
live_entry_ids: vec![36],
|
||||
decoded_record_count: 1,
|
||||
imported_runtime_record_count: 0,
|
||||
records: vec![crate::SmpLoadedPackedEventRecordSummary {
|
||||
record_index: 0,
|
||||
live_entry_id: 36,
|
||||
payload_offset: Some(0x7202),
|
||||
payload_len: Some(120),
|
||||
decode_status: "parity_only".to_string(),
|
||||
payload_family: "real_packed_v1".to_string(),
|
||||
trigger_kind: Some(7),
|
||||
active: None,
|
||||
marks_collection_dirty: None,
|
||||
one_shot: Some(false),
|
||||
compact_control: Some(real_compact_control()),
|
||||
text_bands: vec![],
|
||||
standalone_condition_row_count: 0,
|
||||
standalone_condition_rows: vec![],
|
||||
negative_sentinel_scope: None,
|
||||
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),
|
||||
],
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: vec![],
|
||||
executable_import_ready: false,
|
||||
notes: vec![
|
||||
"scalar locomotive cost rows use overlay catalog context".to_string(),
|
||||
],
|
||||
}],
|
||||
}),
|
||||
notes: vec![],
|
||||
};
|
||||
|
||||
let mut import = project_save_slice_overlay_to_runtime_state_import(
|
||||
&base_state,
|
||||
&save_slice,
|
||||
"overlay-locomotive-cost",
|
||||
None,
|
||||
)
|
||||
.expect("overlay import should project");
|
||||
|
||||
assert_eq!(import.state.event_runtime_records.len(), 1);
|
||||
assert_eq!(
|
||||
import
|
||||
.state
|
||||
.packed_event_collection
|
||||
.as_ref()
|
||||
.and_then(|summary| summary.records[0].import_outcome.as_deref()),
|
||||
Some("imported")
|
||||
);
|
||||
|
||||
execute_step_command(
|
||||
&mut import.state,
|
||||
&StepCommand::ServiceTriggerKind { trigger_kind: 7 },
|
||||
)
|
||||
.expect("overlay-imported locomotive cost record should run");
|
||||
|
||||
assert_eq!(
|
||||
import.state.named_locomotive_cost.get("Locomotive 1"),
|
||||
Some(&250000)
|
||||
);
|
||||
assert_eq!(
|
||||
import.state.named_locomotive_cost.get("Locomotive 101"),
|
||||
Some(&325000)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keeps_negative_locomotive_cost_rows_parity_only() {
|
||||
let save_slice = SmpLoadedSaveSlice {
|
||||
file_extension_hint: Some("gms".to_string()),
|
||||
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
|
||||
mechanism_family: "classic-save-rehydrate-v1".to_string(),
|
||||
mechanism_confidence: "grounded".to_string(),
|
||||
trailer_family: None,
|
||||
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(),
|
||||
mechanism_family: "classic-save-rehydrate-v1".to_string(),
|
||||
mechanism_confidence: "grounded".to_string(),
|
||||
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
|
||||
metadata_tag_offset: 0x7100,
|
||||
records_tag_offset: 0x7200,
|
||||
close_tag_offset: 0x7600,
|
||||
packed_state_version: 0x3e9,
|
||||
packed_state_version_hex: "0x000003e9".to_string(),
|
||||
live_id_bound: 37,
|
||||
live_record_count: 1,
|
||||
live_entry_ids: vec![37],
|
||||
decoded_record_count: 1,
|
||||
imported_runtime_record_count: 0,
|
||||
records: vec![crate::SmpLoadedPackedEventRecordSummary {
|
||||
record_index: 0,
|
||||
live_entry_id: 37,
|
||||
payload_offset: Some(0x7202),
|
||||
payload_len: Some(96),
|
||||
decode_status: "parity_only".to_string(),
|
||||
payload_family: "real_packed_v1".to_string(),
|
||||
trigger_kind: Some(7),
|
||||
active: None,
|
||||
marks_collection_dirty: None,
|
||||
one_shot: Some(false),
|
||||
compact_control: Some(real_compact_control()),
|
||||
text_bands: vec![],
|
||||
standalone_condition_row_count: 0,
|
||||
standalone_condition_rows: vec![],
|
||||
negative_sentinel_scope: None,
|
||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||
grouped_effect_rows: vec![real_locomotive_cost_row(352, -1)],
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: vec![],
|
||||
executable_import_ready: false,
|
||||
notes: vec!["negative locomotive cost rows remain parity-only".to_string()],
|
||||
}],
|
||||
}),
|
||||
notes: vec![],
|
||||
};
|
||||
|
||||
let import = project_save_slice_to_runtime_state_import(
|
||||
&save_slice,
|
||||
"packed-events-negative-locomotive-cost-frontier",
|
||||
None,
|
||||
)
|
||||
.expect("save slice should project");
|
||||
|
|
@ -5398,6 +5735,7 @@ mod tests {
|
|||
event_runtime_records: Vec::new(),
|
||||
candidate_availability: BTreeMap::new(),
|
||||
named_locomotive_availability: BTreeMap::new(),
|
||||
named_locomotive_cost: BTreeMap::new(),
|
||||
special_conditions: BTreeMap::new(),
|
||||
service_state: RuntimeServiceState::default(),
|
||||
};
|
||||
|
|
@ -8257,6 +8595,7 @@ mod tests {
|
|||
}],
|
||||
candidate_availability: BTreeMap::new(),
|
||||
named_locomotive_availability: BTreeMap::new(),
|
||||
named_locomotive_cost: BTreeMap::new(),
|
||||
special_conditions: BTreeMap::new(),
|
||||
service_state: RuntimeServiceState {
|
||||
periodic_boundary_calls: 9,
|
||||
|
|
@ -8426,6 +8765,7 @@ mod tests {
|
|||
event_runtime_records: Vec::new(),
|
||||
candidate_availability: BTreeMap::new(),
|
||||
named_locomotive_availability: BTreeMap::new(),
|
||||
named_locomotive_cost: BTreeMap::new(),
|
||||
special_conditions: BTreeMap::new(),
|
||||
service_state: RuntimeServiceState::default(),
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue