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

@ -29,9 +29,14 @@ chairman ordinals remain explicit frontier. Checked-in save-slice
documents can now also carry explicit company rosters and chairman-profile tables, so the current documents can now also carry explicit company rosters and chairman-profile tables, so the current
company-targeted and chairman-targeted descriptor and condition batches can execute from standalone company-targeted and chairman-targeted descriptor and condition batches can execute from standalone
save-slice fixtures without overlay snapshots when that context is present; raw `.gms` inspection save-slice fixtures without overlay snapshots when that context is present; raw `.gms` inspection
still does not reconstruct those company/chairman collections automatically. A generic still does not reconstruct those company/chairman collections automatically. A checked-in
company-governance scalar effect surface now exists in runtime too, but real governance descriptor `EventEffects` export now exists too in
ids are still deferred until the checked-in effect-table evidence is stronger. The first grounded `artifacts/exports/rt3-1.06/event-effects-table.json`, and the first recovered governance
descriptor tranche now imports through the generic company-governance scalar effect surface:
descriptor `56` `Credit Rating` and descriptor `57` `Prime Rate` execute from ordinary real packed
rows, while adjacent recovered finance/control-transfer descriptors such as `55` `Stock Prices`
and `58` `Merger Premium` now land on explicit shell-owned parity instead of anonymous unmapped
descriptor residue. The first grounded
condition-side unlock now exists for negative-sentinel `raw_condition_id = -1` company scopes, and condition-side unlock now exists for negative-sentinel `raw_condition_id = -1` company scopes, and
the first ordinary nonnegative condition batch now executes too: numeric-threshold company the first ordinary nonnegative condition batch now executes too: numeric-threshold company
finance, company track, aggregate territory track, and company-territory track rows can import finance, company track, aggregate territory track, and company-territory track rows can import

File diff suppressed because it is too large Load diff

View file

@ -4532,6 +4532,12 @@ mod tests {
.join( .join(
"../../fixtures/runtime/packed-event-company-governance-condition-save-slice-fixture.json", "../../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!( let investor_confidence_condition_save_fixture = PathBuf::from(env!(
"CARGO_MANIFEST_DIR" "CARGO_MANIFEST_DIR"
)) ))
@ -4617,6 +4623,10 @@ mod tests {
.expect("overlay-backed company governance condition fixture should summarize"); .expect("overlay-backed company governance condition fixture should summarize");
run_runtime_summarize_fixture(&company_governance_condition_save_fixture) run_runtime_summarize_fixture(&company_governance_condition_save_fixture)
.expect("save-slice-backed company governance condition fixture should summarize"); .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) run_runtime_summarize_fixture(&investor_confidence_condition_save_fixture)
.expect("save-slice-backed investor-confidence condition fixture should summarize"); .expect("save-slice-backed investor-confidence condition fixture should summarize");
run_runtime_summarize_fixture(&management_attitude_condition_save_fixture) run_runtime_summarize_fixture(&management_attitude_condition_save_fixture)

View file

@ -146,6 +146,8 @@ pub struct ExpectedRuntimeSummary {
#[serde(default)] #[serde(default)]
pub packed_event_blocked_missing_compact_control_count: Option<usize>, pub packed_event_blocked_missing_compact_control_count: Option<usize>,
#[serde(default)] #[serde(default)]
pub packed_event_blocked_shell_owned_descriptor_count: Option<usize>,
#[serde(default)]
pub packed_event_blocked_unmapped_real_descriptor_count: Option<usize>, pub packed_event_blocked_unmapped_real_descriptor_count: Option<usize>,
#[serde(default)] #[serde(default)]
pub packed_event_blocked_unmapped_world_descriptor_count: Option<usize>, 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 let Some(count) = self.packed_event_blocked_unmapped_real_descriptor_count {
if actual.packed_event_blocked_unmapped_real_descriptor_count != count { if actual.packed_event_blocked_unmapped_real_descriptor_count != count {
mismatches.push(format!( 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-functions.csv",
"artifacts/exports/rt3-1.06/pending-template-store-record-kinds.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/pending-template-store-management.md",
"artifacts/exports/rt3-1.06/event-effects-table.json",
]; ];
pub const REQUIRED_ATLAS_HEADINGS: &[&str] = &[ 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(); 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 if record
.grouped_effect_rows .grouped_effect_rows
.iter() .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( fn packed_record_company_target_import_blocker(
record: &SmpLoadedPackedEventRecordSummary, record: &SmpLoadedPackedEventRecordSummary,
company_context: &ImportRuntimeContext, 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( fn real_deactivate_player_row(
enabled: bool, enabled: bool,
) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary { ) -> 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] #[test]
fn blocks_scalar_locomotive_availability_rows_without_catalog_context() { fn blocks_scalar_locomotive_availability_rows_without_catalog_context() {
let save_slice = SmpLoadedSaveSlice { let save_slice = SmpLoadedSaveSlice {

View file

@ -128,9 +128,35 @@ struct RealGroupedEffectDescriptorMetadata {
target_mask_bits: u8, target_mask_bits: u8,
parameter_family: &'static str, parameter_family: &'static str,
runtime_key: Option<&'static str>, runtime_key: Option<&'static str>,
runtime_status: RealGroupedEffectRuntimeStatus,
executable_in_runtime: bool, 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] = [ const REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA: [RealGroupedEffectDescriptorMetadata; 12] = [
RealGroupedEffectDescriptorMetadata { RealGroupedEffectDescriptorMetadata {
descriptor_id: 1, descriptor_id: 1,
@ -138,6 +164,7 @@ const REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA: [RealGroupedEffectDescriptorMetad
target_mask_bits: 0x02, target_mask_bits: 0x02,
parameter_family: "player_finance_scalar", parameter_family: "player_finance_scalar",
runtime_key: None, runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true, executable_in_runtime: true,
}, },
RealGroupedEffectDescriptorMetadata { RealGroupedEffectDescriptorMetadata {
@ -146,6 +173,7 @@ const REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA: [RealGroupedEffectDescriptorMetad
target_mask_bits: 0x01, target_mask_bits: 0x01,
parameter_family: "company_finance_scalar", parameter_family: "company_finance_scalar",
runtime_key: None, runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true, executable_in_runtime: true,
}, },
RealGroupedEffectDescriptorMetadata { RealGroupedEffectDescriptorMetadata {
@ -154,6 +182,7 @@ const REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA: [RealGroupedEffectDescriptorMetad
target_mask_bits: 0x05, target_mask_bits: 0x05,
parameter_family: "territory_access_toggle", parameter_family: "territory_access_toggle",
runtime_key: None, runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true, executable_in_runtime: true,
}, },
RealGroupedEffectDescriptorMetadata { RealGroupedEffectDescriptorMetadata {
@ -162,6 +191,7 @@ const REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA: [RealGroupedEffectDescriptorMetad
target_mask_bits: 0x08, target_mask_bits: 0x08,
parameter_family: "whole_game_state_enum", parameter_family: "whole_game_state_enum",
runtime_key: None, runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true, executable_in_runtime: true,
}, },
RealGroupedEffectDescriptorMetadata { RealGroupedEffectDescriptorMetadata {
@ -170,6 +200,7 @@ const REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA: [RealGroupedEffectDescriptorMetad
target_mask_bits: 0x08, target_mask_bits: 0x08,
parameter_family: "special_condition_scalar", parameter_family: "special_condition_scalar",
runtime_key: None, runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true, executable_in_runtime: true,
}, },
RealGroupedEffectDescriptorMetadata { RealGroupedEffectDescriptorMetadata {
@ -178,6 +209,7 @@ const REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA: [RealGroupedEffectDescriptorMetad
target_mask_bits: 0x08, target_mask_bits: 0x08,
parameter_family: "candidate_availability_scalar", parameter_family: "candidate_availability_scalar",
runtime_key: None, runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true, executable_in_runtime: true,
}, },
RealGroupedEffectDescriptorMetadata { RealGroupedEffectDescriptorMetadata {
@ -186,6 +218,7 @@ const REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA: [RealGroupedEffectDescriptorMetad
target_mask_bits: 0x08, target_mask_bits: 0x08,
parameter_family: "world_flag_toggle", parameter_family: "world_flag_toggle",
runtime_key: Some("world.disable_stock_buying_and_selling"), runtime_key: Some("world.disable_stock_buying_and_selling"),
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true, executable_in_runtime: true,
}, },
RealGroupedEffectDescriptorMetadata { RealGroupedEffectDescriptorMetadata {
@ -194,6 +227,7 @@ const REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA: [RealGroupedEffectDescriptorMetad
target_mask_bits: 0x01, target_mask_bits: 0x01,
parameter_family: "company_confiscation_variant", parameter_family: "company_confiscation_variant",
runtime_key: None, runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true, executable_in_runtime: true,
}, },
RealGroupedEffectDescriptorMetadata { RealGroupedEffectDescriptorMetadata {
@ -202,6 +236,7 @@ const REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA: [RealGroupedEffectDescriptorMetad
target_mask_bits: 0x01, target_mask_bits: 0x01,
parameter_family: "company_lifecycle_toggle", parameter_family: "company_lifecycle_toggle",
runtime_key: None, runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true, executable_in_runtime: true,
}, },
RealGroupedEffectDescriptorMetadata { RealGroupedEffectDescriptorMetadata {
@ -210,6 +245,7 @@ const REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA: [RealGroupedEffectDescriptorMetad
target_mask_bits: 0x02, target_mask_bits: 0x02,
parameter_family: "player_lifecycle_toggle", parameter_family: "player_lifecycle_toggle",
runtime_key: None, runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true, executable_in_runtime: true,
}, },
RealGroupedEffectDescriptorMetadata { RealGroupedEffectDescriptorMetadata {
@ -218,6 +254,7 @@ const REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA: [RealGroupedEffectDescriptorMetad
target_mask_bits: 0x0d, target_mask_bits: 0x0d,
parameter_family: "company_or_territory_asset_toggle", parameter_family: "company_or_territory_asset_toggle",
runtime_key: None, runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true, executable_in_runtime: true,
}, },
RealGroupedEffectDescriptorMetadata { RealGroupedEffectDescriptorMetadata {
@ -226,10 +263,199 @@ const REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA: [RealGroupedEffectDescriptorMetad
target_mask_bits: 0x01, target_mask_bits: 0x01,
parameter_family: "company_build_limit_scalar", parameter_family: "company_build_limit_scalar",
runtime_key: None, runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true, 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)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum RealOrdinaryConditionMetric { enum RealOrdinaryConditionMetric {
Company(RuntimeCompanyMetric), Company(RuntimeCompanyMetric),
@ -3323,7 +3549,14 @@ fn parse_real_grouped_effect_row_summary(
if locomotive_name.is_some() { if locomotive_name.is_some() {
notes.push("grouped effect row carries locomotive-name side string".to_string()); 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()); 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) { 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( fn real_grouped_effect_descriptor_metadata(
descriptor_id: u32, descriptor_id: u32,
) -> Option<RealGroupedEffectDescriptorMetadata> { ) -> Option<RealGroupedEffectDescriptorMetadata> {
REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA recovered_cargo_production_descriptor_metadata(descriptor_id)
.iter()
.copied()
.find(|metadata| metadata.descriptor_id == descriptor_id)
.or_else(|| recovered_cargo_production_descriptor_metadata(descriptor_id))
.or_else(|| recovered_locomotive_availability_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_locomotive_cost_descriptor_metadata(descriptor_id))
.or_else(|| recovered_territory_access_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(|| recovered_locomotive_policy_descriptor_metadata(descriptor_id))
.or_else(|| special_condition_world_scalar_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(|| 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( fn recovered_cargo_production_descriptor_metadata(
@ -3572,6 +3812,7 @@ fn recovered_cargo_production_descriptor_metadata(
target_mask_bits: 0x08, target_mask_bits: 0x08,
parameter_family: "cargo_production_scalar", parameter_family: "cargo_production_scalar",
runtime_key: None, runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true, executable_in_runtime: true,
} }
}) })
@ -3593,6 +3834,7 @@ fn recovered_locomotive_availability_descriptor_metadata(
target_mask_bits: 0x08, target_mask_bits: 0x08,
parameter_family: "locomotive_availability_scalar", parameter_family: "locomotive_availability_scalar",
runtime_key: None, runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::EvidenceBlocked,
executable_in_runtime: false, executable_in_runtime: false,
}) })
.or_else(|| { .or_else(|| {
@ -3604,6 +3846,7 @@ fn recovered_locomotive_availability_descriptor_metadata(
target_mask_bits: 0x08, target_mask_bits: 0x08,
parameter_family: "locomotive_availability_scalar", parameter_family: "locomotive_availability_scalar",
runtime_key: None, runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::EvidenceBlocked,
executable_in_runtime: false, executable_in_runtime: false,
}) })
}) })
@ -3669,6 +3912,7 @@ fn recovered_locomotive_cost_descriptor_metadata(
target_mask_bits: 0x08, target_mask_bits: 0x08,
parameter_family: "locomotive_cost_scalar", parameter_family: "locomotive_cost_scalar",
runtime_key: None, runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::EvidenceBlocked,
executable_in_runtime: false, executable_in_runtime: false,
} }
}) })
@ -3683,6 +3927,7 @@ fn recovered_territory_access_cost_descriptor_metadata(
target_mask_bits: 0x08, target_mask_bits: 0x08,
parameter_family: "territory_access_cost_scalar", parameter_family: "territory_access_cost_scalar",
runtime_key: None, runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true, executable_in_runtime: true,
}) })
} }
@ -3697,6 +3942,7 @@ fn recovered_locomotive_policy_descriptor_metadata(
target_mask_bits: 0x08, target_mask_bits: 0x08,
parameter_family: "world_flag_toggle", parameter_family: "world_flag_toggle",
runtime_key: Some("world.all_steam_locos_available"), runtime_key: Some("world.all_steam_locos_available"),
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true, executable_in_runtime: true,
}), }),
455 => Some(RealGroupedEffectDescriptorMetadata { 455 => Some(RealGroupedEffectDescriptorMetadata {
@ -3705,6 +3951,7 @@ fn recovered_locomotive_policy_descriptor_metadata(
target_mask_bits: 0x08, target_mask_bits: 0x08,
parameter_family: "world_flag_toggle", parameter_family: "world_flag_toggle",
runtime_key: Some("world.all_diesel_locos_available"), runtime_key: Some("world.all_diesel_locos_available"),
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true, executable_in_runtime: true,
}), }),
456 => Some(RealGroupedEffectDescriptorMetadata { 456 => Some(RealGroupedEffectDescriptorMetadata {
@ -3713,6 +3960,7 @@ fn recovered_locomotive_policy_descriptor_metadata(
target_mask_bits: 0x08, target_mask_bits: 0x08,
parameter_family: "world_flag_toggle", parameter_family: "world_flag_toggle",
runtime_key: Some("world.all_electric_locos_available"), runtime_key: Some("world.all_electric_locos_available"),
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true, executable_in_runtime: true,
}), }),
_ => None, _ => None,
@ -3736,6 +3984,7 @@ fn special_condition_world_scalar_descriptor_metadata(
target_mask_bits: 0x08, target_mask_bits: 0x08,
parameter_family: "world_track_build_limit_scalar", parameter_family: "world_track_build_limit_scalar",
runtime_key: None, runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true, executable_in_runtime: true,
}) })
} }
@ -3757,6 +4006,7 @@ fn special_condition_world_toggle_descriptor_metadata(
target_mask_bits: 0x08, target_mask_bits: 0x08,
parameter_family: "world_flag_toggle", parameter_family: "world_flag_toggle",
runtime_key: None, runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true, executable_in_runtime: true,
}) })
} }
@ -3870,10 +4120,26 @@ fn runtime_world_flag_key_from_label(label: &str) -> String {
key 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( fn derive_real_grouped_target_subject(
row: &SmpLoadedPackedEventGroupedEffectRowSummary, row: &SmpLoadedPackedEventGroupedEffectRowSummary,
compact_control: &SmpLoadedPackedEventCompactControlSummary, compact_control: &SmpLoadedPackedEventCompactControlSummary,
) -> Option<RealGroupedTargetSubject> { ) -> Option<RealGroupedTargetSubject> {
if row.parameter_family.as_deref() == Some("company_governance_scalar") {
return Some(RealGroupedTargetSubject::Company);
}
match row.target_mask_bits { match row.target_mask_bits {
Some(0x08) => Some(RealGroupedTargetSubject::WholeGame), Some(0x08) => Some(RealGroupedTargetSubject::WholeGame),
Some(0x01) => Some(RealGroupedTargetSubject::Company), Some(0x01) => Some(RealGroupedTargetSubject::Company),
@ -3986,6 +4252,19 @@ fn decode_real_grouped_effect_action(
.copied()?; .copied()?;
let target_subject = derive_real_grouped_target_subject(row, compact_control); 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 if descriptor_metadata.executable_in_runtime
&& descriptor_metadata.descriptor_id == 1 && descriptor_metadata.descriptor_id == 1
&& row.opcode == 8 && row.opcode == 8
@ -10832,6 +11111,135 @@ mod tests {
assert!(metadata.executable_in_runtime); 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] #[test]
fn looks_up_recovered_world_toggle_descriptor_metadata() { fn looks_up_recovered_world_toggle_descriptor_metadata() {
let 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_ordinary_condition_count: usize,
pub packed_event_blocked_unmapped_world_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_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_real_descriptor_count: usize,
pub packed_event_blocked_unmapped_world_descriptor_count: usize, pub packed_event_blocked_unmapped_world_descriptor_count: usize,
pub packed_event_blocked_territory_access_variant_count: usize, pub packed_event_blocked_territory_access_variant_count: usize,
@ -496,6 +497,20 @@ impl RuntimeSummary {
.count() .count()
}) })
.unwrap_or(0), .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_blocked_unmapped_real_descriptor_count: state
.packed_event_collection .packed_event_collection
.as_ref() .as_ref()
@ -1343,4 +1358,80 @@ mod tests {
1 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);
}
} }

View file

@ -97,9 +97,13 @@ The highest-value next passes are now:
tables too, so the current company-targeted and chairman-targeted descriptor/condition batches tables too, so the current company-targeted and chairman-targeted descriptor/condition batches
can execute from standalone save-slice fixtures without overlay snapshots when that context is can execute from standalone save-slice fixtures without overlay snapshots when that context is
present; raw `.gms` inspection/export still does not reconstruct those company/chairman surfaces present; raw `.gms` inspection/export still does not reconstruct those company/chairman surfaces
- a generic company-governance scalar effect surface now exists in runtime too, but real - a checked-in `EventEffects` export now exists at
governance descriptor ids remain deferred until the checked-in `EventEffects.win` evidence is `artifacts/exports/rt3-1.06/event-effects-table.json`, and the first recovered governance
strong enough to recover them honestly descriptor tranche now executes through the generic company-governance scalar effect surface:
descriptor `56` `Credit Rating` and descriptor `57` `Prime Rate`
- adjacent recovered finance/control-transfer descriptors such as `55` `Stock Prices` and `58`
`Merger Premium` now land on explicit shell-owned descriptor parity instead of generic unmapped
descriptor residue
- widen real packed-event executable coverage descriptor by descriptor after identity, target mask, - widen real packed-event executable coverage descriptor by descriptor after identity, target mask,
and normalized effect semantics are all grounded, not just after row framing is parsed and normalized effect semantics are all grounded, not just after row framing is parsed
- the first grounded condition-side unlock now exists for negative-sentinel `raw_condition_id = -1` - the first grounded condition-side unlock now exists for negative-sentinel `raw_condition_id = -1`
@ -190,6 +194,15 @@ python3 tools/py/export_startup_map.py \
artifacts/exports/rt3-1.06 artifacts/exports/rt3-1.06
``` ```
Regenerate the checked-in `EventEffects` table export with:
```bash
python3 tools/py/extract_event_effects.py \
rt3_wineprefix/drive_c/rt3/RT3.exe \
rt3_wineprefix/drive_c/rt3/Data/Language/RT3.lng \
artifacts/exports/rt3-1.06/event-effects-table.json
```
That default export now walks two roots: That default export now walks two roots:
- `entry:0x005a313b` - `entry:0x005a313b`

View file

@ -60,9 +60,14 @@ Implemented today:
chairman-targeted descriptor/condition batches execute from standalone save-slice fixtures chairman-targeted descriptor/condition batches execute from standalone save-slice fixtures
without overlay snapshots when the checked-in documents include that context, while raw `.gms` without overlay snapshots when the checked-in documents include that context, while raw `.gms`
inspection/export still leaves those company/chairman surfaces absent inspection/export still leaves those company/chairman surfaces absent
- a generic company-governance scalar effect surface now exists in runtime, but real governance - a checked-in `EventEffects` export now exists too at
descriptor recovery is still deferred until the checked-in effect-table evidence can ground the `artifacts/exports/rt3-1.06/event-effects-table.json`, and the first recovered
ids honestly company-governance descriptor tranche now executes through the generic
`SetCompanyGovernanceScalar` surface: descriptor `56` `Credit Rating` and descriptor `57`
`Prime Rate` now import through ordinary company target lowering
- adjacent recovered finance/control-transfer descriptors such as `55` `Stock Prices` and `58`
`Merger Premium` now land on explicit shell-owned descriptor parity instead of generic unmapped
descriptor buckets
- a minimal event-owned train surface and an opaque economic-status lane now exist in runtime - a minimal event-owned train surface and an opaque economic-status lane now exist in runtime
state, and real descriptors `8` = `Economic Status`, `9` = `Confiscate All`, and `15` = state, and real descriptors `8` = `Economic Status`, `9` = `Confiscate All`, and `15` =
`Retire Train` now import and execute through the ordinary runtime path when overlay context `Retire Train` now import and execute through the ordinary runtime path when overlay context
@ -131,14 +136,13 @@ Implemented today:
remaining world-side frontier is broader descriptor/condition breadth rather than missing cargo remaining world-side frontier is broader descriptor/condition breadth rather than missing cargo
classification or save/import context classification or save/import context
That means the next implementation work is breadth, not bootstrap. The recommended next slice is That means the next implementation work is still breadth, not bootstrap. The current descriptor
broader real grouped-descriptor and ordinary condition-id coverage beyond the current access, frontier is no longer anonymous id recovery; it is the remaining recovered-but-nonexecutable
whole-game toggle, train, player, chairman selected-scope grouped effects, grounded families from the checked-in effect table, especially broader company/world scalar bands and the
chairman/governance conditions, numeric-threshold, named locomotive availability, named locomotive shell-owned finance/control-transfer rows that still need final classification or bounded runtime
cost, world scalar override, and world-scalar condition batches, plus eventual raw save landing surfaces. Raw save reconstruction for company/chairman context is still a later tranche
reconstruction for company/chairman context once stronger evidence exists. Richer runtime ownership once stronger evidence exists. Richer runtime ownership should still be added only where a later
should still be added only where a later descriptor or condition family needs more than the descriptor or condition family needs more than the current event-owned roster.
current event-owned roster.
## Why This Boundary ## Why This Boundary

View file

@ -0,0 +1,228 @@
{
"format_version": 1,
"save_slice_id": "packed-event-credit-rating-descriptor-save-slice",
"source": {
"description": "Tracked save-slice document proving recovered Credit Rating descriptor import and execution.",
"original_save_filename": "captured-credit-rating-descriptor.gms",
"original_save_sha256": "credit-rating-descriptor-sample-sha256",
"notes": [
"tracked as JSON save-slice document rather than raw .smp",
"pins the first recovered executable governance descriptor from the checked-in EventEffects table"
]
},
"save_slice": {
"file_extension_hint": "gms",
"container_profile_family": "rt3-classic-save-container-v1",
"mechanism_family": "classic-save-rehydrate-v1",
"mechanism_confidence": "grounded",
"trailer_family": null,
"bridge_family": null,
"profile": null,
"candidate_availability_table": null,
"named_locomotive_availability_table": null,
"cargo_catalog": null,
"special_conditions_table": null,
"event_runtime_collection": {
"source_kind": "packed-event-runtime-collection",
"mechanism_family": "classic-save-rehydrate-v1",
"mechanism_confidence": "grounded",
"container_profile_family": "rt3-classic-save-container-v1",
"metadata_tag_offset": 28928,
"records_tag_offset": 29184,
"close_tag_offset": 29696,
"packed_state_version": 1001,
"packed_state_version_hex": "0x000003e9",
"live_id_bound": 73,
"live_record_count": 1,
"live_entry_ids": [
73
],
"decoded_record_count": 1,
"imported_runtime_record_count": 0,
"records": [
{
"record_index": 0,
"live_entry_id": 73,
"payload_offset": 29186,
"payload_len": 120,
"decode_status": "executable",
"payload_family": "real_packed_v1",
"trigger_kind": 7,
"active": null,
"marks_collection_dirty": null,
"one_shot": false,
"compact_control": {
"mode_byte_0x7ef": 6,
"primary_selector_0x7f0": 99,
"grouped_mode_0x7f4": 2,
"one_shot_header_0x7f5": 1,
"modifier_flag_0x7f9": 1,
"modifier_flag_0x7fa": 0,
"grouped_target_scope_ordinals_0x7fb": [
0,
1,
2,
3
],
"grouped_scope_checkboxes_0x7ff": [
1,
0,
1,
0
],
"summary_toggle_0x800": 1,
"grouped_territory_selectors_0x80f": [
-1,
10,
-1,
22
]
},
"text_bands": [],
"standalone_condition_row_count": 0,
"standalone_condition_rows": [],
"negative_sentinel_scope": null,
"grouped_effect_row_counts": [
1,
0,
0,
0
],
"grouped_effect_rows": [
{
"group_index": 0,
"row_index": 0,
"descriptor_id": 56,
"descriptor_label": "Credit Rating",
"target_mask_bits": 11,
"parameter_family": "company_governance_scalar",
"opcode": 3,
"raw_scalar_value": 640,
"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",
"semantic_family": "scalar_assignment",
"semantic_preview": "Set Credit Rating to 640",
"locomotive_name": null,
"notes": [
"descriptor recovered from checked-in EventEffects table"
]
}
],
"decoded_conditions": [],
"decoded_actions": [
{
"kind": "set_company_governance_scalar",
"target": {
"kind": "selected_company"
},
"metric": "credit_rating",
"value": 640
}
],
"executable_import_ready": true,
"notes": [
"credit-rating descriptor lowers through the checked-in EventEffects table"
]
}
]
},
"notes": [
"real company-governance descriptor sample"
],
"company_roster": {
"source_kind": "tracked-save-slice-company-roster",
"semantic_family": "save-slice-runtime-company-context",
"observed_entry_count": 2,
"selected_company_id": 1,
"entries": [
{
"company_id": 1,
"active": true,
"controller_kind": "human",
"current_cash": 150,
"debt": 80,
"credit_rating_score": 650,
"prime_rate": 5,
"available_track_laying_capacity": 6,
"track_piece_counts": {
"total": 20,
"single": 5,
"double": 8,
"transition": 1,
"electric": 3,
"non_electric": 17
},
"linked_chairman_profile_id": 1,
"book_value_per_share": 2620,
"investor_confidence": 37,
"management_attitude": 58,
"takeover_cooldown_year": 1839,
"merger_cooldown_year": 1838
},
{
"company_id": 2,
"active": true,
"controller_kind": "ai",
"current_cash": 90,
"debt": 40,
"credit_rating_score": 480,
"prime_rate": 6,
"available_track_laying_capacity": 2,
"track_piece_counts": {
"total": 8,
"single": 2,
"double": 2,
"transition": 0,
"electric": 1,
"non_electric": 7
},
"linked_chairman_profile_id": 2,
"book_value_per_share": 1400,
"investor_confidence": 22,
"management_attitude": 31,
"takeover_cooldown_year": null,
"merger_cooldown_year": null
}
]
},
"chairman_profile_table": {
"source_kind": "tracked-save-slice-chairman-profile-table",
"semantic_family": "save-slice-runtime-chairman-context",
"observed_entry_count": 2,
"selected_chairman_profile_id": 1,
"entries": [
{
"profile_id": 1,
"name": "Chairman One",
"active": true,
"current_cash": 500,
"linked_company_id": 1,
"company_holdings": {
"1": 1000
},
"holdings_value_total": 700,
"net_worth_total": 1200,
"purchasing_power_total": 1500
},
{
"profile_id": 2,
"name": "Chairman Two",
"active": true,
"current_cash": 250,
"linked_company_id": 2,
"company_holdings": {
"2": 900
},
"holdings_value_total": 600,
"net_worth_total": 900,
"purchasing_power_total": 1100
}
]
}
}
}

View file

@ -0,0 +1,53 @@
{
"format_version": 1,
"fixture_id": "packed-event-credit-rating-descriptor-save-slice-fixture",
"source": {
"kind": "captured-runtime",
"description": "Fixture proving recovered Credit Rating descriptor executes from save-slice-backed company context."
},
"state_save_slice_path": "packed-event-credit-rating-descriptor-context-save-slice.json",
"commands": [
{
"kind": "service_trigger_kind",
"trigger_kind": 7
}
],
"expected_summary": {
"calendar_projection_source": "default-1830-placeholder",
"calendar_projection_is_placeholder": true,
"company_count": 2,
"chairman_profile_count": 2,
"packed_event_collection_present": true,
"packed_event_record_count": 1,
"packed_event_decoded_record_count": 1,
"packed_event_imported_runtime_record_count": 1,
"event_runtime_record_count": 1,
"total_event_record_service_count": 1,
"total_trigger_dispatch_count": 1
},
"expected_state_fragment": {
"companies": [
{
"company_id": 1,
"credit_rating_score": 640
},
{
"company_id": 2,
"credit_rating_score": 480
}
],
"packed_event_collection": {
"records": [
{
"import_outcome": "imported"
}
]
},
"event_runtime_records": [
{
"record_id": 73,
"service_count": 1
}
]
}
}

View file

@ -0,0 +1,39 @@
{
"format_version": 1,
"fixture_id": "packed-event-merger-premium-shell-save-slice-fixture",
"source": {
"kind": "captured-runtime",
"description": "Fixture pinning the explicit shell-owned descriptor frontier for recovered Merger Premium rows."
},
"state_save_slice_path": "packed-event-merger-premium-shell-save-slice.json",
"commands": [
{
"kind": "service_trigger_kind",
"trigger_kind": 7
}
],
"expected_summary": {
"calendar_projection_source": "default-1830-placeholder",
"calendar_projection_is_placeholder": true,
"company_count": 2,
"chairman_profile_count": 2,
"packed_event_collection_present": true,
"packed_event_record_count": 1,
"packed_event_decoded_record_count": 1,
"packed_event_parity_only_record_count": 1,
"packed_event_blocked_shell_owned_descriptor_count": 1,
"event_runtime_record_count": 0,
"total_event_record_service_count": 0,
"total_trigger_dispatch_count": 1
},
"expected_state_fragment": {
"packed_event_collection": {
"records": [
{
"import_outcome": "blocked_shell_owned_descriptor"
}
]
},
"event_runtime_records": []
}
}

View file

@ -0,0 +1,219 @@
{
"format_version": 1,
"save_slice_id": "packed-event-merger-premium-shell-save-slice",
"source": {
"description": "Tracked save-slice document pinning a recovered shell-owned Merger Premium descriptor.",
"original_save_filename": "captured-merger-premium-shell.gms",
"original_save_sha256": "merger-premium-shell-sample-sha256",
"notes": [
"tracked as JSON save-slice document rather than raw .smp",
"pins the explicit shell-owned descriptor frontier for recovered company finance rows"
]
},
"save_slice": {
"file_extension_hint": "gms",
"container_profile_family": "rt3-classic-save-container-v1",
"mechanism_family": "classic-save-rehydrate-v1",
"mechanism_confidence": "grounded",
"trailer_family": null,
"bridge_family": null,
"profile": null,
"candidate_availability_table": null,
"named_locomotive_availability_table": null,
"cargo_catalog": null,
"special_conditions_table": null,
"event_runtime_collection": {
"source_kind": "packed-event-runtime-collection",
"mechanism_family": "classic-save-rehydrate-v1",
"mechanism_confidence": "grounded",
"container_profile_family": "rt3-classic-save-container-v1",
"metadata_tag_offset": 28928,
"records_tag_offset": 29184,
"close_tag_offset": 29696,
"packed_state_version": 1001,
"packed_state_version_hex": "0x000003e9",
"live_id_bound": 74,
"live_record_count": 1,
"live_entry_ids": [
74
],
"decoded_record_count": 1,
"imported_runtime_record_count": 0,
"records": [
{
"record_index": 0,
"live_entry_id": 74,
"payload_offset": 29186,
"payload_len": 120,
"decode_status": "parity_only",
"payload_family": "real_packed_v1",
"trigger_kind": 7,
"active": null,
"marks_collection_dirty": null,
"one_shot": false,
"compact_control": {
"mode_byte_0x7ef": 6,
"primary_selector_0x7f0": 99,
"grouped_mode_0x7f4": 2,
"one_shot_header_0x7f5": 1,
"modifier_flag_0x7f9": 1,
"modifier_flag_0x7fa": 0,
"grouped_target_scope_ordinals_0x7fb": [
0,
1,
2,
3
],
"grouped_scope_checkboxes_0x7ff": [
1,
0,
1,
0
],
"summary_toggle_0x800": 1,
"grouped_territory_selectors_0x80f": [
-1,
10,
-1,
22
]
},
"text_bands": [],
"standalone_condition_row_count": 0,
"standalone_condition_rows": [],
"negative_sentinel_scope": null,
"grouped_effect_row_counts": [
1,
0,
0,
0
],
"grouped_effect_rows": [
{
"group_index": 0,
"row_index": 0,
"descriptor_id": 58,
"descriptor_label": "Merger Premium",
"target_mask_bits": 11,
"parameter_family": "company_finance_shell_scalar",
"opcode": 3,
"raw_scalar_value": 25,
"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",
"semantic_family": "scalar_assignment",
"semantic_preview": "Set Merger Premium to 25",
"locomotive_name": null,
"notes": [
"descriptor recovered in the checked-in effect table as shell_owned parity"
]
}
],
"decoded_conditions": [],
"decoded_actions": [],
"executable_import_ready": false,
"notes": [
"merger-premium descriptor is recovered but remains shell-owned parity"
]
}
]
},
"notes": [
"recovered shell-owned company-finance descriptor sample"
],
"company_roster": {
"source_kind": "tracked-save-slice-company-roster",
"semantic_family": "save-slice-runtime-company-context",
"observed_entry_count": 2,
"selected_company_id": 1,
"entries": [
{
"company_id": 1,
"active": true,
"controller_kind": "human",
"current_cash": 150,
"debt": 80,
"credit_rating_score": 650,
"prime_rate": 5,
"available_track_laying_capacity": 6,
"track_piece_counts": {
"total": 20,
"single": 5,
"double": 8,
"transition": 1,
"electric": 3,
"non_electric": 17
},
"linked_chairman_profile_id": 1,
"book_value_per_share": 2620,
"investor_confidence": 37,
"management_attitude": 58,
"takeover_cooldown_year": 1839,
"merger_cooldown_year": 1838
},
{
"company_id": 2,
"active": true,
"controller_kind": "ai",
"current_cash": 90,
"debt": 40,
"credit_rating_score": 480,
"prime_rate": 6,
"available_track_laying_capacity": 2,
"track_piece_counts": {
"total": 8,
"single": 2,
"double": 2,
"transition": 0,
"electric": 1,
"non_electric": 7
},
"linked_chairman_profile_id": 2,
"book_value_per_share": 1400,
"investor_confidence": 22,
"management_attitude": 31,
"takeover_cooldown_year": null,
"merger_cooldown_year": null
}
]
},
"chairman_profile_table": {
"source_kind": "tracked-save-slice-chairman-profile-table",
"semantic_family": "save-slice-runtime-chairman-context",
"observed_entry_count": 2,
"selected_chairman_profile_id": 1,
"entries": [
{
"profile_id": 1,
"name": "Chairman One",
"active": true,
"current_cash": 500,
"linked_company_id": 1,
"company_holdings": {
"1": 1000
},
"holdings_value_total": 700,
"net_worth_total": 1200,
"purchasing_power_total": 1500
},
{
"profile_id": 2,
"name": "Chairman Two",
"active": true,
"current_cash": 250,
"linked_company_id": 2,
"company_holdings": {
"2": 900
},
"holdings_value_total": 600,
"net_worth_total": 900,
"purchasing_power_total": 1100
}
]
}
}
}

View file

@ -0,0 +1,75 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import hashlib
import json
import re
import struct
from pathlib import Path
TABLE_BASE_VA = 0x00610398
ROW_STRIDE = 0x6E
ROW_COUNT = 520
IMAGE_BASE = 0x00400000
def load_lng_labels(path: Path) -> dict[int, str]:
labels: dict[int, str] = {}
for line in path.read_text(encoding="latin1").splitlines():
match = re.match(r"\s*(\d+)\s+\"(.*)\"\s*$", line)
if match:
labels[int(match.group(1))] = match.group(2)
return labels
def extract_rows(exe_bytes: bytes, labels: dict[int, str]) -> list[dict[str, object]]:
table_offset = TABLE_BASE_VA - IMAGE_BASE
rows: list[dict[str, object]] = []
for row_index in range(ROW_COUNT):
row = exe_bytes[
table_offset + row_index * ROW_STRIDE : table_offset + (row_index + 1) * ROW_STRIDE
]
if len(row) < ROW_STRIDE:
break
label_id = struct.unpack_from("<H", row, 0x6A)[0]
rows.append(
{
"row_index": row_index,
"descriptor_id": struct.unpack_from("<I", row, 0x04)[0],
"selector_order": struct.unpack_from("<f", row, 0x00)[0],
"target_mask_bits": row[0x65],
"label_id": label_id,
"label": labels.get(label_id, ""),
"signature_byte_0x63": row[0x63],
"signature_byte_0x64": row[0x64],
"signature_hex_0x63_0x6d": row[0x63:0x6E].hex(),
}
)
return rows
def main() -> None:
parser = argparse.ArgumentParser(description="Extract the RT3 EventEffects descriptor table.")
parser.add_argument("exe", type=Path)
parser.add_argument("lng", type=Path)
parser.add_argument("out", type=Path)
args = parser.parse_args()
exe_bytes = args.exe.read_bytes()
labels = load_lng_labels(args.lng)
artifact = {
"table_base_va": f"0x{TABLE_BASE_VA:08x}",
"row_stride_hex": f"0x{ROW_STRIDE:02x}",
"descriptor_count": ROW_COUNT,
"binary_path_hint": str(args.exe),
"language_path_hint": str(args.lng),
"binary_sha256": hashlib.sha256(exe_bytes).hexdigest(),
"descriptors": extract_rows(exe_bytes, labels),
}
args.out.write_text(json.dumps(artifact, indent=2) + "\n", encoding="utf-8")
if __name__ == "__main__":
main()