Decode real packed event compact control

This commit is contained in:
Jan Petykiewicz 2026-04-14 23:01:18 -07:00
commit 4ff6d65774
12 changed files with 483 additions and 41 deletions

View file

@ -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,
RuntimePackedEventCompactControlSummary,
RuntimePackedEventConditionRowSummary, RuntimePackedEventGroupedEffectRowSummary,
RuntimePackedEventCollectionSummary, RuntimePackedEventRecordSummary,
RuntimePackedEventTextBandSummary, RuntimeSaveProfileState, RuntimeServiceState, RuntimeState,
@ -564,6 +565,10 @@ fn runtime_packed_event_record_summary_from_smp(
active: record.active,
marks_collection_dirty: record.marks_collection_dirty,
one_shot: record.one_shot,
compact_control: record
.compact_control
.as_ref()
.map(runtime_packed_event_compact_control_summary_from_smp),
text_bands: record
.text_bands
.iter()
@ -592,6 +597,23 @@ fn runtime_packed_event_record_summary_from_smp(
}
}
fn runtime_packed_event_compact_control_summary_from_smp(
control: &crate::SmpLoadedPackedEventCompactControlSummary,
) -> RuntimePackedEventCompactControlSummary {
RuntimePackedEventCompactControlSummary {
mode_byte_0x7ef: control.mode_byte_0x7ef,
primary_selector_0x7f0: control.primary_selector_0x7f0,
grouped_mode_0x7f4: control.grouped_mode_0x7f4,
one_shot_header_0x7f5: control.one_shot_header_0x7f5,
modifier_flag_0x7f9: control.modifier_flag_0x7f9,
modifier_flag_0x7fa: control.modifier_flag_0x7fa,
grouped_target_scope_ordinals_0x7fb: control.grouped_target_scope_ordinals_0x7fb.clone(),
grouped_scope_checkboxes_0x7ff: control.grouped_scope_checkboxes_0x7ff.clone(),
summary_toggle_0x800: control.summary_toggle_0x800,
grouped_territory_selectors_0x80f: control.grouped_territory_selectors_0x80f.clone(),
}
}
fn runtime_packed_event_text_band_summary_from_smp(
band: &SmpLoadedPackedEventTextBandSummary,
) -> RuntimePackedEventTextBandSummary {
@ -803,7 +825,10 @@ fn determine_packed_event_import_outcome(
return "blocked_unsupported_decode".to_string();
}
if record.payload_family == "real_packed_v1" {
return "blocked_structural_only".to_string();
if record.compact_control.is_none() {
return "blocked_missing_compact_control".to_string();
}
return "blocked_unmapped_real_descriptor".to_string();
}
if packed_record_requires_missing_company_context(record, known_company_ids) {
return "blocked_missing_company_context".to_string();
@ -1208,6 +1233,21 @@ mod tests {
}]
}
fn real_compact_control() -> crate::SmpLoadedPackedEventCompactControlSummary {
crate::SmpLoadedPackedEventCompactControlSummary {
mode_byte_0x7ef: 6,
primary_selector_0x7f0: 0x63,
grouped_mode_0x7f4: 2,
one_shot_header_0x7f5: 1,
modifier_flag_0x7f9: 1,
modifier_flag_0x7fa: 0,
grouped_target_scope_ordinals_0x7fb: vec![0, 1, 2, 3],
grouped_scope_checkboxes_0x7ff: vec![1, 0, 1, 0],
summary_toggle_0x800: 1,
grouped_territory_selectors_0x80f: vec![-1, 10, -1, 22],
}
}
#[test]
fn loads_dump_document() {
let text = serde_json::to_string(&RuntimeStateDumpDocument {
@ -1429,6 +1469,7 @@ mod tests {
active: None,
marks_collection_dirty: None,
one_shot: None,
compact_control: None,
text_bands: Vec::new(),
standalone_condition_row_count: 0,
standalone_condition_rows: Vec::new(),
@ -1449,6 +1490,7 @@ mod tests {
active: None,
marks_collection_dirty: None,
one_shot: None,
compact_control: None,
text_bands: Vec::new(),
standalone_condition_row_count: 0,
standalone_condition_rows: Vec::new(),
@ -1469,6 +1511,7 @@ mod tests {
active: None,
marks_collection_dirty: None,
one_shot: None,
compact_control: None,
text_bands: Vec::new(),
standalone_condition_row_count: 0,
standalone_condition_rows: Vec::new(),
@ -1679,6 +1722,7 @@ mod tests {
active: Some(true),
marks_collection_dirty: Some(true),
one_shot: Some(false),
compact_control: None,
text_bands: packed_text_bands(),
standalone_condition_row_count: 1,
standalone_condition_rows: vec![],
@ -1788,6 +1832,7 @@ mod tests {
active: Some(true),
marks_collection_dirty: Some(false),
one_shot: Some(false),
compact_control: None,
text_bands: packed_text_bands(),
standalone_condition_row_count: 0,
standalone_condition_rows: vec![],
@ -1839,7 +1884,7 @@ mod tests {
}
#[test]
fn leaves_real_structural_records_blocked_structural_only() {
fn leaves_real_records_without_compact_control_blocked_missing_compact_control() {
let save_slice = SmpLoadedSaveSlice {
file_extension_hint: Some("gms".to_string()),
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
@ -1876,6 +1921,7 @@ mod tests {
active: None,
marks_collection_dirty: None,
one_shot: None,
compact_control: None,
text_bands: packed_text_bands(),
standalone_condition_row_count: 1,
standalone_condition_rows: real_condition_rows(),
@ -1903,7 +1949,7 @@ mod tests {
.packed_event_collection
.as_ref()
.and_then(|summary| summary.records[0].import_outcome.as_deref()),
Some("blocked_structural_only")
Some("blocked_missing_compact_control")
);
assert_eq!(
import
@ -1923,6 +1969,85 @@ mod tests {
);
}
#[test]
fn leaves_real_records_with_compact_control_blocked_unmapped_real_descriptor() {
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(133),
decode_status: "parity_only".to_string(),
payload_family: "real_packed_v1".to_string(),
trigger_kind: Some(6),
active: None,
marks_collection_dirty: None,
one_shot: Some(true),
compact_control: Some(real_compact_control()),
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-real-descriptor-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].compact_control.as_ref())
.map(|control| control.mode_byte_0x7ef),
Some(6)
);
assert_eq!(
import
.state
.packed_event_collection
.as_ref()
.and_then(|summary| summary.records[0].import_outcome.as_deref()),
Some("blocked_unmapped_real_descriptor")
);
}
#[test]
fn overlays_save_slice_events_onto_base_company_context() {
let base_state = RuntimeState {
@ -1997,6 +2122,7 @@ mod tests {
active: Some(true),
marks_collection_dirty: Some(false),
one_shot: Some(false),
compact_control: None,
text_bands: packed_text_bands(),
standalone_condition_row_count: 0,
standalone_condition_rows: vec![],
@ -2150,6 +2276,7 @@ mod tests {
active: Some(true),
marks_collection_dirty: Some(false),
one_shot: Some(false),
compact_control: None,
text_bands: packed_text_bands(),
standalone_condition_row_count: 0,
standalone_condition_rows: vec![],