Close out EventEffects descriptor metadata
This commit is contained in:
parent
a63de904fa
commit
3dbcec688f
15 changed files with 7156 additions and 23 deletions
11
README.md
11
README.md
|
|
@ -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
|
||||
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
|
||||
still does not reconstruct those company/chairman collections automatically. A generic
|
||||
company-governance scalar effect surface now exists in runtime too, but real governance descriptor
|
||||
ids are still deferred until the checked-in effect-table evidence is stronger. The first grounded
|
||||
still does not reconstruct those company/chairman collections automatically. A checked-in
|
||||
`EventEffects` export now exists too in
|
||||
`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
|
||||
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
|
||||
|
|
|
|||
5730
artifacts/exports/rt3-1.06/event-effects-table.json
Normal file
5730
artifacts/exports/rt3-1.06/event-effects-table.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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!(
|
||||
|
|
|
|||
|
|
@ -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] = &[
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 =
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,9 +97,13 @@ The highest-value next passes are now:
|
|||
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
|
||||
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
|
||||
governance descriptor ids remain deferred until the checked-in `EventEffects.win` evidence is
|
||||
strong enough to recover them honestly
|
||||
- a checked-in `EventEffects` export now exists at
|
||||
`artifacts/exports/rt3-1.06/event-effects-table.json`, and the first recovered governance
|
||||
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,
|
||||
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`
|
||||
|
|
@ -190,6 +194,15 @@ python3 tools/py/export_startup_map.py \
|
|||
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:
|
||||
|
||||
- `entry:0x005a313b`
|
||||
|
|
|
|||
|
|
@ -60,9 +60,14 @@ Implemented today:
|
|||
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`
|
||||
inspection/export still leaves those company/chairman surfaces absent
|
||||
- a generic company-governance scalar effect surface now exists in runtime, but real governance
|
||||
descriptor recovery is still deferred until the checked-in effect-table evidence can ground the
|
||||
ids honestly
|
||||
- a checked-in `EventEffects` export now exists too at
|
||||
`artifacts/exports/rt3-1.06/event-effects-table.json`, and the first recovered
|
||||
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
|
||||
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
|
||||
|
|
@ -131,14 +136,13 @@ Implemented today:
|
|||
remaining world-side frontier is broader descriptor/condition breadth rather than missing cargo
|
||||
classification or save/import context
|
||||
|
||||
That means the next implementation work is breadth, not bootstrap. The recommended next slice is
|
||||
broader real grouped-descriptor and ordinary condition-id coverage beyond the current access,
|
||||
whole-game toggle, train, player, chairman selected-scope grouped effects, grounded
|
||||
chairman/governance conditions, numeric-threshold, named locomotive availability, named locomotive
|
||||
cost, world scalar override, and world-scalar condition batches, plus eventual raw save
|
||||
reconstruction for company/chairman context once stronger evidence exists. Richer runtime ownership
|
||||
should still be added only where a later descriptor or condition family needs more than the
|
||||
current event-owned roster.
|
||||
That means the next implementation work is still breadth, not bootstrap. The current descriptor
|
||||
frontier is no longer anonymous id recovery; it is the remaining recovered-but-nonexecutable
|
||||
families from the checked-in effect table, especially broader company/world scalar bands and the
|
||||
shell-owned finance/control-transfer rows that still need final classification or bounded runtime
|
||||
landing surfaces. Raw save reconstruction for company/chairman context is still a later tranche
|
||||
once stronger evidence exists. Richer runtime ownership should still be added only where a later
|
||||
descriptor or condition family needs more than the current event-owned roster.
|
||||
|
||||
## Why This Boundary
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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": []
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
75
tools/py/extract_event_effects.py
Normal file
75
tools/py/extract_event_effects.py
Normal 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()
|
||||
Loading…
Add table
Add a link
Reference in a new issue