Close out EventEffects descriptor metadata

This commit is contained in:
Jan Petykiewicz 2026-04-16 19:03:07 -07:00
commit 3dbcec688f
15 changed files with 7156 additions and 23 deletions

View file

@ -4532,6 +4532,12 @@ mod tests {
.join(
"../../fixtures/runtime/packed-event-company-governance-condition-save-slice-fixture.json",
);
let credit_rating_descriptor_save_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
"../../fixtures/runtime/packed-event-credit-rating-descriptor-save-slice-fixture.json",
);
let merger_premium_shell_save_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
"../../fixtures/runtime/packed-event-merger-premium-shell-save-slice-fixture.json",
);
let investor_confidence_condition_save_fixture = PathBuf::from(env!(
"CARGO_MANIFEST_DIR"
))
@ -4617,6 +4623,10 @@ mod tests {
.expect("overlay-backed company governance condition fixture should summarize");
run_runtime_summarize_fixture(&company_governance_condition_save_fixture)
.expect("save-slice-backed company governance condition fixture should summarize");
run_runtime_summarize_fixture(&credit_rating_descriptor_save_fixture)
.expect("save-slice-backed credit-rating descriptor fixture should summarize");
run_runtime_summarize_fixture(&merger_premium_shell_save_fixture)
.expect("save-slice-backed shell-owned merger-premium fixture should summarize");
run_runtime_summarize_fixture(&investor_confidence_condition_save_fixture)
.expect("save-slice-backed investor-confidence condition fixture should summarize");
run_runtime_summarize_fixture(&management_attitude_condition_save_fixture)

View file

@ -146,6 +146,8 @@ pub struct ExpectedRuntimeSummary {
#[serde(default)]
pub packed_event_blocked_missing_compact_control_count: Option<usize>,
#[serde(default)]
pub packed_event_blocked_shell_owned_descriptor_count: Option<usize>,
#[serde(default)]
pub packed_event_blocked_unmapped_real_descriptor_count: Option<usize>,
#[serde(default)]
pub packed_event_blocked_unmapped_world_descriptor_count: Option<usize>,
@ -751,6 +753,14 @@ impl ExpectedRuntimeSummary {
));
}
}
if let Some(count) = self.packed_event_blocked_shell_owned_descriptor_count {
if actual.packed_event_blocked_shell_owned_descriptor_count != count {
mismatches.push(format!(
"packed_event_blocked_shell_owned_descriptor_count mismatch: expected {count}, got {}",
actual.packed_event_blocked_shell_owned_descriptor_count
));
}
}
if let Some(count) = self.packed_event_blocked_unmapped_real_descriptor_count {
if actual.packed_event_blocked_unmapped_real_descriptor_count != count {
mismatches.push(format!(

View file

@ -30,6 +30,7 @@ pub const REQUIRED_EXPORTS: &[&str] = &[
"artifacts/exports/rt3-1.06/pending-template-store-functions.csv",
"artifacts/exports/rt3-1.06/pending-template-store-record-kinds.csv",
"artifacts/exports/rt3-1.06/pending-template-store-management.md",
"artifacts/exports/rt3-1.06/event-effects-table.json",
];
pub const REQUIRED_ATLAS_HEADINGS: &[&str] = &[

View file

@ -2683,6 +2683,13 @@ fn determine_packed_event_import_outcome(
return "blocked_unmapped_ordinary_condition".to_string();
}
}
if record
.grouped_effect_rows
.iter()
.any(real_grouped_row_is_shell_owned_descriptor_family)
{
return "blocked_shell_owned_descriptor".to_string();
}
if record
.grouped_effect_rows
.iter()
@ -2779,6 +2786,13 @@ fn real_grouped_row_is_world_state_family(
})
}
fn real_grouped_row_is_shell_owned_descriptor_family(
row: &SmpLoadedPackedEventGroupedEffectRowSummary,
) -> bool {
crate::smp::grouped_effect_descriptor_runtime_status_name(row.descriptor_id)
.is_some_and(|status| status == "shell_owned")
}
fn packed_record_company_target_import_blocker(
record: &SmpLoadedPackedEventRecordSummary,
company_context: &ImportRuntimeContext,
@ -3661,6 +3675,69 @@ mod tests {
}
}
fn real_credit_rating_row(value: i32) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary {
crate::SmpLoadedPackedEventGroupedEffectRowSummary {
group_index: 0,
row_index: 0,
descriptor_id: 56,
descriptor_label: Some("Credit Rating".to_string()),
target_mask_bits: Some(0x0b),
parameter_family: Some("company_governance_scalar".to_string()),
grouped_target_subject: Some("company".to_string()),
grouped_target_scope: Some("selected_company".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 Credit Rating to {value}")),
recovered_cargo_slot: None,
recovered_cargo_class: None,
recovered_locomotive_id: None,
locomotive_name: None,
notes: vec![],
}
}
fn real_merger_premium_shell_row(
value: i32,
) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary {
crate::SmpLoadedPackedEventGroupedEffectRowSummary {
group_index: 0,
row_index: 0,
descriptor_id: 58,
descriptor_label: Some("Merger Premium".to_string()),
target_mask_bits: Some(0x0b),
parameter_family: Some("company_finance_shell_scalar".to_string()),
grouped_target_subject: Some("company".to_string()),
grouped_target_scope: Some("selected_company".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 Merger Premium to {value}")),
recovered_cargo_slot: None,
recovered_cargo_class: None,
recovered_locomotive_id: None,
locomotive_name: None,
notes: vec![
"descriptor is recovered in the checked-in effect table as shell_owned parity"
.to_string(),
],
}
}
fn real_deactivate_player_row(
enabled: bool,
) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary {
@ -6053,6 +6130,176 @@ mod tests {
);
}
#[test]
fn leaves_recovered_shell_owned_descriptor_rows_on_explicit_shell_owned_frontier() {
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,
locomotive_catalog: None,
cargo_catalog: None,
company_roster: Some(save_company_roster()),
chairman_profile_table: Some(save_chairman_profile_table()),
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: 8,
live_record_count: 1,
live_entry_ids: vec![8],
decoded_record_count: 1,
imported_runtime_record_count: 0,
records: vec![crate::SmpLoadedPackedEventRecordSummary {
record_index: 0,
live_entry_id: 8,
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(true),
compact_control: Some(real_compact_control_without_symbolic_company_scope()),
text_bands: packed_text_bands(),
standalone_condition_row_count: 0,
standalone_condition_rows: Vec::new(),
negative_sentinel_scope: None,
grouped_effect_row_counts: vec![1, 0, 0, 0],
grouped_effect_rows: vec![real_merger_premium_shell_row(25)],
decoded_conditions: Vec::new(),
decoded_actions: Vec::new(),
executable_import_ready: false,
notes: vec!["synthetic shell-owned descriptor test record".to_string()],
}],
}),
notes: vec![],
};
let import = project_save_slice_to_runtime_state_import(
&save_slice,
"packed-events-shell-owned-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].import_outcome.as_deref()),
Some("blocked_shell_owned_descriptor")
);
}
#[test]
fn imports_credit_rating_descriptor_from_save_slice_company_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,
locomotive_catalog: None,
cargo_catalog: None,
company_roster: Some(save_company_roster()),
chairman_profile_table: Some(save_chairman_profile_table()),
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: 9,
live_record_count: 1,
live_entry_ids: vec![9],
decoded_record_count: 1,
imported_runtime_record_count: 0,
records: vec![crate::SmpLoadedPackedEventRecordSummary {
record_index: 0,
live_entry_id: 9,
payload_offset: Some(0x7202),
payload_len: Some(120),
decode_status: "executable".to_string(),
payload_family: "real_packed_v1".to_string(),
trigger_kind: Some(7),
active: None,
marks_collection_dirty: None,
one_shot: Some(true),
compact_control: Some(real_compact_control_without_symbolic_company_scope()),
text_bands: packed_text_bands(),
standalone_condition_row_count: 0,
standalone_condition_rows: Vec::new(),
negative_sentinel_scope: None,
grouped_effect_row_counts: vec![1, 0, 0, 0],
grouped_effect_rows: vec![real_credit_rating_row(640)],
decoded_conditions: Vec::new(),
decoded_actions: vec![RuntimeEffect::SetCompanyGovernanceScalar {
target: RuntimeCompanyTarget::SelectedCompany,
metric: crate::RuntimeCompanyMetric::CreditRating,
value: 640,
}],
executable_import_ready: true,
notes: vec!["synthetic governance descriptor test record".to_string()],
}],
}),
notes: vec![],
};
let import = project_save_slice_to_runtime_state_import(
&save_slice,
"packed-events-credit-rating-descriptor",
None,
)
.expect("save slice should project");
assert_eq!(import.state.event_runtime_records.len(), 1);
assert_eq!(
import
.state
.event_runtime_records
.first()
.map(|record| record.effects.clone()),
Some(vec![RuntimeEffect::SetCompanyGovernanceScalar {
target: RuntimeCompanyTarget::SelectedCompany,
metric: crate::RuntimeCompanyMetric::CreditRating,
value: 640,
}])
);
assert_eq!(
import
.state
.packed_event_collection
.as_ref()
.and_then(|summary| summary.records[0].import_outcome.as_deref()),
Some("imported")
);
}
#[test]
fn blocks_scalar_locomotive_availability_rows_without_catalog_context() {
let save_slice = SmpLoadedSaveSlice {

View file

@ -128,9 +128,35 @@ struct RealGroupedEffectDescriptorMetadata {
target_mask_bits: u8,
parameter_family: &'static str,
runtime_key: Option<&'static str>,
runtime_status: RealGroupedEffectRuntimeStatus,
executable_in_runtime: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum RealGroupedEffectRuntimeStatus {
Executable,
ShellOwned,
EvidenceBlocked,
VariantOrScopeBlocked,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
struct CheckedInEventEffectsTableArtifact {
descriptors: Vec<CheckedInEventEffectDescriptorRow>,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
struct CheckedInEventEffectDescriptorRow {
descriptor_id: u32,
selector_order: f32,
target_mask_bits: u8,
label_id: u32,
label: String,
signature_byte_0x63: u8,
signature_byte_0x64: u8,
signature_hex_0x63_0x6d: String,
}
const REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA: [RealGroupedEffectDescriptorMetadata; 12] = [
RealGroupedEffectDescriptorMetadata {
descriptor_id: 1,
@ -138,6 +164,7 @@ const REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA: [RealGroupedEffectDescriptorMetad
target_mask_bits: 0x02,
parameter_family: "player_finance_scalar",
runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true,
},
RealGroupedEffectDescriptorMetadata {
@ -146,6 +173,7 @@ const REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA: [RealGroupedEffectDescriptorMetad
target_mask_bits: 0x01,
parameter_family: "company_finance_scalar",
runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true,
},
RealGroupedEffectDescriptorMetadata {
@ -154,6 +182,7 @@ const REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA: [RealGroupedEffectDescriptorMetad
target_mask_bits: 0x05,
parameter_family: "territory_access_toggle",
runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true,
},
RealGroupedEffectDescriptorMetadata {
@ -162,6 +191,7 @@ const REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA: [RealGroupedEffectDescriptorMetad
target_mask_bits: 0x08,
parameter_family: "whole_game_state_enum",
runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true,
},
RealGroupedEffectDescriptorMetadata {
@ -170,6 +200,7 @@ const REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA: [RealGroupedEffectDescriptorMetad
target_mask_bits: 0x08,
parameter_family: "special_condition_scalar",
runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true,
},
RealGroupedEffectDescriptorMetadata {
@ -178,6 +209,7 @@ const REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA: [RealGroupedEffectDescriptorMetad
target_mask_bits: 0x08,
parameter_family: "candidate_availability_scalar",
runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true,
},
RealGroupedEffectDescriptorMetadata {
@ -186,6 +218,7 @@ const REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA: [RealGroupedEffectDescriptorMetad
target_mask_bits: 0x08,
parameter_family: "world_flag_toggle",
runtime_key: Some("world.disable_stock_buying_and_selling"),
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true,
},
RealGroupedEffectDescriptorMetadata {
@ -194,6 +227,7 @@ const REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA: [RealGroupedEffectDescriptorMetad
target_mask_bits: 0x01,
parameter_family: "company_confiscation_variant",
runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true,
},
RealGroupedEffectDescriptorMetadata {
@ -202,6 +236,7 @@ const REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA: [RealGroupedEffectDescriptorMetad
target_mask_bits: 0x01,
parameter_family: "company_lifecycle_toggle",
runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true,
},
RealGroupedEffectDescriptorMetadata {
@ -210,6 +245,7 @@ const REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA: [RealGroupedEffectDescriptorMetad
target_mask_bits: 0x02,
parameter_family: "player_lifecycle_toggle",
runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true,
},
RealGroupedEffectDescriptorMetadata {
@ -218,6 +254,7 @@ const REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA: [RealGroupedEffectDescriptorMetad
target_mask_bits: 0x0d,
parameter_family: "company_or_territory_asset_toggle",
runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true,
},
RealGroupedEffectDescriptorMetadata {
@ -226,10 +263,199 @@ const REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA: [RealGroupedEffectDescriptorMetad
target_mask_bits: 0x01,
parameter_family: "company_build_limit_scalar",
runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true,
},
];
fn real_grouped_effect_runtime_status_name(status: RealGroupedEffectRuntimeStatus) -> &'static str {
match status {
RealGroupedEffectRuntimeStatus::Executable => "executable",
RealGroupedEffectRuntimeStatus::ShellOwned => "shell_owned",
RealGroupedEffectRuntimeStatus::EvidenceBlocked => "evidence_blocked",
RealGroupedEffectRuntimeStatus::VariantOrScopeBlocked => "variant_or_scope_blocked",
}
}
fn checked_in_event_effect_descriptor_rows()
-> &'static BTreeMap<u32, RealGroupedEffectDescriptorMetadata> {
static ROWS: OnceLock<BTreeMap<u32, RealGroupedEffectDescriptorMetadata>> = OnceLock::new();
ROWS.get_or_init(|| {
let artifact: CheckedInEventEffectsTableArtifact = serde_json::from_str(include_str!(
"../../../artifacts/exports/rt3-1.06/event-effects-table.json"
))
.expect("checked-in event-effects artifact should parse");
artifact
.descriptors
.into_iter()
.map(|row| {
(
row.descriptor_id,
checked_in_event_effect_descriptor_metadata(row),
)
})
.collect()
})
}
fn checked_in_event_effect_descriptor_metadata(
row: CheckedInEventEffectDescriptorRow,
) -> RealGroupedEffectDescriptorMetadata {
let label = Box::leak(row.label.clone().into_boxed_str()) as &'static str;
let (parameter_family, runtime_key, runtime_status, executable_in_runtime) =
classify_checked_in_event_effect_descriptor(&row, label);
debug_assert!(!row.signature_hex_0x63_0x6d.is_empty());
debug_assert!(row.label_id <= u16::MAX as u32);
let _ = row.selector_order;
RealGroupedEffectDescriptorMetadata {
descriptor_id: row.descriptor_id,
label,
target_mask_bits: row.target_mask_bits,
parameter_family,
runtime_key,
runtime_status,
executable_in_runtime,
}
}
fn classify_checked_in_event_effect_descriptor(
row: &CheckedInEventEffectDescriptorRow,
label: &'static str,
) -> (
&'static str,
Option<&'static str>,
RealGroupedEffectRuntimeStatus,
bool,
) {
let descriptor_id = row.descriptor_id;
match descriptor_id {
4..=7 => {
return (
"scenario_outcome_shell_action",
None,
RealGroupedEffectRuntimeStatus::ShellOwned,
false,
);
}
10 | 11 | 12 | 23 => {
return (
"company_confiscation_variant",
None,
RealGroupedEffectRuntimeStatus::VariantOrScopeBlocked,
false,
);
}
17..=21 => {
return (
"company_or_territory_destruction_variant",
None,
RealGroupedEffectRuntimeStatus::ShellOwned,
false,
);
}
24 => {
return (
"control_transfer_shell_action",
None,
RealGroupedEffectRuntimeStatus::ShellOwned,
false,
);
}
56 => {
return (
"company_governance_scalar",
None,
RealGroupedEffectRuntimeStatus::Executable,
true,
);
}
57 => {
return (
"company_governance_scalar",
None,
RealGroupedEffectRuntimeStatus::Executable,
true,
);
}
58 => {
return (
"company_finance_shell_scalar",
None,
RealGroupedEffectRuntimeStatus::ShellOwned,
false,
);
}
_ => {}
}
if row.signature_byte_0x63 == 0 && row.signature_byte_0x64 == 0x8f {
return (
"runtime_variable_scalar",
None,
RealGroupedEffectRuntimeStatus::EvidenceBlocked,
false,
);
}
if row.target_mask_bits == 0x0b && label == "Stock Prices" {
return (
"company_finance_shell_scalar",
None,
RealGroupedEffectRuntimeStatus::ShellOwned,
false,
);
}
if label.contains("Cost")
|| label.contains("Revenue")
|| label.contains("Speed")
|| label.contains("Reliability")
|| label.contains("Acceleration")
|| label.contains("Pulling Power")
|| label.contains("Usage Rate")
|| label.contains("Maintenance")
{
return (
"world_scalar_economy_or_locomotive",
None,
RealGroupedEffectRuntimeStatus::EvidenceBlocked,
false,
);
}
if label.contains("Earthquake") || label.contains("Storm") {
return (
"world_disaster_scalar",
None,
RealGroupedEffectRuntimeStatus::EvidenceBlocked,
false,
);
}
if label.contains("Add Building") {
return (
"world_building_spawn",
None,
RealGroupedEffectRuntimeStatus::ShellOwned,
false,
);
}
(
"recovered_effect_table_descriptor",
None,
RealGroupedEffectRuntimeStatus::EvidenceBlocked,
false,
)
}
pub(crate) fn grouped_effect_descriptor_runtime_status_name(
descriptor_id: u32,
) -> Option<&'static str> {
real_grouped_effect_descriptor_metadata(descriptor_id)
.map(|metadata| real_grouped_effect_runtime_status_name(metadata.runtime_status))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum RealOrdinaryConditionMetric {
Company(RuntimeCompanyMetric),
@ -3323,7 +3549,14 @@ fn parse_real_grouped_effect_row_summary(
if locomotive_name.is_some() {
notes.push("grouped effect row carries locomotive-name side string".to_string());
}
if descriptor_metadata.is_none() {
if let Some(metadata) = descriptor_metadata {
if metadata.runtime_status != RealGroupedEffectRuntimeStatus::Executable {
notes.push(format!(
"descriptor is recovered in the checked-in effect table as {} parity",
real_grouped_effect_runtime_status_name(metadata.runtime_status)
));
}
} else {
notes.push("descriptor id not yet recovered in the checked-in effect table".to_string());
}
if let Some(loco_id) = recovered_locomotive_availability_loco_id(descriptor_id) {
@ -3549,17 +3782,24 @@ fn real_condition_chairman_target(
fn real_grouped_effect_descriptor_metadata(
descriptor_id: u32,
) -> Option<RealGroupedEffectDescriptorMetadata> {
REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA
.iter()
.copied()
.find(|metadata| metadata.descriptor_id == descriptor_id)
.or_else(|| recovered_cargo_production_descriptor_metadata(descriptor_id))
recovered_cargo_production_descriptor_metadata(descriptor_id)
.or_else(|| recovered_locomotive_availability_descriptor_metadata(descriptor_id))
.or_else(|| recovered_locomotive_cost_descriptor_metadata(descriptor_id))
.or_else(|| recovered_territory_access_cost_descriptor_metadata(descriptor_id))
.or_else(|| recovered_locomotive_policy_descriptor_metadata(descriptor_id))
.or_else(|| special_condition_world_scalar_descriptor_metadata(descriptor_id))
.or_else(|| special_condition_world_toggle_descriptor_metadata(descriptor_id))
.or_else(|| {
REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA
.iter()
.copied()
.find(|metadata| metadata.descriptor_id == descriptor_id)
})
.or_else(|| {
checked_in_event_effect_descriptor_rows()
.get(&descriptor_id)
.copied()
})
}
fn recovered_cargo_production_descriptor_metadata(
@ -3572,6 +3812,7 @@ fn recovered_cargo_production_descriptor_metadata(
target_mask_bits: 0x08,
parameter_family: "cargo_production_scalar",
runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true,
}
})
@ -3593,6 +3834,7 @@ fn recovered_locomotive_availability_descriptor_metadata(
target_mask_bits: 0x08,
parameter_family: "locomotive_availability_scalar",
runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::EvidenceBlocked,
executable_in_runtime: false,
})
.or_else(|| {
@ -3604,6 +3846,7 @@ fn recovered_locomotive_availability_descriptor_metadata(
target_mask_bits: 0x08,
parameter_family: "locomotive_availability_scalar",
runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::EvidenceBlocked,
executable_in_runtime: false,
})
})
@ -3669,6 +3912,7 @@ fn recovered_locomotive_cost_descriptor_metadata(
target_mask_bits: 0x08,
parameter_family: "locomotive_cost_scalar",
runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::EvidenceBlocked,
executable_in_runtime: false,
}
})
@ -3683,6 +3927,7 @@ fn recovered_territory_access_cost_descriptor_metadata(
target_mask_bits: 0x08,
parameter_family: "territory_access_cost_scalar",
runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true,
})
}
@ -3697,6 +3942,7 @@ fn recovered_locomotive_policy_descriptor_metadata(
target_mask_bits: 0x08,
parameter_family: "world_flag_toggle",
runtime_key: Some("world.all_steam_locos_available"),
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true,
}),
455 => Some(RealGroupedEffectDescriptorMetadata {
@ -3705,6 +3951,7 @@ fn recovered_locomotive_policy_descriptor_metadata(
target_mask_bits: 0x08,
parameter_family: "world_flag_toggle",
runtime_key: Some("world.all_diesel_locos_available"),
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true,
}),
456 => Some(RealGroupedEffectDescriptorMetadata {
@ -3713,6 +3960,7 @@ fn recovered_locomotive_policy_descriptor_metadata(
target_mask_bits: 0x08,
parameter_family: "world_flag_toggle",
runtime_key: Some("world.all_electric_locos_available"),
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true,
}),
_ => None,
@ -3736,6 +3984,7 @@ fn special_condition_world_scalar_descriptor_metadata(
target_mask_bits: 0x08,
parameter_family: "world_track_build_limit_scalar",
runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true,
})
}
@ -3757,6 +4006,7 @@ fn special_condition_world_toggle_descriptor_metadata(
target_mask_bits: 0x08,
parameter_family: "world_flag_toggle",
runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true,
})
}
@ -3870,10 +4120,26 @@ fn runtime_world_flag_key_from_label(label: &str) -> String {
key
}
fn real_grouped_company_governance_metric(
descriptor_metadata: RealGroupedEffectDescriptorMetadata,
) -> Option<RuntimeCompanyMetric> {
match descriptor_metadata.label {
"Credit Rating" => Some(RuntimeCompanyMetric::CreditRating),
"Prime Rate" => Some(RuntimeCompanyMetric::PrimeRate),
"Book Value Per Share" => Some(RuntimeCompanyMetric::BookValuePerShare),
"Investor Confidence" => Some(RuntimeCompanyMetric::InvestorConfidence),
"Management Attitude" => Some(RuntimeCompanyMetric::ManagementAttitude),
_ => None,
}
}
fn derive_real_grouped_target_subject(
row: &SmpLoadedPackedEventGroupedEffectRowSummary,
compact_control: &SmpLoadedPackedEventCompactControlSummary,
) -> Option<RealGroupedTargetSubject> {
if row.parameter_family.as_deref() == Some("company_governance_scalar") {
return Some(RealGroupedTargetSubject::Company);
}
match row.target_mask_bits {
Some(0x08) => Some(RealGroupedTargetSubject::WholeGame),
Some(0x01) => Some(RealGroupedTargetSubject::Company),
@ -3986,6 +4252,19 @@ fn decode_real_grouped_effect_action(
.copied()?;
let target_subject = derive_real_grouped_target_subject(row, compact_control);
if descriptor_metadata.executable_in_runtime
&& descriptor_metadata.parameter_family == "company_governance_scalar"
&& row.row_shape == "scalar_assignment"
{
let target = real_grouped_company_target(target_scope_ordinal)?;
let metric = real_grouped_company_governance_metric(descriptor_metadata)?;
return Some(RuntimeEffect::SetCompanyGovernanceScalar {
target,
metric,
value: i64::from(row.raw_scalar_value),
});
}
if descriptor_metadata.executable_in_runtime
&& descriptor_metadata.descriptor_id == 1
&& row.opcode == 8
@ -10832,6 +11111,135 @@ mod tests {
assert!(metadata.executable_in_runtime);
}
#[test]
fn looks_up_checked_in_credit_rating_descriptor_metadata() {
let metadata =
real_grouped_effect_descriptor_metadata(56).expect("descriptor metadata should exist");
assert_eq!(metadata.label, "Credit Rating");
assert_eq!(metadata.target_mask_bits, 0x0b);
assert_eq!(metadata.parameter_family, "company_governance_scalar");
assert_eq!(
real_grouped_effect_runtime_status_name(metadata.runtime_status),
"executable"
);
assert!(metadata.executable_in_runtime);
}
#[test]
fn checked_in_event_effect_table_covers_the_full_exported_descriptor_set() {
let rows = checked_in_event_effect_descriptor_rows();
assert_eq!(rows.len(), 520);
for descriptor_id in 0..520_u32 {
assert!(
real_grouped_effect_descriptor_metadata(descriptor_id).is_some(),
"descriptor {descriptor_id} should be recoverable from the checked-in effect table"
);
}
}
#[test]
fn looks_up_checked_in_prime_rate_descriptor_metadata() {
let metadata =
real_grouped_effect_descriptor_metadata(57).expect("descriptor metadata should exist");
assert_eq!(metadata.label, "Prime Rate");
assert_eq!(metadata.target_mask_bits, 0x0b);
assert_eq!(metadata.parameter_family, "company_governance_scalar");
assert_eq!(
real_grouped_effect_runtime_status_name(metadata.runtime_status),
"executable"
);
assert!(metadata.executable_in_runtime);
}
#[test]
fn classifies_shell_owned_finance_descriptor_from_checked_in_effect_table() {
let metadata =
real_grouped_effect_descriptor_metadata(58).expect("descriptor metadata should exist");
assert_eq!(metadata.label, "Merger Premium");
assert_eq!(metadata.parameter_family, "company_finance_shell_scalar");
assert_eq!(
real_grouped_effect_runtime_status_name(metadata.runtime_status),
"shell_owned"
);
assert!(!metadata.executable_in_runtime);
}
#[test]
fn decodes_credit_rating_descriptor_into_company_governance_scalar_effect() {
let row_bytes = build_real_grouped_effect_row(RealGroupedEffectRowSpec {
descriptor_id: 56,
raw_scalar_value: 640,
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 record_body = build_real_event_record(
[b"Gov", b"", b"", b"", b"", b""],
Some(RealCompactControlSpec {
mode_byte_0x7ef: 7,
primary_selector_0x7f0: 0,
grouped_mode_0x7f4: 2,
one_shot_header_0x7f5: 0,
modifier_flag_0x7f9: 0,
modifier_flag_0x7fa: 0,
grouped_target_scope_ordinals_0x7fb: [1, 0, 0, 0],
grouped_scope_checkboxes_0x7ff: [1, 0, 0, 0],
summary_toggle_0x800: 1,
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
}),
&[],
[&[row_bytes], &[], &[], &[]],
);
let mut bytes = Vec::new();
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_METADATA_TAG.to_le_bytes());
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_PACKED_STATE_VERSION.to_le_bytes());
let header_words = [1u32, 4, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
for word in header_words {
bytes.extend_from_slice(&word.to_le_bytes());
}
bytes.extend_from_slice(&[0x00, 0x00]);
bytes.extend_from_slice(&[0xaa, 0xbb, 0xcc, 0xdd]);
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_RECORDS_TAG.to_le_bytes());
bytes.extend_from_slice(&record_body);
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_CLOSE_TAG.to_le_bytes());
let report = inspect_smp_bytes(&bytes);
let summary = report
.event_runtime_collection_summary
.as_ref()
.expect("event runtime collection summary should parse");
assert_eq!(
summary.records[0].grouped_effect_rows[0]
.descriptor_label
.as_deref(),
Some("Credit Rating")
);
assert_eq!(
summary.records[0].grouped_effect_rows[0]
.grouped_target_subject
.as_deref(),
Some("company")
);
assert_eq!(
summary.records[0].decoded_actions,
vec![RuntimeEffect::SetCompanyGovernanceScalar {
target: RuntimeCompanyTarget::SelectedCompany,
metric: RuntimeCompanyMetric::CreditRating,
value: 640,
}]
);
}
#[test]
fn looks_up_recovered_world_toggle_descriptor_metadata() {
let metadata =

View file

@ -70,6 +70,7 @@ pub struct RuntimeSummary {
pub packed_event_blocked_unmapped_ordinary_condition_count: usize,
pub packed_event_blocked_unmapped_world_condition_count: usize,
pub packed_event_blocked_missing_compact_control_count: usize,
pub packed_event_blocked_shell_owned_descriptor_count: usize,
pub packed_event_blocked_unmapped_real_descriptor_count: usize,
pub packed_event_blocked_unmapped_world_descriptor_count: usize,
pub packed_event_blocked_territory_access_variant_count: usize,
@ -496,6 +497,20 @@ impl RuntimeSummary {
.count()
})
.unwrap_or(0),
packed_event_blocked_shell_owned_descriptor_count: state
.packed_event_collection
.as_ref()
.map(|summary| {
summary
.records
.iter()
.filter(|record| {
record.import_outcome.as_deref()
== Some("blocked_shell_owned_descriptor")
})
.count()
})
.unwrap_or(0),
packed_event_blocked_unmapped_real_descriptor_count: state
.packed_event_collection
.as_ref()
@ -1343,4 +1358,80 @@ mod tests {
1
);
}
#[test]
fn counts_shell_owned_descriptor_frontier() {
let state = RuntimeState {
calendar: CalendarPoint {
year: 1830,
month_slot: 0,
phase_slot: 0,
tick_slot: 0,
},
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,
chairman_profiles: Vec::new(),
selected_chairman_profile_id: None,
trains: Vec::new(),
locomotive_catalog: Vec::new(),
cargo_catalog: Vec::new(),
territories: Vec::new(),
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
packed_event_collection: Some(RuntimePackedEventCollectionSummary {
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()),
packed_state_version: 0x3e9,
packed_state_version_hex: "0x000003e9".to_string(),
live_id_bound: 1,
live_record_count: 1,
live_entry_ids: vec![1],
decoded_record_count: 1,
imported_runtime_record_count: 0,
records: vec![RuntimePackedEventRecordSummary {
record_index: 0,
live_entry_id: 1,
payload_offset: Some(0),
payload_len: Some(0),
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: None,
compact_control: None,
text_bands: Vec::new(),
standalone_condition_row_count: 0,
standalone_condition_rows: Vec::new(),
negative_sentinel_scope: None,
grouped_effect_row_counts: vec![1, 0, 0, 0],
grouped_effect_rows: Vec::new(),
grouped_company_targets: Vec::new(),
decoded_conditions: Vec::new(),
decoded_actions: Vec::new(),
executable_import_ready: false,
import_outcome: Some("blocked_shell_owned_descriptor".to_string()),
notes: Vec::new(),
}],
}),
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState::default(),
};
let summary = RuntimeSummary::from_state(&state);
assert_eq!(summary.packed_event_blocked_shell_owned_descriptor_count, 1);
}
}