Import locomotive availability descriptors with overlay context
This commit is contained in:
parent
8c7ff335cb
commit
87108f357b
21 changed files with 1154 additions and 27 deletions
|
|
@ -117,6 +117,7 @@ struct ImportRuntimeContext {
|
|||
territory_name_to_id: BTreeMap<String, u32>,
|
||||
has_train_context: bool,
|
||||
has_train_territory_context: bool,
|
||||
locomotive_catalog_names_by_id: BTreeMap<u32, String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
|
|
@ -136,6 +137,7 @@ enum ImportBlocker {
|
|||
UnmappedWorldCondition,
|
||||
MissingTrainContext,
|
||||
MissingTrainTerritoryContext,
|
||||
MissingLocomotiveCatalogContext,
|
||||
}
|
||||
|
||||
impl ImportRuntimeContext {
|
||||
|
|
@ -152,6 +154,7 @@ impl ImportRuntimeContext {
|
|||
territory_name_to_id: BTreeMap::new(),
|
||||
has_train_context: false,
|
||||
has_train_territory_context: false,
|
||||
locomotive_catalog_names_by_id: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -199,6 +202,11 @@ impl ImportRuntimeContext {
|
|||
.trains
|
||||
.iter()
|
||||
.any(|train| train.territory_id.is_some()),
|
||||
locomotive_catalog_names_by_id: state
|
||||
.locomotive_catalog
|
||||
.iter()
|
||||
.map(|entry| (entry.locomotive_id, entry.name.clone()))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -233,6 +241,7 @@ pub fn project_save_slice_to_runtime_state_import(
|
|||
players: Vec::new(),
|
||||
selected_player_id: None,
|
||||
trains: Vec::new(),
|
||||
locomotive_catalog: Vec::new(),
|
||||
territories: Vec::new(),
|
||||
company_territory_track_piece_counts: Vec::new(),
|
||||
company_territory_access: Vec::new(),
|
||||
|
|
@ -289,6 +298,7 @@ pub fn project_save_slice_overlay_to_runtime_state_import(
|
|||
players: base_state.players.clone(),
|
||||
selected_player_id: base_state.selected_player_id,
|
||||
trains: base_state.trains.clone(),
|
||||
locomotive_catalog: base_state.locomotive_catalog.clone(),
|
||||
territories: base_state.territories.clone(),
|
||||
company_territory_track_piece_counts: base_state
|
||||
.company_territory_track_piece_counts
|
||||
|
|
@ -839,6 +849,7 @@ fn runtime_packed_event_grouped_effect_row_summary_from_smp(
|
|||
row_shape: row.row_shape.clone(),
|
||||
semantic_family: row.semantic_family.clone(),
|
||||
semantic_preview: row.semantic_preview.clone(),
|
||||
recovered_locomotive_id: row.recovered_locomotive_id,
|
||||
locomotive_name: row.locomotive_name.clone(),
|
||||
notes: row.notes.clone(),
|
||||
}
|
||||
|
|
@ -851,10 +862,8 @@ fn smp_packed_record_to_runtime_event_record(
|
|||
if record.decode_status == "unsupported_framing" {
|
||||
return None;
|
||||
}
|
||||
if record.payload_family == "real_packed_v1" {
|
||||
if record.compact_control.is_none() || !record.executable_import_ready {
|
||||
return None;
|
||||
}
|
||||
if record.payload_family == "real_packed_v1" && record.compact_control.is_none() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let lowered_conditions = match lowered_record_decoded_conditions(record, company_context) {
|
||||
|
|
@ -934,8 +943,14 @@ fn lowered_record_decoded_actions(
|
|||
|
||||
let lowered_company_target = lowered_condition_true_company_target(record)?;
|
||||
let lowered_player_target = lowered_condition_true_player_target(record)?;
|
||||
record
|
||||
.decoded_actions
|
||||
let base_effects = if record.payload_family != "real_packed_v1"
|
||||
|| record.decoded_actions.len() == record.grouped_effect_rows.len()
|
||||
{
|
||||
record.decoded_actions.clone()
|
||||
} else {
|
||||
lower_contextual_real_grouped_effects(record, company_context)?
|
||||
};
|
||||
base_effects
|
||||
.iter()
|
||||
.map(|effect| {
|
||||
lower_condition_targets_in_effect(
|
||||
|
|
@ -947,6 +962,66 @@ fn lowered_record_decoded_actions(
|
|||
.collect()
|
||||
}
|
||||
|
||||
fn lower_contextual_real_grouped_effects(
|
||||
record: &SmpLoadedPackedEventRecordSummary,
|
||||
company_context: &ImportRuntimeContext,
|
||||
) -> Result<Vec<RuntimeEffect>, ImportBlocker> {
|
||||
if record.payload_family != "real_packed_v1" || record.compact_control.is_none() {
|
||||
return Err(ImportBlocker::UnmappedWorldCondition);
|
||||
}
|
||||
|
||||
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_availability_effect(row, company_context)?
|
||||
{
|
||||
effects.push(effect);
|
||||
continue;
|
||||
}
|
||||
return Err(if real_grouped_row_is_world_state_family(row) {
|
||||
ImportBlocker::UnmappedWorldCondition
|
||||
} else {
|
||||
ImportBlocker::UnmappedOrdinaryCondition
|
||||
});
|
||||
}
|
||||
|
||||
if effects.is_empty() {
|
||||
return Err(ImportBlocker::UnmappedWorldCondition);
|
||||
}
|
||||
|
||||
Ok(effects)
|
||||
}
|
||||
|
||||
fn lower_contextual_locomotive_availability_effect(
|
||||
row: &SmpLoadedPackedEventGroupedEffectRowSummary,
|
||||
company_context: &ImportRuntimeContext,
|
||||
) -> Result<Option<RuntimeEffect>, ImportBlocker> {
|
||||
if row.parameter_family.as_deref() != Some("locomotive_availability_scalar") {
|
||||
return Ok(None);
|
||||
}
|
||||
if row.row_shape != "scalar_assignment" {
|
||||
return Ok(None);
|
||||
}
|
||||
let value = match row.raw_scalar_value {
|
||||
0 => false,
|
||||
1 => true,
|
||||
_ => 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::SetNamedLocomotiveAvailability {
|
||||
name,
|
||||
value,
|
||||
}))
|
||||
}
|
||||
|
||||
fn packed_record_condition_scope_import_blocker(
|
||||
record: &SmpLoadedPackedEventRecordSummary,
|
||||
company_context: &ImportRuntimeContext,
|
||||
|
|
@ -1781,6 +1856,9 @@ fn company_target_import_error_message(
|
|||
Some(ImportBlocker::MissingTrainTerritoryContext) => {
|
||||
"packed train effect requires runtime train territory context".to_string()
|
||||
}
|
||||
Some(ImportBlocker::MissingLocomotiveCatalogContext) => {
|
||||
"packed locomotive availability row requires locomotive catalog context".to_string()
|
||||
}
|
||||
Some(ImportBlocker::MissingPlayerContext)
|
||||
| Some(ImportBlocker::MissingPlayerSelectionContext)
|
||||
| Some(ImportBlocker::MissingPlayerRoleContext)
|
||||
|
|
@ -1871,6 +1949,11 @@ fn determine_packed_event_import_outcome(
|
|||
return "blocked_missing_compact_control".to_string();
|
||||
}
|
||||
if !record.executable_import_ready {
|
||||
if let Err(blocker) = lowered_record_decoded_actions(record, company_context) {
|
||||
if blocker == ImportBlocker::MissingLocomotiveCatalogContext {
|
||||
return company_target_import_outcome(blocker).to_string();
|
||||
}
|
||||
}
|
||||
if record
|
||||
.grouped_effect_rows
|
||||
.iter()
|
||||
|
|
@ -2085,6 +2168,9 @@ fn company_target_import_outcome(blocker: ImportBlocker) -> &'static str {
|
|||
ImportBlocker::UnmappedWorldCondition => "blocked_unmapped_world_condition",
|
||||
ImportBlocker::MissingTrainContext => "blocked_missing_train_context",
|
||||
ImportBlocker::MissingTrainTerritoryContext => "blocked_missing_train_territory_context",
|
||||
ImportBlocker::MissingLocomotiveCatalogContext => {
|
||||
"blocked_missing_locomotive_catalog_context"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2571,6 +2657,7 @@ mod tests {
|
|||
players: Vec::new(),
|
||||
selected_player_id: None,
|
||||
trains: Vec::new(),
|
||||
locomotive_catalog: Vec::new(),
|
||||
territories: Vec::new(),
|
||||
company_territory_track_piece_counts: Vec::new(),
|
||||
company_territory_access: Vec::new(),
|
||||
|
|
@ -2719,6 +2806,7 @@ mod tests {
|
|||
row_shape: "multivalue_scalar".to_string(),
|
||||
semantic_family: Some("multivalue_scalar".to_string()),
|
||||
semantic_preview: Some("Set Company Cash to 7 with aux [2, 3, 24, 36]".to_string()),
|
||||
recovered_locomotive_id: None,
|
||||
locomotive_name: Some("Mikado".to_string()),
|
||||
notes: vec!["grouped effect row carries locomotive-name side string".to_string()],
|
||||
}]
|
||||
|
|
@ -2748,6 +2836,7 @@ mod tests {
|
|||
"Set Deactivate Company to {}",
|
||||
if enabled { "TRUE" } else { "FALSE" }
|
||||
)),
|
||||
recovered_locomotive_id: None,
|
||||
locomotive_name: None,
|
||||
notes: vec![],
|
||||
}
|
||||
|
|
@ -2772,6 +2861,7 @@ mod tests {
|
|||
row_shape: "scalar_assignment".to_string(),
|
||||
semantic_family: Some("scalar_assignment".to_string()),
|
||||
semantic_preview: Some(format!("Set Company Track Pieces Buildable to {value}")),
|
||||
recovered_locomotive_id: None,
|
||||
locomotive_name: None,
|
||||
notes: vec![],
|
||||
}
|
||||
|
|
@ -2801,6 +2891,7 @@ mod tests {
|
|||
"Set Deactivate Player to {}",
|
||||
if enabled { "TRUE" } else { "FALSE" }
|
||||
)),
|
||||
recovered_locomotive_id: None,
|
||||
locomotive_name: None,
|
||||
notes: vec![],
|
||||
}
|
||||
|
|
@ -2831,6 +2922,7 @@ mod tests {
|
|||
"Set Territory - Allow All to {}",
|
||||
if enabled { "TRUE" } else { "FALSE" }
|
||||
)),
|
||||
recovered_locomotive_id: None,
|
||||
locomotive_name: None,
|
||||
notes,
|
||||
}
|
||||
|
|
@ -2855,6 +2947,7 @@ mod tests {
|
|||
row_shape: "scalar_assignment".to_string(),
|
||||
semantic_family: Some("scalar_assignment".to_string()),
|
||||
semantic_preview: Some(format!("Set Economic Status to {value}")),
|
||||
recovered_locomotive_id: None,
|
||||
locomotive_name: None,
|
||||
notes: vec![],
|
||||
}
|
||||
|
|
@ -2881,6 +2974,7 @@ mod tests {
|
|||
row_shape: "scalar_assignment".to_string(),
|
||||
semantic_family: Some("scalar_assignment".to_string()),
|
||||
semantic_preview: Some(format!("Set Limited Track Building Amount to {value}")),
|
||||
recovered_locomotive_id: None,
|
||||
locomotive_name: None,
|
||||
notes: vec![],
|
||||
}
|
||||
|
|
@ -2907,6 +3001,7 @@ mod tests {
|
|||
row_shape: "scalar_assignment".to_string(),
|
||||
semantic_family: Some("scalar_assignment".to_string()),
|
||||
semantic_preview: Some(format!("Set Use Wartime Cargos to {value}")),
|
||||
recovered_locomotive_id: None,
|
||||
locomotive_name: None,
|
||||
notes: vec![],
|
||||
}
|
||||
|
|
@ -2933,6 +3028,71 @@ mod tests {
|
|||
row_shape: "scalar_assignment".to_string(),
|
||||
semantic_family: Some("scalar_assignment".to_string()),
|
||||
semantic_preview: Some(format!("Set Turbo Diesel Availability to {value}")),
|
||||
recovered_locomotive_id: None,
|
||||
locomotive_name: None,
|
||||
notes: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn real_locomotive_availability_row(
|
||||
descriptor_id: u32,
|
||||
value: i32,
|
||||
) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||
crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||
group_index: 0,
|
||||
row_index: 0,
|
||||
descriptor_id,
|
||||
descriptor_label: Some("Unknown Loco Available".to_string()),
|
||||
target_mask_bits: Some(0x08),
|
||||
parameter_family: Some("locomotive_availability_scalar".to_string()),
|
||||
opcode: 3,
|
||||
raw_scalar_value: value,
|
||||
value_byte_0x09: 0,
|
||||
value_dword_0x0d: 0,
|
||||
value_byte_0x11: 0,
|
||||
value_byte_0x12: 0,
|
||||
value_word_0x14: 0,
|
||||
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}")),
|
||||
recovered_locomotive_id: match descriptor_id {
|
||||
241..=351 => Some(descriptor_id - 240),
|
||||
457..=474 => Some(descriptor_id - 345),
|
||||
_ => None,
|
||||
},
|
||||
locomotive_name: None,
|
||||
notes: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn real_locomotive_cost_row(
|
||||
descriptor_id: u32,
|
||||
value: i32,
|
||||
) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||
crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||
group_index: 0,
|
||||
row_index: 0,
|
||||
descriptor_id,
|
||||
descriptor_label: Some("Unknown Loco Cost".to_string()),
|
||||
target_mask_bits: Some(0x08),
|
||||
parameter_family: Some("locomotive_cost_scalar".to_string()),
|
||||
opcode: 3,
|
||||
raw_scalar_value: value,
|
||||
value_byte_0x09: 0,
|
||||
value_dword_0x0d: 0,
|
||||
value_byte_0x11: 0,
|
||||
value_byte_0x12: 0,
|
||||
value_word_0x14: 0,
|
||||
value_word_0x16: 0,
|
||||
row_shape: "scalar_assignment".to_string(),
|
||||
semantic_family: Some("scalar_assignment".to_string()),
|
||||
semantic_preview: Some(format!("Set Unknown Loco Cost to {value}")),
|
||||
recovered_locomotive_id: match descriptor_id {
|
||||
352..=451 => Some(descriptor_id - 351),
|
||||
475..=500 => Some(descriptor_id - 374),
|
||||
_ => None,
|
||||
},
|
||||
locomotive_name: None,
|
||||
notes: vec![],
|
||||
}
|
||||
|
|
@ -2964,6 +3124,7 @@ mod tests {
|
|||
"Set {label} to {}",
|
||||
if enabled { "TRUE" } else { "FALSE" }
|
||||
)),
|
||||
recovered_locomotive_id: None,
|
||||
locomotive_name: None,
|
||||
notes: vec![],
|
||||
}
|
||||
|
|
@ -2993,6 +3154,7 @@ mod tests {
|
|||
"Set Confiscate All to {}",
|
||||
if enabled { "TRUE" } else { "FALSE" }
|
||||
)),
|
||||
recovered_locomotive_id: None,
|
||||
locomotive_name: None,
|
||||
notes: vec![],
|
||||
}
|
||||
|
|
@ -3024,6 +3186,7 @@ mod tests {
|
|||
"Set Retire Train to {}",
|
||||
if enabled { "TRUE" } else { "FALSE" }
|
||||
)),
|
||||
recovered_locomotive_id: None,
|
||||
locomotive_name: locomotive_name.map(ToString::to_string),
|
||||
notes,
|
||||
}
|
||||
|
|
@ -3048,6 +3211,7 @@ mod tests {
|
|||
row_shape: "bool_toggle".to_string(),
|
||||
semantic_family: Some("bool_toggle".to_string()),
|
||||
semantic_preview: Some("Set Confiscate All to FALSE".to_string()),
|
||||
recovered_locomotive_id: None,
|
||||
locomotive_name: None,
|
||||
notes: vec![],
|
||||
}
|
||||
|
|
@ -4667,6 +4831,7 @@ mod tests {
|
|||
row_shape: "scalar_assignment".to_string(),
|
||||
semantic_family: Some("scalar_assignment".to_string()),
|
||||
semantic_preview: Some("Set Unknown Loco Available to 42".to_string()),
|
||||
recovered_locomotive_id: Some(10),
|
||||
locomotive_name: None,
|
||||
notes: vec![],
|
||||
}],
|
||||
|
|
@ -4701,6 +4866,295 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blocks_boolean_locomotive_availability_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: 32,
|
||||
live_record_count: 1,
|
||||
live_entry_ids: vec![32],
|
||||
decoded_record_count: 1,
|
||||
imported_runtime_record_count: 0,
|
||||
records: vec![crate::SmpLoadedPackedEventRecordSummary {
|
||||
record_index: 0,
|
||||
live_entry_id: 32,
|
||||
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_availability_row(250, 1)],
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: vec![],
|
||||
executable_import_ready: false,
|
||||
notes: vec![
|
||||
"boolean locomotive availability row still needs catalog context"
|
||||
.to_string(),
|
||||
],
|
||||
}],
|
||||
}),
|
||||
notes: vec![],
|
||||
};
|
||||
|
||||
let import = project_save_slice_to_runtime_state_import(
|
||||
&save_slice,
|
||||
"packed-events-locomotive-availability-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_boolean_locomotive_availability_rows_into_named_availability_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: 10,
|
||||
name: "Locomotive 10".to_string(),
|
||||
},
|
||||
crate::RuntimeLocomotiveCatalogEntry {
|
||||
locomotive_id: 112,
|
||||
name: "Locomotive 112".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::from([
|
||||
("Locomotive 10".to_string(), 0),
|
||||
("Locomotive 112".to_string(), 1),
|
||||
]),
|
||||
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: 33,
|
||||
live_record_count: 1,
|
||||
live_entry_ids: vec![33],
|
||||
decoded_record_count: 1,
|
||||
imported_runtime_record_count: 0,
|
||||
records: vec![crate::SmpLoadedPackedEventRecordSummary {
|
||||
record_index: 0,
|
||||
live_entry_id: 33,
|
||||
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_availability_row(250, 1),
|
||||
real_locomotive_availability_row(457, 0),
|
||||
],
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: vec![],
|
||||
executable_import_ready: false,
|
||||
notes: vec![
|
||||
"boolean locomotive availability 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-availability",
|
||||
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 availability record should run");
|
||||
|
||||
assert_eq!(
|
||||
import
|
||||
.state
|
||||
.named_locomotive_availability
|
||||
.get("Locomotive 10"),
|
||||
Some(&1)
|
||||
);
|
||||
assert_eq!(
|
||||
import
|
||||
.state
|
||||
.named_locomotive_availability
|
||||
.get("Locomotive 112"),
|
||||
Some(&0)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keeps_recovered_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: 34,
|
||||
live_record_count: 1,
|
||||
live_entry_ids: vec![34],
|
||||
decoded_record_count: 1,
|
||||
imported_runtime_record_count: 0,
|
||||
records: vec![crate::SmpLoadedPackedEventRecordSummary {
|
||||
record_index: 0,
|
||||
live_entry_id: 34,
|
||||
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!["locomotive cost rows remain metadata-only".to_string()],
|
||||
}],
|
||||
}),
|
||||
notes: vec![],
|
||||
};
|
||||
|
||||
let import = project_save_slice_to_runtime_state_import(
|
||||
&save_slice,
|
||||
"packed-events-locomotive-cost-frontier",
|
||||
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_unmapped_world_descriptor")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn overlays_real_company_cash_descriptor_into_executable_runtime_record() {
|
||||
let base_state = RuntimeState {
|
||||
|
|
@ -4729,6 +5183,7 @@ mod tests {
|
|||
players: Vec::new(),
|
||||
selected_player_id: None,
|
||||
trains: Vec::new(),
|
||||
locomotive_catalog: Vec::new(),
|
||||
territories: Vec::new(),
|
||||
company_territory_track_piece_counts: Vec::new(),
|
||||
company_territory_access: Vec::new(),
|
||||
|
|
@ -4813,6 +5268,7 @@ mod tests {
|
|||
semantic_preview: Some(
|
||||
"Set Company Cash to 250 with aux [2, 3, 24, 36]".to_string(),
|
||||
),
|
||||
recovered_locomotive_id: None,
|
||||
locomotive_name: Some("Mikado".to_string()),
|
||||
notes: vec![
|
||||
"grouped effect row carries locomotive-name side string".to_string(),
|
||||
|
|
@ -6353,6 +6809,7 @@ mod tests {
|
|||
row_shape: "scalar_assignment".to_string(),
|
||||
semantic_family: Some("scalar_assignment".to_string()),
|
||||
semantic_preview: Some("Set Turbo Diesel Availability to 1".to_string()),
|
||||
recovered_locomotive_id: None,
|
||||
locomotive_name: None,
|
||||
notes: vec!["checked-in whole-game grouped-effect sample".to_string()],
|
||||
}],
|
||||
|
|
@ -7575,6 +8032,7 @@ mod tests {
|
|||
players: Vec::new(),
|
||||
selected_player_id: None,
|
||||
trains: Vec::new(),
|
||||
locomotive_catalog: Vec::new(),
|
||||
territories: Vec::new(),
|
||||
company_territory_track_piece_counts: Vec::new(),
|
||||
company_territory_access: Vec::new(),
|
||||
|
|
@ -7753,6 +8211,7 @@ mod tests {
|
|||
players: Vec::new(),
|
||||
selected_player_id: None,
|
||||
trains: Vec::new(),
|
||||
locomotive_catalog: Vec::new(),
|
||||
territories: Vec::new(),
|
||||
company_territory_track_piece_counts: Vec::new(),
|
||||
company_territory_access: Vec::new(),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue