Recover scalar-band packed event metadata
This commit is contained in:
parent
87108f357b
commit
e2174713a9
8 changed files with 685 additions and 39 deletions
|
|
@ -4463,6 +4463,9 @@ mod tests {
|
|||
let overlay_locomotive_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
|
||||
"../../fixtures/runtime/packed-event-locomotive-availability-overlay-fixture.json",
|
||||
);
|
||||
let scalar_band_parity_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
|
||||
"../../fixtures/runtime/packed-event-world-scalar-band-parity-save-slice-fixture.json",
|
||||
);
|
||||
|
||||
run_runtime_summarize_fixture(&parity_fixture)
|
||||
.expect("save-slice-backed parity fixture should summarize");
|
||||
|
|
@ -4487,6 +4490,8 @@ mod tests {
|
|||
);
|
||||
run_runtime_summarize_fixture(&overlay_locomotive_fixture)
|
||||
.expect("overlay-backed locomotive availability fixture should summarize");
|
||||
run_runtime_summarize_fixture(&scalar_band_parity_fixture)
|
||||
.expect("save-slice-backed recovered scalar-band parity fixture should summarize");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -3070,11 +3070,19 @@ mod tests {
|
|||
descriptor_id: u32,
|
||||
value: i32,
|
||||
) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||
let recovered_locomotive_id = match descriptor_id {
|
||||
352..=451 => Some(descriptor_id - 351),
|
||||
475..=500 => Some(descriptor_id - 374),
|
||||
_ => None,
|
||||
};
|
||||
let descriptor_label = recovered_locomotive_id
|
||||
.map(|loco_id| format!("Locomotive {loco_id} Cost"))
|
||||
.unwrap_or_else(|| "Locomotive Cost".to_string());
|
||||
crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||
group_index: 0,
|
||||
row_index: 0,
|
||||
descriptor_id,
|
||||
descriptor_label: Some("Unknown Loco Cost".to_string()),
|
||||
descriptor_label: Some(descriptor_label.clone()),
|
||||
target_mask_bits: Some(0x08),
|
||||
parameter_family: Some("locomotive_cost_scalar".to_string()),
|
||||
opcode: 3,
|
||||
|
|
@ -3087,12 +3095,65 @@ mod tests {
|
|||
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,
|
||||
},
|
||||
semantic_preview: Some(format!("Set {descriptor_label} to {value}")),
|
||||
recovered_locomotive_id,
|
||||
locomotive_name: None,
|
||||
notes: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn real_cargo_production_row(
|
||||
descriptor_id: u32,
|
||||
value: i32,
|
||||
) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||
let slot = descriptor_id.saturating_sub(229);
|
||||
let descriptor_label = format!("Cargo Production Slot {slot}");
|
||||
crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||
group_index: 0,
|
||||
row_index: 0,
|
||||
descriptor_id,
|
||||
descriptor_label: Some(descriptor_label.clone()),
|
||||
target_mask_bits: Some(0x08),
|
||||
parameter_family: Some("cargo_production_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 {descriptor_label} to {value}")),
|
||||
recovered_locomotive_id: None,
|
||||
locomotive_name: None,
|
||||
notes: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn real_territory_access_cost_row(
|
||||
value: i32,
|
||||
) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||
crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||
group_index: 0,
|
||||
row_index: 0,
|
||||
descriptor_id: 453,
|
||||
descriptor_label: Some("Territory Access Cost".to_string()),
|
||||
target_mask_bits: Some(0x08),
|
||||
parameter_family: Some("territory_access_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 Territory Access Cost to {value}")),
|
||||
recovered_locomotive_id: None,
|
||||
locomotive_name: None,
|
||||
notes: vec![],
|
||||
}
|
||||
|
|
@ -5155,6 +5216,152 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keeps_recovered_cargo_production_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: 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_cargo_production_row(230, 125)],
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: vec![],
|
||||
executable_import_ready: false,
|
||||
notes: vec!["cargo production rows remain metadata-only".to_string()],
|
||||
}],
|
||||
}),
|
||||
notes: vec![],
|
||||
};
|
||||
|
||||
let import = project_save_slice_to_runtime_state_import(
|
||||
&save_slice,
|
||||
"packed-events-cargo-production-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 keeps_recovered_territory_access_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: 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(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_territory_access_cost_row(750000)],
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: vec![],
|
||||
executable_import_ready: false,
|
||||
notes: vec!["territory access cost rows remain metadata-only".to_string()],
|
||||
}],
|
||||
}),
|
||||
notes: vec![],
|
||||
};
|
||||
|
||||
let import = project_save_slice_to_runtime_state_import(
|
||||
&save_slice,
|
||||
"packed-events-territory-access-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 {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
use std::collections::BTreeMap;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest, Sha256};
|
||||
|
|
@ -2684,6 +2686,11 @@ fn parse_real_grouped_effect_row_summary(
|
|||
"locomotive availability descriptor maps to live locomotive id {loco_id}"
|
||||
));
|
||||
}
|
||||
if let Some(loco_id) = recovered_locomotive_cost_loco_id(descriptor_id) {
|
||||
notes.push(format!(
|
||||
"locomotive cost descriptor maps to live locomotive id {loco_id}"
|
||||
));
|
||||
}
|
||||
|
||||
Some(SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||
group_index,
|
||||
|
|
@ -2711,7 +2718,8 @@ fn parse_real_grouped_effect_row_summary(
|
|||
value_word_0x14,
|
||||
value_word_0x16,
|
||||
)),
|
||||
recovered_locomotive_id: recovered_locomotive_availability_loco_id(descriptor_id),
|
||||
recovered_locomotive_id: recovered_locomotive_availability_loco_id(descriptor_id)
|
||||
.or_else(|| recovered_locomotive_cost_loco_id(descriptor_id)),
|
||||
locomotive_name,
|
||||
notes,
|
||||
})
|
||||
|
|
@ -2823,16 +2831,16 @@ fn real_grouped_effect_descriptor_metadata(
|
|||
fn recovered_cargo_production_descriptor_metadata(
|
||||
descriptor_id: u32,
|
||||
) -> Option<RealGroupedEffectDescriptorMetadata> {
|
||||
(230..=240)
|
||||
.contains(&descriptor_id)
|
||||
.then_some(RealGroupedEffectDescriptorMetadata {
|
||||
recovered_cargo_production_label(descriptor_id).map(|label| {
|
||||
RealGroupedEffectDescriptorMetadata {
|
||||
descriptor_id,
|
||||
label: "Unknown Cargo Production",
|
||||
label,
|
||||
target_mask_bits: 0x08,
|
||||
parameter_family: "unknown_cargo_production_scalar",
|
||||
parameter_family: "cargo_production_scalar",
|
||||
runtime_key: None,
|
||||
executable_in_runtime: false,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn recovered_locomotive_availability_descriptor_metadata(
|
||||
|
|
@ -2872,31 +2880,66 @@ fn recovered_locomotive_availability_loco_id(descriptor_id: u32) -> Option<u32>
|
|||
None
|
||||
}
|
||||
|
||||
fn recovered_cargo_production_label(descriptor_id: u32) -> Option<&'static str> {
|
||||
static LABELS: OnceLock<BTreeMap<u32, &'static str>> = OnceLock::new();
|
||||
LABELS
|
||||
.get_or_init(|| {
|
||||
(230..=240)
|
||||
.enumerate()
|
||||
.map(|(slot_index, descriptor_id)| {
|
||||
let label = Box::leak(
|
||||
format!("Cargo Production Slot {}", slot_index + 1).into_boxed_str(),
|
||||
) as &'static str;
|
||||
(descriptor_id, label)
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.get(&descriptor_id)
|
||||
.copied()
|
||||
}
|
||||
|
||||
fn recovered_locomotive_cost_loco_id(descriptor_id: u32) -> Option<u32> {
|
||||
if (352..=451).contains(&descriptor_id) {
|
||||
return Some(descriptor_id - 351);
|
||||
}
|
||||
if (475..=500).contains(&descriptor_id) {
|
||||
return Some(descriptor_id - 374);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn recovered_locomotive_cost_label(descriptor_id: u32) -> Option<&'static str> {
|
||||
static LABELS: OnceLock<BTreeMap<u32, &'static str>> = OnceLock::new();
|
||||
LABELS
|
||||
.get_or_init(|| {
|
||||
(352..=451)
|
||||
.chain(475..=500)
|
||||
.filter_map(|descriptor_id| {
|
||||
recovered_locomotive_cost_loco_id(descriptor_id).map(|loco_id| {
|
||||
let label = Box::leak(format!("Locomotive {loco_id} Cost").into_boxed_str())
|
||||
as &'static str;
|
||||
(descriptor_id, label)
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.get(&descriptor_id)
|
||||
.copied()
|
||||
}
|
||||
|
||||
fn recovered_locomotive_cost_descriptor_metadata(
|
||||
descriptor_id: u32,
|
||||
) -> Option<RealGroupedEffectDescriptorMetadata> {
|
||||
(352..=451)
|
||||
.contains(&descriptor_id)
|
||||
.then_some(RealGroupedEffectDescriptorMetadata {
|
||||
recovered_locomotive_cost_label(descriptor_id).map(|label| {
|
||||
RealGroupedEffectDescriptorMetadata {
|
||||
descriptor_id,
|
||||
label: "Unknown Loco Cost",
|
||||
label,
|
||||
target_mask_bits: 0x08,
|
||||
parameter_family: "locomotive_cost_scalar",
|
||||
runtime_key: None,
|
||||
executable_in_runtime: false,
|
||||
})
|
||||
.or_else(|| {
|
||||
(475..=500)
|
||||
.contains(&descriptor_id)
|
||||
.then_some(RealGroupedEffectDescriptorMetadata {
|
||||
descriptor_id,
|
||||
label: "Unknown Loco Cost",
|
||||
target_mask_bits: 0x08,
|
||||
parameter_family: "locomotive_cost_scalar",
|
||||
runtime_key: None,
|
||||
executable_in_runtime: false,
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn recovered_territory_access_cost_descriptor_metadata(
|
||||
|
|
@ -2904,7 +2947,7 @@ fn recovered_territory_access_cost_descriptor_metadata(
|
|||
) -> Option<RealGroupedEffectDescriptorMetadata> {
|
||||
(descriptor_id == 453).then_some(RealGroupedEffectDescriptorMetadata {
|
||||
descriptor_id,
|
||||
label: "Territory access cost",
|
||||
label: "Territory Access Cost",
|
||||
target_mask_bits: 0x08,
|
||||
parameter_family: "territory_access_cost_scalar",
|
||||
runtime_key: None,
|
||||
|
|
@ -9292,6 +9335,81 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn looks_up_recovered_cargo_production_descriptor_metadata() {
|
||||
let metadata =
|
||||
real_grouped_effect_descriptor_metadata(230).expect("descriptor metadata should exist");
|
||||
|
||||
assert_eq!(metadata.label, "Cargo Production Slot 1");
|
||||
assert_eq!(metadata.target_mask_bits, 0x08);
|
||||
assert_eq!(metadata.parameter_family, "cargo_production_scalar");
|
||||
assert_eq!(metadata.runtime_key, None);
|
||||
assert!(!metadata.executable_in_runtime);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn looks_up_recovered_lower_band_locomotive_cost_descriptor_metadata() {
|
||||
let metadata =
|
||||
real_grouped_effect_descriptor_metadata(352).expect("descriptor metadata should exist");
|
||||
|
||||
assert_eq!(metadata.label, "Locomotive 1 Cost");
|
||||
assert_eq!(metadata.target_mask_bits, 0x08);
|
||||
assert_eq!(metadata.parameter_family, "locomotive_cost_scalar");
|
||||
assert_eq!(metadata.runtime_key, None);
|
||||
assert_eq!(recovered_locomotive_cost_loco_id(352), Some(1));
|
||||
assert!(!metadata.executable_in_runtime);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn looks_up_recovered_upper_band_locomotive_cost_descriptor_metadata() {
|
||||
let metadata =
|
||||
real_grouped_effect_descriptor_metadata(475).expect("descriptor metadata should exist");
|
||||
|
||||
assert_eq!(metadata.label, "Locomotive 101 Cost");
|
||||
assert_eq!(metadata.parameter_family, "locomotive_cost_scalar");
|
||||
assert_eq!(recovered_locomotive_cost_loco_id(475), Some(101));
|
||||
assert!(!metadata.executable_in_runtime);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn looks_up_recovered_territory_access_cost_descriptor_metadata() {
|
||||
let metadata =
|
||||
real_grouped_effect_descriptor_metadata(453).expect("descriptor metadata should exist");
|
||||
|
||||
assert_eq!(metadata.label, "Territory Access Cost");
|
||||
assert_eq!(metadata.target_mask_bits, 0x08);
|
||||
assert_eq!(metadata.parameter_family, "territory_access_cost_scalar");
|
||||
assert_eq!(metadata.runtime_key, None);
|
||||
assert!(!metadata.executable_in_runtime);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_recovered_locomotive_cost_row_with_structured_locomotive_id() {
|
||||
let row_bytes = build_real_grouped_effect_row(RealGroupedEffectRowSpec {
|
||||
descriptor_id: 352,
|
||||
raw_scalar_value: 250000,
|
||||
opcode: 3,
|
||||
value_byte_0x09: 0,
|
||||
value_dword_0x0d: 0,
|
||||
value_byte_0x11: 0,
|
||||
value_byte_0x12: 0,
|
||||
value_word_0x14: 0,
|
||||
value_word_0x16: 0,
|
||||
locomotive_name: None,
|
||||
});
|
||||
|
||||
let row = parse_real_grouped_effect_row_summary(&row_bytes, 0, 0, None)
|
||||
.expect("row should parse");
|
||||
|
||||
assert_eq!(row.descriptor_id, 352);
|
||||
assert_eq!(row.descriptor_label.as_deref(), Some("Locomotive 1 Cost"));
|
||||
assert_eq!(row.recovered_locomotive_id, Some(1));
|
||||
assert_eq!(
|
||||
row.parameter_family.as_deref(),
|
||||
Some("locomotive_cost_scalar")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn looks_up_recovered_locomotive_policy_descriptor_metadata() {
|
||||
let metadata =
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue