Decode real packed event record structure
This commit is contained in:
parent
fa63cefb70
commit
45f258cf5d
16 changed files with 1011 additions and 44 deletions
|
|
@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize};
|
|||
use crate::persistence::{load_runtime_snapshot_document, validate_runtime_snapshot_document};
|
||||
use crate::{
|
||||
CalendarPoint, RuntimeEffect, RuntimeEventRecord, RuntimeEventRecordTemplate,
|
||||
RuntimePackedEventConditionRowSummary, RuntimePackedEventGroupedEffectRowSummary,
|
||||
RuntimePackedEventCollectionSummary, RuntimePackedEventRecordSummary,
|
||||
RuntimePackedEventTextBandSummary, RuntimeSaveProfileState, RuntimeServiceState, RuntimeState,
|
||||
RuntimeWorldRestoreState, SmpLoadedPackedEventRecordSummary,
|
||||
|
|
@ -558,6 +559,7 @@ fn runtime_packed_event_record_summary_from_smp(
|
|||
payload_offset: record.payload_offset,
|
||||
payload_len: record.payload_len,
|
||||
decode_status: record.decode_status.clone(),
|
||||
payload_family: record.payload_family.clone(),
|
||||
trigger_kind: record.trigger_kind,
|
||||
active: record.active,
|
||||
marks_collection_dirty: record.marks_collection_dirty,
|
||||
|
|
@ -568,7 +570,17 @@ fn runtime_packed_event_record_summary_from_smp(
|
|||
.map(runtime_packed_event_text_band_summary_from_smp)
|
||||
.collect(),
|
||||
standalone_condition_row_count: record.standalone_condition_row_count,
|
||||
standalone_condition_rows: record
|
||||
.standalone_condition_rows
|
||||
.iter()
|
||||
.map(runtime_packed_event_condition_row_summary_from_smp)
|
||||
.collect(),
|
||||
grouped_effect_row_counts: record.grouped_effect_row_counts.clone(),
|
||||
grouped_effect_rows: record
|
||||
.grouped_effect_rows
|
||||
.iter()
|
||||
.map(runtime_packed_event_grouped_effect_row_summary_from_smp)
|
||||
.collect(),
|
||||
decoded_actions: record.decoded_actions.clone(),
|
||||
executable_import_ready: record.executable_import_ready,
|
||||
import_outcome: Some(determine_packed_event_import_outcome(
|
||||
|
|
@ -591,11 +603,45 @@ fn runtime_packed_event_text_band_summary_from_smp(
|
|||
}
|
||||
}
|
||||
|
||||
fn runtime_packed_event_condition_row_summary_from_smp(
|
||||
row: &crate::SmpLoadedPackedEventConditionRowSummary,
|
||||
) -> RuntimePackedEventConditionRowSummary {
|
||||
RuntimePackedEventConditionRowSummary {
|
||||
row_index: row.row_index,
|
||||
raw_condition_id: row.raw_condition_id,
|
||||
subtype: row.subtype,
|
||||
flag_bytes: row.flag_bytes.clone(),
|
||||
candidate_name: row.candidate_name.clone(),
|
||||
notes: row.notes.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn runtime_packed_event_grouped_effect_row_summary_from_smp(
|
||||
row: &crate::SmpLoadedPackedEventGroupedEffectRowSummary,
|
||||
) -> RuntimePackedEventGroupedEffectRowSummary {
|
||||
RuntimePackedEventGroupedEffectRowSummary {
|
||||
group_index: row.group_index,
|
||||
row_index: row.row_index,
|
||||
descriptor_id: row.descriptor_id,
|
||||
opcode: row.opcode,
|
||||
raw_scalar_value: row.raw_scalar_value,
|
||||
value_byte_0x09: row.value_byte_0x09,
|
||||
value_dword_0x0d: row.value_dword_0x0d,
|
||||
value_byte_0x11: row.value_byte_0x11,
|
||||
value_byte_0x12: row.value_byte_0x12,
|
||||
value_word_0x14: row.value_word_0x14,
|
||||
value_word_0x16: row.value_word_0x16,
|
||||
row_shape: row.row_shape.clone(),
|
||||
locomotive_name: row.locomotive_name.clone(),
|
||||
notes: row.notes.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn smp_packed_record_to_runtime_event_record(
|
||||
record: &SmpLoadedPackedEventRecordSummary,
|
||||
known_company_ids: &BTreeSet<u32>,
|
||||
) -> Option<Result<RuntimeEventRecord, String>> {
|
||||
if record.decode_status == "unsupported_framing" {
|
||||
if record.decode_status == "unsupported_framing" || record.payload_family == "real_packed_v1" {
|
||||
return None;
|
||||
}
|
||||
|
||||
|
|
@ -756,6 +802,9 @@ fn determine_packed_event_import_outcome(
|
|||
if record.decode_status == "unsupported_framing" {
|
||||
return "blocked_unsupported_decode".to_string();
|
||||
}
|
||||
if record.payload_family == "real_packed_v1" {
|
||||
return "blocked_structural_only".to_string();
|
||||
}
|
||||
if packed_record_requires_missing_company_context(record, known_company_ids) {
|
||||
return "blocked_missing_company_context".to_string();
|
||||
}
|
||||
|
|
@ -1129,6 +1178,36 @@ mod tests {
|
|||
]
|
||||
}
|
||||
|
||||
fn real_condition_rows() -> Vec<crate::SmpLoadedPackedEventConditionRowSummary> {
|
||||
vec![crate::SmpLoadedPackedEventConditionRowSummary {
|
||||
row_index: 0,
|
||||
raw_condition_id: -1,
|
||||
subtype: 4,
|
||||
flag_bytes: vec![0x30; 25],
|
||||
candidate_name: Some("AutoPlant".to_string()),
|
||||
notes: vec!["negative sentinel-style condition row id".to_string()],
|
||||
}]
|
||||
}
|
||||
|
||||
fn real_grouped_rows() -> Vec<crate::SmpLoadedPackedEventGroupedEffectRowSummary> {
|
||||
vec![crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||
group_index: 0,
|
||||
row_index: 0,
|
||||
descriptor_id: 2,
|
||||
opcode: 8,
|
||||
raw_scalar_value: 7,
|
||||
value_byte_0x09: 1,
|
||||
value_dword_0x0d: 12,
|
||||
value_byte_0x11: 2,
|
||||
value_byte_0x12: 3,
|
||||
value_word_0x14: 24,
|
||||
value_word_0x16: 36,
|
||||
row_shape: "multivalue_scalar".to_string(),
|
||||
locomotive_name: Some("Mikado".to_string()),
|
||||
notes: vec!["grouped effect row carries locomotive-name side string".to_string()],
|
||||
}]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loads_dump_document() {
|
||||
let text = serde_json::to_string(&RuntimeStateDumpDocument {
|
||||
|
|
@ -1345,13 +1424,16 @@ mod tests {
|
|||
payload_offset: None,
|
||||
payload_len: None,
|
||||
decode_status: "unsupported_framing".to_string(),
|
||||
payload_family: "unsupported_framing".to_string(),
|
||||
trigger_kind: None,
|
||||
active: None,
|
||||
marks_collection_dirty: None,
|
||||
one_shot: None,
|
||||
text_bands: Vec::new(),
|
||||
standalone_condition_row_count: 0,
|
||||
standalone_condition_rows: Vec::new(),
|
||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||
grouped_effect_rows: Vec::new(),
|
||||
decoded_actions: Vec::new(),
|
||||
executable_import_ready: false,
|
||||
notes: vec!["test".to_string()],
|
||||
|
|
@ -1362,13 +1444,16 @@ mod tests {
|
|||
payload_offset: None,
|
||||
payload_len: None,
|
||||
decode_status: "unsupported_framing".to_string(),
|
||||
payload_family: "unsupported_framing".to_string(),
|
||||
trigger_kind: None,
|
||||
active: None,
|
||||
marks_collection_dirty: None,
|
||||
one_shot: None,
|
||||
text_bands: Vec::new(),
|
||||
standalone_condition_row_count: 0,
|
||||
standalone_condition_rows: Vec::new(),
|
||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||
grouped_effect_rows: Vec::new(),
|
||||
decoded_actions: Vec::new(),
|
||||
executable_import_ready: false,
|
||||
notes: vec!["test".to_string()],
|
||||
|
|
@ -1379,13 +1464,16 @@ mod tests {
|
|||
payload_offset: None,
|
||||
payload_len: None,
|
||||
decode_status: "unsupported_framing".to_string(),
|
||||
payload_family: "unsupported_framing".to_string(),
|
||||
trigger_kind: None,
|
||||
active: None,
|
||||
marks_collection_dirty: None,
|
||||
one_shot: None,
|
||||
text_bands: Vec::new(),
|
||||
standalone_condition_row_count: 0,
|
||||
standalone_condition_rows: Vec::new(),
|
||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||
grouped_effect_rows: Vec::new(),
|
||||
decoded_actions: Vec::new(),
|
||||
executable_import_ready: false,
|
||||
notes: vec!["test".to_string()],
|
||||
|
|
@ -1586,13 +1674,16 @@ mod tests {
|
|||
payload_offset: Some(0x7202),
|
||||
payload_len: Some(64),
|
||||
decode_status: "executable".to_string(),
|
||||
payload_family: "synthetic_harness".to_string(),
|
||||
trigger_kind: Some(7),
|
||||
active: Some(true),
|
||||
marks_collection_dirty: Some(true),
|
||||
one_shot: Some(false),
|
||||
text_bands: packed_text_bands(),
|
||||
standalone_condition_row_count: 1,
|
||||
standalone_condition_rows: vec![],
|
||||
grouped_effect_row_counts: vec![0, 1, 0, 0],
|
||||
grouped_effect_rows: vec![],
|
||||
decoded_actions: vec![
|
||||
RuntimeEffect::SetWorldFlag {
|
||||
key: "from_packed_root".to_string(),
|
||||
|
|
@ -1692,13 +1783,16 @@ mod tests {
|
|||
payload_offset: Some(0x7202),
|
||||
payload_len: Some(48),
|
||||
decode_status: "parity_only".to_string(),
|
||||
payload_family: "synthetic_harness".to_string(),
|
||||
trigger_kind: Some(7),
|
||||
active: Some(true),
|
||||
marks_collection_dirty: Some(false),
|
||||
one_shot: Some(false),
|
||||
text_bands: packed_text_bands(),
|
||||
standalone_condition_row_count: 0,
|
||||
standalone_condition_rows: vec![],
|
||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||
grouped_effect_rows: vec![],
|
||||
decoded_actions: vec![RuntimeEffect::AdjustCompanyCash {
|
||||
target: crate::RuntimeCompanyTarget::Ids { ids: vec![42] },
|
||||
delta: 50,
|
||||
|
|
@ -1744,6 +1838,91 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn leaves_real_structural_records_blocked_structural_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,
|
||||
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: 7,
|
||||
live_record_count: 1,
|
||||
live_entry_ids: vec![7],
|
||||
decoded_record_count: 1,
|
||||
imported_runtime_record_count: 0,
|
||||
records: vec![crate::SmpLoadedPackedEventRecordSummary {
|
||||
record_index: 0,
|
||||
live_entry_id: 7,
|
||||
payload_offset: Some(0x7202),
|
||||
payload_len: Some(96),
|
||||
decode_status: "parity_only".to_string(),
|
||||
payload_family: "real_packed_v1".to_string(),
|
||||
trigger_kind: None,
|
||||
active: None,
|
||||
marks_collection_dirty: None,
|
||||
one_shot: None,
|
||||
text_bands: packed_text_bands(),
|
||||
standalone_condition_row_count: 1,
|
||||
standalone_condition_rows: real_condition_rows(),
|
||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||
grouped_effect_rows: real_grouped_rows(),
|
||||
decoded_actions: vec![],
|
||||
executable_import_ready: false,
|
||||
notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()],
|
||||
}],
|
||||
}),
|
||||
notes: vec![],
|
||||
};
|
||||
|
||||
let import = project_save_slice_to_runtime_state_import(
|
||||
&save_slice,
|
||||
"packed-events-structural-only",
|
||||
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_structural_only")
|
||||
);
|
||||
assert_eq!(
|
||||
import
|
||||
.state
|
||||
.packed_event_collection
|
||||
.as_ref()
|
||||
.map(|summary| summary.records[0].standalone_condition_rows.len()),
|
||||
Some(1)
|
||||
);
|
||||
assert_eq!(
|
||||
import
|
||||
.state
|
||||
.packed_event_collection
|
||||
.as_ref()
|
||||
.map(|summary| summary.records[0].grouped_effect_rows.len()),
|
||||
Some(1)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn overlays_save_slice_events_onto_base_company_context() {
|
||||
let base_state = RuntimeState {
|
||||
|
|
@ -1813,13 +1992,16 @@ mod tests {
|
|||
payload_offset: Some(0x7202),
|
||||
payload_len: Some(48),
|
||||
decode_status: "parity_only".to_string(),
|
||||
payload_family: "synthetic_harness".to_string(),
|
||||
trigger_kind: Some(7),
|
||||
active: Some(true),
|
||||
marks_collection_dirty: Some(false),
|
||||
one_shot: Some(false),
|
||||
text_bands: packed_text_bands(),
|
||||
standalone_condition_row_count: 0,
|
||||
standalone_condition_rows: vec![],
|
||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||
grouped_effect_rows: vec![],
|
||||
decoded_actions: vec![RuntimeEffect::AdjustCompanyCash {
|
||||
target: crate::RuntimeCompanyTarget::Ids { ids: vec![42] },
|
||||
delta: 50,
|
||||
|
|
@ -1963,13 +2145,16 @@ mod tests {
|
|||
payload_offset: Some(0x7202),
|
||||
payload_len: Some(48),
|
||||
decode_status: "parity_only".to_string(),
|
||||
payload_family: "synthetic_harness".to_string(),
|
||||
trigger_kind: Some(7),
|
||||
active: Some(true),
|
||||
marks_collection_dirty: Some(false),
|
||||
one_shot: Some(false),
|
||||
text_bands: packed_text_bands(),
|
||||
standalone_condition_row_count: 0,
|
||||
standalone_condition_rows: vec![],
|
||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||
grouped_effect_rows: vec![],
|
||||
decoded_actions: vec![RuntimeEffect::AdjustCompanyCash {
|
||||
target: crate::RuntimeCompanyTarget::Ids { ids: vec![42] },
|
||||
delta: 50,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue